From 1e6054ccc15513d2b6a5ad22863ccae447613f84 Mon Sep 17 00:00:00 2001 From: Myzel394 <50424412+Myzel394@users.noreply.github.com> Date: Tue, 1 Oct 2024 21:44:37 +0200 Subject: [PATCH] feat(ssh_config): Improve context --- handlers/ssh_config/analyzer/analyzer.go | 28 ++++---- handlers/ssh_config/analyzer/block_test.go | 2 +- handlers/ssh_config/analyzer/match_test.go | 2 +- handlers/ssh_config/analyzer/options.go | 74 +++++++++----------- handlers/ssh_config/analyzer/options_test.go | 56 +++++++++++---- handlers/ssh_config/analyzer/quotes.go | 54 ++++++-------- handlers/ssh_config/analyzer/quotes_test.go | 48 ++++++++----- handlers/ssh_config/analyzer/values_test.go | 6 +- 8 files changed, 145 insertions(+), 125 deletions(-) diff --git a/handlers/ssh_config/analyzer/analyzer.go b/handlers/ssh_config/analyzer/analyzer.go index 2167966..d9fc6b8 100644 --- a/handlers/ssh_config/analyzer/analyzer.go +++ b/handlers/ssh_config/analyzer/analyzer.go @@ -9,34 +9,32 @@ import ( ) type analyzerContext struct { - document sshconfig.SSHDocument + document *sshconfig.SSHDocument diagnostics []protocol.Diagnostic } func Analyze( d *sshconfig.SSHDocument, ) []protocol.Diagnostic { - errors := analyzeStructureIsValid(d) + ctx := &analyzerContext{ + document: d, + diagnostics: make([]protocol.Diagnostic, 0), + } - if len(errors) > 0 { - return common.ErrsToDiagnostics(errors) + analyzeStructureIsValid(ctx) + + if len(ctx.diagnostics) > 0 { + return ctx.diagnostics } i, indexErrors := indexes.CreateIndexes(*d.Config) + if len(indexErrors) > 0 { + return common.ErrsToDiagnostics(indexErrors) + } + d.Indexes = i - errors = append(errors, indexErrors...) - - if len(errors) > 0 { - return common.ErrsToDiagnostics(errors) - } - - ctx := &analyzerContext{ - document: *d, - diagnostics: make([]protocol.Diagnostic, 0), - } - analyzeValuesAreValid(ctx) analyzeDependents(ctx) analyzeBlocks(ctx) diff --git a/handlers/ssh_config/analyzer/block_test.go b/handlers/ssh_config/analyzer/block_test.go index 35bbad9..4d09361 100644 --- a/handlers/ssh_config/analyzer/block_test.go +++ b/handlers/ssh_config/analyzer/block_test.go @@ -14,7 +14,7 @@ func TestBlockEmptyBlock( Host * `) ctx := &analyzerContext{ - document: *d, + document: d, diagnostics: make([]protocol.Diagnostic, 0), } diff --git a/handlers/ssh_config/analyzer/match_test.go b/handlers/ssh_config/analyzer/match_test.go index c642c5c..a1278a7 100644 --- a/handlers/ssh_config/analyzer/match_test.go +++ b/handlers/ssh_config/analyzer/match_test.go @@ -14,7 +14,7 @@ func TestMatchInvalidAllArgument( Match user lena all `) ctx := &analyzerContext{ - document: *d, + document: d, diagnostics: make([]protocol.Diagnostic, 0), } diff --git a/handlers/ssh_config/analyzer/options.go b/handlers/ssh_config/analyzer/options.go index 9b358d3..1c51243 100644 --- a/handlers/ssh_config/analyzer/options.go +++ b/handlers/ssh_config/analyzer/options.go @@ -2,100 +2,92 @@ package analyzer import ( "config-lsp/common" - sshconfig "config-lsp/handlers/ssh_config" "config-lsp/handlers/ssh_config/ast" "config-lsp/handlers/ssh_config/fields" "config-lsp/utils" - "errors" "fmt" + + protocol "github.com/tliron/glsp/protocol_3_16" ) func analyzeStructureIsValid( - d *sshconfig.SSHDocument, -) []common.LSPError { - errs := make([]common.LSPError, 0) - it := d.Config.Options.Iterator() + ctx *analyzerContext, +) { + it := ctx.document.Config.Options.Iterator() for it.Next() { entry := it.Value().(ast.SSHEntry) switch entry.(type) { case *ast.SSHOption: - errs = append(errs, checkOption(d, entry.(*ast.SSHOption), nil)...) + checkOption(ctx, entry.(*ast.SSHOption), nil) case *ast.SSHMatchBlock: matchBlock := entry.(*ast.SSHMatchBlock) - errs = append(errs, checkBlock(d, matchBlock)...) + checkBlock(ctx, matchBlock) case *ast.SSHHostBlock: hostBlock := entry.(*ast.SSHHostBlock) - errs = append(errs, checkBlock(d, hostBlock)...) + checkBlock(ctx, hostBlock) } } - - return errs } func checkOption( - d *sshconfig.SSHDocument, + ctx *analyzerContext, option *ast.SSHOption, block ast.SSHBlock, -) []common.LSPError { - errs := make([]common.LSPError, 0) - +) { if option.Key == nil { - return errs + return } - errs = append(errs, checkIsUsingDoubleQuotes(option.Key.Value, option.Key.LocationRange)...) - errs = append(errs, checkQuotesAreClosed(option.Key.Value, option.Key.LocationRange)...) + checkIsUsingDoubleQuotes(ctx, option.Key.Value, option.Key.LocationRange) + checkQuotesAreClosed(ctx, option.Key.Value, option.Key.LocationRange) // Check for values that are not allowed in Host blocks if block != nil && block.GetBlockType() == ast.SSHBlockTypeHost { if utils.KeyExists(fields.HostDisallowedOptions, option.Key.Key) { - errs = append(errs, common.LSPError{ - Range: option.Key.LocationRange, - Err: errors.New(fmt.Sprintf("Option '%s' is not allowed in Host blocks", 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, }) } } if option.OptionValue == nil || option.OptionValue.Value.Value == "" { - errs = append(errs, common.LSPError{ - Range: option.Key.LocationRange, - Err: errors.New(fmt.Sprintf("Option '%s' requires a value", option.Key.Key)), + ctx.diagnostics = append(ctx.diagnostics, protocol.Diagnostic{ + Range: option.Key.LocationRange.ToLSPRange(), + Message: fmt.Sprintf("Option '%s' requires a value", option.Key.Key), + Severity: &common.SeverityError, }) } else { - errs = append(errs, checkIsUsingDoubleQuotes(option.OptionValue.Value, option.OptionValue.LocationRange)...) - errs = append(errs, checkQuotesAreClosed(option.OptionValue.Value, option.OptionValue.LocationRange)...) + checkIsUsingDoubleQuotes(ctx, option.OptionValue.Value, option.OptionValue.LocationRange) + checkQuotesAreClosed(ctx, option.OptionValue.Value, option.OptionValue.LocationRange) } if option.Separator == nil || option.Separator.Value.Value == "" { - errs = append(errs, common.LSPError{ - Range: option.Key.LocationRange, - Err: errors.New(fmt.Sprintf("There should be a separator between an option and its value")), + ctx.diagnostics = append(ctx.diagnostics, protocol.Diagnostic{ + Range: option.Key.LocationRange.ToLSPRange(), + Message: fmt.Sprintf("There should be a separator between an option and its value"), + Severity: &common.SeverityError, }) } else { - errs = append(errs, checkIsUsingDoubleQuotes(option.Separator.Value, option.Separator.LocationRange)...) - errs = append(errs, checkQuotesAreClosed(option.Separator.Value, option.Separator.LocationRange)...) + checkIsUsingDoubleQuotes(ctx, option.Separator.Value, option.Separator.LocationRange) + checkQuotesAreClosed(ctx, option.Separator.Value, option.Separator.LocationRange) } - - return errs } func checkBlock( - d *sshconfig.SSHDocument, + ctx *analyzerContext, block ast.SSHBlock, -) []common.LSPError { - errs := make([]common.LSPError, 0) - - errs = append(errs, checkOption(d, block.GetEntryOption(), block)...) +) { + checkOption(ctx, block.GetEntryOption(), block) it := block.GetOptions().Iterator() for it.Next() { option := it.Value().(*ast.SSHOption) - errs = append(errs, checkOption(d, option, block)...) + checkOption(ctx, option, block) } - - return errs } diff --git a/handlers/ssh_config/analyzer/options_test.go b/handlers/ssh_config/analyzer/options_test.go index 49a99d5..a7855eb 100644 --- a/handlers/ssh_config/analyzer/options_test.go +++ b/handlers/ssh_config/analyzer/options_test.go @@ -3,6 +3,8 @@ package analyzer import ( testutils_test "config-lsp/handlers/ssh_config/test_utils" "testing" + + protocol "github.com/tliron/glsp/protocol_3_16" ) func TestSimpleExample( @@ -13,11 +15,15 @@ ProxyCommand hello User root `) + ctx := &analyzerContext{ + document: d, + diagnostics: make([]protocol.Diagnostic, 0), + } - errors := analyzeStructureIsValid(d) + analyzeStructureIsValid(ctx) - if len(errors) != 0 { - t.Fatalf("Expected no errors, got %v", errors) + if len(ctx.diagnostics) != 0 { + t.Fatalf("Expected no errors, got %v", ctx.diagnostics) } } @@ -29,10 +35,15 @@ ProxyCommand User root `) - errors := analyzeStructureIsValid(d) + ctx := &analyzerContext{ + document: d, + diagnostics: make([]protocol.Diagnostic, 0), + } - if len(errors) != 1 { - t.Fatalf("Expected 1 error, got %v", errors) + analyzeStructureIsValid(ctx) + + if len(ctx.diagnostics) != 1 { + t.Fatalf("Expected 1 error, got %v", ctx.diagnostics) } } @@ -44,10 +55,15 @@ func TestNoSeparator( User root `) - errors := analyzeStructureIsValid(d) + ctx := &analyzerContext{ + document: d, + diagnostics: make([]protocol.Diagnostic, 0), + } - if len(errors) != 1 { - t.Fatalf("Expected 1 error, got %v", errors) + analyzeStructureIsValid(ctx) + + if len(ctx.diagnostics) != 1 { + t.Fatalf("Expected 1 error, got %v", ctx.diagnostics) } } @@ -62,10 +78,15 @@ Host example.com Match `) - errors := analyzeStructureIsValid(d) + ctx := &analyzerContext{ + document: d, + diagnostics: make([]protocol.Diagnostic, 0), + } - if len(errors) != 2 { - t.Fatalf("Expected 1 error, got %v", errors) + analyzeStructureIsValid(ctx) + + if len(ctx.diagnostics) != 2 { + t.Fatalf("Expected 1 error, got %v", ctx.diagnostics) } } @@ -80,9 +101,14 @@ Host example.com Match `) - errors := analyzeStructureIsValid(d) + ctx := &analyzerContext{ + document: d, + diagnostics: make([]protocol.Diagnostic, 0), + } - if len(errors) != 1 { - t.Fatalf("Expected 1 error, got %v", errors) + analyzeStructureIsValid(ctx) + + if len(ctx.diagnostics) != 1 { + t.Fatalf("Expected 1 error, got %v", ctx.diagnostics) } } diff --git a/handlers/ssh_config/analyzer/quotes.go b/handlers/ssh_config/analyzer/quotes.go index 3609dde..eca872a 100644 --- a/handlers/ssh_config/analyzer/quotes.go +++ b/handlers/ssh_config/analyzer/quotes.go @@ -3,60 +3,52 @@ package analyzer import ( "config-lsp/common" commonparser "config-lsp/common/parser" - sshconfig "config-lsp/handlers/ssh_config" "config-lsp/utils" - "errors" "strings" + + protocol "github.com/tliron/glsp/protocol_3_16" ) func analyzeQuotesAreValid( - d *sshconfig.SSHDocument, -) []common.LSPError { - errs := make([]common.LSPError, 0) + ctx *analyzerContext, +) { + for _, info := range ctx.document.Config.GetAllOptions() { + checkIsUsingDoubleQuotes(ctx, info.Option.Key.Value, info.Option.Key.LocationRange) + checkIsUsingDoubleQuotes(ctx, info.Option.OptionValue.Value, info.Option.OptionValue.LocationRange) - for _, info := range d.Config.GetAllOptions() { - errs = append(errs, checkIsUsingDoubleQuotes(info.Option.Key.Value, info.Option.Key.LocationRange)...) - errs = append(errs, checkIsUsingDoubleQuotes(info.Option.OptionValue.Value, info.Option.OptionValue.LocationRange)...) - - errs = append(errs, checkQuotesAreClosed(info.Option.Key.Value, info.Option.Key.LocationRange)...) - errs = append(errs, checkQuotesAreClosed(info.Option.OptionValue.Value, info.Option.OptionValue.LocationRange)...) + checkQuotesAreClosed(ctx, info.Option.Key.Value, info.Option.Key.LocationRange) + checkQuotesAreClosed(ctx, info.Option.OptionValue.Value, info.Option.OptionValue.LocationRange) } - - return errs } func checkIsUsingDoubleQuotes( + ctx *analyzerContext, value commonparser.ParsedString, valueRange common.LocationRange, -) []common.LSPError { +) { quoteRanges := utils.GetQuoteRanges(value.Raw) singleQuotePosition := strings.Index(value.Raw, "'") // Single quoe if singleQuotePosition != -1 && !quoteRanges.IsCharInside(singleQuotePosition) { - return []common.LSPError{ - { - Range: valueRange, - Err: errors.New("ssh_config does not support single quotes. Use double quotes (\") instead."), - }, - } + ctx.diagnostics = append(ctx.diagnostics, protocol.Diagnostic{ + Range: valueRange.ToLSPRange(), + Message: "ssh_config does not support single quotes. Use double quotes (\") instead.", + Severity: &common.SeverityError, + }) } - - return nil } func checkQuotesAreClosed( + ctx *analyzerContext, value commonparser.ParsedString, valueRange common.LocationRange, -) []common.LSPError { +) { if strings.Count(value.Raw, "\"")%2 != 0 { - return []common.LSPError{ - { - Range: valueRange, - Err: errors.New("There are unclosed quotes here. Make sure all quotes are closed."), - }, - } + ctx.diagnostics = append(ctx.diagnostics, protocol.Diagnostic{ + Range: valueRange.ToLSPRange(), + Message: "There are unclosed quotes here. Make sure all quotes are closed.", + Severity: &common.SeverityError, + }) } - - return nil } diff --git a/handlers/ssh_config/analyzer/quotes_test.go b/handlers/ssh_config/analyzer/quotes_test.go index 2914e32..accb08a 100644 --- a/handlers/ssh_config/analyzer/quotes_test.go +++ b/handlers/ssh_config/analyzer/quotes_test.go @@ -13,11 +13,14 @@ func TestSimpleInvalidQuotesExample( d := testutils_test.DocumentFromInput(t, ` PermitRootLogin 'yes' `) + ctx := &analyzerContext{ + document: d, + diagnostics: make([]protocol.Diagnostic, 0), + } + analyzeQuotesAreValid(ctx) - errors := analyzeQuotesAreValid(d) - - if !(len(errors) == 1) { - t.Errorf("Expected 1 error, got %v", len(errors)) + if !(len(ctx.diagnostics) == 1) { + t.Errorf("Expected 1 error, got %v", len(ctx.diagnostics)) } } @@ -27,11 +30,14 @@ func TestSingleQuotesKeyAndOptionExample( d := testutils_test.DocumentFromInput(t, ` 'Port' '22' `) + ctx := &analyzerContext{ + document: d, + diagnostics: make([]protocol.Diagnostic, 0), + } + analyzeQuotesAreValid(ctx) - errors := analyzeQuotesAreValid(d) - - if !(len(errors) == 2) { - t.Errorf("Expected 2 errors, got %v", len(errors)) + if !(len(ctx.diagnostics) == 2) { + t.Errorf("Expected 2 ctx.diagnostics, got %v", len(ctx.diagnostics)) } } @@ -41,11 +47,14 @@ func TestSimpleUnclosedQuoteExample( d := testutils_test.DocumentFromInput(t, ` PermitRootLogin "yes `) + ctx := &analyzerContext{ + document: d, + diagnostics: make([]protocol.Diagnostic, 0), + } + analyzeQuotesAreValid(ctx) - errors := analyzeQuotesAreValid(d) - - if !(len(errors) == 1) { - t.Errorf("Expected 1 error, got %v", len(errors)) + if !(len(ctx.diagnostics) == 1) { + t.Errorf("Expected 1 error, got %v", len(ctx.diagnostics)) } } @@ -55,11 +64,14 @@ func TestIncompleteQuotesExample( d := testutils_test.DocumentFromInput(t, ` "Port `) + ctx := &analyzerContext{ + document: d, + diagnostics: make([]protocol.Diagnostic, 0), + } + analyzeQuotesAreValid(ctx) - errors := analyzeQuotesAreValid(d) - - if !(len(errors) == 1) { - t.Errorf("Expected 1 error, got %v", len(errors)) + if !(len(ctx.diagnostics) == 1) { + t.Errorf("Expected 1 error, got %v", len(ctx.diagnostics)) } } @@ -71,7 +83,7 @@ Port 1234 CanonicalDomains example.com `) ctx := &analyzerContext{ - document: *d, + document: d, diagnostics: make([]protocol.Diagnostic, 0), } @@ -92,7 +104,7 @@ CanonicalizeHostname yes CanonicalDomains example.com `) ctx := &analyzerContext{ - document: *d, + document: d, diagnostics: make([]protocol.Diagnostic, 0), } diff --git a/handlers/ssh_config/analyzer/values_test.go b/handlers/ssh_config/analyzer/values_test.go index ac843d6..14d8a59 100644 --- a/handlers/ssh_config/analyzer/values_test.go +++ b/handlers/ssh_config/analyzer/values_test.go @@ -14,7 +14,7 @@ func TestUnknownOptionExample( ThisOptionDoesNotExist okay `) ctx := &analyzerContext{ - document: *d, + document: d, diagnostics: make([]protocol.Diagnostic, 0), } @@ -33,7 +33,7 @@ IgnoreUnknown ThisOptionDoesNotExist ThisOptionDoesNotExist okay `) ctx := &analyzerContext{ - document: *d, + document: d, diagnostics: make([]protocol.Diagnostic, 0), } @@ -52,7 +52,7 @@ ThisOptionDoesNotExist okay IgnoreUnknown ThisOptionDoesNotExist `) ctx := &analyzerContext{ - document: *d, + document: d, diagnostics: make([]protocol.Diagnostic, 0), }