142 lines
3.7 KiB
Go

package analyzer
import (
"config-lsp/common"
docvalues "config-lsp/doc-values"
"config-lsp/handlers/sshd_config/fields"
"config-lsp/handlers/sshd_config/match-parser"
"config-lsp/utils"
"fmt"
"strings"
protocol "github.com/tliron/glsp/protocol_3_16"
)
var matchOption = fields.CreateNormalizedName("Match")
func analyzeMatchBlocks(
ctx *analyzerContext,
) {
for matchBlock, options := range ctx.document.Indexes.AllOptionsPerName[matchOption] {
option := options[0]
// Check if the match block has filled out all fields
if matchBlock == nil || matchBlock.MatchValue == nil || len(matchBlock.MatchValue.Entries) == 0 {
ctx.diagnostics = append(ctx.diagnostics, protocol.Diagnostic{
Range: option.ToLSPRange(),
Message: "A match expression is required",
Severity: &common.SeverityError,
})
continue
}
for _, entry := range matchBlock.MatchValue.Entries {
if entry.Values == nil {
ctx.diagnostics = append(ctx.diagnostics, protocol.Diagnostic{
Range: entry.ToLSPRange(),
Message: fmt.Sprintf("A value for %s is required", entry.Criteria.Type),
Severity: &common.SeverityError,
})
continue
}
analyzeMatchValuesContainsPositiveValue(ctx, entry.Values)
for _, value := range entry.Values.Values {
analyzeMatchValueNegation(ctx, value)
analyzeMatchValueIsValid(ctx, value, entry.Criteria.Type)
}
}
// Check if match blocks are not empty
if matchBlock.Options.Size() == 0 {
ctx.diagnostics = append(ctx.diagnostics, protocol.Diagnostic{
Range: option.ToLSPRange(),
Message: "This match block is empty",
Severity: &common.SeverityInformation,
Tags: []protocol.DiagnosticTag{
protocol.DiagnosticTagUnnecessary,
},
})
}
}
}
func analyzeMatchValueNegation(
ctx *analyzerContext,
value *matchparser.MatchValue,
) {
positionsAsList := utils.AllIndexes(value.Value.Raw, "!")
positions := utils.SliceToMap(positionsAsList, struct{}{})
delete(positions, 0)
for position := range positions {
ctx.diagnostics = append(ctx.diagnostics, protocol.Diagnostic{
Range: common.LocationRange{
Start: common.Location{
Line: value.Start.Line,
Character: uint32(position) + value.Start.Character,
},
End: common.Location{
Line: value.End.Line,
Character: uint32(position) + value.End.Character,
},
}.ToLSPRange(),
Message: "The negation operator (!) may only occur at the beginning of a value",
Severity: &common.SeverityError,
})
}
}
func analyzeMatchValuesContainsPositiveValue(
ctx *analyzerContext,
values *matchparser.MatchValues,
) {
if len(values.Values) == 0 {
return
}
containsPositive := false
for _, value := range values.Values {
if !strings.HasPrefix(value.Value.Value, "!") {
containsPositive = true
break
}
}
if !containsPositive {
ctx.diagnostics = append(ctx.diagnostics, protocol.Diagnostic{
Range: values.LocationRange.ToLSPRange(),
Message: "At least one positive value is required. A negated match will never produce a positive result by itself",
Severity: &common.SeverityError,
})
}
}
func analyzeMatchValueIsValid(
ctx *analyzerContext,
value *matchparser.MatchValue,
criteria matchparser.MatchCriteriaType,
) {
if value.Value.Raw == "" {
return
}
docOption := fields.MatchValueFieldMap[criteria]
invalidValues := docOption.DeprecatedCheckIsValid(value.Value.Raw)
for _, invalidValue := range invalidValues {
err := docvalues.LSPErrorFromInvalidValue(value.Start.Line, *invalidValue)
err.ShiftCharacter(value.Start.Character)
ctx.diagnostics = append(ctx.diagnostics, protocol.Diagnostic{
Range: err.Range.ToLSPRange(),
Message: err.Err.Error(),
Severity: &common.SeverityError,
})
}
}