chore: more work done and planning with AI.
This commit is contained in:
+270
-49
@@ -16,9 +16,8 @@ import (
|
||||
type Case string
|
||||
|
||||
const (
|
||||
CaseUpper Case = "upper"
|
||||
CaseLower Case = "lower"
|
||||
// CasePreserve leaves the token text unchanged.
|
||||
CaseUpper Case = "upper"
|
||||
CaseLower Case = "lower"
|
||||
CasePreserve Case = "preserve"
|
||||
)
|
||||
|
||||
@@ -26,51 +25,174 @@ const (
|
||||
type CommaStyle string
|
||||
|
||||
const (
|
||||
// CommaLeading puts the comma at the start of the continuation line
|
||||
// (",col"), the house style.
|
||||
CommaLeading CommaStyle = "leading"
|
||||
// CommaTrailing puts the comma at the end of the preceding line ("col,").
|
||||
CommaLeading CommaStyle = "leading"
|
||||
CommaTrailing CommaStyle = "trailing"
|
||||
)
|
||||
|
||||
// WrapMode controls whether a construct wraps to multiple lines.
|
||||
type WrapMode string
|
||||
|
||||
const (
|
||||
WrapAlways WrapMode = "always"
|
||||
WrapWhenLong WrapMode = "when_long"
|
||||
WrapNever WrapMode = "never"
|
||||
)
|
||||
|
||||
// Placement controls where a token or block is placed relative to surrounding content.
|
||||
type Placement string
|
||||
|
||||
const (
|
||||
PlacementSameLine Placement = "same_line"
|
||||
PlacementNewLine Placement = "new_line"
|
||||
)
|
||||
|
||||
// Style is the formatter configuration.
|
||||
type Style struct {
|
||||
// Indent is one indentation level (default two spaces).
|
||||
Indent string
|
||||
// Newline is the line terminator emitted by the formatter.
|
||||
// --- Core ---
|
||||
Indent string
|
||||
Newline string
|
||||
// KeywordCase controls SQL keyword casing (types excluded — see TypeCase).
|
||||
KeywordCase Case
|
||||
// IdentCase controls unquoted identifier casing (quoted identifiers are
|
||||
// never touched).
|
||||
IdentCase Case
|
||||
// TypeCase controls built-in type-name casing.
|
||||
TypeCase Case
|
||||
// Commas controls list separator placement.
|
||||
Commas CommaStyle
|
||||
|
||||
// --- Casing ---
|
||||
KeywordCase Case
|
||||
IdentCase Case
|
||||
TypeCase Case
|
||||
AliasCase Case // token immediately following AS in SELECT/FROM
|
||||
BuiltinCase Case // built-in function names (COALESCE, MAX, NOW, …)
|
||||
CustomTypeCase Case // user-defined / domain types not in the built-in set
|
||||
|
||||
// --- Query layout ---
|
||||
Commas CommaStyle
|
||||
AlignColumns bool // pad SELECT list so values align
|
||||
AlignLineComments bool // align trailing -- comments in a block
|
||||
SelectAlignAs bool // pad between expression and AS in SELECT list
|
||||
SetAlignEqual bool // align = in UPDATE SET list
|
||||
IndentJoin bool // extra indentation for JOIN … ON lines
|
||||
JoinIndentSize int // extra indent levels for JOINs (default 1)
|
||||
WhereWrap WrapMode // always|when_long|never — each AND/OR on its own line
|
||||
WhereAndOrIndent bool // AND/OR indented one level under WHERE
|
||||
|
||||
// --- Subqueries ---
|
||||
SubqueryOpening Placement // opening ( placement: same_line|new_line
|
||||
SubqueryContent Placement // content indentation: same_line|new_line
|
||||
SubqueryClosing Placement // closing ) placement: same_line|new_line
|
||||
SubquerySpaceBeforeParen bool // space before ( in subqueries
|
||||
|
||||
// --- INSERT ---
|
||||
InsertCollapseValues bool // fold multiple VALUES rows onto fewer lines
|
||||
|
||||
// --- Routines ---
|
||||
AlignParamTypes bool // pad param names so type column aligns
|
||||
RoutineAsWrap bool // newline before AS $$
|
||||
|
||||
// --- PL/pgSQL body ---
|
||||
PlpgsqlMaxBlankLines int // max consecutive blank lines in body
|
||||
PlpgsqlDeclareAlignType bool // align type column in DECLARE block
|
||||
PlpgsqlDeclareAlignEq bool // align := / = in DECLARE block
|
||||
PlpgsqlIfThenNewline bool // THEN on its own line
|
||||
PlpgsqlLoopCollapse bool // collapse empty loop bodies to one line
|
||||
|
||||
// --- Expressions ---
|
||||
BinaryOpAlign bool // align =, <>, || etc. vertically in WHERE/expr lists
|
||||
SpaceAfterCommaInCalls bool // space after , in function calls: func(a, b)
|
||||
CaseWhenWrap bool // each WHEN … THEN on its own line
|
||||
CaseEnd Placement // END placement: same_line|new_line
|
||||
CaseCollapse bool // collapse short CASE to one line
|
||||
RecordSpaceBeforeParen bool // space before ( in ROW(…) / record constructors
|
||||
}
|
||||
|
||||
// Default returns the house-style configuration.
|
||||
func Default() Style {
|
||||
return Style{
|
||||
Indent: " ",
|
||||
Newline: "\n",
|
||||
KeywordCase: CaseUpper,
|
||||
IdentCase: CaseLower,
|
||||
TypeCase: CaseLower,
|
||||
Commas: CommaLeading,
|
||||
Indent: " ",
|
||||
Newline: "\n",
|
||||
|
||||
KeywordCase: CaseUpper,
|
||||
IdentCase: CaseLower,
|
||||
TypeCase: CaseLower,
|
||||
AliasCase: CaseLower,
|
||||
BuiltinCase: CaseLower,
|
||||
CustomTypeCase: CaseLower,
|
||||
|
||||
Commas: CommaLeading,
|
||||
AlignColumns: false,
|
||||
AlignLineComments: false,
|
||||
SelectAlignAs: false,
|
||||
SetAlignEqual: false,
|
||||
IndentJoin: false,
|
||||
JoinIndentSize: 1,
|
||||
WhereWrap: WrapAlways,
|
||||
WhereAndOrIndent: true,
|
||||
|
||||
SubqueryOpening: PlacementSameLine,
|
||||
SubqueryContent: PlacementNewLine,
|
||||
SubqueryClosing: PlacementNewLine,
|
||||
SubquerySpaceBeforeParen: false,
|
||||
|
||||
InsertCollapseValues: true,
|
||||
|
||||
AlignParamTypes: true,
|
||||
RoutineAsWrap: true,
|
||||
|
||||
PlpgsqlMaxBlankLines: 1,
|
||||
PlpgsqlDeclareAlignType: false,
|
||||
PlpgsqlDeclareAlignEq: false,
|
||||
PlpgsqlIfThenNewline: true,
|
||||
PlpgsqlLoopCollapse: true,
|
||||
|
||||
BinaryOpAlign: false,
|
||||
SpaceAfterCommaInCalls: false,
|
||||
CaseWhenWrap: false,
|
||||
CaseEnd: PlacementNewLine,
|
||||
CaseCollapse: false,
|
||||
RecordSpaceBeforeParen: false,
|
||||
}
|
||||
}
|
||||
|
||||
// yamlFile is the on-disk representation of .pgtidy.yaml.
|
||||
// All fields are pointers so we can distinguish "not set" from "set to zero value".
|
||||
type yamlFile struct {
|
||||
Indent *string `yaml:"indent"`
|
||||
Newline *string `yaml:"newline"`
|
||||
KeywordCase *string `yaml:"keyword_case"`
|
||||
IdentCase *string `yaml:"ident_case"`
|
||||
TypeCase *string `yaml:"type_case"`
|
||||
Commas *string `yaml:"commas"`
|
||||
Indent *string `yaml:"indent"`
|
||||
Newline *string `yaml:"newline"`
|
||||
|
||||
KeywordCase *string `yaml:"keyword_case"`
|
||||
IdentCase *string `yaml:"ident_case"`
|
||||
TypeCase *string `yaml:"type_case"`
|
||||
AliasCase *string `yaml:"alias_case"`
|
||||
BuiltinCase *string `yaml:"builtin_case"`
|
||||
CustomTypeCase *string `yaml:"custom_type_case"`
|
||||
|
||||
Commas *string `yaml:"commas"`
|
||||
AlignColumns *bool `yaml:"align_columns"`
|
||||
AlignLineComments *bool `yaml:"align_line_comments"`
|
||||
SelectAlignAs *bool `yaml:"select_align_as"`
|
||||
SetAlignEqual *bool `yaml:"set_align_equal"`
|
||||
IndentJoin *bool `yaml:"indent_join"`
|
||||
JoinIndentSize *int `yaml:"join_indent_size"`
|
||||
WhereWrap *string `yaml:"where_wrap"`
|
||||
WhereAndOrIndent *bool `yaml:"where_and_or_indent"`
|
||||
|
||||
SubqueryOpening *string `yaml:"subquery_opening"`
|
||||
SubqueryContent *string `yaml:"subquery_content"`
|
||||
SubqueryClosing *string `yaml:"subquery_closing"`
|
||||
SubquerySpaceBeforeParen *bool `yaml:"subquery_space_before_paren"`
|
||||
|
||||
InsertCollapseValues *bool `yaml:"insert_collapse_values"`
|
||||
|
||||
AlignParamTypes *bool `yaml:"align_param_types"`
|
||||
RoutineAsWrap *bool `yaml:"routine_as_wrap"`
|
||||
|
||||
PlpgsqlMaxBlankLines *int `yaml:"plpgsql_max_blank_lines"`
|
||||
PlpgsqlDeclareAlignType *bool `yaml:"plpgsql_declare_align_type"`
|
||||
PlpgsqlDeclareAlignEq *bool `yaml:"plpgsql_declare_align_eq"`
|
||||
PlpgsqlIfThenNewline *bool `yaml:"plpgsql_if_then_newline"`
|
||||
PlpgsqlLoopCollapse *bool `yaml:"plpgsql_loop_collapse"`
|
||||
|
||||
BinaryOpAlign *bool `yaml:"binary_op_align"`
|
||||
SpaceAfterCommaInCalls *bool `yaml:"space_after_comma_in_calls"`
|
||||
CaseWhenWrap *bool `yaml:"case_when_wrap"`
|
||||
CaseEnd *string `yaml:"case_end"`
|
||||
CaseCollapse *bool `yaml:"case_collapse"`
|
||||
RecordSpaceBeforeParen *bool `yaml:"record_space_before_paren"`
|
||||
}
|
||||
|
||||
// Load discovers and parses the nearest .pgtidy.yaml by walking up from
|
||||
@@ -100,27 +222,26 @@ func Load(startDir string) (Style, error) {
|
||||
if yf.Newline != nil {
|
||||
st.Newline = *yf.Newline
|
||||
}
|
||||
if yf.KeywordCase != nil {
|
||||
c := Case(*yf.KeywordCase)
|
||||
if err := validCase(c); err != nil {
|
||||
return st, fmt.Errorf("pgtidy: %s: keyword_case: %w", path, err)
|
||||
}
|
||||
st.KeywordCase = c
|
||||
|
||||
if err := loadCase(yf.KeywordCase, &st.KeywordCase, path, "keyword_case"); err != nil {
|
||||
return st, err
|
||||
}
|
||||
if yf.IdentCase != nil {
|
||||
c := Case(*yf.IdentCase)
|
||||
if err := validCase(c); err != nil {
|
||||
return st, fmt.Errorf("pgtidy: %s: ident_case: %w", path, err)
|
||||
}
|
||||
st.IdentCase = c
|
||||
if err := loadCase(yf.IdentCase, &st.IdentCase, path, "ident_case"); err != nil {
|
||||
return st, err
|
||||
}
|
||||
if yf.TypeCase != nil {
|
||||
c := Case(*yf.TypeCase)
|
||||
if err := validCase(c); err != nil {
|
||||
return st, fmt.Errorf("pgtidy: %s: type_case: %w", path, err)
|
||||
}
|
||||
st.TypeCase = c
|
||||
if err := loadCase(yf.TypeCase, &st.TypeCase, path, "type_case"); err != nil {
|
||||
return st, err
|
||||
}
|
||||
if err := loadCase(yf.AliasCase, &st.AliasCase, path, "alias_case"); err != nil {
|
||||
return st, err
|
||||
}
|
||||
if err := loadCase(yf.BuiltinCase, &st.BuiltinCase, path, "builtin_case"); err != nil {
|
||||
return st, err
|
||||
}
|
||||
if err := loadCase(yf.CustomTypeCase, &st.CustomTypeCase, path, "custom_type_case"); err != nil {
|
||||
return st, err
|
||||
}
|
||||
|
||||
if yf.Commas != nil {
|
||||
cs := CommaStyle(*yf.Commas)
|
||||
if cs != CommaLeading && cs != CommaTrailing {
|
||||
@@ -129,9 +250,93 @@ func Load(startDir string) (Style, error) {
|
||||
st.Commas = cs
|
||||
}
|
||||
|
||||
loadBool(yf.AlignColumns, &st.AlignColumns)
|
||||
loadBool(yf.AlignLineComments, &st.AlignLineComments)
|
||||
loadBool(yf.SelectAlignAs, &st.SelectAlignAs)
|
||||
loadBool(yf.SetAlignEqual, &st.SetAlignEqual)
|
||||
loadBool(yf.IndentJoin, &st.IndentJoin)
|
||||
if yf.JoinIndentSize != nil {
|
||||
st.JoinIndentSize = *yf.JoinIndentSize
|
||||
}
|
||||
if yf.WhereWrap != nil {
|
||||
wm := WrapMode(*yf.WhereWrap)
|
||||
if err := validWrap(wm); err != nil {
|
||||
return st, fmt.Errorf("pgtidy: %s: where_wrap: %w", path, err)
|
||||
}
|
||||
st.WhereWrap = wm
|
||||
}
|
||||
loadBool(yf.WhereAndOrIndent, &st.WhereAndOrIndent)
|
||||
|
||||
if yf.SubqueryOpening != nil {
|
||||
pl := Placement(*yf.SubqueryOpening)
|
||||
if err := validPlacement(pl); err != nil {
|
||||
return st, fmt.Errorf("pgtidy: %s: subquery_opening: %w", path, err)
|
||||
}
|
||||
st.SubqueryOpening = pl
|
||||
}
|
||||
if yf.SubqueryContent != nil {
|
||||
pl := Placement(*yf.SubqueryContent)
|
||||
if err := validPlacement(pl); err != nil {
|
||||
return st, fmt.Errorf("pgtidy: %s: subquery_content: %w", path, err)
|
||||
}
|
||||
st.SubqueryContent = pl
|
||||
}
|
||||
if yf.SubqueryClosing != nil {
|
||||
pl := Placement(*yf.SubqueryClosing)
|
||||
if err := validPlacement(pl); err != nil {
|
||||
return st, fmt.Errorf("pgtidy: %s: subquery_closing: %w", path, err)
|
||||
}
|
||||
st.SubqueryClosing = pl
|
||||
}
|
||||
loadBool(yf.SubquerySpaceBeforeParen, &st.SubquerySpaceBeforeParen)
|
||||
|
||||
loadBool(yf.InsertCollapseValues, &st.InsertCollapseValues)
|
||||
|
||||
loadBool(yf.AlignParamTypes, &st.AlignParamTypes)
|
||||
loadBool(yf.RoutineAsWrap, &st.RoutineAsWrap)
|
||||
|
||||
if yf.PlpgsqlMaxBlankLines != nil {
|
||||
st.PlpgsqlMaxBlankLines = *yf.PlpgsqlMaxBlankLines
|
||||
}
|
||||
loadBool(yf.PlpgsqlDeclareAlignType, &st.PlpgsqlDeclareAlignType)
|
||||
loadBool(yf.PlpgsqlDeclareAlignEq, &st.PlpgsqlDeclareAlignEq)
|
||||
loadBool(yf.PlpgsqlIfThenNewline, &st.PlpgsqlIfThenNewline)
|
||||
loadBool(yf.PlpgsqlLoopCollapse, &st.PlpgsqlLoopCollapse)
|
||||
|
||||
loadBool(yf.BinaryOpAlign, &st.BinaryOpAlign)
|
||||
loadBool(yf.SpaceAfterCommaInCalls, &st.SpaceAfterCommaInCalls)
|
||||
loadBool(yf.CaseWhenWrap, &st.CaseWhenWrap)
|
||||
if yf.CaseEnd != nil {
|
||||
pl := Placement(*yf.CaseEnd)
|
||||
if err := validPlacement(pl); err != nil {
|
||||
return st, fmt.Errorf("pgtidy: %s: case_end: %w", path, err)
|
||||
}
|
||||
st.CaseEnd = pl
|
||||
}
|
||||
loadBool(yf.CaseCollapse, &st.CaseCollapse)
|
||||
loadBool(yf.RecordSpaceBeforeParen, &st.RecordSpaceBeforeParen)
|
||||
|
||||
return st, nil
|
||||
}
|
||||
|
||||
func loadCase(src *string, dst *Case, path, key string) error {
|
||||
if src == nil {
|
||||
return nil
|
||||
}
|
||||
c := Case(*src)
|
||||
if err := validCase(c); err != nil {
|
||||
return fmt.Errorf("pgtidy: %s: %s: %w", path, key, err)
|
||||
}
|
||||
*dst = c
|
||||
return nil
|
||||
}
|
||||
|
||||
func loadBool(src *bool, dst *bool) {
|
||||
if src != nil {
|
||||
*dst = *src
|
||||
}
|
||||
}
|
||||
|
||||
// findConfig walks parent directories from startDir looking for .pgtidy.yaml.
|
||||
// Returns ("", nil) when no file is found before reaching the filesystem root.
|
||||
func findConfig(startDir string) (string, error) {
|
||||
@@ -159,3 +364,19 @@ func validCase(c Case) error {
|
||||
}
|
||||
return fmt.Errorf("must be \"upper\", \"lower\", or \"preserve\"")
|
||||
}
|
||||
|
||||
func validWrap(w WrapMode) error {
|
||||
switch w {
|
||||
case WrapAlways, WrapWhenLong, WrapNever:
|
||||
return nil
|
||||
}
|
||||
return fmt.Errorf("must be \"always\", \"when_long\", or \"never\"")
|
||||
}
|
||||
|
||||
func validPlacement(p Placement) error {
|
||||
switch p {
|
||||
case PlacementSameLine, PlacementNewLine:
|
||||
return nil
|
||||
}
|
||||
return fmt.Errorf("must be \"same_line\" or \"new_line\"")
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user