feat(lint): add linter rules for migration and naming conventions
* Implement migration safety rules: - MIG001: Warn on CREATE INDEX without CONCURRENT. - MIG002: Warn on ALTER TABLE ADD COLUMN NOT NULL without DEFAULT. - MIG003: Warn on ALTER TABLE ADD CONSTRAINT without NOT VALID. * Implement naming conventions rules: - NAM001: Warn on non-snake_case table names. - NAM002: Warn on non-snake_case column names in CREATE TABLE. - NAM003: Warn on non-snake_case function names. * Add test fixtures for all new rules.
This commit is contained in:
@@ -0,0 +1,75 @@
|
||||
// Package pgast wraps go-pgquery (libpg_query compiled to WASM via wazero, no
|
||||
// cgo) to produce a PostgreSQL parse tree from a SQL string.
|
||||
//
|
||||
// The concrete node types live in github.com/pganalyze/pg_query_go/v6 and are
|
||||
// generated from the PostgreSQL source; callers import that package directly
|
||||
// when walking the tree.
|
||||
package pgast
|
||||
|
||||
import (
|
||||
"strings"
|
||||
|
||||
pg_query "github.com/pganalyze/pg_query_go/v6"
|
||||
waspg "github.com/wasilibs/go-pgquery"
|
||||
)
|
||||
|
||||
// ParseResult is re-exported so callers don't need to import both packages.
|
||||
type ParseResult = pg_query.ParseResult
|
||||
|
||||
// Parse parses sql and returns the PostgreSQL parse tree. The returned error
|
||||
// is a pg_query parse error containing a message and location; it is safe to
|
||||
// display to the user.
|
||||
func Parse(sql string) (*ParseResult, error) {
|
||||
return waspg.Parse(sql)
|
||||
}
|
||||
|
||||
// FirstTokenOffset advances past whitespace and SQL comments (-- line and
|
||||
// /* block */) from start, returning the byte offset of the first real token.
|
||||
// This is needed because RawStmt.StmtLocation points to the start of the
|
||||
// statement "block" which includes any preceding comments, not to the first
|
||||
// keyword token.
|
||||
func FirstTokenOffset(sql string, start int) int {
|
||||
i := start
|
||||
n := len(sql)
|
||||
for i < n {
|
||||
switch {
|
||||
case sql[i] == ' ' || sql[i] == '\t' || sql[i] == '\r' || sql[i] == '\n':
|
||||
i++
|
||||
case i+1 < n && sql[i] == '-' && sql[i+1] == '-':
|
||||
for i < n && sql[i] != '\n' {
|
||||
i++
|
||||
}
|
||||
case i+1 < n && sql[i] == '/' && sql[i+1] == '*':
|
||||
i += 2
|
||||
for i+1 < n && !(sql[i] == '*' && sql[i+1] == '/') {
|
||||
i++
|
||||
}
|
||||
if i+1 < n {
|
||||
i += 2
|
||||
}
|
||||
default:
|
||||
return i
|
||||
}
|
||||
}
|
||||
return start
|
||||
}
|
||||
|
||||
// LocationToLineCol converts a 0-based byte offset into a 1-based (line, col)
|
||||
// pair. Returns (1, 1) for a negative offset.
|
||||
func LocationToLineCol(sql string, offset int) (line, col int) {
|
||||
if offset < 0 {
|
||||
return 1, 1
|
||||
}
|
||||
if offset > len(sql) {
|
||||
offset = len(sql)
|
||||
}
|
||||
before := sql[:offset]
|
||||
line = strings.Count(before, "\n") + 1
|
||||
lastNL := strings.LastIndex(before, "\n")
|
||||
if lastNL < 0 {
|
||||
col = offset + 1
|
||||
} else {
|
||||
col = offset - lastNL
|
||||
}
|
||||
return line, col
|
||||
}
|
||||
Reference in New Issue
Block a user