feat(ssh_config): Add support for tokens check

This commit is contained in:
Myzel394 2024-10-14 12:39:23 +02:00
parent 4cdc916d51
commit 3caaf6aec4
No known key found for this signature in database
GPG Key ID: ED20A1D1D423AF3F
6 changed files with 194 additions and 22 deletions

View File

@ -55,6 +55,7 @@ func Analyze(
} }
analyzeValuesAreValid(ctx) analyzeValuesAreValid(ctx)
analyzeTokens(ctx)
analyzeIgnoreUnknownHasNoUnnecessary(ctx) analyzeIgnoreUnknownHasNoUnnecessary(ctx)
analyzeDependents(ctx) analyzeDependents(ctx)
analyzeBlocks(ctx) analyzeBlocks(ctx)

View File

@ -4,6 +4,7 @@ import (
"config-lsp/common" "config-lsp/common"
sshconfig "config-lsp/handlers/ssh_config" sshconfig "config-lsp/handlers/ssh_config"
"config-lsp/handlers/ssh_config/ast" "config-lsp/handlers/ssh_config/ast"
"config-lsp/handlers/ssh_config/fields"
"config-lsp/handlers/ssh_config/indexes" "config-lsp/handlers/ssh_config/indexes"
"config-lsp/utils" "config-lsp/utils"
"errors" "errors"
@ -19,27 +20,6 @@ import (
var whitespacePattern = regexp.MustCompile(`\S+`) var whitespacePattern = regexp.MustCompile(`\S+`)
var environmtalVariablePattern = regexp.MustCompile(`\${.+?}`) var environmtalVariablePattern = regexp.MustCompile(`\${.+?}`)
var availableTokens = []string{
"%%",
"%C",
"%d",
"%f",
"%H",
"%h",
"%l",
"%i",
"%j",
"%K",
"%k",
"%L",
"%l",
"%n",
"%p",
"%r",
"%T",
"%t",
"%u",
}
func analyzeIncludeValues( func analyzeIncludeValues(
ctx *analyzerContext, ctx *analyzerContext,
@ -74,7 +54,7 @@ func isImpossibleToVerify(
return true return true
} }
for _, token := range availableTokens { for _, token := range fields.AvailableTokens {
if strings.Contains(path, token) { if strings.Contains(path, token) {
return true return true
} }

View File

@ -0,0 +1,49 @@
package analyzer
import (
"config-lsp/common"
"config-lsp/handlers/ssh_config/fields"
"config-lsp/utils"
"fmt"
"strings"
protocol "github.com/tliron/glsp/protocol_3_16"
)
func analyzeTokens(
ctx *analyzerContext,
) {
for _, info := range ctx.document.Config.GetAllOptions() {
if info.Option.Key == nil || info.Option.OptionValue == nil {
continue
}
key := info.Option.Key.Key
text := info.Option.OptionValue.Value.Value
var tokens []string
if foundTokens, found := fields.OptionsTokensMap[key]; found {
tokens = foundTokens
} else {
tokens = []string{}
}
disallowedTokens := utils.Without(fields.AvailableTokens, tokens)
for _, token := range disallowedTokens {
if strings.Contains(text, token) {
optionName := string(key)
if formatted, found := fields.FieldsNameFormattedMap[key]; found {
optionName = formatted
}
ctx.diagnostics = append(ctx.diagnostics, protocol.Diagnostic{
Range: info.Option.OptionValue.ToLSPRange(),
Message: fmt.Sprintf("Token '%s' is not allowed for option '%s'", token, optionName),
Severity: &common.SeverityError,
})
}
}
}
}

View File

@ -0,0 +1,62 @@
package analyzer
import (
testutils_test "config-lsp/handlers/ssh_config/test_utils"
"testing"
protocol "github.com/tliron/glsp/protocol_3_16"
)
func TestInvalidTokensForNonExisting(
t *testing.T,
) {
d := testutils_test.DocumentFromInput(t, `
ThisOptionDoesNotExist Hello%%World
`)
ctx := &analyzerContext{
document: d,
diagnostics: make([]protocol.Diagnostic, 0),
}
analyzeTokens(ctx)
if !(len(ctx.diagnostics) == 1) {
t.Errorf("Expected 1 error, got %v", len(ctx.diagnostics))
}
}
func TestInvalidTokensForExistingOption(
t *testing.T,
) {
d := testutils_test.DocumentFromInput(t, `
Tunnel Hello%%World
`)
ctx := &analyzerContext{
document: d,
diagnostics: make([]protocol.Diagnostic, 0),
}
analyzeTokens(ctx)
if !(len(ctx.diagnostics) == 1) {
t.Errorf("Expected 1 error, got %v", len(ctx.diagnostics))
}
}
func TestValidTokens(
t *testing.T,
) {
d := testutils_test.DocumentFromInput(t, `
LocalCommand Hello World %% and %d
`)
ctx := &analyzerContext{
document: d,
diagnostics: make([]protocol.Diagnostic, 0),
}
analyzeTokens(ctx)
if len(ctx.diagnostics) > 0 {
t.Fatalf("Expected no errors, but got %v", len(ctx.diagnostics))
}
}

View File

@ -0,0 +1,71 @@
package fields
var AvailableTokens = []string{
"%%",
"%C",
"%d",
"%f",
"%H",
"%h",
"%l",
"%i",
"%j",
"%K",
"%k",
"%L",
"%l",
"%n",
"%p",
"%r",
"%T",
"%t",
"%u",
}
// A map of <option name> to <list of supported tokens>
// This is derived from the TOKENS section of ssh_config
var OptionsTokensMap = map[NormalizedOptionName][]string{
"certificatefile": firstTokens,
"controlpath": firstTokens,
"identityagent": firstTokens,
"identityfile": firstTokens,
"include": firstTokens,
"localforward": firstTokens,
"match": firstTokens,
"exec": firstTokens,
"remotecommand": firstTokens,
"remoteforward": firstTokens,
"revokedhostkeys": firstTokens,
"userknownhostsfile": firstTokens,
"knownhostscommand": append(firstTokens, []string{
"%f", "%H", "%I", "%K", "%t",
}...),
"hostname": {
"%%",
"%h",
},
"localcommand": AvailableTokens,
"proxycommand": {
"%%", "%h", "%n", "%p", "%r",
},
}
var firstTokens = []string{
"%%",
"%C",
"%d",
"%h",
"%i",
"%j",
"%k",
"%L",
"%l",
"%n",
"%p",
"%r",
"%u",
}

View File

@ -141,3 +141,12 @@ func MergeMaps[T comparable, O any](maps ...map[T]O) map[T]O {
return result return result
} }
func Without[T comparable](a []T, b []T) []T {
set := SliceToSet(b)
return FilterWhere(a, func(value T) bool {
_, found := set[value]
return !found
})
}