mirror of
https://github.com/Myzel394/config-lsp.git
synced 2025-06-19 15:35:28 +02:00
feat(aliases): Add duplicates analyzer
This commit is contained in:
parent
bbe083621e
commit
d7f8b905a4
@ -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,
|
||||||
|
193
handlers/aliases/analyzer/double_values.go
Normal file
193
handlers/aliases/analyzer/double_values.go
Normal 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
|
||||||
|
}
|
119
handlers/aliases/analyzer/double_values_test.go
Normal file
119
handlers/aliases/analyzer/double_values_test.go
Normal 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)
|
||||||
|
}
|
||||||
|
}
|
@ -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)
|
||||||
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user