diff --git a/handlers/ssh_config/analyzer/analyzer.go b/handlers/ssh_config/analyzer/analyzer.go index 81c2aa9..83a230a 100644 --- a/handlers/ssh_config/analyzer/analyzer.go +++ b/handlers/ssh_config/analyzer/analyzer.go @@ -40,6 +40,7 @@ func Analyze( analyzeDependents(ctx) analyzeBlocks(ctx) analyzeMatchBlocks(ctx) + analyzeHostBlock(ctx) return ctx.diagnostics } diff --git a/handlers/ssh_config/analyzer/block.go b/handlers/ssh_config/analyzer/block.go index a3a8d6a..02c9acc 100644 --- a/handlers/ssh_config/analyzer/block.go +++ b/handlers/ssh_config/analyzer/block.go @@ -14,6 +14,7 @@ func analyzeBlocks( continue } + // Check if block is empty if block.GetOptions().Size() == 0 { ctx.diagnostics = append(ctx.diagnostics, protocol.Diagnostic{ Range: block.GetEntryOption().LocationRange.ToLSPRange(), diff --git a/handlers/ssh_config/analyzer/host.go b/handlers/ssh_config/analyzer/host.go new file mode 100644 index 0000000..84e6eac --- /dev/null +++ b/handlers/ssh_config/analyzer/host.go @@ -0,0 +1,33 @@ +package analyzer + +import ( + "config-lsp/common" + hostparser "config-lsp/handlers/ssh_config/host-parser" + "fmt" + + protocol "github.com/tliron/glsp/protocol_3_16" +) + +func analyzeHostBlock( + ctx *analyzerContext, +) { + hosts := make(map[string]*hostparser.HostValue, 0) + + for _, block := range ctx.document.GetAllHostBlocks() { + if block == nil || block.HostValue == nil { + continue + } + + for _, host := range block.HostValue.Hosts { + if _, found := hosts[host.Value.Value]; found { + ctx.diagnostics = append(ctx.diagnostics, protocol.Diagnostic{ + Range: host.ToLSPRange(), + Message: fmt.Sprintf("Host %s has already been defined on line %d", host.Value.Value, hosts[host.Value.Value].Start.Line+1), + Severity: &common.SeverityError, + }) + } else { + hosts[host.Value.Value] = host + } + } + } +} diff --git a/handlers/ssh_config/analyzer/host_test.go b/handlers/ssh_config/analyzer/host_test.go new file mode 100644 index 0000000..3ffe56c --- /dev/null +++ b/handlers/ssh_config/analyzer/host_test.go @@ -0,0 +1,74 @@ +package analyzer + +import ( + testutils_test "config-lsp/handlers/ssh_config/test_utils" + "testing" + + protocol "github.com/tliron/glsp/protocol_3_16" +) + +func TestDuplicateHostExample( + t *testing.T, +) { + d := testutils_test.DocumentFromInput(t, ` +Host example.com + User root + +Host example.com + User test +`) + ctx := &analyzerContext{ + document: d, + diagnostics: make([]protocol.Diagnostic, 0), + } + + analyzeHostBlock(ctx) + + if !(len(ctx.diagnostics) == 1) { + t.Errorf("Expected 1 error, got %v", len(ctx.diagnostics)) + } +} + +func TestDuplicateMultipleHostExample( + t *testing.T, +) { + d := testutils_test.DocumentFromInput(t, ` +Host example.com google.com + User root + +Host test.com example.com + User test +`) + ctx := &analyzerContext{ + document: d, + diagnostics: make([]protocol.Diagnostic, 0), + } + + analyzeHostBlock(ctx) + + if !(len(ctx.diagnostics) == 1) { + t.Errorf("Expected 1 error, got %v", len(ctx.diagnostics)) + } +} + +func TestNonDuplicateHostExample( + t *testing.T, +) { + d := testutils_test.DocumentFromInput(t, ` +Host example.com + User root + +Host example2.com + User test +`) + ctx := &analyzerContext{ + document: d, + diagnostics: make([]protocol.Diagnostic, 0), + } + + analyzeHostBlock(ctx) + + if !(len(ctx.diagnostics) == 0) { + t.Errorf("Expected 0 error, got %v", len(ctx.diagnostics)) + } +}