mirror of
https://github.com/Myzel394/config-lsp.git
synced 2025-06-18 23:15:26 +02:00
refactor(sshd_config): Use analyzer context instead of LSPError
This commit is contained in:
parent
15b732e136
commit
e9fa7a651b
@ -8,39 +8,45 @@ import (
|
|||||||
protocol "github.com/tliron/glsp/protocol_3_16"
|
protocol "github.com/tliron/glsp/protocol_3_16"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
type analyzerContext struct {
|
||||||
|
document *sshdconfig.SSHDDocument
|
||||||
|
diagnostics []protocol.Diagnostic
|
||||||
|
}
|
||||||
|
|
||||||
func Analyze(
|
func Analyze(
|
||||||
d *sshdconfig.SSHDDocument,
|
d *sshdconfig.SSHDDocument,
|
||||||
) []protocol.Diagnostic {
|
) []protocol.Diagnostic {
|
||||||
errors := analyzeStructureIsValid(d)
|
ctx := &analyzerContext{
|
||||||
|
document: d,
|
||||||
|
diagnostics: make([]protocol.Diagnostic, 0),
|
||||||
|
}
|
||||||
|
|
||||||
if len(errors) > 0 {
|
analyzeStructureIsValid(ctx)
|
||||||
return common.ErrsToDiagnostics(errors)
|
|
||||||
|
if len(ctx.diagnostics) > 0 {
|
||||||
|
return ctx.diagnostics
|
||||||
}
|
}
|
||||||
|
|
||||||
i, indexErrors := indexes.CreateIndexes(*d.Config)
|
i, indexErrors := indexes.CreateIndexes(*d.Config)
|
||||||
|
|
||||||
d.Indexes = i
|
d.Indexes = i
|
||||||
|
|
||||||
errors = append(errors, indexErrors...)
|
if len(indexErrors) > 0 {
|
||||||
|
return common.ErrsToDiagnostics(indexErrors)
|
||||||
if len(errors) > 0 {
|
|
||||||
return common.ErrsToDiagnostics(errors)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
includeErrors := analyzeIncludeValues(d)
|
analyzeIncludeValues(ctx)
|
||||||
|
|
||||||
if len(includeErrors) > 0 {
|
if len(ctx.diagnostics) == 0 {
|
||||||
errors = append(errors, includeErrors...)
|
|
||||||
} else {
|
|
||||||
for _, include := range d.Indexes.Includes {
|
for _, include := range d.Indexes.Includes {
|
||||||
for _, value := range include.Values {
|
for _, value := range include.Values {
|
||||||
for _, path := range value.Paths {
|
for _, path := range value.Paths {
|
||||||
_, err := parseFile(string(path))
|
_, err := parseFile(string(path))
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
errors = append(errors, common.LSPError{
|
ctx.diagnostics = append(ctx.diagnostics, protocol.Diagnostic{
|
||||||
Range: value.LocationRange,
|
Range: value.LocationRange.ToLSPRange(),
|
||||||
Err: err,
|
Message: err.Error(),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -48,11 +54,7 @@ func Analyze(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
errors = append(errors, analyzeMatchBlocks(d)...)
|
analyzeMatchBlocks(ctx)
|
||||||
|
|
||||||
if len(errors) > 0 {
|
return ctx.diagnostics
|
||||||
return common.ErrsToDiagnostics(errors)
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
}
|
||||||
|
@ -12,31 +12,30 @@ import (
|
|||||||
"path"
|
"path"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"regexp"
|
"regexp"
|
||||||
|
|
||||||
|
protocol "github.com/tliron/glsp/protocol_3_16"
|
||||||
)
|
)
|
||||||
|
|
||||||
var whitespacePattern = regexp.MustCompile(`\S+`)
|
var whitespacePattern = regexp.MustCompile(`\S+`)
|
||||||
|
|
||||||
func analyzeIncludeValues(
|
func analyzeIncludeValues(
|
||||||
d *sshdconfig.SSHDDocument,
|
ctx *analyzerContext,
|
||||||
) []common.LSPError {
|
) {
|
||||||
errs := make([]common.LSPError, 0)
|
for _, include := range ctx.document.Indexes.Includes {
|
||||||
|
|
||||||
for _, include := range d.Indexes.Includes {
|
|
||||||
for _, value := range include.Values {
|
for _, value := range include.Values {
|
||||||
validPaths, err := createIncludePaths(value.Value)
|
validPaths, err := createIncludePaths(value.Value)
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
errs = append(errs, common.LSPError{
|
ctx.diagnostics = append(ctx.diagnostics, protocol.Diagnostic{
|
||||||
Range: value.LocationRange,
|
Range: value.LocationRange.ToLSPRange(),
|
||||||
Err: err,
|
Message: err.Error(),
|
||||||
|
Severity: &common.SeverityError,
|
||||||
})
|
})
|
||||||
} else {
|
} else {
|
||||||
value.Paths = validPaths
|
value.Paths = validPaths
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return errs
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func createIncludePaths(
|
func createIncludePaths(
|
||||||
|
@ -3,27 +3,26 @@ package analyzer
|
|||||||
import (
|
import (
|
||||||
"config-lsp/common"
|
"config-lsp/common"
|
||||||
docvalues "config-lsp/doc-values"
|
docvalues "config-lsp/doc-values"
|
||||||
sshdconfig "config-lsp/handlers/sshd_config"
|
|
||||||
"config-lsp/handlers/sshd_config/fields"
|
"config-lsp/handlers/sshd_config/fields"
|
||||||
"config-lsp/handlers/sshd_config/match-parser"
|
"config-lsp/handlers/sshd_config/match-parser"
|
||||||
"config-lsp/utils"
|
"config-lsp/utils"
|
||||||
"errors"
|
|
||||||
"fmt"
|
"fmt"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
|
protocol "github.com/tliron/glsp/protocol_3_16"
|
||||||
)
|
)
|
||||||
|
|
||||||
func analyzeMatchBlocks(
|
func analyzeMatchBlocks(
|
||||||
d *sshdconfig.SSHDDocument,
|
ctx *analyzerContext,
|
||||||
) []common.LSPError {
|
) {
|
||||||
errs := make([]common.LSPError, 0)
|
for matchBlock, options := range ctx.document.Indexes.AllOptionsPerName["Match"] {
|
||||||
|
|
||||||
for matchBlock, options := range d.Indexes.AllOptionsPerName["Match"] {
|
|
||||||
option := options[0]
|
option := options[0]
|
||||||
// Check if the match block has filled out all fields
|
// Check if the match block has filled out all fields
|
||||||
if matchBlock == nil || matchBlock.MatchValue == nil || len(matchBlock.MatchValue.Entries) == 0 {
|
if matchBlock == nil || matchBlock.MatchValue == nil || len(matchBlock.MatchValue.Entries) == 0 {
|
||||||
errs = append(errs, common.LSPError{
|
ctx.diagnostics = append(ctx.diagnostics, protocol.Diagnostic{
|
||||||
Range: option.LocationRange,
|
Range: option.ToLSPRange(),
|
||||||
Err: errors.New("A match expression is required"),
|
Message: "A match expression is required",
|
||||||
|
Severity: &common.SeverityError,
|
||||||
})
|
})
|
||||||
|
|
||||||
continue
|
continue
|
||||||
@ -31,46 +30,48 @@ func analyzeMatchBlocks(
|
|||||||
|
|
||||||
for _, entry := range matchBlock.MatchValue.Entries {
|
for _, entry := range matchBlock.MatchValue.Entries {
|
||||||
if entry.Values == nil {
|
if entry.Values == nil {
|
||||||
errs = append(errs, common.LSPError{
|
ctx.diagnostics = append(ctx.diagnostics, protocol.Diagnostic{
|
||||||
Range: entry.LocationRange,
|
Range: entry.ToLSPRange(),
|
||||||
Err: errors.New(fmt.Sprintf("A value for %s is required", entry.Criteria.Type)),
|
Message: fmt.Sprintf("A value for %s is required", entry.Criteria.Type),
|
||||||
|
Severity: &common.SeverityError,
|
||||||
})
|
})
|
||||||
|
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
errs = append(errs, analyzeMatchValuesContainsPositiveValue(entry.Values)...)
|
analyzeMatchValuesContainsPositiveValue(ctx, entry.Values)
|
||||||
|
|
||||||
for _, value := range entry.Values.Values {
|
for _, value := range entry.Values.Values {
|
||||||
errs = append(errs, analyzeMatchValueNegation(value)...)
|
analyzeMatchValueNegation(ctx, value)
|
||||||
errs = append(errs, analyzeMatchValueIsValid(value, entry.Criteria.Type)...)
|
analyzeMatchValueIsValid(ctx, value, entry.Criteria.Type)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check if match blocks are not empty
|
// Check if match blocks are not empty
|
||||||
if matchBlock.Options.Size() == 0 {
|
if matchBlock.Options.Size() == 0 {
|
||||||
errs = append(errs, common.LSPError{
|
ctx.diagnostics = append(ctx.diagnostics, protocol.Diagnostic{
|
||||||
Range: option.LocationRange,
|
Range: option.ToLSPRange(),
|
||||||
Err: errors.New("This match block is empty"),
|
Message: "This match block is empty",
|
||||||
|
Severity: &common.SeverityInformation,
|
||||||
|
Tags: []protocol.DiagnosticTag{
|
||||||
|
protocol.DiagnosticTagUnnecessary,
|
||||||
|
},
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return errs
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func analyzeMatchValueNegation(
|
func analyzeMatchValueNegation(
|
||||||
|
ctx *analyzerContext,
|
||||||
value *matchparser.MatchValue,
|
value *matchparser.MatchValue,
|
||||||
) []common.LSPError {
|
) {
|
||||||
errs := make([]common.LSPError, 0)
|
|
||||||
|
|
||||||
positionsAsList := utils.AllIndexes(value.Value.Raw, "!")
|
positionsAsList := utils.AllIndexes(value.Value.Raw, "!")
|
||||||
positions := utils.SliceToMap(positionsAsList, struct{}{})
|
positions := utils.SliceToMap(positionsAsList, struct{}{})
|
||||||
|
|
||||||
delete(positions, 0)
|
delete(positions, 0)
|
||||||
|
|
||||||
for position := range positions {
|
for position := range positions {
|
||||||
errs = append(errs, common.LSPError{
|
ctx.diagnostics = append(ctx.diagnostics, protocol.Diagnostic{
|
||||||
Range: common.LocationRange{
|
Range: common.LocationRange{
|
||||||
Start: common.Location{
|
Start: common.Location{
|
||||||
Line: value.Start.Line,
|
Line: value.Start.Line,
|
||||||
@ -80,19 +81,19 @@ func analyzeMatchValueNegation(
|
|||||||
Line: value.End.Line,
|
Line: value.End.Line,
|
||||||
Character: uint32(position) + value.End.Character,
|
Character: uint32(position) + value.End.Character,
|
||||||
},
|
},
|
||||||
},
|
}.ToLSPRange(),
|
||||||
Err: errors.New("The negation operator (!) may only occur at the beginning of a value"),
|
Message: "The negation operator (!) may only occur at the beginning of a value",
|
||||||
|
Severity: &common.SeverityError,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
return errs
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func analyzeMatchValuesContainsPositiveValue(
|
func analyzeMatchValuesContainsPositiveValue(
|
||||||
|
ctx *analyzerContext,
|
||||||
values *matchparser.MatchValues,
|
values *matchparser.MatchValues,
|
||||||
) []common.LSPError {
|
) {
|
||||||
if len(values.Values) == 0 {
|
if len(values.Values) == 0 {
|
||||||
return nil
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
containsPositive := false
|
containsPositive := false
|
||||||
@ -105,42 +106,34 @@ func analyzeMatchValuesContainsPositiveValue(
|
|||||||
}
|
}
|
||||||
|
|
||||||
if !containsPositive {
|
if !containsPositive {
|
||||||
return []common.LSPError{
|
ctx.diagnostics = append(ctx.diagnostics, protocol.Diagnostic{
|
||||||
{
|
Range: values.LocationRange.ToLSPRange(),
|
||||||
Range: values.LocationRange,
|
Message: "At least one positive value is required. A negated match will never produce a positive result by itself",
|
||||||
Err: errors.New("At least one positive value is required. A negated match will never produce a positive result by itself"),
|
Severity: &common.SeverityError,
|
||||||
},
|
})
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func analyzeMatchValueIsValid(
|
func analyzeMatchValueIsValid(
|
||||||
|
ctx *analyzerContext,
|
||||||
value *matchparser.MatchValue,
|
value *matchparser.MatchValue,
|
||||||
criteria matchparser.MatchCriteriaType,
|
criteria matchparser.MatchCriteriaType,
|
||||||
) []common.LSPError {
|
) {
|
||||||
errs := make([]common.LSPError, 0)
|
|
||||||
|
|
||||||
if value.Value.Raw == "" {
|
if value.Value.Raw == "" {
|
||||||
return errs
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
docOption := fields.MatchValueFieldMap[criteria]
|
docOption := fields.MatchValueFieldMap[criteria]
|
||||||
invalidValues := docOption.DeprecatedCheckIsValid(value.Value.Raw)
|
invalidValues := docOption.DeprecatedCheckIsValid(value.Value.Raw)
|
||||||
|
|
||||||
errs = append(
|
for _, invalidValue := range invalidValues {
|
||||||
errs,
|
err := docvalues.LSPErrorFromInvalidValue(value.Start.Line, *invalidValue)
|
||||||
utils.Map(
|
err.ShiftCharacter(value.Start.Character)
|
||||||
invalidValues,
|
|
||||||
func(invalidValue *docvalues.InvalidValue) common.LSPError {
|
|
||||||
err := docvalues.LSPErrorFromInvalidValue(value.Start.Line, *invalidValue)
|
|
||||||
err.ShiftCharacter(value.Start.Character)
|
|
||||||
|
|
||||||
return err
|
ctx.diagnostics = append(ctx.diagnostics, protocol.Diagnostic{
|
||||||
},
|
Range: err.Range.ToLSPRange(),
|
||||||
)...,
|
Message: err.Err.Error(),
|
||||||
)
|
Severity: &common.SeverityError,
|
||||||
|
})
|
||||||
return errs
|
}
|
||||||
}
|
}
|
||||||
|
@ -6,6 +6,8 @@ import (
|
|||||||
"config-lsp/handlers/sshd_config/indexes"
|
"config-lsp/handlers/sshd_config/indexes"
|
||||||
"config-lsp/utils"
|
"config-lsp/utils"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
|
protocol "github.com/tliron/glsp/protocol_3_16"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestEmptyMatchBlocksMakesErrors(
|
func TestEmptyMatchBlocksMakesErrors(
|
||||||
@ -22,6 +24,42 @@ Match User root
|
|||||||
t.Fatalf("Parse error: %v", errors)
|
t.Fatalf("Parse error: %v", errors)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
i, errors := indexes.CreateIndexes(*c)
|
||||||
|
|
||||||
|
if len(errors) > 0 {
|
||||||
|
t.Fatalf("Index error: %v", errors)
|
||||||
|
}
|
||||||
|
|
||||||
|
d := &sshdconfig.SSHDDocument{
|
||||||
|
Config: c,
|
||||||
|
Indexes: i,
|
||||||
|
}
|
||||||
|
ctx := &analyzerContext{
|
||||||
|
document: d,
|
||||||
|
diagnostics: make([]protocol.Diagnostic, 0),
|
||||||
|
}
|
||||||
|
|
||||||
|
analyzeMatchBlocks(ctx)
|
||||||
|
|
||||||
|
if !(len(ctx.diagnostics) == 1) {
|
||||||
|
t.Errorf("Expected 1 error, got %v", len(ctx.diagnostics))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestContainsOnlyNegativeValues(
|
||||||
|
t *testing.T,
|
||||||
|
) {
|
||||||
|
input := utils.Dedent(`
|
||||||
|
PermitRootLogin yes
|
||||||
|
Match User !root,!admin
|
||||||
|
`)
|
||||||
|
c := ast.NewSSHDConfig()
|
||||||
|
errors := c.Parse(input)
|
||||||
|
|
||||||
|
if len(errors) > 0 {
|
||||||
|
t.Fatalf("Parse error: %v", errors)
|
||||||
|
}
|
||||||
|
|
||||||
indexes, errors := indexes.CreateIndexes(*c)
|
indexes, errors := indexes.CreateIndexes(*c)
|
||||||
|
|
||||||
if len(errors) > 0 {
|
if len(errors) > 0 {
|
||||||
@ -32,33 +70,16 @@ Match User root
|
|||||||
Config: c,
|
Config: c,
|
||||||
Indexes: indexes,
|
Indexes: indexes,
|
||||||
}
|
}
|
||||||
|
ctx := &analyzerContext{
|
||||||
errors = analyzeMatchBlocks(d)
|
document: d,
|
||||||
|
diagnostics: make([]protocol.Diagnostic, 0),
|
||||||
if !(len(errors) == 1) {
|
|
||||||
t.Errorf("Expected 1 error, got %v", len(errors))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestContainsOnlyNegativeValues(
|
|
||||||
t *testing.T,
|
|
||||||
) {
|
|
||||||
input := utils.Dedent(`
|
|
||||||
PermitRootLogin yes
|
|
||||||
Match User !root,!admin
|
|
||||||
`)
|
|
||||||
c := ast.NewSSHDConfig()
|
|
||||||
errors := c.Parse(input)
|
|
||||||
|
|
||||||
if len(errors) > 0 {
|
|
||||||
t.Fatalf("Parse error: %v", errors)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
_, matchBlock := c.FindOption(uint32(1))
|
_, matchBlock := c.FindOption(uint32(1))
|
||||||
errors = analyzeMatchValuesContainsPositiveValue(matchBlock.MatchValue.Entries[0].Values)
|
analyzeMatchValuesContainsPositiveValue(ctx, matchBlock.MatchValue.Entries[0].Values)
|
||||||
|
|
||||||
if !(len(errors) == 1) {
|
if !(len(ctx.diagnostics) == 1) {
|
||||||
t.Errorf("Expected 1 error, got %v", len(errors))
|
t.Errorf("Expected 1 error, got %v", len(ctx.diagnostics))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -87,11 +108,15 @@ Match User
|
|||||||
Config: c,
|
Config: c,
|
||||||
Indexes: i,
|
Indexes: i,
|
||||||
}
|
}
|
||||||
|
ctx := &analyzerContext{
|
||||||
|
document: d,
|
||||||
|
diagnostics: make([]protocol.Diagnostic, 0),
|
||||||
|
}
|
||||||
|
|
||||||
errors = analyzeMatchBlocks(d)
|
analyzeMatchBlocks(ctx)
|
||||||
|
|
||||||
if !(len(errors) == 1) {
|
if !(len(ctx.diagnostics) == 1) {
|
||||||
t.Errorf("Expected 1 error, got %v", len(errors))
|
t.Errorf("Expected 1 error, got %v", len(ctx.diagnostics))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -120,10 +145,14 @@ Match User
|
|||||||
Config: c,
|
Config: c,
|
||||||
Indexes: i,
|
Indexes: i,
|
||||||
}
|
}
|
||||||
|
ctx := &analyzerContext{
|
||||||
|
document: d,
|
||||||
|
diagnostics: make([]protocol.Diagnostic, 0),
|
||||||
|
}
|
||||||
|
|
||||||
errors = analyzeMatchBlocks(d)
|
analyzeMatchBlocks(ctx)
|
||||||
|
|
||||||
if !(len(errors) == 1) {
|
if !(len(ctx.diagnostics) == 1) {
|
||||||
t.Errorf("Expected 1 error, got %v", len(errors))
|
t.Errorf("Expected 1 error, got %v", len(ctx.diagnostics))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -3,119 +3,102 @@ package analyzer
|
|||||||
import (
|
import (
|
||||||
"config-lsp/common"
|
"config-lsp/common"
|
||||||
docvalues "config-lsp/doc-values"
|
docvalues "config-lsp/doc-values"
|
||||||
sshdconfig "config-lsp/handlers/sshd_config"
|
|
||||||
"config-lsp/handlers/sshd_config/ast"
|
"config-lsp/handlers/sshd_config/ast"
|
||||||
"config-lsp/handlers/sshd_config/fields"
|
"config-lsp/handlers/sshd_config/fields"
|
||||||
"config-lsp/utils"
|
|
||||||
"errors"
|
|
||||||
"fmt"
|
"fmt"
|
||||||
|
|
||||||
|
protocol "github.com/tliron/glsp/protocol_3_16"
|
||||||
)
|
)
|
||||||
|
|
||||||
func analyzeStructureIsValid(
|
func analyzeStructureIsValid(
|
||||||
d *sshdconfig.SSHDDocument,
|
ctx *analyzerContext,
|
||||||
) []common.LSPError {
|
) {
|
||||||
errs := make([]common.LSPError, 0)
|
it := ctx.document.Config.Options.Iterator()
|
||||||
it := d.Config.Options.Iterator()
|
|
||||||
|
|
||||||
for it.Next() {
|
for it.Next() {
|
||||||
entry := it.Value().(ast.SSHDEntry)
|
entry := it.Value().(ast.SSHDEntry)
|
||||||
|
|
||||||
switch entry.(type) {
|
switch entry.(type) {
|
||||||
case *ast.SSHDOption:
|
case *ast.SSHDOption:
|
||||||
errs = append(errs, checkOption(entry.(*ast.SSHDOption), false)...)
|
checkOption(ctx, entry.(*ast.SSHDOption), false)
|
||||||
case *ast.SSHDMatchBlock:
|
case *ast.SSHDMatchBlock:
|
||||||
matchBlock := entry.(*ast.SSHDMatchBlock)
|
matchBlock := entry.(*ast.SSHDMatchBlock)
|
||||||
errs = append(errs, checkMatchBlock(matchBlock)...)
|
checkMatchBlock(ctx, matchBlock)
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return errs
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func checkOption(
|
func checkOption(
|
||||||
|
ctx *analyzerContext,
|
||||||
option *ast.SSHDOption,
|
option *ast.SSHDOption,
|
||||||
isInMatchBlock bool,
|
isInMatchBlock bool,
|
||||||
) []common.LSPError {
|
) {
|
||||||
errs := make([]common.LSPError, 0)
|
|
||||||
|
|
||||||
if option.Key == nil {
|
if option.Key == nil {
|
||||||
return errs
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
errs = append(errs, checkIsUsingDoubleQuotes(option.Key.Value, option.Key.LocationRange)...)
|
checkIsUsingDoubleQuotes(ctx, option.Key.Value, option.Key.LocationRange)
|
||||||
errs = append(errs, checkQuotesAreClosed(option.Key.Value, option.Key.LocationRange)...)
|
checkQuotesAreClosed(ctx, option.Key.Value, option.Key.LocationRange)
|
||||||
|
|
||||||
docOption, found := fields.Options[option.Key.Key]
|
docOption, found := fields.Options[option.Key.Key]
|
||||||
|
|
||||||
if !found {
|
if !found {
|
||||||
errs = append(errs, common.LSPError{
|
ctx.diagnostics = append(ctx.diagnostics, protocol.Diagnostic{
|
||||||
Range: option.Key.LocationRange,
|
Range: option.Key.ToLSPRange(),
|
||||||
Err: errors.New(fmt.Sprintf("Unknown option: %s", option.Key.Key)),
|
Message: fmt.Sprintf("Unknown option: %s", option.Key.Key),
|
||||||
|
Severity: &common.SeverityError,
|
||||||
})
|
})
|
||||||
|
|
||||||
return errs
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if _, found := fields.MatchAllowedOptions[option.Key.Key]; !found && isInMatchBlock {
|
if _, found := fields.MatchAllowedOptions[option.Key.Key]; !found && isInMatchBlock {
|
||||||
errs = append(errs, common.LSPError{
|
ctx.diagnostics = append(ctx.diagnostics, protocol.Diagnostic{
|
||||||
Range: option.Key.LocationRange,
|
Range: option.Key.ToLSPRange(),
|
||||||
Err: errors.New(fmt.Sprintf("Option '%s' is not allowed inside Match blocks", option.Key.Key)),
|
Message: fmt.Sprintf("Option '%s' is not allowed inside Match blocks", option.Key.Key),
|
||||||
|
Severity: &common.SeverityError,
|
||||||
})
|
})
|
||||||
|
|
||||||
return errs
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if option.OptionValue == nil || option.OptionValue.Value.Value == "" {
|
if option.OptionValue != nil {
|
||||||
errs = append(errs, common.LSPError{
|
checkIsUsingDoubleQuotes(ctx, option.OptionValue.Value, option.OptionValue.LocationRange)
|
||||||
Range: option.Key.LocationRange,
|
checkQuotesAreClosed(ctx, option.OptionValue.Value, option.OptionValue.LocationRange)
|
||||||
Err: errors.New(fmt.Sprintf("Option '%s' requires a value", option.Key.Key)),
|
|
||||||
})
|
|
||||||
} else {
|
|
||||||
errs = append(errs, checkIsUsingDoubleQuotes(option.OptionValue.Value, option.OptionValue.LocationRange)...)
|
|
||||||
errs = append(errs, checkQuotesAreClosed(option.OptionValue.Value, option.OptionValue.LocationRange)...)
|
|
||||||
|
|
||||||
invalidValues := docOption.DeprecatedCheckIsValid(option.OptionValue.Value.Value)
|
invalidValues := docOption.DeprecatedCheckIsValid(option.OptionValue.Value.Value)
|
||||||
|
|
||||||
errs = append(
|
for _, invalidValue := range invalidValues {
|
||||||
errs,
|
err := docvalues.LSPErrorFromInvalidValue(option.Start.Line, *invalidValue)
|
||||||
utils.Map(
|
err.ShiftCharacter(option.OptionValue.Start.Character)
|
||||||
invalidValues,
|
|
||||||
func(invalidValue *docvalues.InvalidValue) common.LSPError {
|
|
||||||
err := docvalues.LSPErrorFromInvalidValue(option.Start.Line, *invalidValue)
|
|
||||||
err.ShiftCharacter(option.OptionValue.Start.Character)
|
|
||||||
|
|
||||||
return err
|
ctx.diagnostics = append(ctx.diagnostics, protocol.Diagnostic{
|
||||||
},
|
Range: err.Range.ToLSPRange(),
|
||||||
)...,
|
Message: err.Err.Error(),
|
||||||
)
|
Severity: &common.SeverityError,
|
||||||
|
})
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if option.Separator == nil || option.Separator.Value.Value == "" {
|
if option.Separator == nil || option.Separator.Value.Value == "" {
|
||||||
errs = append(errs, common.LSPError{
|
ctx.diagnostics = append(ctx.diagnostics, protocol.Diagnostic{
|
||||||
Range: option.Key.LocationRange,
|
Range: option.Key.LocationRange.ToLSPRange(),
|
||||||
Err: errors.New(fmt.Sprintf("There should be a separator between an option and its value")),
|
Message: fmt.Sprintf("There should be a separator between an option and its value"),
|
||||||
|
Severity: &common.SeverityError,
|
||||||
})
|
})
|
||||||
} else {
|
} else {
|
||||||
errs = append(errs, checkIsUsingDoubleQuotes(option.Separator.Value, option.Separator.LocationRange)...)
|
checkIsUsingDoubleQuotes(ctx, option.Separator.Value, option.Separator.LocationRange)
|
||||||
errs = append(errs, checkQuotesAreClosed(option.Separator.Value, option.Separator.LocationRange)...)
|
checkQuotesAreClosed(ctx, option.Separator.Value, option.Separator.LocationRange)
|
||||||
}
|
}
|
||||||
|
|
||||||
return errs
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func checkMatchBlock(
|
func checkMatchBlock(
|
||||||
|
ctx *analyzerContext,
|
||||||
matchBlock *ast.SSHDMatchBlock,
|
matchBlock *ast.SSHDMatchBlock,
|
||||||
) []common.LSPError {
|
) {
|
||||||
errs := make([]common.LSPError, 0)
|
|
||||||
|
|
||||||
it := matchBlock.Options.Iterator()
|
it := matchBlock.Options.Iterator()
|
||||||
|
|
||||||
for it.Next() {
|
for it.Next() {
|
||||||
option := it.Value().(*ast.SSHDOption)
|
option := it.Value().(*ast.SSHDOption)
|
||||||
|
|
||||||
errs = append(errs, checkOption(option, true)...)
|
checkOption(ctx, option, true)
|
||||||
}
|
}
|
||||||
|
|
||||||
return errs
|
|
||||||
}
|
}
|
||||||
|
@ -3,57 +3,52 @@ package analyzer
|
|||||||
import (
|
import (
|
||||||
"config-lsp/common"
|
"config-lsp/common"
|
||||||
commonparser "config-lsp/common/parser"
|
commonparser "config-lsp/common/parser"
|
||||||
sshdconfig "config-lsp/handlers/sshd_config"
|
"config-lsp/utils"
|
||||||
"errors"
|
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
|
protocol "github.com/tliron/glsp/protocol_3_16"
|
||||||
)
|
)
|
||||||
|
|
||||||
func analyzeQuotesAreValid(
|
func analyzeQuotesAreValid(
|
||||||
d *sshdconfig.SSHDDocument,
|
ctx *analyzerContext,
|
||||||
) []common.LSPError {
|
) {
|
||||||
errs := make([]common.LSPError, 0)
|
for _, option := range ctx.document.Config.GetAllOptions() {
|
||||||
|
checkIsUsingDoubleQuotes(ctx, option.Key.Value, option.Key.LocationRange)
|
||||||
|
checkIsUsingDoubleQuotes(ctx, option.OptionValue.Value, option.OptionValue.LocationRange)
|
||||||
|
|
||||||
for _, option := range d.Config.GetAllOptions() {
|
checkQuotesAreClosed(ctx, option.Key.Value, option.Key.LocationRange)
|
||||||
errs = append(errs, checkIsUsingDoubleQuotes(option.Key.Value, option.Key.LocationRange)...)
|
checkQuotesAreClosed(ctx, option.OptionValue.Value, option.OptionValue.LocationRange)
|
||||||
errs = append(errs, checkIsUsingDoubleQuotes(option.OptionValue.Value, option.OptionValue.LocationRange)...)
|
|
||||||
|
|
||||||
errs = append(errs, checkQuotesAreClosed(option.Key.Value, option.Key.LocationRange)...)
|
|
||||||
errs = append(errs, checkQuotesAreClosed(option.OptionValue.Value, option.OptionValue.LocationRange)...)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return errs
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func checkIsUsingDoubleQuotes(
|
func checkIsUsingDoubleQuotes(
|
||||||
|
ctx *analyzerContext,
|
||||||
value commonparser.ParsedString,
|
value commonparser.ParsedString,
|
||||||
valueRange common.LocationRange,
|
valueRange common.LocationRange,
|
||||||
) []common.LSPError {
|
) {
|
||||||
|
quoteRanges := utils.GetQuoteRanges(value.Raw)
|
||||||
singleQuotePosition := strings.Index(value.Raw, "'")
|
singleQuotePosition := strings.Index(value.Raw, "'")
|
||||||
|
|
||||||
if singleQuotePosition != -1 {
|
// Single quote
|
||||||
return []common.LSPError{
|
if singleQuotePosition != -1 && !quoteRanges.IsCharInside(singleQuotePosition) {
|
||||||
{
|
ctx.diagnostics = append(ctx.diagnostics, protocol.Diagnostic{
|
||||||
Range: valueRange,
|
Range: valueRange.ToLSPRange(),
|
||||||
Err: errors.New("sshd_config does not support single quotes. Use double quotes (\") instead."),
|
Message: "sshd_config does not support single quotes. Use double quotes (\") instead.",
|
||||||
},
|
Severity: &common.SeverityError,
|
||||||
}
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func checkQuotesAreClosed(
|
func checkQuotesAreClosed(
|
||||||
|
ctx *analyzerContext,
|
||||||
value commonparser.ParsedString,
|
value commonparser.ParsedString,
|
||||||
valueRange common.LocationRange,
|
valueRange common.LocationRange,
|
||||||
) []common.LSPError {
|
) {
|
||||||
if strings.Count(value.Raw, "\"")%2 != 0 {
|
if strings.Count(value.Raw, "\"")%2 != 0 {
|
||||||
return []common.LSPError{
|
ctx.diagnostics = append(ctx.diagnostics, protocol.Diagnostic{
|
||||||
{
|
Range: valueRange.ToLSPRange(),
|
||||||
Range: valueRange,
|
Message: "There are unclosed quotes here. Make sure all quotes are closed.",
|
||||||
Err: errors.New("There are unclosed quotes here. Make sure all quotes are closed."),
|
Severity: &common.SeverityError,
|
||||||
},
|
})
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
}
|
||||||
|
@ -3,6 +3,8 @@ package analyzer
|
|||||||
import (
|
import (
|
||||||
testutils_test "config-lsp/handlers/sshd_config/test_utils"
|
testutils_test "config-lsp/handlers/sshd_config/test_utils"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
|
protocol "github.com/tliron/glsp/protocol_3_16"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestSimpleInvalidQuotesExample(
|
func TestSimpleInvalidQuotesExample(
|
||||||
@ -11,11 +13,15 @@ func TestSimpleInvalidQuotesExample(
|
|||||||
d := testutils_test.DocumentFromInput(t, `
|
d := testutils_test.DocumentFromInput(t, `
|
||||||
PermitRootLogin 'yes'
|
PermitRootLogin 'yes'
|
||||||
`)
|
`)
|
||||||
|
ctx := &analyzerContext{
|
||||||
|
document: d,
|
||||||
|
diagnostics: make([]protocol.Diagnostic, 0),
|
||||||
|
}
|
||||||
|
|
||||||
errors := analyzeQuotesAreValid(d)
|
analyzeQuotesAreValid(ctx)
|
||||||
|
|
||||||
if !(len(errors) == 1) {
|
if !(len(ctx.diagnostics) == 1) {
|
||||||
t.Errorf("Expected 1 error, got %v", len(errors))
|
t.Errorf("Expected 1 error, got %v", len(ctx.diagnostics))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -25,11 +31,15 @@ func TestSingleQuotesKeyAndOptionExample(
|
|||||||
d := testutils_test.DocumentFromInput(t, `
|
d := testutils_test.DocumentFromInput(t, `
|
||||||
'Port' '22'
|
'Port' '22'
|
||||||
`)
|
`)
|
||||||
|
ctx := &analyzerContext{
|
||||||
|
document: d,
|
||||||
|
diagnostics: make([]protocol.Diagnostic, 0),
|
||||||
|
}
|
||||||
|
|
||||||
errors := analyzeQuotesAreValid(d)
|
analyzeQuotesAreValid(ctx)
|
||||||
|
|
||||||
if !(len(errors) == 2) {
|
if !(len(ctx.diagnostics) == 2) {
|
||||||
t.Errorf("Expected 2 errors, got %v", len(errors))
|
t.Errorf("Expected 2 errors, got %v", len(ctx.diagnostics))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -39,11 +49,15 @@ func TestSimpleUnclosedQuoteExample(
|
|||||||
d := testutils_test.DocumentFromInput(t, `
|
d := testutils_test.DocumentFromInput(t, `
|
||||||
PermitRootLogin "yes
|
PermitRootLogin "yes
|
||||||
`)
|
`)
|
||||||
|
ctx := &analyzerContext{
|
||||||
|
document: d,
|
||||||
|
diagnostics: make([]protocol.Diagnostic, 0),
|
||||||
|
}
|
||||||
|
|
||||||
errors := analyzeQuotesAreValid(d)
|
analyzeQuotesAreValid(ctx)
|
||||||
|
|
||||||
if !(len(errors) == 1) {
|
if !(len(ctx.diagnostics) == 1) {
|
||||||
t.Errorf("Expected 1 error, got %v", len(errors))
|
t.Errorf("Expected 1 error, got %v", len(ctx.diagnostics))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -53,10 +67,14 @@ func TestIncompleteQuotesExample(
|
|||||||
d := testutils_test.DocumentFromInput(t, `
|
d := testutils_test.DocumentFromInput(t, `
|
||||||
"Port
|
"Port
|
||||||
`)
|
`)
|
||||||
|
ctx := &analyzerContext{
|
||||||
|
document: d,
|
||||||
|
diagnostics: make([]protocol.Diagnostic, 0),
|
||||||
|
}
|
||||||
|
|
||||||
errors := analyzeQuotesAreValid(d)
|
analyzeQuotesAreValid(ctx)
|
||||||
|
|
||||||
if !(len(errors) == 1) {
|
if !(len(ctx.diagnostics) == 1) {
|
||||||
t.Errorf("Expected 1 error, got %v", len(errors))
|
t.Errorf("Expected 1 error, got %v", len(ctx.diagnostics))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user