refactor(ssh_config): Use Diagnostics instead of LSPError

This commit is contained in:
Myzel394 2024-10-01 16:22:32 +02:00
parent b7f01f0bd4
commit a0c0ccce25
No known key found for this signature in database
GPG Key ID: ED20A1D1D423AF3F
7 changed files with 99 additions and 87 deletions

View File

@ -38,12 +38,9 @@ func Analyze(
} }
analyzeValuesAreValid(ctx) analyzeValuesAreValid(ctx)
analyzeDependents(ctx)
analyzeBlocks(ctx)
analyzeMatchBlocks(ctx)
return ctx.diagnostics return ctx.diagnostics
errors = append(errors, analyzeDependents(d)...)
errors = append(errors, analyzeBlocks(d)...)
errors = append(errors, analyzeMatchBlocks(d)...)
return common.ErrsToDiagnostics(errors)
} }

View File

@ -2,23 +2,23 @@ package analyzer
import ( import (
"config-lsp/common" "config-lsp/common"
sshconfig "config-lsp/handlers/ssh_config"
"errors" protocol "github.com/tliron/glsp/protocol_3_16"
) )
func analyzeBlocks( func analyzeBlocks(
d *sshconfig.SSHDocument, ctx *analyzerContext,
) []common.LSPError { ) {
errs := make([]common.LSPError, 0) for _, block := range ctx.document.GetAllBlocks() {
for _, block := range d.GetAllBlocks() {
if block.GetOptions().Size() == 0 { if block.GetOptions().Size() == 0 {
errs = append(errs, common.LSPError{ ctx.diagnostics = append(ctx.diagnostics, protocol.Diagnostic{
Range: block.GetEntryOption().LocationRange, Range: block.GetEntryOption().LocationRange.ToLSPRange(),
Err: errors.New("This block is empty"), Message: "This block is empty",
Severity: &common.SeverityHint,
Tags: []protocol.DiagnosticTag{
protocol.DiagnosticTagUnnecessary,
},
}) })
} }
} }
return errs
} }

View File

@ -3,6 +3,8 @@ package analyzer
import ( import (
testutils_test "config-lsp/handlers/ssh_config/test_utils" testutils_test "config-lsp/handlers/ssh_config/test_utils"
"testing" "testing"
protocol "github.com/tliron/glsp/protocol_3_16"
) )
func TestBlockEmptyBlock( func TestBlockEmptyBlock(
@ -11,10 +13,14 @@ func TestBlockEmptyBlock(
d := testutils_test.DocumentFromInput(t, ` d := testutils_test.DocumentFromInput(t, `
Host * Host *
`) `)
ctx := &analyzerContext{
document: *d,
diagnostics: make([]protocol.Diagnostic, 0),
}
errors := analyzeBlocks(d) analyzeBlocks(ctx)
if !(len(errors) == 1) { if !(len(ctx.diagnostics) == 1) {
t.Errorf("Expected an error, but got %v", len(errors)) t.Errorf("Expected an error, but got %v", len(ctx.diagnostics))
} }
} }

View File

@ -2,40 +2,34 @@ package analyzer
import ( import (
"config-lsp/common" "config-lsp/common"
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/fields"
"errors"
"fmt" "fmt"
protocol "github.com/tliron/glsp/protocol_3_16"
) )
func analyzeDependents( func analyzeDependents(
d *sshconfig.SSHDocument, ctx *analyzerContext,
) []common.LSPError { ) {
errs := make([]common.LSPError, 0) for _, option := range ctx.document.Config.GetAllOptions() {
checkIsDependent(ctx, option.Option.Key, option.Block)
for _, option := range d.Config.GetAllOptions() {
errs = append(errs, checkIsDependent(d, option.Option.Key, option.Block)...)
} }
return errs
} }
func checkIsDependent( func checkIsDependent(
d *sshconfig.SSHDocument, ctx *analyzerContext,
key *ast.SSHKey, key *ast.SSHKey,
block ast.SSHBlock, block ast.SSHBlock,
) []common.LSPError { ) {
errs := make([]common.LSPError, 0)
dependentOptions, found := fields.DependentFields[key.Key] dependentOptions, found := fields.DependentFields[key.Key]
if !found { if !found {
return errs return
} }
for _, dependentOption := range dependentOptions { for _, dependentOption := range dependentOptions {
if opts, found := d.Indexes.AllOptionsPerName[dependentOption]; found { if opts, found := ctx.document.Indexes.AllOptionsPerName[dependentOption]; found {
_, existsInBlock := opts[block] _, existsInBlock := opts[block]
_, existsInGlobal := opts[nil] _, existsInGlobal := opts[nil]
@ -44,11 +38,10 @@ func checkIsDependent(
} }
} }
errs = append(errs, common.LSPError{ ctx.diagnostics = append(ctx.diagnostics, protocol.Diagnostic{
Range: key.LocationRange, Range: key.LocationRange.ToLSPRange(),
Err: errors.New(fmt.Sprintf("Option '%s' requires option '%s' to be present", key.Key, dependentOption)), Message: fmt.Sprintf("Option '%s' requires option '%s' to be present", key.Key, dependentOption),
Severity: &common.SeverityError,
}) })
} }
return errs
} }

View File

@ -2,77 +2,78 @@ package analyzer
import ( import (
"config-lsp/common" "config-lsp/common"
sshconfig "config-lsp/handlers/ssh_config"
"config-lsp/handlers/ssh_config/fields" "config-lsp/handlers/ssh_config/fields"
matchparser "config-lsp/handlers/ssh_config/match-parser" matchparser "config-lsp/handlers/ssh_config/match-parser"
"config-lsp/utils" "config-lsp/utils"
"errors"
"fmt" "fmt"
protocol "github.com/tliron/glsp/protocol_3_16"
) )
func analyzeMatchBlocks( func analyzeMatchBlocks(
d *sshconfig.SSHDocument, ctx *analyzerContext,
) []common.LSPError { ) {
errs := make([]common.LSPError, 0) for _, matchBlock := range ctx.document.GetAllMatchBlocks() {
isValid := isMatchStructureValid(ctx, matchBlock.MatchValue)
for _, matchBlock := range d.GetAllMatchBlocks() { if !isValid {
structureErrs := isMatchStructureValid(matchBlock.MatchValue)
errs = append(errs, structureErrs...)
if len(structureErrs) > 0 {
continue continue
} }
errs = append(errs, checkMatch(matchBlock.MatchValue)...) checkMatch(ctx, matchBlock.MatchValue)
} }
return errs
} }
func isMatchStructureValid( func isMatchStructureValid(
ctx *analyzerContext,
m *matchparser.Match, m *matchparser.Match,
) []common.LSPError { ) bool {
errs := make([]common.LSPError, 0) isValid := true
for _, entry := range m.Entries { for _, entry := range m.Entries {
if !utils.KeyExists(fields.MatchSingleOptionCriterias, entry.Criteria.Type) && entry.Value.Value == "" { if !utils.KeyExists(fields.MatchSingleOptionCriterias, entry.Criteria.Type) && entry.Value.Value == "" {
errs = append(errs, common.LSPError{ ctx.diagnostics = append(ctx.diagnostics, protocol.Diagnostic{
Range: entry.LocationRange, Range: entry.LocationRange.ToLSPRange(),
Err: errors.New(fmt.Sprintf("Argument '%s' requires a value", entry.Criteria.Type)), Message: fmt.Sprintf("Argument '%s' requires a value", entry.Criteria.Type),
Severity: &common.SeverityError,
}) })
isValid = false
} }
} }
return errs return isValid
} }
func checkMatch( func checkMatch(
ctx *analyzerContext,
m *matchparser.Match, m *matchparser.Match,
) []common.LSPError { ) {
errs := make([]common.LSPError, 0)
// Check single options // Check single options
allEntries := m.FindEntries("all") allEntries := m.FindEntries("all")
if len(allEntries) > 1 { if len(allEntries) > 1 {
errs = append(errs, common.LSPError{ ctx.diagnostics = append(ctx.diagnostics, protocol.Diagnostic{
Range: allEntries[1].LocationRange, Range: allEntries[1].LocationRange.ToLSPRange(),
Err: errors.New("'all' may only be used once"), Message: "'all' may only be used once",
Severity: &common.SeverityError,
}) })
} }
canonicalEntries := m.FindEntries("canonical") canonicalEntries := m.FindEntries("canonical")
if len(canonicalEntries) > 1 { if len(canonicalEntries) > 1 {
errs = append(errs, common.LSPError{ ctx.diagnostics = append(ctx.diagnostics, protocol.Diagnostic{
Range: canonicalEntries[1].LocationRange, Range: canonicalEntries[1].LocationRange.ToLSPRange(),
Err: errors.New("'canonical' may only be used once"), Message: "'canonical' may only be used once",
Severity: &common.SeverityError,
}) })
} }
finalEntries := m.FindEntries("final") finalEntries := m.FindEntries("final")
if len(finalEntries) > 1 { if len(finalEntries) > 1 {
errs = append(errs, common.LSPError{ ctx.diagnostics = append(ctx.diagnostics, protocol.Diagnostic{
Range: finalEntries[1].LocationRange, Range: finalEntries[1].LocationRange.ToLSPRange(),
Err: errors.New("'final' may only be used once"), Message: "'final' may only be used once",
Severity: &common.SeverityError,
}) })
} }
@ -82,12 +83,11 @@ func checkMatch(
previousEntry := m.GetPreviousEntry(allEntry) previousEntry := m.GetPreviousEntry(allEntry)
if previousEntry != nil && !utils.KeyExists(fields.MatchAllArgumentAllowedPreviousOptions, previousEntry.Criteria.Type) { if previousEntry != nil && !utils.KeyExists(fields.MatchAllArgumentAllowedPreviousOptions, previousEntry.Criteria.Type) {
errs = append(errs, common.LSPError{ ctx.diagnostics = append(ctx.diagnostics, protocol.Diagnostic{
Range: allEntry.LocationRange, Range: allEntry.LocationRange.ToLSPRange(),
Err: errors.New("'all' should either be the first entry or immediately follow 'final' or 'canonical'"), Message: "'all' should either be the first entry or immediately follow 'final' or 'canonical'",
Severity: &common.SeverityError,
}) })
} }
} }
return errs
} }

View File

@ -3,6 +3,8 @@ package analyzer
import ( import (
testutils_test "config-lsp/handlers/ssh_config/test_utils" testutils_test "config-lsp/handlers/ssh_config/test_utils"
"testing" "testing"
protocol "github.com/tliron/glsp/protocol_3_16"
) )
func TestMatchInvalidAllArgument( func TestMatchInvalidAllArgument(
@ -11,10 +13,14 @@ func TestMatchInvalidAllArgument(
d := testutils_test.DocumentFromInput(t, ` d := testutils_test.DocumentFromInput(t, `
Match user lena all Match user lena all
`) `)
ctx := &analyzerContext{
document: *d,
diagnostics: make([]protocol.Diagnostic, 0),
}
errors := analyzeMatchBlocks(d) analyzeMatchBlocks(ctx)
if !(len(errors) == 1 && errors[0].Range.Start.Line == 0) { if !(len(ctx.diagnostics) == 1 && ctx.diagnostics[0].Range.Start.Line == 0) {
t.Fatalf("Expected one error, got %v", errors) t.Fatalf("Expected one error, got %v", ctx.diagnostics)
} }
} }

View File

@ -3,6 +3,8 @@ package analyzer
import ( import (
testutils_test "config-lsp/handlers/ssh_config/test_utils" testutils_test "config-lsp/handlers/ssh_config/test_utils"
"testing" "testing"
protocol "github.com/tliron/glsp/protocol_3_16"
) )
func TestSimpleInvalidQuotesExample( func TestSimpleInvalidQuotesExample(
@ -68,12 +70,16 @@ func TestDependentOptionsExample(
Port 1234 Port 1234
CanonicalDomains example.com CanonicalDomains example.com
`) `)
ctx := &analyzerContext{
document: *d,
diagnostics: make([]protocol.Diagnostic, 0),
}
option := d.FindOptionsByName("canonicaldomains")[0] option := d.FindOptionsByName("canonicaldomains")[0]
errors := checkIsDependent(d, option.Option.Key, option.Block) checkIsDependent(ctx, option.Option.Key, option.Block)
if !(len(errors) == 1) { if !(len(ctx.diagnostics) == 1) {
t.Errorf("Expected 1 error, got %v", len(errors)) t.Errorf("Expected 1 error, got %v", len(ctx.diagnostics))
} }
} }
@ -85,11 +91,15 @@ Port 1234
CanonicalizeHostname yes CanonicalizeHostname yes
CanonicalDomains example.com CanonicalDomains example.com
`) `)
ctx := &analyzerContext{
document: *d,
diagnostics: make([]protocol.Diagnostic, 0),
}
option := d.FindOptionsByName("canonicaldomains")[0] option := d.FindOptionsByName("canonicaldomains")[0]
errors := checkIsDependent(d, option.Option.Key, option.Block) checkIsDependent(ctx, option.Option.Key, option.Block)
if len(errors) > 0 { if len(ctx.diagnostics) > 0 {
t.Errorf("Expected no errors, got %v", len(errors)) t.Errorf("Expected no errors, got %v", len(ctx.diagnostics))
} }
} }