From 31faa9a96d71a5b0e1ecbfd78860299100c52c53 Mon Sep 17 00:00:00 2001 From: Myzel394 <50424412+Myzel394@users.noreply.github.com> Date: Sat, 31 Aug 2024 19:18:57 +0200 Subject: [PATCH] fix(aliases): Improve aliases --- go.mod | 1 + go.sum | 2 + handlers/aliases/analyzer/analyzer.go | 13 +++++ handlers/aliases/analyzer/parser.go | 49 ++++++++++++++++ handlers/aliases/ast/aliases.go | 5 +- handlers/aliases/ast/handler.go | 2 +- handlers/aliases/ast/listener.go | 45 ++++++++++----- handlers/aliases/ast/parser.go | 9 ++- handlers/aliases/ast/parser_test.go | 80 +++++++++++++++++++++------ handlers/aliases/ast/values.go | 2 +- handlers/aliases/indexes/indexes.go | 43 ++++++++++++++ handlers/aliases/shared.go | 15 +++++ handlers/aliases/shared/errors.go | 12 ++++ handlers/fstab/fstab_test.go | 2 + handlers/hosts/analyzer/double_ips.go | 12 +++- 15 files changed, 253 insertions(+), 39 deletions(-) create mode 100644 handlers/aliases/analyzer/analyzer.go create mode 100644 handlers/aliases/analyzer/parser.go create mode 100644 handlers/aliases/indexes/indexes.go create mode 100644 handlers/aliases/shared.go create mode 100644 handlers/aliases/shared/errors.go diff --git a/go.mod b/go.mod index 0340385..661cc9a 100644 --- a/go.mod +++ b/go.mod @@ -10,6 +10,7 @@ require ( require ( github.com/antlr4-go/antlr/v4 v4.13.1 // indirect github.com/aymanbagabas/go-osc52/v2 v2.0.1 // indirect + github.com/emirpasic/gods v1.18.1 // indirect github.com/gorilla/websocket v1.5.3 // indirect github.com/iancoleman/strcase v0.3.0 // indirect github.com/k0kubun/pp v3.0.1+incompatible // indirect diff --git a/go.sum b/go.sum index 4934342..60c2e89 100644 --- a/go.sum +++ b/go.sum @@ -2,6 +2,8 @@ github.com/antlr4-go/antlr/v4 v4.13.1 h1:SqQKkuVZ+zWkMMNkjy5FZe5mr5WURWnlpmOuzYW github.com/antlr4-go/antlr/v4 v4.13.1/go.mod h1:GKmUxMtwp6ZgGwZSva4eWPC5mS6vUAmOABFgjdkM7Nw= github.com/aymanbagabas/go-osc52/v2 v2.0.1 h1:HwpRHbFMcZLEVr42D4p7XBqjyuxQH5SMiErDT4WkJ2k= github.com/aymanbagabas/go-osc52/v2 v2.0.1/go.mod h1:uYgXzlJ7ZpABp8OJ+exZzJJhRNQ2ASbcXHWsFqH8hp8= +github.com/emirpasic/gods v1.18.1 h1:FXtiHYKDGKCW2KzwZKx0iC0PQmdlorYgdFG9jPXJ1Bc= +github.com/emirpasic/gods v1.18.1/go.mod h1:8tpGGwCnJ5H4r6BWwaV6OrWmMoPhUl5jm/FMNAnJvWQ= github.com/gorilla/websocket v1.4.1/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= github.com/gorilla/websocket v1.5.3 h1:saDtZ6Pbx/0u+bgYQ3q96pZgCzfhKXGPqt7kZ72aNNg= github.com/gorilla/websocket v1.5.3/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= diff --git a/handlers/aliases/analyzer/analyzer.go b/handlers/aliases/analyzer/analyzer.go new file mode 100644 index 0000000..98cd418 --- /dev/null +++ b/handlers/aliases/analyzer/analyzer.go @@ -0,0 +1,13 @@ +package analyzer + +import ( + "config-lsp/handlers/hosts" + + protocol "github.com/tliron/glsp/protocol_3_16" +) + +func Analyze( + d *hosts.HostsDocument, +) []protocol.Diagnostic { + return nil +} diff --git a/handlers/aliases/analyzer/parser.go b/handlers/aliases/analyzer/parser.go new file mode 100644 index 0000000..e1c5820 --- /dev/null +++ b/handlers/aliases/analyzer/parser.go @@ -0,0 +1,49 @@ +package analyzer + +import ( + "config-lsp/common" + "config-lsp/handlers/aliases/ast" + + ers "errors" +) + +func analyzeValuesAreValid( + p ast.AliasesParser, +) []common.LSPError { + errors := make([]common.LSPError, 0) + + it := p.Aliases.Iterator() + + for it.Next() { + entry := it.Value().(*ast.AliasEntry) + + if entry.Key == nil { + errors = append(errors, common.LSPError{ + Range: entry.Location, + Err: ers.New("A name is required"), + }) + + continue + } + + if entry.Separator == nil { + errors = append(errors, common.LSPError{ + Range: entry.Location, + Err: ers.New("The separator is required"), + }) + + continue + } + + if entry.Values == nil || len(entry.Values.Values) == 0 { + errors = append(errors, common.LSPError{ + Range: entry.Location, + Err: ers.New("A value is required"), + }) + + continue + } + } + + return errors +} diff --git a/handlers/aliases/ast/aliases.go b/handlers/aliases/ast/aliases.go index 7fda400..5f40b4d 100644 --- a/handlers/aliases/ast/aliases.go +++ b/handlers/aliases/ast/aliases.go @@ -1,7 +1,8 @@ -package tree +package ast import ( "config-lsp/common" + "github.com/emirpasic/gods/maps/treemap" ) // Procedure @@ -34,6 +35,6 @@ type AliasEntry struct { } type AliasesParser struct { - Aliases map[uint32]*AliasEntry + Aliases *treemap.Map CommentLines map[uint32]struct{} } diff --git a/handlers/aliases/ast/handler.go b/handlers/aliases/ast/handler.go index c6545c1..bd41296 100644 --- a/handlers/aliases/ast/handler.go +++ b/handlers/aliases/ast/handler.go @@ -1 +1 @@ -package tree +package ast diff --git a/handlers/aliases/ast/listener.go b/handlers/aliases/ast/listener.go index 65a8315..aee0c7d 100644 --- a/handlers/aliases/ast/listener.go +++ b/handlers/aliases/ast/listener.go @@ -1,4 +1,4 @@ -package tree +package ast import ( "config-lsp/common" @@ -23,16 +23,21 @@ func (s *aliasesParserListener) EnterEntry(ctx *parser.EntryContext) { location := common.CharacterRangeFromCtx(ctx.BaseParserRuleContext) location.ChangeBothLines(s.aliasContext.line) - s.Parser.Aliases[location.Start.Line] = &AliasEntry{ - Location: location, - } + s.Parser.Aliases.Put( + location.Start.Line, + &AliasEntry{ + Location: location, + }, + ) } func (s *aliasesParserListener) EnterSeparator(ctx *parser.SeparatorContext) { location := common.CharacterRangeFromCtx(ctx.BaseParserRuleContext) location.ChangeBothLines(s.aliasContext.line) - entry := s.Parser.Aliases[location.Start.Line] + rawEntry, _ := s.Parser.Aliases.Get(location.Start.Line) + entry := rawEntry.(*AliasEntry) + entry.Separator = &location } @@ -40,7 +45,9 @@ func (s *aliasesParserListener) EnterKey(ctx *parser.KeyContext) { location := common.CharacterRangeFromCtx(ctx.BaseParserRuleContext) location.ChangeBothLines(s.aliasContext.line) - entry := s.Parser.Aliases[location.Start.Line] + rawEntry, _ := s.Parser.Aliases.Get(location.Start.Line) + entry := rawEntry.(*AliasEntry) + entry.Key = &AliasKey{ Location: location, Value: ctx.GetText(), @@ -51,7 +58,9 @@ func (s *aliasesParserListener) EnterValues(ctx *parser.ValuesContext) { location := common.CharacterRangeFromCtx(ctx.BaseParserRuleContext) location.ChangeBothLines(s.aliasContext.line) - entry := s.Parser.Aliases[location.Start.Line] + rawEntry, _ := s.Parser.Aliases.Get(location.Start.Line) + entry := rawEntry.(*AliasEntry) + entry.Values = &AliasValues{ Location: location, Values: make([]AliasValueInterface, 0, 5), @@ -71,7 +80,9 @@ func (s *aliasesParserListener) EnterUser(ctx *parser.UserContext) { }, } - entry := s.Parser.Aliases[location.Start.Line] + rawEntry, _ := s.Parser.Aliases.Get(location.Start.Line) + entry := rawEntry.(*AliasEntry) + entry.Values.Values = append(entry.Values.Values, user) } @@ -79,9 +90,12 @@ func (s *aliasesParserListener) EnterFile(ctx *parser.FileContext) { location := common.CharacterRangeFromCtx(ctx.BaseParserRuleContext) location.ChangeBothLines(s.aliasContext.line) + rawEntry, _ := s.Parser.Aliases.Get(location.Start.Line) + entry := rawEntry.(*AliasEntry) + if s.aliasContext.currentIncludeIndex != nil { // This `file` is inside an `include`, so we need to set the path on the include - values := s.Parser.Aliases[location.Start.Line].Values + values := entry.Values rawValue := values.Values[*s.aliasContext.currentIncludeIndex] // Set the path @@ -107,7 +121,6 @@ func (s *aliasesParserListener) EnterFile(ctx *parser.FileContext) { Path: path(ctx.GetText()), } - entry := s.Parser.Aliases[location.Start.Line] entry.Values.Values = append(entry.Values.Values, file) } @@ -123,7 +136,9 @@ func (s *aliasesParserListener) EnterCommand(ctx *parser.CommandContext) { Command: ctx.GetText()[1:], } - entry := s.Parser.Aliases[location.Start.Line] + rawEntry, _ := s.Parser.Aliases.Get(location.Start.Line) + entry := rawEntry.(*AliasEntry) + entry.Values.Values = append(entry.Values.Values, command) } @@ -138,7 +153,9 @@ func (s *aliasesParserListener) EnterInclude(ctx *parser.IncludeContext) { }, } - entry := s.Parser.Aliases[location.Start.Line] + rawEntry, _ := s.Parser.Aliases.Get(location.Start.Line) + entry := rawEntry.(*AliasEntry) + entry.Values.Values = append(entry.Values.Values, include) index := uint32(len(entry.Values.Values) - 1) @@ -156,7 +173,9 @@ func (s *aliasesParserListener) EnterEmail(ctx *parser.EmailContext) { }, } - entry := s.Parser.Aliases[location.Start.Line] + rawEntry, _ := s.Parser.Aliases.Get(location.Start.Line) + entry := rawEntry.(*AliasEntry) + entry.Values.Values = append(entry.Values.Values, email) } diff --git a/handlers/aliases/ast/parser.go b/handlers/aliases/ast/parser.go index a31971c..2abe986 100644 --- a/handlers/aliases/ast/parser.go +++ b/handlers/aliases/ast/parser.go @@ -1,11 +1,14 @@ -package tree +package ast import ( "config-lsp/common" "config-lsp/handlers/aliases/parser" "config-lsp/utils" - "github.com/antlr4-go/antlr/v4" "regexp" + + "github.com/antlr4-go/antlr/v4" + "github.com/emirpasic/gods/maps/treemap" + gods "github.com/emirpasic/gods/utils" ) func NewAliasesParser() AliasesParser { @@ -17,7 +20,7 @@ func NewAliasesParser() AliasesParser { func (p *AliasesParser) Clear() { p.CommentLines = make(map[uint32]struct{}) - p.Aliases = make(map[uint32]*AliasEntry) + p.Aliases = treemap.NewWith(gods.UInt32Comparator) } var commentPattern = regexp.MustCompile(`^\s*#.*$`) diff --git a/handlers/aliases/ast/parser_test.go b/handlers/aliases/ast/parser_test.go index 9ae4de5..9ad9165 100644 --- a/handlers/aliases/ast/parser_test.go +++ b/handlers/aliases/ast/parser_test.go @@ -1,4 +1,4 @@ -package tree +package ast import ( "config-lsp/utils" @@ -19,15 +19,17 @@ postmaster: root t.Fatalf("Expected no errors, got %v", errors) } - if !(len(parser.Aliases) == 1) { - t.Fatalf("Expected 1 alias, got %v", len(parser.Aliases)) + if !(parser.Aliases.Size() == 1) { + t.Fatalf("Expected 1 alias, got %v", parser.Aliases.Size()) } - if !(parser.Aliases[0].Key.Value == "postmaster") { - t.Fatalf("Expected key to be 'postmaster', got %v", parser.Aliases[1].Key.Value) + rawEntry, _ := parser.Aliases.Get(uint32(0)) + entry := rawEntry.(*AliasEntry) + if !(entry.Key.Value == "postmaster") { + t.Fatalf("Expected key to be 'postmaster', got %v", entry.Key.Value) } - userValue := parser.Aliases[0].Values.Values[0].(AliasValueUser) + userValue := entry.Values.Values[0].(AliasValueUser) if !(userValue.Value == "root") { t.Fatalf("Expected value to be 'root', got %v", userValue.Value) } @@ -51,19 +53,23 @@ michel: raiks@example.com t.Fatalf("Expected no errors, got %v", errors) } - if !(len(parser.Aliases) == 2) { - t.Fatalf("Expected 2 aliases, got %v", len(parser.Aliases)) + if !(parser.Aliases.Size() == 2) { + t.Fatalf("Expected 2 aliases, got %v", parser.Aliases.Size()) } - if !(parser.Aliases[0].Key.Value == "heinz") { - t.Fatalf("Expected key to be 'heinz', got %v", parser.Aliases[1].Key.Value) + rawEntry, _ := parser.Aliases.Get(uint32(0)) + entry := rawEntry.(*AliasEntry) + if !(entry.Key.Value == "heinz") { + t.Fatalf("Expected key to be 'heinz', got %v", entry.Key.Value) } - if !(parser.Aliases[1].Key.Value == "michel") { - t.Fatalf("Expected key to be 'michel', got %v", parser.Aliases[1].Key.Value) + rawEntry, _ = parser.Aliases.Get(uint32(1)) + entry = rawEntry.(*AliasEntry) + if !(entry.Key.Value == "michel") { + t.Fatalf("Expected key to be 'michel', got %v", entry.Key.Value) } - emailValue := parser.Aliases[1].Values.Values[0].(AliasValueEmail) + emailValue := entry.Values.Values[0].(AliasValueEmail) if !(emailValue.Value == "raiks@example.com") { t.Fatalf("Expected value to be 'raiks@example.com', got %v", emailValue.Value) @@ -83,15 +89,17 @@ luke: :include:/etc/other_aliases t.Fatalf("Expected no errors, got %v", errors) } - if !(len(parser.Aliases) == 1) { - t.Fatalf("Expected 1 alias, got %v", len(parser.Aliases)) + if !(parser.Aliases.Size() == 1) { + t.Fatalf("Expected 1 alias, got %v", parser.Aliases.Size()) } - if !(parser.Aliases[0].Key.Value == "luke") { - t.Fatalf("Expected key to be 'luke', got %v", parser.Aliases[1].Key.Value) + rawEntry, _ := parser.Aliases.Get(uint32(0)) + entry := rawEntry.(*AliasEntry) + if !(entry.Key.Value == "luke") { + t.Fatalf("Expected key to be 'luke', got %v", entry.Key.Value) } - includeValue := parser.Aliases[0].Values.Values[0].(AliasValueInclude) + includeValue := entry.Values.Values[0].(AliasValueInclude) if !(includeValue.Path.Path == "/etc/other_aliases") { t.Fatalf("Expected path to be '/etc/other_aliases', got %v", includeValue.Path.Path) @@ -105,3 +113,39 @@ luke: :include:/etc/other_aliases t.Fatalf("Expected path location to be 15-33, got %v-%v", includeValue.Path.Location.Start.Character, includeValue.Path.Location.End.Character) } } + +func TestInvalidWithEmptyValuesWorks( + t *testing.T, +) { + input := utils.Dedent(` +luke: +`) + parser := NewAliasesParser() + errors := parser.Parse(input) + + if len(errors) == 0 { + t.Fatalf("Expected 1 error, got %v", errors) + } + + if !(errors[0].Range.Start.Character == 5 && errors[0].Range.End.Character == 5) { + t.Fatalf("Expected error to be at 6, got %v", errors[0].Range.Start.Character) + } +} + +func TestInvalidWithEmptyKeyWorks( + t *testing.T, +) { + input := utils.Dedent(` +: root +`) + parser := NewAliasesParser() + errors := parser.Parse(input) + + if len(errors) == 0 { + t.Fatalf("Expected 1 error, got %v", errors) + } + + if !(errors[0].Range.Start.Character == 0 && errors[0].Range.End.Character == 0) { + t.Fatalf("Expected error to be at 0, got %v", errors[0].Range.Start.Character) + } +} diff --git a/handlers/aliases/ast/values.go b/handlers/aliases/ast/values.go index 1233c83..ed6ab59 100644 --- a/handlers/aliases/ast/values.go +++ b/handlers/aliases/ast/values.go @@ -1,4 +1,4 @@ -package tree +package ast import ( "config-lsp/common" diff --git a/handlers/aliases/indexes/indexes.go b/handlers/aliases/indexes/indexes.go new file mode 100644 index 0000000..345c212 --- /dev/null +++ b/handlers/aliases/indexes/indexes.go @@ -0,0 +1,43 @@ +package indexes + +import ( + "config-lsp/common" + "config-lsp/handlers/aliases/ast" + "config-lsp/handlers/aliases/shared" + "strings" +) + +type AliasesIndexes struct { + Keys map[string]*ast.AliasKey +} + +func CreateIndexes(parser ast.AliasesParser) (AliasesIndexes, []common.LSPError) { + errors := make([]common.LSPError, 0) + indexes := &AliasesIndexes{ + Keys: make(map[string]*ast.AliasKey), + } + + it := parser.Aliases.Iterator() + + for it.Next() { + entry := it.Value().(*ast.AliasEntry) + + normalizedAlias := strings.ToLower(entry.Key.Value) + + if existingEntry, found := indexes.Keys[normalizedAlias]; found { + errors = append(errors, common.LSPError{ + Range: entry.Location, + Err: shared.DuplicateKeyEntry{ + AlreadyFoundAt: existingEntry.Location.Start.Line, + Key: entry.Key.Value, + }, + }) + + continue + } + + indexes.Keys[normalizedAlias] = entry.Key + } + + return *indexes, errors +} diff --git a/handlers/aliases/shared.go b/handlers/aliases/shared.go new file mode 100644 index 0000000..86ef6be --- /dev/null +++ b/handlers/aliases/shared.go @@ -0,0 +1,15 @@ +package aliases + +import ( + "config-lsp/handlers/aliases/ast" + "config-lsp/handlers/aliases/indexes" + + protocol "github.com/tliron/glsp/protocol_3_16" +) + +type HostsDocument struct { + Parser *ast.AliasesParser + Indexes *indexes.AliasesIndexes +} + +var DocumentParserMap = map[protocol.DocumentUri]*HostsDocument{} diff --git a/handlers/aliases/shared/errors.go b/handlers/aliases/shared/errors.go new file mode 100644 index 0000000..92f2dce --- /dev/null +++ b/handlers/aliases/shared/errors.go @@ -0,0 +1,12 @@ +package shared + +import "fmt" + +type DuplicateKeyEntry struct { + AlreadyFoundAt uint32 + Key string +} + +func (d DuplicateKeyEntry) Error() string { + return fmt.Sprintf("Alias '%s' already defined on line %d", d.Key, d.AlreadyFoundAt+1) +} diff --git a/handlers/fstab/fstab_test.go b/handlers/fstab/fstab_test.go index 52585d3..4e6d47d 100644 --- a/handlers/fstab/fstab_test.go +++ b/handlers/fstab/fstab_test.go @@ -13,6 +13,8 @@ var sampleInvalidOptionsExample = ` LABEL=test /mnt/test btrfs subvol=backup,fat=32 0 0 ` +// TODO: Improve `entries`, sometimes the indexes seem +// to be wrong. Use a treemap instead of a map. func TestValidBasicExample(t *testing.T) { // Arrange parser := FstabParser{} diff --git a/handlers/hosts/analyzer/double_ips.go b/handlers/hosts/analyzer/double_ips.go index 237d5a6..4fcd2f4 100644 --- a/handlers/hosts/analyzer/double_ips.go +++ b/handlers/hosts/analyzer/double_ips.go @@ -4,7 +4,9 @@ import ( "config-lsp/common" "config-lsp/handlers/hosts" "config-lsp/handlers/hosts/shared" + "config-lsp/utils" "net" + "slices" ) func ipToString(ip net.IPAddr) string { @@ -17,7 +19,15 @@ func analyzeDoubleIPs(d *hosts.HostsDocument) []common.LSPError { d.Indexes.DoubleIPs = make(map[uint32]shared.DuplicateIPDeclaration) - for lineNumber, entry := range d.Parser.Tree.Entries { + // TODO: `range` does not seem to properly + // iterate in a sorted way. + // Instead, use a treemap + lines := utils.KeysOfMap(d.Parser.Tree.Entries) + slices.Sort(lines) + + for _, lineNumber := range lines { + entry := d.Parser.Tree.Entries[lineNumber] + if entry.IPAddress != nil { key := ipToString(entry.IPAddress.Value)