feat(sshd_config): Add analyzer to check whether values are valid

This commit is contained in:
Myzel394 2024-09-12 21:39:28 +02:00
parent ea6fdd8cda
commit 47c511996b
No known key found for this signature in database
GPG Key ID: DEC4AAB876F73185
6 changed files with 134 additions and 11 deletions

View File

@ -104,7 +104,7 @@ func (v KeyValueAssignmentValue) CheckIsValid(value string) []*InvalidValue {
} }
func (v KeyValueAssignmentValue) FetchCompletions(line string, cursor uint32) []protocol.CompletionItem { func (v KeyValueAssignmentValue) FetchCompletions(line string, cursor uint32) []protocol.CompletionItem {
if cursor == 0 { if cursor == 0 || line == "" {
return v.Key.FetchCompletions(line, cursor) return v.Key.FetchCompletions(line, cursor)
} }

View File

@ -1,12 +1,26 @@
package analyzer package analyzer
import ( import (
"config-lsp/common"
"config-lsp/handlers/sshd_config" "config-lsp/handlers/sshd_config"
"config-lsp/utils"
protocol "github.com/tliron/glsp/protocol_3_16" protocol "github.com/tliron/glsp/protocol_3_16"
) )
func Analyze( func Analyze(
d *sshdconfig.SSHDocument, d *sshdconfig.SSHDocument,
) []protocol.Diagnostic { ) []protocol.Diagnostic {
errors := analyzeOptionsAreValid(d)
if len(errors) > 0 {
return utils.Map(
errors,
func(err common.LSPError) protocol.Diagnostic {
return err.ToDiagnostic()
},
)
}
return nil return nil
} }

View File

@ -0,0 +1,60 @@
package analyzer
import (
"config-lsp/common"
docvalues "config-lsp/doc-values"
sshdconfig "config-lsp/handlers/sshd_config"
"config-lsp/handlers/sshd_config/ast"
"config-lsp/handlers/sshd_config/fields"
"config-lsp/utils"
"errors"
"fmt"
)
func analyzeOptionsAreValid(
d *sshdconfig.SSHDocument,
) []common.LSPError {
errs := make([]common.LSPError, 0)
it := d.Config.Options.Iterator()
for it.Next() {
line := it.Key().(uint32)
entry := it.Value().(ast.SSHEntry)
option := entry.GetOption()
if option.Key != nil {
docOption, found := fields.Options[option.Key.Value]
if !found {
errs = append(errs, common.LSPError{
Range: option.Key.LocationRange,
Err: errors.New(fmt.Sprintf("Unknown option: %s", option.Key.Value)),
})
continue
}
if option.OptionValue == nil {
continue
}
invalidValues := docOption.CheckIsValid(option.OptionValue.Value)
errs = append(
errs,
utils.Map(
invalidValues,
func(invalidValue *docvalues.InvalidValue) common.LSPError {
err := docvalues.LSPErrorFromInvalidValue(line, *invalidValue)
err.ShiftCharacter(option.OptionValue.Start.Character)
return err
},
)...,
)
}
}
return errs
}

View File

@ -25,6 +25,7 @@ const (
type SSHEntry interface { type SSHEntry interface {
GetType() SSHEntryType GetType() SSHEntryType
GetOption() SSHOption
} }
type SSHSeparator struct { type SSHSeparator struct {
@ -44,6 +45,10 @@ func (o SSHOption) GetType() SSHEntryType {
return SSHEntryTypeOption return SSHEntryTypeOption
} }
func (o SSHOption) GetOption() SSHOption {
return o
}
type SSHMatchBlock struct { type SSHMatchBlock struct {
common.LocationRange common.LocationRange
MatchEntry *SSHOption MatchEntry *SSHOption
@ -56,6 +61,10 @@ func (m SSHMatchBlock) GetType() SSHEntryType {
return SSHEntryTypeMatchBlock return SSHEntryTypeMatchBlock
} }
func (m SSHMatchBlock) GetOption() SSHOption {
return *m.MatchEntry
}
type SSHConfig struct { type SSHConfig struct {
// [uint32]SSHOption -> line number -> *SSHEntry // [uint32]SSHOption -> line number -> *SSHEntry
Options *treemap.Map Options *treemap.Map

View File

@ -13,23 +13,51 @@ import (
func GetRootCompletions( func GetRootCompletions(
d *sshdconfig.SSHDocument, d *sshdconfig.SSHDocument,
parentMatchBlock *ast.SSHMatchBlock, parentMatchBlock *ast.SSHMatchBlock,
suggestValue bool,
) ([]protocol.CompletionItem, error) { ) ([]protocol.CompletionItem, error) {
kind := protocol.CompletionItemKindField kind := protocol.CompletionItemKindField
format := protocol.InsertTextFormatSnippet
return utils.MapMapToSlice( return utils.MapMapToSlice(
fields.Options, fields.Options,
func(name string, rawValue docvalues.Value) protocol.CompletionItem { func(name string, rawValue docvalues.Value) protocol.CompletionItem {
doc := rawValue.(docvalues.DocumentationValue) doc := rawValue.(docvalues.DocumentationValue)
insertText := name + " " + "${1:value}" completion := &protocol.CompletionItem{
return protocol.CompletionItem{ Label: name,
Label: name, Kind: &kind,
Kind: &kind, Documentation: doc.Documentation,
Documentation: doc.Documentation,
InsertText: &insertText,
InsertTextFormat: &format,
} }
if suggestValue {
format := protocol.InsertTextFormatSnippet
insertText := name + " " + "${1:value}"
completion.InsertTextFormat = &format
completion.InsertText = &insertText
}
return *completion
}, },
), nil ), nil
} }
func GetOptionCompletions(
d *sshdconfig.SSHDocument,
entry *ast.SSHOption,
cursor uint32,
) ([]protocol.CompletionItem, error) {
option, found := fields.Options[entry.Key.Value]
if !found {
return nil, nil
}
if entry.OptionValue == nil {
return option.FetchCompletions("", 0), nil
}
relativeCursor := cursor - entry.OptionValue.Start.Character
line := entry.OptionValue.Value
return option.FetchCompletions(line, relativeCursor), nil
}

View File

@ -1,6 +1,7 @@
package lsp package lsp
import ( import (
"config-lsp/common"
sshdconfig "config-lsp/handlers/sshd_config" sshdconfig "config-lsp/handlers/sshd_config"
"config-lsp/handlers/sshd_config/handlers" "config-lsp/handlers/sshd_config/handlers"
"regexp" "regexp"
@ -14,7 +15,6 @@ var containsSeparatorPattern = regexp.MustCompile(`\s+$`)
func TextDocumentCompletion(context *glsp.Context, params *protocol.CompletionParams) (any, error) { func TextDocumentCompletion(context *glsp.Context, params *protocol.CompletionParams) (any, error) {
line := params.Position.Line line := params.Position.Line
cursor := params.Position.Character cursor := params.Position.Character
_ = cursor
d := sshdconfig.DocumentParserMap[params.TextDocument.URI] d := sshdconfig.DocumentParserMap[params.TextDocument.URI]
@ -24,11 +24,23 @@ func TextDocumentCompletion(context *glsp.Context, params *protocol.CompletionPa
entry, matchBlock := d.Config.FindOption(line) entry, matchBlock := d.Config.FindOption(line)
if entry == nil || entry.Separator == nil { if entry == nil ||
entry.Separator == nil ||
entry.Key == nil ||
(common.CursorToCharacterIndex(cursor)) <= entry.Key.End.Character {
// Empty line // Empty line
return handlers.GetRootCompletions( return handlers.GetRootCompletions(
d, d,
matchBlock, matchBlock,
entry == nil || containsSeparatorPattern.Match([]byte(entry.Value)),
)
}
if entry.Separator != nil && cursor > entry.Separator.End.Character {
return handlers.GetOptionCompletions(
d,
entry,
cursor,
) )
} }