diff --git a/server/handlers/ssh_config/analyzer/quotes.go b/server/handlers/ssh_config/analyzer/quotes.go index 2155fe9..50a3399 100644 --- a/server/handlers/ssh_config/analyzer/quotes.go +++ b/server/handlers/ssh_config/analyzer/quotes.go @@ -27,15 +27,18 @@ func checkIsUsingDoubleQuotes( valueRange common.LocationRange, ) { quoteRanges := utils.GetQuoteRanges(value.Raw) - singleQuotePosition := strings.Index(value.Raw, "'") + invertedRanges := quoteRanges.GetInvertedRanges(len(value.Raw)) - // Single quote - if singleQuotePosition != -1 && !quoteRanges.IsIndexInsideQuotes(singleQuotePosition) { - 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, - }) + for _, rang := range invertedRanges { + text := value.Raw[rang[0]:rang[1]] + + if strings.Contains(text, "'") { + 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, + }) + } } } diff --git a/server/handlers/ssh_config/analyzer/quotes_test.go b/server/handlers/ssh_config/analyzer/quotes_test.go index accb08a..c52a218 100644 --- a/server/handlers/ssh_config/analyzer/quotes_test.go +++ b/server/handlers/ssh_config/analyzer/quotes_test.go @@ -101,7 +101,8 @@ func TestValidDependentOptionsExample( d := testutils_test.DocumentFromInput(t, ` Port 1234 CanonicalizeHostname yes -CanonicalDomains example.com +CanonicalDomains "example.com" +Test "hello world 'test' " `) ctx := &analyzerContext{ document: d, diff --git a/server/handlers/sshd_config/analyzer/quotes.go b/server/handlers/sshd_config/analyzer/quotes.go index 7dbf275..a563bc5 100644 --- a/server/handlers/sshd_config/analyzer/quotes.go +++ b/server/handlers/sshd_config/analyzer/quotes.go @@ -3,7 +3,6 @@ package analyzer import ( "config-lsp/common" commonparser "config-lsp/common/parser" - "config-lsp/utils" "strings" protocol "github.com/tliron/glsp/protocol_3_16" @@ -26,14 +25,10 @@ func checkIsUsingDoubleQuotes( value commonparser.ParsedString, valueRange common.LocationRange, ) { - quoteRanges := utils.GetQuoteRanges(value.Raw) - singleQuotePosition := strings.Index(value.Raw, "'") - - // Single quote - if singleQuotePosition != -1 && !quoteRanges.IsIndexInsideQuotes(singleQuotePosition) { + if strings.HasPrefix(value.Raw, "'") && strings.HasSuffix(value.Raw, "'") { ctx.diagnostics = append(ctx.diagnostics, protocol.Diagnostic{ Range: valueRange.ToLSPRange(), - Message: "sshd_config does not support single quotes. Use double quotes (\") instead.", + Message: "ssh_config does not support single quotes. Use double quotes (\") instead.", Severity: &common.SeverityError, }) } diff --git a/server/utils/quotes.go b/server/utils/quotes.go index f7155ac..3badaca 100644 --- a/server/utils/quotes.go +++ b/server/utils/quotes.go @@ -33,6 +33,45 @@ func (q quoteRanges) GetQuoteForIndex(index int) *quoteRange { return &q[index] } +func (q quoteRanges) GetInvertedRanges(textLength int) [][2]int { + if textLength == 0 { + return nil + } + + if len(q) == 0 { + return [][2]int{ + { + 0, + textLength - 1, + }, + } + } + + inverted := make([][2]int, 0, len(q)) + + firstRange := q[0] + + if firstRange[0] != 0 { + inverted = append(inverted, [2]int{0, firstRange[0]}) + } + + if len(q) > 1 { + for index, currentRange := range q[:len(q)-1] { + nextRange := q[index+1] + + inverted = append(inverted, [2]int{currentRange[1] + 1, nextRange[0]}) + } + } + + lastRange := q[len(q)-1] + + if lastRange[1] != (textLength - 1) { + inverted = append(inverted, [2]int{lastRange[1] + 1, textLength}) + } + + return inverted +} + func GetQuoteRanges(s string) quoteRanges { quoteRanges := make(quoteRanges, 0, 2) inQuote := false diff --git a/server/utils/quotes_test.go b/server/utils/quotes_test.go index 929ae0d..86fe71a 100644 --- a/server/utils/quotes_test.go +++ b/server/utils/quotes_test.go @@ -21,3 +21,75 @@ func TestQuotesEscapedQuotes( t.Fatalf("Unexpected quote ranges: %v", quoteRanges) } } + +func TestInvertedQuotesSimple( + t *testing.T, +) { + text := `"hello" "world"` + quoteRanges := GetQuoteRanges(text) + inverted := quoteRanges.GetInvertedRanges(len(text)) + + if !(len(inverted) == 1 && inverted[0][0] == 7 && inverted[0][1] == 8) { + t.Fatalf("Unexpected inverted quote ranges: %v", inverted) + } +} + +func TestInvertedQuotesImmediatelyFollowing( + t *testing.T, +) { + text := `"hello""world"` + quoteRanges := GetQuoteRanges(text) + inverted := quoteRanges.GetInvertedRanges(len(text)) + + if !(len(inverted) == 1 && inverted[0][0] == 7 && inverted[0][1] == 7) { + t.Fatalf("Unexpected inverted quote ranges: %v", inverted) + } +} + +func TestInvertedQuotesEscapedQuotes( + t *testing.T, +) { + text := `hello \"world\"` + quoteRanges := GetQuoteRanges(text) + inverted := quoteRanges.GetInvertedRanges(len(text)) + + if !(len(inverted) == 1 && inverted[0][0] == 0 && inverted[0][1] == 14) { + t.Fatalf("Unexpected inverted quote ranges: %v", inverted) + } +} + +func TestInvertedQuotesMultiple( + t *testing.T, +) { + text := `"hello" "world" "hello"` + quoteRanges := GetQuoteRanges(text) + inverted := quoteRanges.GetInvertedRanges(len(text)) + + if !(len(inverted) == 2 && inverted[0][0] == 7 && inverted[0][1] == 8 && inverted[1][0] == 15 && inverted[1][1] == 16) { + t.Fatalf("Unexpected inverted quote ranges: %v", inverted) + } +} + +func TestInvertedQuotesFullyQuoted( + t *testing.T, +) { + text := `"hello world"` + quoteRanges := GetQuoteRanges(text) + inverted := quoteRanges.GetInvertedRanges(len(text)) + + if !(len(inverted) == 0) { + t.Fatalf("Unexpected inverted quote ranges: %v", inverted) + } +} + +func TestInvertedQuotesFirstThenRemaining( + t *testing.T, +) { + text := `"hello world" i am here` + quoteRanges := GetQuoteRanges(text) + inverted := quoteRanges.GetInvertedRanges(len(text)) + + if !(len(inverted) == 1 && inverted[0][0] == 13 && inverted[0][1] == 23) { + t.Fatalf("Unexpected inverted quote ranges: %v", inverted) + } +}