feat(aliases): Add duplicates analyzer

This commit is contained in:
Myzel394 2024-09-07 14:35:47 +02:00
parent bbe083621e
commit d7f8b905a4
No known key found for this signature in database
GPG Key ID: DEC4AAB876F73185
4 changed files with 338 additions and 14 deletions

View File

@ -27,6 +27,7 @@ func Analyze(
} }
errors = append(errors, analyzeContainsRequiredKeys(*d)...) errors = append(errors, analyzeContainsRequiredKeys(*d)...)
errors = append(errors, analyzeContainsNoDoubleValues(*d.Parser)...)
return utils.Map( return utils.Map(
errors, errors,

View File

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

View File

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

View File

@ -11,6 +11,7 @@ import (
type AliasValueInterface interface { type AliasValueInterface interface {
GetAliasValue() AliasValue GetAliasValue() AliasValue
GetStructName() string
} }
func (a AliasValue) String() string { func (a AliasValue) String() string {
@ -21,6 +22,10 @@ func (a AliasValue) GetAliasValue() AliasValue {
return a return a
} }
func (a AliasValue) GetStructName() string {
return "AliasValue"
}
type AliasValue struct { type AliasValue struct {
Location common.LocationRange Location common.LocationRange
Value string Value string
@ -30,6 +35,10 @@ type AliasValueUser struct {
AliasValue AliasValue
} }
func (a AliasValueUser) GetStructName() string {
return "AliasValueUser"
}
type path string type path string
type AliasValueFile struct { type AliasValueFile struct {
@ -37,13 +46,8 @@ type AliasValueFile struct {
Path path Path path
} }
func (a AliasValueFile) CheckIsValid() []common.LSPError { func (a AliasValueFile) GetStructName() string {
return utils.Map( return "AliasValueFile"
fields.PathField.CheckIsValid(string(a.Path)),
func(invalidValue *docvalues.InvalidValue) common.LSPError {
return docvalues.LSPErrorFromInvalidValue(a.Location.Start.Line, *invalidValue)
},
)
} }
type AliasValueCommand struct { type AliasValueCommand struct {
@ -51,13 +55,8 @@ type AliasValueCommand struct {
Command string Command string
} }
func (a AliasValueCommand) CheckIsValid() []common.LSPError { func (a AliasValueCommand) GetStructName() string {
return utils.Map( return "AliasValueCommand"
fields.CommandField.CheckIsValid(a.Command),
func(invalidValue *docvalues.InvalidValue) common.LSPError {
return docvalues.LSPErrorFromInvalidValue(a.Location.Start.Line, *invalidValue)
},
)
} }
type AliasValueIncludePath struct { type AliasValueIncludePath struct {
@ -79,6 +78,10 @@ func (a AliasValueInclude) CheckIsValid() []common.LSPError {
) )
} }
func (a AliasValueInclude) GetStructName() string {
return "AliasValueInclude"
}
type AliasValueEmail struct { type AliasValueEmail struct {
AliasValue AliasValue
} }
@ -92,6 +95,10 @@ func (a AliasValueEmail) CheckIsValid() []common.LSPError {
) )
} }
func (a AliasValueEmail) GetStructName() string {
return "AliasValueEmail"
}
type AliasValueError struct { type AliasValueError struct {
AliasValue AliasValue
@ -103,6 +110,10 @@ type AliasValueErrorCode struct {
AliasValue AliasValue
} }
func (a AliasValueError) GetStructName() string {
return "AliasValueError"
}
func (a AliasValueErrorCode) ErrorCodeAsInt() uint16 { func (a AliasValueErrorCode) ErrorCodeAsInt() uint16 {
code, err := strconv.Atoi(a.Value) code, err := strconv.Atoi(a.Value)