fix(aliases): Improve aliases

This commit is contained in:
Myzel394 2024-08-31 19:18:57 +02:00
parent 5cbf5be8ad
commit 31faa9a96d
No known key found for this signature in database
GPG Key ID: DEC4AAB876F73185
15 changed files with 253 additions and 39 deletions

1
go.mod
View File

@ -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

2
go.sum
View File

@ -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=

View File

@ -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
}

View File

@ -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
}

View File

@ -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{}
}

View File

@ -1 +1 @@
package tree
package ast

View File

@ -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{
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)
}

View File

@ -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*#.*$`)

View File

@ -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)
}
}

View File

@ -1,4 +1,4 @@
package tree
package ast
import (
"config-lsp/common"

View File

@ -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
}

View File

@ -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{}

View File

@ -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)
}

View File

@ -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{}

View File

@ -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)