mirror of
https://github.com/Myzel394/config-lsp.git
synced 2025-06-18 15:05:28 +02:00
fix(server): Improve sshd_config
This commit is contained in:
parent
0c827b04cd
commit
e569516aae
@ -54,7 +54,6 @@ func Analyze(
|
||||
}
|
||||
}
|
||||
|
||||
analyzeValuesAreValid(ctx)
|
||||
analyzeMatchBlocks(ctx)
|
||||
analyzeTokens(ctx)
|
||||
|
||||
|
@ -4,6 +4,7 @@ import (
|
||||
"config-lsp/common"
|
||||
docvalues "config-lsp/doc-values"
|
||||
"config-lsp/handlers/sshd_config/ast"
|
||||
"config-lsp/handlers/sshd_config/diagnostics"
|
||||
"config-lsp/handlers/sshd_config/fields"
|
||||
"fmt"
|
||||
|
||||
@ -20,7 +21,7 @@ func analyzeStructureIsValid(
|
||||
|
||||
switch entry.(type) {
|
||||
case *ast.SSHDOption:
|
||||
checkOption(ctx, entry.(*ast.SSHDOption), false)
|
||||
checkOption(ctx, entry.(*ast.SSHDOption), nil)
|
||||
case *ast.SSHDMatchBlock:
|
||||
matchBlock := entry.(*ast.SSHDMatchBlock)
|
||||
checkMatchBlock(ctx, matchBlock)
|
||||
@ -31,7 +32,7 @@ func analyzeStructureIsValid(
|
||||
func checkOption(
|
||||
ctx *analyzerContext,
|
||||
option *ast.SSHDOption,
|
||||
isInMatchBlock bool,
|
||||
matchBlock *ast.SSHDMatchBlock,
|
||||
) {
|
||||
if option.Key == nil {
|
||||
return
|
||||
@ -44,16 +45,19 @@ func checkOption(
|
||||
docOption, found := fields.Options[key]
|
||||
|
||||
if !found {
|
||||
ctx.diagnostics = append(ctx.diagnostics, protocol.Diagnostic{
|
||||
Range: option.Key.ToLSPRange(),
|
||||
Message: fmt.Sprintf("Unknown option: %s", option.Key.Key),
|
||||
Severity: &common.SeverityError,
|
||||
})
|
||||
ctx.diagnostics = append(ctx.diagnostics, diagnostics.GenerateUnknownOption(
|
||||
option.Key.ToLSPRange(),
|
||||
option.Key.Value.Value,
|
||||
))
|
||||
ctx.document.Indexes.UnknownOptions[option.Start.Line] = ast.SSHDOptionInfo{
|
||||
Option: option,
|
||||
MatchBlock: matchBlock,
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
if _, found := fields.MatchAllowedOptions[key]; !found && isInMatchBlock {
|
||||
if _, found := fields.MatchAllowedOptions[key]; !found && matchBlock != nil {
|
||||
ctx.diagnostics = append(ctx.diagnostics, protocol.Diagnostic{
|
||||
Range: option.Key.ToLSPRange(),
|
||||
Message: fmt.Sprintf("Option '%s' is not allowed inside Match blocks", option.Key.Key),
|
||||
@ -99,6 +103,6 @@ func checkMatchBlock(
|
||||
for it.Next() {
|
||||
option := it.Value().(*ast.SSHDOption)
|
||||
|
||||
checkOption(ctx, option, true)
|
||||
checkOption(ctx, option, matchBlock)
|
||||
}
|
||||
}
|
||||
|
@ -18,7 +18,7 @@ ThisOptionDoesNotExist okay
|
||||
diagnostics: make([]protocol.Diagnostic, 0),
|
||||
}
|
||||
|
||||
analyzeValuesAreValid(ctx)
|
||||
analyzeStructureIsValid(ctx)
|
||||
|
||||
if !(len(ctx.diagnostics) == 1) {
|
||||
t.Errorf("Expected 1 error, got %v", len(ctx.diagnostics))
|
@ -1,39 +0,0 @@
|
||||
package analyzer
|
||||
|
||||
import (
|
||||
"config-lsp/handlers/sshd_config/diagnostics"
|
||||
"config-lsp/handlers/sshd_config/fields"
|
||||
)
|
||||
|
||||
func analyzeValuesAreValid(
|
||||
ctx *analyzerContext,
|
||||
) {
|
||||
// Check if there are unknown options
|
||||
for _, info := range ctx.document.Config.GetAllOptions() {
|
||||
normalizedName := fields.CreateNormalizedName(info.Option.Key.Value.Value)
|
||||
|
||||
var isUnknown bool = true
|
||||
|
||||
// Check if the option is unknown
|
||||
if info.MatchBlock == nil {
|
||||
// All options are allowed
|
||||
if _, found := fields.Options[normalizedName]; found {
|
||||
isUnknown = false
|
||||
}
|
||||
} else {
|
||||
// Only `MatchAllowedOptions` are allowed
|
||||
if _, found := fields.MatchAllowedOptions[normalizedName]; found {
|
||||
isUnknown = false
|
||||
}
|
||||
}
|
||||
|
||||
if isUnknown {
|
||||
ctx.diagnostics = append(ctx.diagnostics, diagnostics.GenerateUnknownOption(
|
||||
info.Option.Key.ToLSPRange(),
|
||||
info.Option.Key.Value.Value,
|
||||
))
|
||||
|
||||
ctx.document.Indexes.UnknownOptions[info.Option.Start.Line] = info
|
||||
}
|
||||
}
|
||||
}
|
20
server/handlers/sshd_config/handlers/fetch-code-actions.go
Normal file
20
server/handlers/sshd_config/handlers/fetch-code-actions.go
Normal file
@ -0,0 +1,20 @@
|
||||
package handlers
|
||||
|
||||
import (
|
||||
sshdconfig "config-lsp/handlers/sshd_config"
|
||||
|
||||
protocol "github.com/tliron/glsp/protocol_3_16"
|
||||
)
|
||||
|
||||
func FetchCodeActions(
|
||||
d *sshdconfig.SSHDDocument,
|
||||
params *protocol.CodeActionParams,
|
||||
) []protocol.CodeAction {
|
||||
if d.Indexes == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
actions := getKeywordTypoFixes(d, params)
|
||||
|
||||
return actions
|
||||
}
|
110
server/handlers/sshd_config/handlers/fetch-code-actions_typos.go
Normal file
110
server/handlers/sshd_config/handlers/fetch-code-actions_typos.go
Normal file
@ -0,0 +1,110 @@
|
||||
package handlers
|
||||
|
||||
import (
|
||||
"config-lsp/common"
|
||||
sshdconfig "config-lsp/handlers/sshd_config"
|
||||
"config-lsp/handlers/sshd_config/diagnostics"
|
||||
"config-lsp/handlers/sshd_config/fields"
|
||||
"fmt"
|
||||
|
||||
"github.com/hbollon/go-edlib"
|
||||
protocol "github.com/tliron/glsp/protocol_3_16"
|
||||
)
|
||||
|
||||
func getKeywordTypoFixes(
|
||||
d *sshdconfig.SSHDDocument,
|
||||
params *protocol.CodeActionParams,
|
||||
) []protocol.CodeAction {
|
||||
if common.ServerOptions.NoTypoSuggestions {
|
||||
return nil
|
||||
}
|
||||
|
||||
line := params.Range.Start.Line
|
||||
|
||||
if typoOption, found := d.Indexes.UnknownOptions[line]; found {
|
||||
name := typoOption.Option.Key.Value.Value
|
||||
|
||||
suggestedOptions := findSimilarOptions(name, typoOption.MatchBlock != nil)
|
||||
|
||||
actions := make([]protocol.CodeAction, 0, len(suggestedOptions))
|
||||
|
||||
kind := protocol.CodeActionKindQuickFix
|
||||
for index, normalizedOptionName := range suggestedOptions {
|
||||
isPreferred := index == 0
|
||||
optionName := fields.FieldsNameFormattedMap[normalizedOptionName]
|
||||
|
||||
actions = append(actions, protocol.CodeAction{
|
||||
Title: fmt.Sprintf("Typo Fix: %s", optionName),
|
||||
IsPreferred: &isPreferred,
|
||||
Kind: &kind,
|
||||
Diagnostics: []protocol.Diagnostic{
|
||||
diagnostics.GenerateUnknownOption(
|
||||
typoOption.Option.Key.ToLSPRange(),
|
||||
typoOption.Option.Key.Value.Value,
|
||||
),
|
||||
},
|
||||
Edit: &protocol.WorkspaceEdit{
|
||||
Changes: map[protocol.DocumentUri][]protocol.TextEdit{
|
||||
params.TextDocument.URI: {
|
||||
{
|
||||
Range: typoOption.Option.Key.ToLSPRange(),
|
||||
NewText: optionName,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
return actions
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Find options that are similar to the given option name.
|
||||
// This is used to find typos & suggest the correct option name.
|
||||
// Once an option is found that has a Damerau-Levenshtein distance of 1, it is immediately returned.
|
||||
// If not, then the next 2 options of similarity 2, or 3 options of similarity 3 are returned.
|
||||
// If no options with similarity <= 3 are found, then an empty slice is returned.
|
||||
func findSimilarOptions(
|
||||
optionName string,
|
||||
restrictToMatchOptions bool,
|
||||
) []fields.NormalizedOptionName {
|
||||
normalizedOptionName := string(fields.CreateNormalizedName(optionName))
|
||||
|
||||
optionsPerSimilarity := map[uint8][]fields.NormalizedOptionName{
|
||||
2: make([]fields.NormalizedOptionName, 0, 2),
|
||||
3: make([]fields.NormalizedOptionName, 0, 3),
|
||||
}
|
||||
|
||||
for name := range fields.Options {
|
||||
if restrictToMatchOptions {
|
||||
if _, found := fields.MatchAllowedOptions[name]; !found {
|
||||
continue
|
||||
}
|
||||
}
|
||||
|
||||
normalizedName := string(name)
|
||||
similarity := edlib.DamerauLevenshteinDistance(normalizedName, normalizedOptionName)
|
||||
|
||||
switch similarity {
|
||||
case 1:
|
||||
return []fields.NormalizedOptionName{name}
|
||||
case 2:
|
||||
optionsPerSimilarity[2] = append(optionsPerSimilarity[2], name)
|
||||
|
||||
if len(optionsPerSimilarity[2]) >= 2 {
|
||||
return optionsPerSimilarity[2]
|
||||
}
|
||||
case 3:
|
||||
optionsPerSimilarity[3] = append(optionsPerSimilarity[3], name)
|
||||
|
||||
if len(optionsPerSimilarity[3]) >= 3 {
|
||||
return optionsPerSimilarity[3]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return append(optionsPerSimilarity[2], optionsPerSimilarity[3]...)
|
||||
}
|
16
server/handlers/sshd_config/lsp/text-document-code-action.go
Normal file
16
server/handlers/sshd_config/lsp/text-document-code-action.go
Normal file
@ -0,0 +1,16 @@
|
||||
package lsp
|
||||
|
||||
import (
|
||||
sshdconfig "config-lsp/handlers/sshd_config"
|
||||
"config-lsp/handlers/sshd_config/handlers"
|
||||
|
||||
"github.com/tliron/glsp"
|
||||
protocol "github.com/tliron/glsp/protocol_3_16"
|
||||
)
|
||||
|
||||
func TextDocumentCodeAction(context *glsp.Context, params *protocol.CodeActionParams) ([]protocol.CodeAction, error) {
|
||||
d := sshdconfig.DocumentParserMap[params.TextDocument.URI]
|
||||
actions := handlers.FetchCodeActions(d, params)
|
||||
|
||||
return actions, nil
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user