mirror of
https://github.com/Myzel394/config-lsp.git
synced 2025-06-18 23:15:26 +02:00
feat(sshd_config): Add match block analyzer
This commit is contained in:
parent
5d84e27a1f
commit
6326ccb609
@ -1,6 +1,7 @@
|
||||
package lsp
|
||||
|
||||
import (
|
||||
"config-lsp/common"
|
||||
"config-lsp/handlers/aliases"
|
||||
"config-lsp/handlers/aliases/ast"
|
||||
"config-lsp/handlers/aliases/handlers"
|
||||
@ -13,7 +14,7 @@ func TextDocumentSignatureHelp(context *glsp.Context, params *protocol.Signature
|
||||
document := aliases.DocumentParserMap[params.TextDocument.URI]
|
||||
|
||||
line := params.Position.Line
|
||||
character := params.Position.Character
|
||||
character := common.CursorToCharacterIndex(params.Position.Character)
|
||||
|
||||
if _, found := document.Parser.CommentLines[line]; found {
|
||||
// Comment
|
||||
|
@ -1,7 +1,7 @@
|
||||
grammar Config;
|
||||
|
||||
lineStatement
|
||||
: (entry | (leadingComment) | WHITESPACE?) EOF
|
||||
: (entry | leadingComment | WHITESPACE?) EOF
|
||||
;
|
||||
|
||||
entry
|
||||
|
@ -49,6 +49,8 @@ func Analyze(
|
||||
}
|
||||
}
|
||||
|
||||
errors = append(errors, analyzeMatchBlocks(d)...)
|
||||
|
||||
if len(errors) > 0 {
|
||||
return errsToDiagnostics(errors)
|
||||
}
|
||||
|
112
handlers/sshd_config/analyzer/match.go
Normal file
112
handlers/sshd_config/analyzer/match.go
Normal file
@ -0,0 +1,112 @@
|
||||
package analyzer
|
||||
|
||||
import (
|
||||
"config-lsp/common"
|
||||
sshdconfig "config-lsp/handlers/sshd_config"
|
||||
match_parser "config-lsp/handlers/sshd_config/fields/match-parser"
|
||||
"config-lsp/utils"
|
||||
"errors"
|
||||
"fmt"
|
||||
"strings"
|
||||
)
|
||||
|
||||
func analyzeMatchBlocks(
|
||||
d *sshdconfig.SSHDocument,
|
||||
) []common.LSPError {
|
||||
errs := make([]common.LSPError, 0)
|
||||
|
||||
for _, indexOption := range d.Indexes.AllOptionsPerName["Match"] {
|
||||
matchBlock := indexOption.MatchBlock.MatchValue
|
||||
|
||||
// Check if the match block has filled out all fields
|
||||
if matchBlock == nil || len(matchBlock.Entries) == 0 {
|
||||
errs = append(errs, common.LSPError{
|
||||
Range: indexOption.Option.LocationRange,
|
||||
Err: errors.New("A match expression is required"),
|
||||
})
|
||||
continue
|
||||
}
|
||||
|
||||
for _, entry := range matchBlock.Entries {
|
||||
if entry.Values == nil {
|
||||
errs = append(errs, common.LSPError{
|
||||
Range: entry.LocationRange,
|
||||
Err: errors.New(fmt.Sprintf("A value for %s is required", entry.Criteria.Type)),
|
||||
})
|
||||
} else {
|
||||
errs = append(errs, analyzeMatchValuesContainsPositiveValue(entry.Values)...)
|
||||
|
||||
for _, value := range entry.Values.Values {
|
||||
errs = append(errs, analyzeMatchValueNegation(value)...)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Check if match blocks are not empty
|
||||
if indexOption.MatchBlock.Options.Size() == 0 {
|
||||
errs = append(errs, common.LSPError{
|
||||
Range: indexOption.Option.LocationRange,
|
||||
Err: errors.New("This match block is empty"),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
return errs
|
||||
}
|
||||
|
||||
func analyzeMatchValueNegation(
|
||||
value *match_parser.MatchValue,
|
||||
) []common.LSPError {
|
||||
errs := make([]common.LSPError, 0)
|
||||
|
||||
positionsAsList := utils.AllIndexes(value.Value, "!")
|
||||
positions := utils.SliceToMap(positionsAsList, struct{}{})
|
||||
|
||||
delete(positions, 0)
|
||||
|
||||
for position := range positions {
|
||||
errs = append(errs, common.LSPError{
|
||||
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,
|
||||
},
|
||||
},
|
||||
Err: errors.New("The negation operator (!) may only occur at the beginning of a value"),
|
||||
})
|
||||
}
|
||||
|
||||
return errs
|
||||
}
|
||||
|
||||
func analyzeMatchValuesContainsPositiveValue(
|
||||
values *match_parser.MatchValues,
|
||||
) []common.LSPError {
|
||||
if len(values.Values) == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
containsPositive := false
|
||||
|
||||
for _, value := range values.Values {
|
||||
if !strings.HasPrefix(value.Value, "!") {
|
||||
containsPositive = true
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if !containsPositive {
|
||||
return []common.LSPError{
|
||||
{
|
||||
Range: values.LocationRange,
|
||||
Err: errors.New("At least one positive value is required. A negated match will never produce a positive result by itself"),
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
63
handlers/sshd_config/analyzer/match_test.go
Normal file
63
handlers/sshd_config/analyzer/match_test.go
Normal file
@ -0,0 +1,63 @@
|
||||
package analyzer
|
||||
|
||||
import (
|
||||
sshdconfig "config-lsp/handlers/sshd_config"
|
||||
"config-lsp/handlers/sshd_config/ast"
|
||||
"config-lsp/handlers/sshd_config/indexes"
|
||||
"config-lsp/utils"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestEmptyMatchBlocksMakesErrors(
|
||||
t *testing.T,
|
||||
) {
|
||||
input := utils.Dedent(`
|
||||
PermitRootLogin yes
|
||||
Match User root
|
||||
`)
|
||||
c := ast.NewSSHConfig()
|
||||
errors := c.Parse(input)
|
||||
|
||||
if len(errors) > 0 {
|
||||
t.Fatalf("Parse error: %v", errors)
|
||||
}
|
||||
|
||||
indexes, errors := indexes.CreateIndexes(*c)
|
||||
|
||||
if len(errors) > 0 {
|
||||
t.Fatalf("Index error: %v", errors)
|
||||
}
|
||||
|
||||
d := &sshdconfig.SSHDocument{
|
||||
Config: c,
|
||||
Indexes: indexes,
|
||||
}
|
||||
|
||||
errors = analyzeMatchBlocks(d)
|
||||
|
||||
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.NewSSHConfig()
|
||||
errors := c.Parse(input)
|
||||
|
||||
if len(errors) > 0 {
|
||||
t.Fatalf("Parse error: %v", errors)
|
||||
}
|
||||
|
||||
_, matchBlock := c.FindOption(uint32(1))
|
||||
errors = analyzeMatchValuesContainsPositiveValue(matchBlock.MatchValue.Entries[0].Values)
|
||||
|
||||
if !(len(errors) == 1) {
|
||||
t.Errorf("Expected 1 error, got %v", len(errors))
|
||||
}
|
||||
}
|
@ -93,6 +93,10 @@ func (s *sshParserListener) ExitEntry(ctx *parser.EntryContext) {
|
||||
location := common.CharacterRangeFromCtx(ctx.BaseParserRuleContext)
|
||||
location.ChangeBothLines(s.sshContext.line)
|
||||
|
||||
defer (func() {
|
||||
s.sshContext.currentOption = nil
|
||||
})()
|
||||
|
||||
if s.sshContext.isKeyAMatchBlock {
|
||||
// Add new match block
|
||||
var match *match_parser.Match
|
||||
@ -131,17 +135,20 @@ func (s *sshParserListener) ExitEntry(ctx *parser.EntryContext) {
|
||||
s.sshContext.currentMatchBlock = matchBlock
|
||||
|
||||
s.sshContext.isKeyAMatchBlock = false
|
||||
} else if s.sshContext.currentMatchBlock != nil {
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
if s.sshContext.currentMatchBlock != nil {
|
||||
s.sshContext.currentMatchBlock.Options.Put(
|
||||
location.Start.Line,
|
||||
s.sshContext.currentOption,
|
||||
)
|
||||
s.sshContext.currentMatchBlock.End = s.sshContext.currentOption.End
|
||||
} else {
|
||||
s.Config.Options.Put(
|
||||
location.Start.Line,
|
||||
s.sshContext.currentOption,
|
||||
)
|
||||
}
|
||||
|
||||
s.sshContext.currentOption = nil
|
||||
}
|
||||
|
@ -92,6 +92,10 @@ Match Address 192.168.0.1
|
||||
t.Errorf("Expected second entry to be 'Match Address 192.168.0.1', but got: %v", secondEntry.MatchEntry.Value)
|
||||
}
|
||||
|
||||
if !(secondEntry.Start.Line == 2 && secondEntry.Start.Character == 0 && secondEntry.End.Line == 3 && secondEntry.End.Character == 26) {
|
||||
t.Errorf("Expected second entry's location to be 2:0-3:25, but got: %v", secondEntry.LocationRange)
|
||||
}
|
||||
|
||||
if !(secondEntry.MatchValue.Entries[0].Criteria.Type == "Address" && secondEntry.MatchValue.Entries[0].Values.Values[0].Value == "192.168.0.1" && secondEntry.MatchEntry.OptionValue.Start.Character == 6) {
|
||||
t.Errorf("Expected second entry to be 'Match Address 192.168.0.1', but got: %v", secondEntry.MatchValue)
|
||||
}
|
||||
@ -235,6 +239,17 @@ Match Address 192.168.0.2
|
||||
if !(matchOption.Value == "Match User lena" && matchBlock.MatchEntry.Value == "Match User lena" && matchBlock.MatchValue.Entries[0].Values.Values[0].Value == "lena" && matchBlock.MatchEntry.OptionValue.Start.Character == 6) {
|
||||
t.Errorf("Expected match option to be 'Match User lena', but got: %v, %v", matchOption, matchBlock)
|
||||
}
|
||||
|
||||
if !(matchOption.Start.Line == 2 && matchOption.End.Line == 2 && matchOption.Start.Character == 0 && matchOption.End.Character == 14) {
|
||||
t.Errorf("Expected match option to be at 2:0-14, but got: %v", matchOption.LocationRange)
|
||||
}
|
||||
|
||||
if !(matchBlock.Start.Line == 2 &&
|
||||
matchBlock.Start.Character == 0 &&
|
||||
matchBlock.End.Line == 4 &&
|
||||
matchBlock.End.Character == 20) {
|
||||
t.Errorf("Expected match block to be at 2:0-4:20, but got: %v", matchBlock.LocationRange)
|
||||
}
|
||||
}
|
||||
|
||||
func TestSimpleExampleWithComments(
|
||||
|
@ -13,7 +13,7 @@ func getMatchCompletions(
|
||||
match *match_parser.Match,
|
||||
cursor uint32,
|
||||
) ([]protocol.CompletionItem, error) {
|
||||
if len(match.Entries) == 0 {
|
||||
if match == nil || len(match.Entries) == 0 {
|
||||
completions := getMatchCriteriaCompletions()
|
||||
completions = append(completions, getMatchAllKeywordCompletion())
|
||||
|
||||
|
@ -82,6 +82,8 @@ func CreateIndexes(config ast.SSHConfig) (*SSHIndexes, []common.LSPError) {
|
||||
case *ast.SSHMatchBlock:
|
||||
matchBlock := entry.(*ast.SSHMatchBlock)
|
||||
|
||||
errs = append(errs, addOption(indexes, matchBlock.MatchEntry, matchBlock)...)
|
||||
|
||||
it := matchBlock.Options.Iterator()
|
||||
for it.Next() {
|
||||
option := it.Value().(*ast.SSHOption)
|
||||
|
@ -42,15 +42,7 @@ func SetUpRootHandler() {
|
||||
func initialize(context *glsp.Context, params *protocol.InitializeParams) (any, error) {
|
||||
capabilities := lspHandler.CreateServerCapabilities()
|
||||
capabilities.TextDocumentSync = protocol.TextDocumentSyncKindFull
|
||||
capabilities.SignatureHelpProvider = &protocol.SignatureHelpOptions{
|
||||
TriggerCharacters: []string{
|
||||
"a", "b", "c", "d", "e", "f", "g", "h", "i", "j", "k", "l", "m", "n", "o", "p", "q", "r", "s", "t", "u", "v", "w", "x", "y", "z",
|
||||
"A", "B", "C", "D", "E", "F", "G", "H", "I", "J", "K", "L", "M", "N", "O", "P", "Q", "R", "S", "T", "U", "V", "W", "X", "Y", "Z",
|
||||
"0", "1", "2", "3", "4", "5", "6", "7", "8", "9",
|
||||
"_", "-", ".", "/", ":", "@", "#", "!", "$", "%", "^", "&", "*", "(", ")", "+", "=", "[", "]", "{", "}", "<", ">", "?", ";", ",", "|",
|
||||
" ",
|
||||
},
|
||||
}
|
||||
capabilities.SignatureHelpProvider = &protocol.SignatureHelpOptions{}
|
||||
|
||||
if (*params.Capabilities.TextDocument.Rename.PrepareSupport) == true {
|
||||
// Client supports rename preparation
|
||||
|
@ -2,6 +2,7 @@ package utils
|
||||
|
||||
import (
|
||||
"regexp"
|
||||
"strings"
|
||||
)
|
||||
|
||||
var trimIndexPattern = regexp.MustCompile(`^\s*(.+?)\s*$`)
|
||||
@ -57,3 +58,21 @@ var emptyRegex = regexp.MustCompile(`^\s*$`)
|
||||
func IsEmpty(s string) bool {
|
||||
return emptyRegex.MatchString(s)
|
||||
}
|
||||
|
||||
func AllIndexes(s string, sub string) []int {
|
||||
indexes := make([]int, 0)
|
||||
current := s
|
||||
|
||||
for {
|
||||
index := strings.Index(current, sub)
|
||||
|
||||
if index == -1 {
|
||||
break
|
||||
}
|
||||
|
||||
indexes = append(indexes, index)
|
||||
current = current[index+1:]
|
||||
}
|
||||
|
||||
return indexes
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user