diff --git a/handlers/ssh_config/analyzer/analyzer.go b/handlers/ssh_config/analyzer/analyzer.go index d9fc6b8..81c2aa9 100644 --- a/handlers/ssh_config/analyzer/analyzer.go +++ b/handlers/ssh_config/analyzer/analyzer.go @@ -36,6 +36,7 @@ func Analyze( d.Indexes = i analyzeValuesAreValid(ctx) + analyzeIgnoreUnknownHasNoUnnecessary(ctx) analyzeDependents(ctx) analyzeBlocks(ctx) analyzeMatchBlocks(ctx) diff --git a/handlers/ssh_config/analyzer/block.go b/handlers/ssh_config/analyzer/block.go index 1eb48f1..a3a8d6a 100644 --- a/handlers/ssh_config/analyzer/block.go +++ b/handlers/ssh_config/analyzer/block.go @@ -10,6 +10,10 @@ func analyzeBlocks( ctx *analyzerContext, ) { for _, block := range ctx.document.GetAllBlocks() { + if block == nil { + continue + } + if block.GetOptions().Size() == 0 { ctx.diagnostics = append(ctx.diagnostics, protocol.Diagnostic{ Range: block.GetEntryOption().LocationRange.ToLSPRange(), diff --git a/handlers/ssh_config/analyzer/ignore_unknown.go b/handlers/ssh_config/analyzer/ignore_unknown.go new file mode 100644 index 0000000..3e78749 --- /dev/null +++ b/handlers/ssh_config/analyzer/ignore_unknown.go @@ -0,0 +1,39 @@ +package analyzer + +import ( + "config-lsp/common" + "config-lsp/handlers/ssh_config/fields" + "fmt" + + protocol "github.com/tliron/glsp/protocol_3_16" +) + +var ignoreUnknownOption = fields.CreateNormalizedName("IgnoreUnknown") + +func analyzeIgnoreUnknownHasNoUnnecessary( + ctx *analyzerContext, +) { + for _, block := range ctx.document.GetAllBlocks() { + ignoreUnknown, found := ctx.document.Indexes.IgnoredOptions[block] + + if !found { + // No `IgnoreUnknown` option specified + continue + } + + for optionName, ignoreInfo := range ignoreUnknown.IgnoredOptions { + info := ctx.document.FindOptionByNameAndBlock(optionName, block) + + if info == nil { + ctx.diagnostics = append(ctx.diagnostics, protocol.Diagnostic{ + Range: ignoreInfo.ToLSPRange(), + Message: fmt.Sprintf("Option %s is not present", optionName), + Tags: []protocol.DiagnosticTag{ + protocol.DiagnosticTagUnnecessary, + }, + Severity: &common.SeverityHint, + }) + } + } + } +} diff --git a/handlers/ssh_config/analyzer/ignore_unknown_test.go b/handlers/ssh_config/analyzer/ignore_unknown_test.go new file mode 100644 index 0000000..e2b079f --- /dev/null +++ b/handlers/ssh_config/analyzer/ignore_unknown_test.go @@ -0,0 +1,26 @@ +package analyzer + +import ( + testutils_test "config-lsp/handlers/ssh_config/test_utils" + "testing" + + protocol "github.com/tliron/glsp/protocol_3_16" +) + +func TestIgnoreUnknownUnnecessary( + t *testing.T, +) { + d := testutils_test.DocumentFromInput(t, ` +IgnoreUnknown helloWorld +PermitRootLogin 'yes' +`) + ctx := &analyzerContext{ + document: d, + diagnostics: make([]protocol.Diagnostic, 0), + } + analyzeIgnoreUnknownHasNoUnnecessary(ctx) + + if !(len(ctx.diagnostics) == 1) { + t.Errorf("Expected 1 error, got %v", len(ctx.diagnostics)) + } +} diff --git a/handlers/ssh_config/document_fields.go b/handlers/ssh_config/document_fields.go index e1d10d7..cf673c4 100644 --- a/handlers/ssh_config/document_fields.go +++ b/handlers/ssh_config/document_fields.go @@ -72,8 +72,10 @@ func (d SSHDocument) GetAllHostBlocks() []*ast.SSHHostBlock { // GetAllBlocks returns all blocks in the document // Note: The blocks are **not** sorted +// Note: This also returns `nil` (as the global block) func (d SSHDocument) GetAllBlocks() []ast.SSHBlock { blocks := make([]ast.SSHBlock, 0) + blocks = append(blocks, nil) for _, block := range d.GetAllHostBlocks() { blocks = append(blocks, block) diff --git a/handlers/ssh_config/indexes/indexes.go b/handlers/ssh_config/indexes/indexes.go index dd2a54f..2702d5a 100644 --- a/handlers/ssh_config/indexes/indexes.go +++ b/handlers/ssh_config/indexes/indexes.go @@ -26,9 +26,13 @@ type SSHIndexIncludeLine struct { Block ast.SSHBlock } +type SSHIndexIgnoredUnknownInfo struct { + common.LocationRange +} + type SSHIndexIgnoredUnknowns struct { OptionValue *ast.SSHOption - IgnoredOptions map[fields.NormalizedOptionName]struct{} + IgnoredOptions map[fields.NormalizedOptionName]SSHIndexIgnoredUnknownInfo } type SSHIndexes struct { diff --git a/handlers/ssh_config/indexes/indexes_fields.go b/handlers/ssh_config/indexes/indexes_fields.go index 8034c02..a9836df 100644 --- a/handlers/ssh_config/indexes/indexes_fields.go +++ b/handlers/ssh_config/indexes/indexes_fields.go @@ -5,7 +5,7 @@ import ( "config-lsp/handlers/ssh_config/fields" ) -func (u SSHIndexIgnoredUnknowns) GetIgnoredForLine(line uint32) map[fields.NormalizedOptionName]struct{} { +func (u SSHIndexIgnoredUnknowns) GetIgnoredForLine(line uint32) map[fields.NormalizedOptionName]SSHIndexIgnoredUnknownInfo { if line >= u.OptionValue.Start.Line { return u.IgnoredOptions } diff --git a/handlers/ssh_config/indexes/indexes_handlers.go b/handlers/ssh_config/indexes/indexes_handlers.go index 74e822d..d8860af 100644 --- a/handlers/ssh_config/indexes/indexes_handlers.go +++ b/handlers/ssh_config/indexes/indexes_handlers.go @@ -151,11 +151,26 @@ func addIgnoredOption( block ast.SSHBlock, ) { rawIgnored := option.OptionValue.Value.Value - ignoredAsSlice := ignoredValuesPattern.FindAllString(rawIgnored, -1) - ignored := make(map[fields.NormalizedOptionName]struct{}, 0) + ignoredAsSlice := ignoredValuesPattern.FindAllStringIndex(rawIgnored, -1) + ignored := make(map[fields.NormalizedOptionName]SSHIndexIgnoredUnknownInfo, 0) - for _, ig := range ignoredAsSlice { - ignored[fields.CreateNormalizedName(ig)] = struct{}{} + for _, ignoreInfo := range ignoredAsSlice { + start := ignoreInfo[0] + end := ignoreInfo[1] + name := rawIgnored[start:end] + + ignored[fields.CreateNormalizedName(name)] = SSHIndexIgnoredUnknownInfo{ + LocationRange: common.LocationRange{ + Start: common.Location{ + Line: option.OptionValue.Start.Line, + Character: option.OptionValue.Start.Character + uint32(start), + }, + End: common.Location{ + Line: option.OptionValue.End.Line, + Character: option.OptionValue.Start.Character + uint32(end), + }, + }, + } } i.IgnoredOptions[block] = SSHIndexIgnoredUnknowns{ diff --git a/handlers/ssh_config/indexes/indexes_test.go b/handlers/ssh_config/indexes/indexes_test.go index f937df7..5b4bebf 100644 --- a/handlers/ssh_config/indexes/indexes_test.go +++ b/handlers/ssh_config/indexes/indexes_test.go @@ -112,4 +112,8 @@ UseKeychain yes if !(len(indexes.IgnoredOptions[nil].IgnoredOptions) == 1 && utils.KeyExists(indexes.IgnoredOptions[nil].IgnoredOptions, "usekeychain")) { t.Errorf("Expected IgnoreOptions to contain 'UseKeychain', but got: %v", indexes.IgnoredOptions[nil].IgnoredOptions) } + + if !(indexes.IgnoredOptions[nil].IgnoredOptions["usekeychain"].Start.Line == 0 && indexes.IgnoredOptions[nil].IgnoredOptions["usekeychain"].Start.Character == 14 && indexes.IgnoredOptions[nil].IgnoredOptions["usekeychain"].End.Character == 25) { + t.Errorf("Expected IgnoreOptions to contain 'UseKeychain' on line 0 and from position 14-24, but got: %v", indexes.IgnoredOptions[nil].IgnoredOptions) + } }