feat: Add tag import analyzer; Improvements

This commit is contained in:
Myzel394 2024-10-05 17:05:17 +02:00
parent 8517a722d1
commit 05335d42d0
No known key found for this signature in database
GPG Key ID: DEC4AAB876F73185
9 changed files with 195 additions and 48 deletions

View File

@ -42,6 +42,8 @@ func Analyze(
analyzeMatchBlocks(ctx) analyzeMatchBlocks(ctx)
analyzeHostBlock(ctx) analyzeHostBlock(ctx)
analyzeBlocks(ctx) analyzeBlocks(ctx)
analyzeTagOptions(ctx)
analyzeTagImports(ctx)
return ctx.diagnostics return ctx.diagnostics
} }

View File

@ -1,28 +0,0 @@
package analyzer
import (
"config-lsp/common"
"config-lsp/handlers/ssh_config/fields"
"fmt"
protocol "github.com/tliron/glsp/protocol_3_16"
)
var tagOption = fields.CreateNormalizedName("Tag")
func analyzeTags(
ctx *analyzerContext,
) {
// Check if the specified tags actually exist
for _, options := range ctx.document.Indexes.AllOptionsPerName[tagOption] {
for _, option := range options {
if _, found := ctx.document.Indexes.Tags[option.OptionValue.Value.Value]; !found {
ctx.diagnostics = append(ctx.diagnostics, protocol.Diagnostic{
Range: option.OptionValue.ToLSPRange(),
Message: fmt.Sprintf("Unknown tag: %s", option.OptionValue.Value.Value),
Severity: &common.SeverityError,
})
}
}
}
}

View File

@ -0,0 +1,33 @@
package analyzer
import (
"config-lsp/common"
"fmt"
protocol "github.com/tliron/glsp/protocol_3_16"
)
func analyzeTagImports(
ctx *analyzerContext,
) {
for name, info := range ctx.document.Indexes.Tags {
if _, found := ctx.document.Indexes.TagImports[name]; !found {
var diagnosticRange protocol.Range
if len(info.Block.MatchValue.Entries) == 1 {
diagnosticRange = info.Block.MatchOption.ToLSPRange()
} else {
diagnosticRange = info.EntryValue.ToLSPRange()
}
ctx.diagnostics = append(ctx.diagnostics, protocol.Diagnostic{
Range: diagnosticRange,
Message: fmt.Sprintf("Tag %s is not used", name),
Severity: &common.SeverityWarning,
Tags: []protocol.DiagnosticTag{
protocol.DiagnosticTagUnnecessary,
},
})
}
}
}

View File

@ -0,0 +1,49 @@
package analyzer
import (
testutils_test "config-lsp/handlers/ssh_config/test_utils"
"testing"
protocol "github.com/tliron/glsp/protocol_3_16"
)
func TestUsedTagImportsExample(
t *testing.T,
) {
d := testutils_test.DocumentFromInput(t, `
Host test.com
Tag auth
Match tagged auth
User root
`)
ctx := &analyzerContext{
document: d,
diagnostics: make([]protocol.Diagnostic, 0),
}
analyzeTagImports(ctx)
if len(ctx.diagnostics) > 0 {
t.Errorf("Expected no errors, got %v", len(ctx.diagnostics))
}
}
func TestUnusedTagImportsExample(
t *testing.T,
) {
d := testutils_test.DocumentFromInput(t, `
Match tagged auth
User root
`)
ctx := &analyzerContext{
document: d,
diagnostics: make([]protocol.Diagnostic, 0),
}
analyzeTagImports(ctx)
if !(len(ctx.diagnostics) == 1) {
t.Errorf("Expected 1 error, got %v", len(ctx.diagnostics))
}
}

View File

@ -0,0 +1,32 @@
package analyzer
import (
"config-lsp/common"
"config-lsp/handlers/ssh_config/fields"
"fmt"
protocol "github.com/tliron/glsp/protocol_3_16"
)
var tagOption = fields.CreateNormalizedName("Tag")
func analyzeTagOptions(
ctx *analyzerContext,
) {
// Check if the specified tags actually exist
for _, options := range ctx.document.Indexes.AllOptionsPerName[tagOption] {
for _, option := range options {
tag, found := ctx.document.Indexes.Tags[option.OptionValue.Value.Value]
if found && tag.Block.Start.Line > option.Start.Line {
continue
}
ctx.diagnostics = append(ctx.diagnostics, protocol.Diagnostic{
Range: option.OptionValue.ToLSPRange(),
Message: fmt.Sprintf("Unknown tag: %s", option.OptionValue.Value.Value),
Severity: &common.SeverityError,
})
}
}
}

View File

@ -0,0 +1,52 @@
package analyzer
import (
testutils_test "config-lsp/handlers/ssh_config/test_utils"
"testing"
protocol "github.com/tliron/glsp/protocol_3_16"
)
func TestValidTagExample(
t *testing.T,
) {
d := testutils_test.DocumentFromInput(t, `
Host test.com
Tag auth
Match tagged auth
User root
`)
ctx := &analyzerContext{
document: d,
diagnostics: make([]protocol.Diagnostic, 0),
}
analyzeTagOptions(ctx)
if len(ctx.diagnostics) > 0 {
t.Errorf("Expected no errors, got %v", len(ctx.diagnostics))
}
}
func TestTagBlockBeforeExample(
t *testing.T,
) {
d := testutils_test.DocumentFromInput(t, `
Match tagged auth
User root
Host test.com
Tag auth
`)
ctx := &analyzerContext{
document: d,
diagnostics: make([]protocol.Diagnostic, 0),
}
analyzeTagOptions(ctx)
if !(len(ctx.diagnostics) == 1) {
t.Errorf("Expected 1 error, got %v", len(ctx.diagnostics))
}
}

View File

@ -65,6 +65,7 @@ func GetOptionCompletions(
d *sshconfig.SSHDocument, d *sshconfig.SSHDocument,
entry *ast.SSHOption, entry *ast.SSHOption,
block ast.SSHBlock, block ast.SSHBlock,
line uint32,
cursor common.CursorPosition, cursor common.CursorPosition,
) ([]protocol.CompletionItem, error) { ) ([]protocol.CompletionItem, error) {
option, found := fields.Options[entry.Key.Key] option, found := fields.Options[entry.Key.Key]
@ -84,6 +85,7 @@ func GetOptionCompletions(
if entry.Key.Key == tagOption { if entry.Key.Key == tagOption {
return getTagCompletions( return getTagCompletions(
d, d,
line,
cursor, cursor,
entry, entry,
) )
@ -94,13 +96,13 @@ func GetOptionCompletions(
} }
// Hello wo|rld // Hello wo|rld
line := entry.OptionValue.Value.Raw lineValue := entry.OptionValue.Value.Raw
// NEW: docvalues index // NEW: docvalues index
return option.DeprecatedFetchCompletions( return option.DeprecatedFetchCompletions(
line, lineValue,
common.DeprecatedImprovedCursorToIndex( common.DeprecatedImprovedCursorToIndex(
cursor, cursor,
line, lineValue,
entry.OptionValue.Start.Character, entry.OptionValue.Start.Character,
), ),
), nil ), nil

View File

@ -5,8 +5,6 @@ import (
"config-lsp/common/formatting" "config-lsp/common/formatting"
sshconfig "config-lsp/handlers/ssh_config" sshconfig "config-lsp/handlers/ssh_config"
"config-lsp/handlers/ssh_config/ast" "config-lsp/handlers/ssh_config/ast"
"config-lsp/handlers/ssh_config/indexes"
"config-lsp/utils"
"fmt" "fmt"
protocol "github.com/tliron/glsp/protocol_3_16" protocol "github.com/tliron/glsp/protocol_3_16"
@ -14,24 +12,30 @@ import (
func getTagCompletions( func getTagCompletions(
d *sshconfig.SSHDocument, d *sshconfig.SSHDocument,
line uint32,
cursor common.CursorPosition, cursor common.CursorPosition,
entry *ast.SSHOption, entry *ast.SSHOption,
) ([]protocol.CompletionItem, error) { ) ([]protocol.CompletionItem, error) {
return utils.MapMapToSlice( completions := make([]protocol.CompletionItem, 0)
d.Indexes.Tags,
func(name string, info indexes.SSHIndexTagInfo) protocol.CompletionItem { for name, info := range d.Indexes.Tags {
kind := protocol.CompletionItemKindModule if info.Block.Start.Line < line {
text := renderMatchBlock(info.Block) continue
return protocol.CompletionItem{ }
Label: name,
Kind: &kind, kind := protocol.CompletionItemKindModule
Documentation: protocol.MarkupContent{ text := renderMatchBlock(info.Block)
Kind: protocol.MarkupKindMarkdown, completions = append(completions, protocol.CompletionItem{
Value: fmt.Sprintf("```sshconfig\n%s\n```", text), Label: name,
}, Kind: &kind,
} Documentation: protocol.MarkupContent{
}, Kind: protocol.MarkupKindMarkdown,
), nil Value: fmt.Sprintf("```sshconfig\n%s\n```", text),
},
})
}
return completions, nil
} }
func renderMatchBlock( func renderMatchBlock(

View File

@ -42,6 +42,7 @@ func TextDocumentCompletion(context *glsp.Context, params *protocol.CompletionPa
d, d,
option, option,
block, block,
line,
cursor, cursor,
) )
} }