diff --git a/server/handlers/ssh_config/analyzer/analyzer.go b/server/handlers/ssh_config/analyzer/analyzer.go index 447bb34..b8b9ba7 100644 --- a/server/handlers/ssh_config/analyzer/analyzer.go +++ b/server/handlers/ssh_config/analyzer/analyzer.go @@ -54,7 +54,6 @@ func Analyze( } } - analyzeValuesAreValid(ctx) analyzeTokens(ctx) analyzeIgnoreUnknownHasNoUnnecessary(ctx) analyzeDependents(ctx) diff --git a/server/handlers/ssh_config/analyzer/options.go b/server/handlers/ssh_config/analyzer/options.go index cee2bd6..0356cf5 100644 --- a/server/handlers/ssh_config/analyzer/options.go +++ b/server/handlers/ssh_config/analyzer/options.go @@ -4,6 +4,7 @@ import ( "config-lsp/common" docvalues "config-lsp/doc-values" "config-lsp/handlers/ssh_config/ast" + "config-lsp/handlers/ssh_config/diagnostics" "config-lsp/handlers/ssh_config/fields" "config-lsp/utils" "fmt" @@ -41,11 +42,33 @@ func checkOption( checkIsUsingDoubleQuotes(ctx, option.Key.Value, option.Key.LocationRange) checkQuotesAreClosed(ctx, option.Key.Value, option.Key.LocationRange) - docOption, found := fields.Options[option.Key.Key] + docOption, optionFound := fields.Options[option.Key.Key] - if !found { + if !optionFound { // Diagnostics will be handled by `values.go` - return + if !ctx.document.Indexes.CanOptionBeIgnored(option, block) { + ctx.diagnostics = append( + ctx.diagnostics, + diagnostics.GenerateUnknownOption( + option.Key.ToLSPRange(), + option.Key.Value.Value, + ), + ) + ctx.document.Indexes.UnknownOptions[option.Start.Line] = ast.AllOptionInfo{ + Option: option, + Block: block, + } + + return + } + } + + if block != nil && block.GetBlockType() == ast.SSHBlockTypeHost && utils.KeyExists(fields.HostDisallowedOptions, option.Key.Key) { + ctx.diagnostics = append(ctx.diagnostics, protocol.Diagnostic{ + Range: option.Key.LocationRange.ToLSPRange(), + Message: fmt.Sprintf("Option '%s' is not allowed in Host blocks", option.Key.Key), + Severity: &common.SeverityError, + }) } // Check for values that are not allowed in Host blocks @@ -59,7 +82,7 @@ func checkOption( } } - if option.OptionValue != nil { + if option.OptionValue != nil && optionFound { checkIsUsingDoubleQuotes(ctx, option.OptionValue.Value, option.OptionValue.LocationRange) checkQuotesAreClosed(ctx, option.OptionValue.Value, option.OptionValue.LocationRange) diff --git a/server/handlers/ssh_config/analyzer/options_test.go b/server/handlers/ssh_config/analyzer/options_test.go index f4ba881..5a98d7f 100644 --- a/server/handlers/ssh_config/analyzer/options_test.go +++ b/server/handlers/ssh_config/analyzer/options_test.go @@ -112,3 +112,79 @@ Match t.Fatalf("Expected 1 error, got %v", ctx.diagnostics) } } + +func TestUnknownOptionExample( + t *testing.T, +) { + d := testutils_test.DocumentFromInput(t, ` +ThisOptionDoesNotExist okay +`) + ctx := &analyzerContext{ + document: d, + diagnostics: make([]protocol.Diagnostic, 0), + } + + analyzeStructureIsValid(ctx) + + if !(len(ctx.diagnostics) == 1) { + t.Errorf("Expected 1 error, got %v", len(ctx.diagnostics)) + } + + if !(len(ctx.document.Indexes.UnknownOptions) == 1) { + t.Errorf("Expected 1 unknown option, got %v", len(ctx.document.Indexes.UnknownOptions)) + } + + if !(ctx.document.Indexes.UnknownOptions[0].Option.Key.Value.Value == "ThisOptionDoesNotExist") { + t.Errorf("Expected 'ThisOptionDoesNotExist', got %v", ctx.document.Indexes.UnknownOptions[0].Option.Key.Value.Value) + } +} + +func TestUnknownOptionButIgnoredExample( + t *testing.T, +) { + d := testutils_test.DocumentFromInput(t, ` +IgnoreUnknown ThisOptionDoesNotExist +ThisOptionDoesNotExist okay +`) + ctx := &analyzerContext{ + document: d, + diagnostics: make([]protocol.Diagnostic, 0), + } + + analyzeStructureIsValid(ctx) + + if len(ctx.diagnostics) > 0 { + t.Fatalf("Expected no errors, but got %v", len(ctx.diagnostics)) + } + + if !(len(ctx.document.Indexes.UnknownOptions) == 0) { + t.Errorf("Expected 0 unknown options, got %v", len(ctx.document.Indexes.UnknownOptions)) + } +} + +func TestUnknownOptionIgnoredIsAfterDefinitionExample( + t *testing.T, +) { + d := testutils_test.DocumentFromInput(t, ` +ThisOptionDoesNotExist okay +IgnoreUnknown ThisOptionDoesNotExist +`) + ctx := &analyzerContext{ + document: d, + diagnostics: make([]protocol.Diagnostic, 0), + } + + analyzeStructureIsValid(ctx) + + if !(len(ctx.diagnostics) == 1) { + t.Errorf("Expected 1 error, got %v", len(ctx.diagnostics)) + } + + if !(len(ctx.document.Indexes.UnknownOptions) == 1) { + t.Errorf("Expected 1 unknown option, got %v", len(ctx.document.Indexes.UnknownOptions)) + } + + if !(ctx.document.Indexes.UnknownOptions[0].Option.Key.Value.Value == "ThisOptionDoesNotExist") { + t.Errorf("Expected 'ThisOptionDoesNotExist', got %v", ctx.document.Indexes.UnknownOptions[0].Option.Key.Value.Value) + } +} diff --git a/server/handlers/ssh_config/analyzer/values.go b/server/handlers/ssh_config/analyzer/values.go deleted file mode 100644 index a339487..0000000 --- a/server/handlers/ssh_config/analyzer/values.go +++ /dev/null @@ -1,34 +0,0 @@ -package analyzer - -import ( - "config-lsp/handlers/ssh_config/diagnostics" - "config-lsp/handlers/ssh_config/fields" -) - -func analyzeValuesAreValid( - ctx *analyzerContext, -) { - // Check if there are unknown options - for _, info := range ctx.document.Config.GetAllOptions() { - option := info.Option - block := info.Block - - _, found := fields.Options[option.Key.Key] - - if !found { - if ctx.document.Indexes.CanOptionBeIgnored(option, block) { - // Skip - continue - } - - ctx.diagnostics = append( - ctx.diagnostics, - diagnostics.GenerateUnknownOption( - option.Key.ToLSPRange(), - option.Key.Value.Value, - ), - ) - ctx.document.Indexes.UnknownOptions[info.Option.Start.Line] = info - } - } -} diff --git a/server/handlers/ssh_config/analyzer/values_test.go b/server/handlers/ssh_config/analyzer/values_test.go deleted file mode 100644 index 88bffcc..0000000 --- a/server/handlers/ssh_config/analyzer/values_test.go +++ /dev/null @@ -1,84 +0,0 @@ -package analyzer - -import ( - testutils_test "config-lsp/handlers/ssh_config/test_utils" - "testing" - - protocol "github.com/tliron/glsp/protocol_3_16" -) - -func TestUnknownOptionExample( - t *testing.T, -) { - d := testutils_test.DocumentFromInput(t, ` -ThisOptionDoesNotExist okay -`) - ctx := &analyzerContext{ - document: d, - diagnostics: make([]protocol.Diagnostic, 0), - } - - analyzeValuesAreValid(ctx) - - if !(len(ctx.diagnostics) == 1) { - t.Errorf("Expected 1 error, got %v", len(ctx.diagnostics)) - } - - if !(len(ctx.document.Indexes.UnknownOptions) == 1) { - t.Errorf("Expected 1 unknown option, got %v", len(ctx.document.Indexes.UnknownOptions)) - } - - if !(ctx.document.Indexes.UnknownOptions[0].Option.Key.Value.Value == "ThisOptionDoesNotExist") { - t.Errorf("Expected 'ThisOptionDoesNotExist', got %v", ctx.document.Indexes.UnknownOptions[0].Option.Key.Value.Value) - } -} - -func TestUnknownOptionButIgnoredExample( - t *testing.T, -) { - d := testutils_test.DocumentFromInput(t, ` -IgnoreUnknown ThisOptionDoesNotExist -ThisOptionDoesNotExist okay -`) - ctx := &analyzerContext{ - document: d, - diagnostics: make([]protocol.Diagnostic, 0), - } - - analyzeValuesAreValid(ctx) - - if len(ctx.diagnostics) > 0 { - t.Fatalf("Expected no errors, but got %v", len(ctx.diagnostics)) - } - - if !(len(ctx.document.Indexes.UnknownOptions) == 0) { - t.Errorf("Expected 0 unknown options, got %v", len(ctx.document.Indexes.UnknownOptions)) - } -} - -func TestUnknownOptionIgnoredIsAfterDefinitionExample( - t *testing.T, -) { - d := testutils_test.DocumentFromInput(t, ` -ThisOptionDoesNotExist okay -IgnoreUnknown ThisOptionDoesNotExist -`) - ctx := &analyzerContext{ - document: d, - diagnostics: make([]protocol.Diagnostic, 0), - } - - analyzeValuesAreValid(ctx) - - if !(len(ctx.diagnostics) == 1) { - t.Errorf("Expected 1 error, got %v", len(ctx.diagnostics)) - } - - if !(len(ctx.document.Indexes.UnknownOptions) == 1) { - t.Errorf("Expected 1 unknown option, got %v", len(ctx.document.Indexes.UnknownOptions)) - } - - if !(ctx.document.Indexes.UnknownOptions[0].Option.Key.Value.Value == "ThisOptionDoesNotExist") { - t.Errorf("Expected 'ThisOptionDoesNotExist', got %v", ctx.document.Indexes.UnknownOptions[0].Option.Key.Value.Value) - } -} diff --git a/server/handlers/ssh_config/handlers/code-actions-add-to-unknown.go b/server/handlers/ssh_config/handlers/fetch-code-actions_add_to_unknown.go similarity index 100% rename from server/handlers/ssh_config/handlers/code-actions-add-to-unknown.go rename to server/handlers/ssh_config/handlers/fetch-code-actions_add_to_unknown.go diff --git a/server/handlers/ssh_config/handlers/code-action-typos.go b/server/handlers/ssh_config/handlers/fetch-code-actions_typos.go similarity index 100% rename from server/handlers/ssh_config/handlers/code-action-typos.go rename to server/handlers/ssh_config/handlers/fetch-code-actions_typos.go