diff --git a/server/handlers/ssh_config/analyzer/analyzer.go b/server/handlers/ssh_config/analyzer/analyzer.go index b8b9ba7..447bb34 100644 --- a/server/handlers/ssh_config/analyzer/analyzer.go +++ b/server/handlers/ssh_config/analyzer/analyzer.go @@ -54,6 +54,7 @@ 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 90dee52..47d8dc9 100644 --- a/server/handlers/ssh_config/analyzer/options.go +++ b/server/handlers/ssh_config/analyzer/options.go @@ -4,7 +4,6 @@ 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" @@ -63,31 +62,16 @@ func checkOption( if !optionFound { // Diagnostics will be handled by `values.go` - 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, - } - } - - // Since we don't know the option, we can't verify the value return - } else { - // Check for values that are not allowed in Host blocks - 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 + 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 if the value is valid diff --git a/server/handlers/ssh_config/analyzer/options_test.go b/server/handlers/ssh_config/analyzer/options_test.go index 5a98d7f..f4ba881 100644 --- a/server/handlers/ssh_config/analyzer/options_test.go +++ b/server/handlers/ssh_config/analyzer/options_test.go @@ -112,79 +112,3 @@ 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 new file mode 100644 index 0000000..49b7e1e --- /dev/null +++ b/server/handlers/ssh_config/analyzer/values.go @@ -0,0 +1,36 @@ +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 + + continue + } + } +} diff --git a/server/handlers/ssh_config/analyzer/values_test.go b/server/handlers/ssh_config/analyzer/values_test.go new file mode 100644 index 0000000..88bffcc --- /dev/null +++ b/server/handlers/ssh_config/analyzer/values_test.go @@ -0,0 +1,84 @@ +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/formatting.go b/server/handlers/ssh_config/handlers/formatting.go index 169f325..9d1d91b 100644 --- a/server/handlers/ssh_config/handlers/formatting.go +++ b/server/handlers/ssh_config/handlers/formatting.go @@ -33,7 +33,7 @@ func FormatDocument( // it := d.Config.Options.Iterator() // for it.Next() { // line := it.Key().(uint32) - // entry := it.Value().(ast.SSHEntry) + // entry := it.Name().(ast.SSHEntry) // // if !(line >= textRange.Start.Line && line <= textRange.End.Line) { // continue diff --git a/server/handlers/ssh_config/lsp/text-document-did-change.go b/server/handlers/ssh_config/lsp/text-document-did-change.go index 118ace6..830315c 100644 --- a/server/handlers/ssh_config/lsp/text-document-did-change.go +++ b/server/handlers/ssh_config/lsp/text-document-did-change.go @@ -20,6 +20,7 @@ func TextDocumentDidChange( document := sshconfig.DocumentParserMap[params.TextDocument.URI] document.Config.Clear() + println("reparsing everything") diagnostics := make([]protocol.Diagnostic, 0) errors := document.Config.Parse(content)