Skip to content

Commit fad7915

Browse files
Ajit Pratap SinghAjit Pratap Singh
authored andcommitted
fix: address architect review — add tests, unify rule registry
1 parent b5ae69b commit fad7915

File tree

4 files changed

+86
-17
lines changed

4 files changed

+86
-17
lines changed

cmd/gosqlx/cmd/action.go

Lines changed: 20 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,23 @@ import (
1818
"github.com/ajitpratap0/GoSQLX/pkg/sql/parser"
1919
)
2020

21+
// builtinRules returns all built-in lint rules. This is the single source of
22+
// truth — both defaultLinter() and allRules() derive from it.
23+
func builtinRules() []linter.Rule {
24+
return []linter.Rule{
25+
whitespace.NewTrailingWhitespaceRule(), // L001
26+
whitespace.NewMixedIndentationRule(), // L002
27+
whitespace.NewConsecutiveBlankLinesRule(2), // L003
28+
whitespace.NewIndentationDepthRule(10, 4), // L004
29+
whitespace.NewLongLinesRule(120), // L005
30+
style.NewColumnAlignmentRule(), // L006
31+
keywords.NewKeywordCaseRule(keywords.CaseUpper), // L007
32+
style.NewCommaPlacementRule(style.CommaTrailing), // L008
33+
style.NewAliasingConsistencyRule(true), // L009
34+
whitespace.NewRedundantWhitespaceRule(), // L010
35+
}
36+
}
37+
2138
var (
2239
actionFiles string
2340
actionRules string
@@ -293,24 +310,14 @@ type lintViolation struct {
293310
Message string
294311
}
295312

296-
// defaultLinter creates a linter with standard rules.
313+
// defaultLinter creates a linter with all built-in rules.
297314
func defaultLinter() *linter.Linter {
298-
return linter.New(
299-
whitespace.NewTrailingWhitespaceRule(),
300-
whitespace.NewMixedIndentationRule(),
301-
keywords.NewKeywordCaseRule(keywords.CaseUpper),
302-
style.NewColumnAlignmentRule(),
303-
)
315+
return linter.New(builtinRules()...)
304316
}
305317

306318
// allRules returns every built-in rule keyed by its ID.
307319
func allRules() map[string]linter.Rule {
308-
all := []linter.Rule{
309-
whitespace.NewTrailingWhitespaceRule(),
310-
whitespace.NewMixedIndentationRule(),
311-
keywords.NewKeywordCaseRule(keywords.CaseUpper),
312-
style.NewColumnAlignmentRule(),
313-
}
320+
all := builtinRules()
314321
m := make(map[string]linter.Rule, len(all))
315322
for _, r := range all {
316323
m[r.ID()] = r

cmd/gosqlx/cmd/action_test.go

Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
package cmd
2+
3+
import (
4+
"os"
5+
"path/filepath"
6+
"strings"
7+
"testing"
8+
)
9+
10+
func writeTempSQL(t *testing.T, content string) string {
11+
t.Helper()
12+
p := filepath.Join(t.TempDir(), "test.sql")
13+
if err := os.WriteFile(p, []byte(content), 0o644); err != nil {
14+
t.Fatal(err)
15+
}
16+
return p
17+
}
18+
19+
func TestLintFile_FilterSpecificRule(t *testing.T) {
20+
// Trailing whitespace triggers L001; lowercase keyword triggers L007.
21+
path := writeTempSQL(t, "select 1; \n")
22+
23+
violations := lintFile(path, []string{"L001"})
24+
if len(violations) == 0 {
25+
t.Fatal("expected at least one L001 violation for trailing whitespace")
26+
}
27+
for _, v := range violations {
28+
if !strings.Contains(v.Message, "L001") {
29+
t.Errorf("expected only L001 violations, got: %s", v.Message)
30+
}
31+
}
32+
}
33+
34+
func TestLintFile_EmptyRulesRunsAll(t *testing.T) {
35+
// Trailing whitespace (L001) + lowercase keyword (L007).
36+
path := writeTempSQL(t, "select 1; \n")
37+
38+
violations := lintFile(path, nil)
39+
ruleIDs := make(map[string]bool)
40+
for _, v := range violations {
41+
if i := strings.Index(v.Message, "["); i >= 0 {
42+
if j := strings.Index(v.Message[i:], "]"); j > 0 {
43+
ruleIDs[v.Message[i+1:i+j]] = true
44+
}
45+
}
46+
}
47+
if len(ruleIDs) < 2 {
48+
t.Errorf("expected violations from multiple rules, got: %v", ruleIDs)
49+
}
50+
}
51+
52+
func TestLintFile_InvalidRuleIgnored(t *testing.T) {
53+
path := writeTempSQL(t, "SELECT 1;\n")
54+
55+
violations := lintFile(path, []string{"INVALID_RULE"})
56+
if len(violations) != 0 {
57+
t.Errorf("expected no violations for invalid rule, got %d", len(violations))
58+
}
59+
}

pkg/advisor/rules.go

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -171,7 +171,8 @@ func hasJoinCondition(expr ast.Expression, tableNames []string) bool {
171171
return false
172172
}
173173

174-
if e, ok := expr.(*ast.BinaryExpression); ok {
174+
switch e := expr.(type) { //nolint:gocritic // singleCaseSwitch: intentional — may grow to handle more expression types
175+
case *ast.BinaryExpression:
175176
if e.Operator == "AND" || e.Operator == "OR" {
176177
return hasJoinCondition(e.Left, tableNames) || hasJoinCondition(e.Right, tableNames)
177178
}
@@ -375,7 +376,8 @@ func containsOrCondition(expr ast.Expression) bool {
375376
return false
376377
}
377378

378-
if e, ok := expr.(*ast.BinaryExpression); ok {
379+
switch e := expr.(type) { //nolint:gocritic // singleCaseSwitch: intentional — may grow to handle more expression types
380+
case *ast.BinaryExpression:
379381
if strings.EqualFold(e.Operator, "OR") {
380382
// Check if the OR operates on different columns
381383
leftCols := collectColumnNames(e.Left)

pkg/sql/ast/value.go

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -333,8 +333,9 @@ func (d DateTimeField) String() string {
333333
return "MICROSECOND"
334334
case Microseconds:
335335
return "MICROSECONDS"
336-
case Millenium: //nolint:misspell // intentional: SQL accepts both spellings
337-
return "MILLENIUM" //nolint:misspell // intentional: matches SQL keyword
336+
//nolint:misspell // intentional: SQL accepts both spellings (Millenium / MILLENIUM)
337+
case Millenium:
338+
return "MILLENIUM"
338339
case Millennium:
339340
return "MILLENNIUM"
340341
case Millisecond:

0 commit comments

Comments
 (0)