Files
warkanum e88d32f281
CI / Test (push) Failing after 47s
CI / Build snapshot (push) Has been skipped
feat: add LSP server, VSCode + DataGrip extensions, release infra, autofix
- pkg/lsp: JSON-RPC 2.0 LSP server (formatting, diagnostics, codeAction quick-fixes)
- cmd/pgtidy: lsp and config subcommands
- pkg/diagnostics: TextFix struct for byte-range autofixes
- pkg/lint: MIG001/MIG003 autofixes, ApplyFixes helper, --fix flag on lint command
- editors/vscode: TypeScript extension with LanguageClient, showVersion/showConfig/formatDocument commands, logo
- editors/datagrip: Gradle JetBrains plugin via LSP4IJ, pluginIcon
- .goreleaser.yaml, .github/workflows: CI + release pipeline
- Makefile: snapshot, release, vscode-compile, vscode-package targets
- go.mod + all imports: module path updated to git.warky.dev/wdevs/pgtidy
- assets: logo files (256px, 128px, 1024px, ico)
2026-06-28 12:48:28 +02:00

126 lines
2.8 KiB
Go

package lint_test
import (
"os"
"path/filepath"
"testing"
"git.warky.dev/wdevs/pgtidy/pkg/diagnostics"
"git.warky.dev/wdevs/pgtidy/pkg/lint"
)
func fixtureDir() string {
return filepath.Join("..", "..", "testdata", "lint")
}
func checkFile(t *testing.T, name string) []diagnostics.Diagnostic {
t.Helper()
src, err := os.ReadFile(filepath.Join(fixtureDir(), name))
if err != nil {
t.Fatalf("read %s: %v", name, err)
}
eng := lint.New()
diags, err := eng.Check(string(src), name)
if err != nil {
t.Fatalf("Check(%s): %v", name, err)
}
return diags
}
func ruleIDs(diags []diagnostics.Diagnostic) map[string]int {
m := make(map[string]int)
for _, d := range diags {
m[d.RuleID]++
}
return m
}
func TestMigrationViolations(t *testing.T) {
diags := checkFile(t, "migration_violations.sql")
ids := ruleIDs(diags)
cases := []struct {
id string
count int
}{
{"MIG001", 1}, // one non-concurrent index
{"MIG002", 1}, // one NOT NULL + no DEFAULT
{"MIG003", 2}, // one FK + one CHECK without NOT VALID
}
for _, c := range cases {
if got := ids[c.id]; got != c.count {
t.Errorf("rule %s: want %d findings, got %d", c.id, c.count, got)
}
}
}
func TestMigrationClean(t *testing.T) {
diags := checkFile(t, "migration_clean.sql")
ids := ruleIDs(diags)
for _, id := range []string{"MIG001", "MIG002", "MIG003"} {
if n := ids[id]; n != 0 {
t.Errorf("rule %s: want 0 findings on clean fixture, got %d", id, n)
}
}
}
func TestCorrectnessViolations(t *testing.T) {
diags := checkFile(t, "correctness_violations.sql")
ids := ruleIDs(diags)
cases := []struct {
id string
count int
}{
{"COR001", 1},
{"COR002", 1},
{"COR003", 1},
}
for _, c := range cases {
if got := ids[c.id]; got != c.count {
t.Errorf("rule %s: want %d findings, got %d", c.id, c.count, got)
}
}
}
func TestNamingViolations(t *testing.T) {
diags := checkFile(t, "naming_violations.sql")
ids := ruleIDs(diags)
cases := []struct {
id string
count int
}{
{"NAM001", 1}, // UserAccounts
{"NAM002", 2}, // userId, emailAddress
{"NAM003", 1}, // GetUserById
}
for _, c := range cases {
if got := ids[c.id]; got != c.count {
t.Errorf("rule %s: want %d findings, got %d", c.id, c.count, got)
}
}
}
func TestParseError(t *testing.T) {
eng := lint.New()
diags, err := eng.Check("SELECT FROM WHERE", "test.sql")
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
if len(diags) == 0 || diags[0].RuleID != "PARSE" {
t.Errorf("expected a PARSE diagnostic, got %v", diags)
}
}
func TestCustomEngine(t *testing.T) {
eng := lint.Custom()
eng.Register(lint.New().Rules()[0]) // register first rule only
// Just confirm it doesn't panic and returns results.
diags, err := eng.Check("SELECT 1", "stdin")
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
_ = diags
}