Skip to content

Latest commit

 

History

History

Folders and files

NameName
Last commit message
Last commit date

parent directory

..
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

README.md

SQL Linter Package

Overview

The linter package provides a comprehensive SQL linting rules engine similar to SQLFluff. It offers code style checking, auto-fix capabilities, and extensible rule system for SQL quality enforcement.

Status: Phase 1a Complete (3/10 rules implemented) Test Coverage: 98.1% (exceeded 70% target by +28%)

Key Features

  • Extensible Rule System: Plugin-based architecture for custom rules
  • Auto-Fix Capability: Automatic correction for applicable violations
  • Multi-Input Support: Files, directories (recursive), stdin
  • Severity Levels: Error, Warning, Info
  • CLI Integration: gosqlx lint command
  • Context-Aware: Access to SQL text, tokens, and AST
  • Thread-Safe: Safe for concurrent linting operations

Implemented Rules (Phase 1a)

Rule Name Severity Auto-Fix Status
L001 Trailing Whitespace Warning ✅ Yes ✅ Complete
L002 Mixed Indentation Error ✅ Yes ✅ Complete
L005 Long Lines Info ❌ No ✅ Complete

Planned Rules (Phase 1)

Rule Name Status
L003 Consecutive Blank Lines 📋 Planned
L004 Indentation Depth 📋 Planned
L006 SELECT Column Alignment 📋 Planned
L007 Keyword Case Consistency 📋 Planned
L008 Comma Placement 📋 Planned
L009 Aliasing Consistency 📋 Planned
L010 Redundant Whitespace 📋 Planned

Usage

CLI Usage

# Lint a single file
gosqlx lint query.sql

# Auto-fix violations
gosqlx lint --auto-fix query.sql

# Lint directory recursively
gosqlx lint -r ./sql-queries/

# Custom max line length
gosqlx lint --max-length 120 query.sql

# Lint from stdin
cat query.sql | gosqlx lint
echo "SELECT * FROM users" | gosqlx lint

Programmatic Usage

package main

import (
    "github.com/ajitpratap0/GoSQLX/pkg/linter"
    "github.com/ajitpratap0/GoSQLX/pkg/linter/rules/whitespace"
)

func main() {
    // Create linter with rules
    l := linter.New(
        whitespace.NewTrailingWhitespaceRule(),
        whitespace.NewMixedIndentationRule(),
        whitespace.NewLongLinesRule(100), // Max 100 chars
    )

    // Lint SQL string
    sql := `SELECT * FROM users WHERE active = true  `  // Trailing space
    result := l.LintString(sql, "query.sql")
    if result.Error != nil {
        // Handle error
    }

    // Check violations
    for _, violation := range result.Violations {
        fmt.Printf("[%s] Line %d: %s\n",
            violation.Rule,
            violation.Location.Line,
            violation.Message)
    }
}

Auto-Fix Example

l := linter.New(
    whitespace.NewTrailingWhitespaceRule(),
    whitespace.NewMixedIndentationRule(),
)

sql := `SELECT *
FROM users	WHERE active = true`  // Mixed tabs/spaces, trailing space

// Lint and get violations
result := l.LintString(sql, "query.sql")

// Auto-fix violations by rule
for _, rule := range l.Rules() {
    if rule.CanAutoFix() {
        // Get violations for this rule
        ruleViolations := []Violation{}
        for _, v := range result.Violations {
            if v.Rule == rule.ID() {
                ruleViolations = append(ruleViolations, v)
            }
        }

        if len(ruleViolations) > 0 {
            fixedSQL, err := rule.Fix(sql, ruleViolations)
            if err == nil {
                sql = fixedSQL
            }
        }
    }
}

fmt.Println(sql)  // Cleaned SQL

Architecture

Core Components

Rule Interface

type Rule interface {
    ID() string           // L001, L002, etc.
    Name() string         // Human-readable name
    Description() string  // Detailed description
    Severity() Severity   // Error, Warning, Info
    Check(ctx *Context) ([]Violation, error)
    CanAutoFix() bool
    Fix(content string, violations []Violation) (string, error)
}

Context

Provides access to SQL analysis results:

type Context struct {
    SQL      string                     // Raw SQL
    Filename string                     // Source file name
    Lines    []string                   // Split by line
    Tokens   []models.TokenWithSpan     // Tokenization result
    AST      *ast.AST                   // Parsed AST (if available)
    ParseErr error                      // Parse error (if any)
}

Violation

Represents a rule violation:

type Violation struct {
    Rule       string          // Rule ID (e.g., "L001")
    RuleName   string          // Human-readable rule name
    Severity   Severity        // Severity level
    Message    string          // Violation description
    Location   models.Location // Position in source (1-based)
    Line       string          // The actual line content
    Suggestion string          // How to fix the violation
    CanAutoFix bool            // Whether this violation can be auto-fixed
}

Package Structure

pkg/linter/
├── rule.go           # Rule interface, BaseRule, Violation
├── context.go        # Linting context
├── linter.go         # Main linter engine
└── rules/
    └── whitespace/
        ├── trailing_whitespace.go
        ├── mixed_indentation.go
        └── long_lines.go

Creating Custom Rules

Simple Rule Example

package myrules

import "github.com/ajitpratap0/GoSQLX/pkg/linter"

type MyCustomRule struct {
    linter.BaseRule
}

func NewMyCustomRule() *MyCustomRule {
    return &MyCustomRule{
        BaseRule: linter.NewBaseRule(
            "C001",                  // Rule ID
            "My Custom Rule",        // Name
            "Checks custom pattern", // Description
            linter.SeverityWarning,  // Severity
            false,                   // CanAutoFix
        ),
    }
}

func (r *MyCustomRule) Check(ctx *linter.Context) ([]linter.Violation, error) {
    violations := []linter.Violation{}

    // Iterate through lines
    for lineNum, line := range ctx.Lines {
        // Check for your pattern
        if /* violation found */ {
            violations = append(violations, linter.Violation{
                Rule:       r.ID(),
                RuleName:   r.Name(),
                Message:    "Custom violation message",
                Location:   models.Location{Line: lineNum + 1, Column: 1},  // 1-based
                Line:       line,
                Severity:   r.Severity(),
                CanAutoFix: false,
            })
        }
    }

    return violations, nil
}

Rule with Auto-Fix

func (r *MyCustomRule) CanAutoFix() bool {
    return true
}

func (r *MyCustomRule) Fix(content string, violations []linter.Violation) (string, error) {
    // Apply fixes to content
    fixed := content

    for _, violation := range violations {
        // Apply fix for this violation
        // ...
    }

    return fixed, nil
}

Testing

Run linter tests:

# All linter tests (98.1% coverage)
go test -v ./pkg/linter/...

# With race detection
go test -race ./pkg/linter/...

# Specific rules
go test -v ./pkg/linter/rules/whitespace/

# Coverage report
go test -cover -coverprofile=coverage.out ./pkg/linter/...
go tool cover -html=coverage.out

Performance

Benchmarks

go test -bench=. -benchmem ./pkg/linter/...

Characteristics

  • Speed: Designed for batch processing of large SQL codebases
  • Memory: Leverages existing tokenizer/parser infrastructure
  • Graceful Degradation: Works even if parsing fails (text-only rules)
  • Concurrent-Safe: Thread-safe for parallel file processing

Best Practices

1. Use Appropriate Severity

// Critical violations (prevents execution)
linter.SeverityError

// Style violations (should fix)
linter.SeverityWarning

// Informational (nice to have)
linter.SeverityInfo

2. Provide Clear Messages

// GOOD: Specific, actionable message
"Line exceeds maximum length of 100 characters (current: 125 chars)"

// BAD: Vague message
"Line too long"

3. Implement Auto-Fix When Possible

// Auto-fix for deterministic corrections
rule.CanAutoFix() == true

// Manual review for complex/ambiguous cases
rule.CanAutoFix() == false

CLI Exit Codes

Exit Code Meaning
0 No violations found
1 Violations found (errors or warnings)
2 Linter execution error

Configuration (Future)

Configuration file support planned:

# .gosqlx.yml
linter:
  rules:
    L001: enabled   # Trailing whitespace
    L002: enabled   # Mixed indentation
    L005:
      enabled: true
      max-length: 120  # Custom max line length

Examples

Example 1: Trailing Whitespace (L001)

-- VIOLATION
SELECT * FROM users
-- Trailing spaces ^^

-- FIXED
SELECT * FROM users

Example 2: Mixed Indentation (L002)

-- VIOLATION
SELECT *
    FROM users  -- 4 spaces
	WHERE id = 1  -- Tab character

-- FIXED (converted to spaces)
SELECT *
    FROM users
    WHERE id = 1

Example 3: Long Lines (L005)

-- VIOLATION (assuming max-length=80)
SELECT very_long_column_name, another_long_column, yet_another_column, and_more FROM users;

-- SUGGESTION: Break into multiple lines
SELECT
    very_long_column_name,
    another_long_column,
    yet_another_column,
    and_more
FROM users;

Related Packages

  • tokenizer: Provides tokens for token-based rules
  • parser: Provides AST for semantic rules
  • ast: AST node types for tree traversal

Documentation

Roadmap

Phase 1 (10 basic rules)

  • L001: Trailing Whitespace
  • L002: Mixed Indentation
  • L005: Long Lines
  • L003: Consecutive Blank Lines
  • L004: Indentation Depth
  • L006: SELECT Column Alignment
  • L007: Keyword Case Consistency
  • L008: Comma Placement
  • L009: Aliasing Consistency
  • L010: Redundant Whitespace

Phase 2 (10 more rules)

  • Naming conventions
  • Style consistency
  • Custom rule API

Phase 3 (20 advanced rules)

  • Complexity analysis
  • Performance anti-patterns
  • Rule packs (postgres, mysql, style)

Version History

  • v1.5.0: Phase 1b - 98.1% test coverage, bug fixes
  • v1.5.0: Phase 1a - Initial release with 3 whitespace rules