From d7f8b905a4472db3a3c2451b6506b9419b9b43c3 Mon Sep 17 00:00:00 2001 From: Myzel394 <50424412+Myzel394@users.noreply.github.com> Date: Sat, 7 Sep 2024 14:35:47 +0200 Subject: [PATCH] feat(aliases): Add duplicates analyzer --- handlers/aliases/analyzer/analyzer.go | 1 + handlers/aliases/analyzer/double_values.go | 193 ++++++++++++++++++ .../aliases/analyzer/double_values_test.go | 119 +++++++++++ handlers/aliases/ast/values.go | 39 ++-- 4 files changed, 338 insertions(+), 14 deletions(-) create mode 100644 handlers/aliases/analyzer/double_values.go create mode 100644 handlers/aliases/analyzer/double_values_test.go diff --git a/handlers/aliases/analyzer/analyzer.go b/handlers/aliases/analyzer/analyzer.go index 6b7d0f8..68b6382 100644 --- a/handlers/aliases/analyzer/analyzer.go +++ b/handlers/aliases/analyzer/analyzer.go @@ -27,6 +27,7 @@ func Analyze( } errors = append(errors, analyzeContainsRequiredKeys(*d)...) + errors = append(errors, analyzeContainsNoDoubleValues(*d.Parser)...) return utils.Map( errors, diff --git a/handlers/aliases/analyzer/double_values.go b/handlers/aliases/analyzer/double_values.go new file mode 100644 index 0000000..da42351 --- /dev/null +++ b/handlers/aliases/analyzer/double_values.go @@ -0,0 +1,193 @@ +package analyzer + +import ( + "config-lsp/common" + "config-lsp/handlers/aliases/ast" + "config-lsp/handlers/aliases/indexes" + "config-lsp/utils" + "errors" + "fmt" +) + +var valueHandlerMap = map[string]func( + rawValue []ast.AliasValueInterface, +) []common.LSPError{ + "AliasValueUser": analyzeValueUser, + "AliasValueEmail": analyzeValueEmail, + "AliasValueCommand": analyzeValueCommand, + "AliasValueFile": analyzeValueFile, + "AliasValueInclude": analyzeValueInclude, + "AliasValueError": analyzeValueError, +} + +func analyzeContainsNoDoubleValues( + p ast.AliasesParser, +) []common.LSPError { + errors := make([]common.LSPError, 0) + + it := p.Aliases.Iterator() + + for it.Next() { + entry := it.Value().(*ast.AliasEntry) + + valuesPerType := utils.Group( + entry.Values.Values, + func(entry ast.AliasValueInterface) string { + return entry.GetStructName() + }, + ) + + for valueType, values := range valuesPerType { + handler := valueHandlerMap[valueType] + + newErrors := handler(values) + errors = append(errors, newErrors...) + } + } + + return errors +} + +func analyzeValueUser( + rawValues []ast.AliasValueInterface, +) []common.LSPError { + users := make(map[string]struct{}) + errs := make([]common.LSPError, 0) + + // Simple double value check + for _, rawValue := range rawValues { + value := rawValue.(ast.AliasValueUser) + key := indexes.NormalizeKey(value.Value) + + if _, found := users[key]; found { + errs = append(errs, common.LSPError{ + Range: value.Location, + Err: errors.New(fmt.Sprintf("User '%s' is defined multiple times", key)), + }) + } else { + users[key] = struct{}{} + } + + } + + return errs +} + +func analyzeValueEmail( + rawValues []ast.AliasValueInterface, +) []common.LSPError { + emails := make(map[string]struct{}) + errs := make([]common.LSPError, 0) + + for _, rawValue := range rawValues { + value := rawValue.(ast.AliasValueEmail) + + // Simple double value check + if _, found := emails[value.Value]; found { + errs = append(errs, common.LSPError{ + Range: value.Location, + Err: errors.New(fmt.Sprintf("Email '%s' is defined multiple times", value.Value)), + }) + } else { + emails[value.Value] = struct{}{} + } + } + + return errs +} + +func analyzeValueCommand( + rawValues []ast.AliasValueInterface, +) []common.LSPError { + commands := make(map[string]struct{}) + errs := make([]common.LSPError, 0) + + for _, rawValue := range rawValues { + value := rawValue.(ast.AliasValueCommand) + command := value.Command + + // Simple double value check + if _, found := commands[command]; found { + errs = append(errs, common.LSPError{ + Range: value.Location, + Err: errors.New(fmt.Sprintf("Command '%s' is defined multiple times", command)), + }) + } else { + commands[command] = struct{}{} + } + } + + return errs +} + +func analyzeValueFile( + rawValues []ast.AliasValueInterface, +) []common.LSPError { + files := make(map[string]struct{}) + errs := make([]common.LSPError, 0) + + for _, rawValue := range rawValues { + value := rawValue.(ast.AliasValueFile) + path := string(value.Path) + + // Simple double value check + if _, found := files[path]; found { + errs = append(errs, common.LSPError{ + Range: value.Location, + Err: errors.New(fmt.Sprintf("File '%s' is defined multiple times", path)), + }) + } else { + files[path] = struct{}{} + } + } + + return errs +} + +func analyzeValueInclude( + rawValues []ast.AliasValueInterface, +) []common.LSPError { + files := make(map[string]struct{}) + errs := make([]common.LSPError, 0) + + for _, rawValue := range rawValues { + value := rawValue.(ast.AliasValueInclude) + path := string(value.Path.Path) + + // Simple double value check + if _, found := files[path]; found { + errs = append(errs, common.LSPError{ + Range: value.Location, + Err: errors.New(fmt.Sprintf("Inclusion '%s' is included multiple times", path)), + }) + } else { + files[path] = struct{}{} + } + } + + return errs +} + +func analyzeValueError( + rawValues []ast.AliasValueInterface, +) []common.LSPError { + codes := make(map[uint16]struct{}) + errs := make([]common.LSPError, 0) + + for _, rawValue := range rawValues { + value := rawValue.(ast.AliasValueError) + code := value.Code.ErrorCodeAsInt() + + // Simple double value check + if _, found := codes[code]; found { + errs = append(errs, common.LSPError{ + Range: value.Location, + Err: errors.New(fmt.Sprintf("Error code '%d' is defined multiple times", code)), + }) + } else { + codes[code] = struct{}{} + } + } + + return errs +} diff --git a/handlers/aliases/analyzer/double_values_test.go b/handlers/aliases/analyzer/double_values_test.go new file mode 100644 index 0000000..57ae1b6 --- /dev/null +++ b/handlers/aliases/analyzer/double_values_test.go @@ -0,0 +1,119 @@ +package analyzer + +import ( + "config-lsp/handlers/aliases/ast" + "config-lsp/utils" + "testing" +) + +func TestContainsDoubleUsers( + t *testing.T, +) { + input := utils.Dedent(` +alice: root +master: alice, alice +`) + p := ast.NewAliasesParser() + errors := p.Parse(input) + + // d := aliases.AliasesDocument{ + // Parser: &p, + // } + + if len(errors) != 0 { + t.Fatalf("Expected no errors, got %v", errors) + } + + errors = analyzeContainsNoDoubleValues(p) + + if !(len(errors) == 1) { + t.Errorf("Expected errors, got none") + } +} + +func TestContainsDoubleEmails( + t *testing.T, +) { + input := utils.Dedent(` +alice: root@localhost, some, noise, here, root@localhost +`) + p := ast.NewAliasesParser() + errors := p.Parse(input) + + if len(errors) != 0 { + t.Fatalf("Expected no errors, got %v", errors) + } + + errors = analyzeContainsNoDoubleValues(p) + + if !(len(errors) == 1) { + t.Errorf("Expected errors, got none") + } +} + +func TestContainsDoubleCommands( + t *testing.T, +) { + input := utils.Dedent(` +alice: |echo, |test, |echo +`) + p := ast.NewAliasesParser() + errors := p.Parse(input) + + if len(errors) != 0 { + t.Fatalf("Expected no errors, got %v", errors) + } + + errors = analyzeContainsNoDoubleValues(p) + + if !(len(errors) == 1) { + t.Errorf("Expected errors, got none") + } +} + +func TestContainsDoubleErrors( + t *testing.T, +) { + input := utils.Dedent(` +alice: error:450 Nonono, error:450 Some other message +root: error:450 Nonon, error:451 This is not okay +`) + p := ast.NewAliasesParser() + errors := p.Parse(input) + + if len(errors) != 0 { + t.Fatalf("Expected no errors, got %v", errors) + } + + errors = analyzeContainsNoDoubleValues(p) + + if !(len(errors) == 1) { + t.Errorf("Expected no errors, got %v", errors) + } + + if !(errors[0].Range.Start.Line == 0 && errors[0].Range.End.Line == 0) { + t.Errorf("Expected error to be on line 0, got %v-%v", errors[0].Range.Start.Line, errors[0].Range.End.Line) + } +} + +func TestComplexExampleContainsNoDoubleValues( + t *testing.T, +) { + input := utils.Dedent(` +alice: root@localhost, user@localhost +master: alice, root +noreply: error:450 Nonono +`) + p := ast.NewAliasesParser() + errors := p.Parse(input) + + if len(errors) != 0 { + t.Fatalf("Expected no errors, got %v", errors) + } + + errors = analyzeContainsNoDoubleValues(p) + + if !(len(errors) == 0) { + t.Errorf("Expected no errors, got %v", errors) + } +} diff --git a/handlers/aliases/ast/values.go b/handlers/aliases/ast/values.go index d468b62..accc7ab 100644 --- a/handlers/aliases/ast/values.go +++ b/handlers/aliases/ast/values.go @@ -11,6 +11,7 @@ import ( type AliasValueInterface interface { GetAliasValue() AliasValue + GetStructName() string } func (a AliasValue) String() string { @@ -21,6 +22,10 @@ func (a AliasValue) GetAliasValue() AliasValue { return a } +func (a AliasValue) GetStructName() string { + return "AliasValue" +} + type AliasValue struct { Location common.LocationRange Value string @@ -30,6 +35,10 @@ type AliasValueUser struct { AliasValue } +func (a AliasValueUser) GetStructName() string { + return "AliasValueUser" +} + type path string type AliasValueFile struct { @@ -37,13 +46,8 @@ type AliasValueFile struct { Path path } -func (a AliasValueFile) CheckIsValid() []common.LSPError { - return utils.Map( - fields.PathField.CheckIsValid(string(a.Path)), - func(invalidValue *docvalues.InvalidValue) common.LSPError { - return docvalues.LSPErrorFromInvalidValue(a.Location.Start.Line, *invalidValue) - }, - ) +func (a AliasValueFile) GetStructName() string { + return "AliasValueFile" } type AliasValueCommand struct { @@ -51,13 +55,8 @@ type AliasValueCommand struct { Command string } -func (a AliasValueCommand) CheckIsValid() []common.LSPError { - return utils.Map( - fields.CommandField.CheckIsValid(a.Command), - func(invalidValue *docvalues.InvalidValue) common.LSPError { - return docvalues.LSPErrorFromInvalidValue(a.Location.Start.Line, *invalidValue) - }, - ) +func (a AliasValueCommand) GetStructName() string { + return "AliasValueCommand" } type AliasValueIncludePath struct { @@ -79,6 +78,10 @@ func (a AliasValueInclude) CheckIsValid() []common.LSPError { ) } +func (a AliasValueInclude) GetStructName() string { + return "AliasValueInclude" +} + type AliasValueEmail struct { AliasValue } @@ -92,6 +95,10 @@ func (a AliasValueEmail) CheckIsValid() []common.LSPError { ) } +func (a AliasValueEmail) GetStructName() string { + return "AliasValueEmail" +} + type AliasValueError struct { AliasValue @@ -103,6 +110,10 @@ type AliasValueErrorCode struct { AliasValue } +func (a AliasValueError) GetStructName() string { + return "AliasValueError" +} + func (a AliasValueErrorCode) ErrorCodeAsInt() uint16 { code, err := strconv.Atoi(a.Value)