fix(cmd): handle errors and improve output formatting
CI / Test (push) Successful in 28s
CI / Build (push) Successful in 25s

* update error handling in various commands to use blank identifier
* enhance output formatting for better readability
* add golangci-lint to Makefile for linting checks
This commit is contained in:
Hein
2026-07-01 12:53:25 +02:00
parent f17e87e749
commit 04711cf7b2
14 changed files with 80 additions and 82 deletions
+4
View File
@@ -0,0 +1,4 @@
version: "2"
linters:
default: standard
+2 -1
View File
@@ -20,9 +20,10 @@ vet:
fmt: fmt:
go fmt ./... go fmt ./...
## lint: vet + format check ## lint: vet + format check + golangci-lint
lint: vet lint: vet
test -z "$$(gofmt -l .)" || (echo "gofmt needed:"; gofmt -l .; exit 1) test -z "$$(gofmt -l .)" || (echo "gofmt needed:"; gofmt -l .; exit 1)
golangci-lint run ./...
## clean: remove build artifacts ## clean: remove build artifacts
clean: clean:
+8 -8
View File
@@ -11,7 +11,7 @@ import (
func cmdConfig(args []string, stdin io.Reader, stdout, stderr io.Writer) int { func cmdConfig(args []string, stdin io.Reader, stdout, stderr io.Writer) int {
for _, a := range args { for _, a := range args {
if a == "-h" || a == "--help" { if a == "-h" || a == "--help" {
fmt.Fprintln(stdout, "pgtidy config — print effective configuration resolved from .pgtidy.yaml") _, _ = fmt.Fprintln(stdout, "pgtidy config — print effective configuration resolved from .pgtidy.yaml")
return 0 return 0
} }
} }
@@ -21,14 +21,14 @@ func cmdConfig(args []string, stdin io.Reader, stdout, stderr io.Writer) int {
} }
st, err := config.Load(wd) st, err := config.Load(wd)
if err != nil { if err != nil {
fmt.Fprintf(stderr, "pgtidy: %v\n", err) _, _ = fmt.Fprintf(stderr, "pgtidy: %v\n", err)
return 2 return 2
} }
fmt.Fprintf(stdout, "indent: %q\n", st.Indent) _, _ = fmt.Fprintf(stdout, "indent: %q\n", st.Indent)
fmt.Fprintf(stdout, "newline: %q\n", st.Newline) _, _ = fmt.Fprintf(stdout, "newline: %q\n", st.Newline)
fmt.Fprintf(stdout, "keyword_case: %s\n", st.KeywordCase) _, _ = fmt.Fprintf(stdout, "keyword_case: %s\n", st.KeywordCase)
fmt.Fprintf(stdout, "ident_case: %s\n", st.IdentCase) _, _ = fmt.Fprintf(stdout, "ident_case: %s\n", st.IdentCase)
fmt.Fprintf(stdout, "type_case: %s\n", st.TypeCase) _, _ = fmt.Fprintf(stdout, "type_case: %s\n", st.TypeCase)
fmt.Fprintf(stdout, "commas: %s\n", st.Commas) _, _ = fmt.Fprintf(stdout, "commas: %s\n", st.Commas)
return 0 return 0
} }
+10 -10
View File
@@ -37,7 +37,7 @@ func cmdFmt(args []string, stdin io.Reader, stdout, stderr io.Writer) int {
return 0 return 0
default: default:
if len(a) > 1 && a[0] == '-' { if len(a) > 1 && a[0] == '-' {
fmt.Fprintf(stderr, "pgtidy fmt: unknown flag %q\n", a) _, _ = fmt.Fprintf(stderr, "pgtidy fmt: unknown flag %q\n", a)
return 2 return 2
} }
files = append(files, a) files = append(files, a)
@@ -51,7 +51,7 @@ func cmdFmt(args []string, stdin io.Reader, stdout, stderr io.Writer) int {
} }
st, err := config.Load(wd) st, err := config.Load(wd)
if err != nil { if err != nil {
fmt.Fprintf(stderr, "%v\n", err) _, _ = fmt.Fprintf(stderr, "%v\n", err)
return 2 return 2
} }
@@ -59,7 +59,7 @@ func cmdFmt(args []string, stdin io.Reader, stdout, stderr io.Writer) int {
if len(files) == 0 { if len(files) == 0 {
src, err := io.ReadAll(stdin) src, err := io.ReadAll(stdin)
if err != nil { if err != nil {
fmt.Fprintf(stderr, "pgtidy: reading stdin: %v\n", err) _, _ = fmt.Fprintf(stderr, "pgtidy: reading stdin: %v\n", err)
return 2 return 2
} }
out := format.File(parser.Parse(string(src)), st) out := format.File(parser.Parse(string(src)), st)
@@ -69,9 +69,9 @@ func cmdFmt(args []string, stdin io.Reader, stdout, stderr io.Writer) int {
return 1 return 1
} }
case diff: case diff:
io.WriteString(stdout, unifiedDiff(string(src), out, "stdin")) _, _ = io.WriteString(stdout, unifiedDiff(string(src), out, "stdin"))
default: default:
io.WriteString(stdout, out) _, _ = io.WriteString(stdout, out)
} }
return 0 return 0
} }
@@ -81,7 +81,7 @@ func cmdFmt(args []string, stdin io.Reader, stdout, stderr io.Writer) int {
for _, path := range files { for _, path := range files {
src, err := os.ReadFile(path) src, err := os.ReadFile(path)
if err != nil { if err != nil {
fmt.Fprintf(stderr, "pgtidy: %v\n", err) _, _ = fmt.Fprintf(stderr, "pgtidy: %v\n", err)
exit = 2 exit = 2
continue continue
} }
@@ -94,22 +94,22 @@ func cmdFmt(args []string, stdin io.Reader, stdout, stderr io.Writer) int {
case write: case write:
if changed { if changed {
if err := os.WriteFile(path, []byte(out), 0o644); err != nil { if err := os.WriteFile(path, []byte(out), 0o644); err != nil {
fmt.Fprintf(stderr, "pgtidy: writing %s: %v\n", path, err) _, _ = fmt.Fprintf(stderr, "pgtidy: writing %s: %v\n", path, err)
exit = 2 exit = 2
} }
} }
case list: case list:
if changed { if changed {
fmt.Fprintln(stdout, path) _, _ = fmt.Fprintln(stdout, path)
} }
case diff: case diff:
if changed { if changed {
io.WriteString(stdout, unifiedDiff(string(src), out, path)) _, _ = io.WriteString(stdout, unifiedDiff(string(src), out, path))
} }
case check: case check:
// handled after loop via anyDiff // handled after loop via anyDiff
default: default:
io.WriteString(stdout, out) _, _ = io.WriteString(stdout, out)
} }
} }
if check && anyDiff && exit == 0 { if check && anyDiff && exit == 0 {
+10 -10
View File
@@ -37,7 +37,7 @@ func cmdLint(args []string, stdin io.Reader, stdout, stderr io.Writer) int {
} }
} }
case len(a) > 1 && a[0] == '-': case len(a) > 1 && a[0] == '-':
fmt.Fprintf(stderr, "pgtidy lint: unknown flag %q\n", a) _, _ = fmt.Fprintf(stderr, "pgtidy lint: unknown flag %q\n", a)
return 2 return 2
default: default:
files = append(files, a) files = append(files, a)
@@ -75,7 +75,7 @@ func cmdLint(args []string, stdin io.Reader, stdout, stderr io.Writer) int {
if loc == "" { if loc == "" {
loc = "stdin" loc = "stdin"
} }
fmt.Fprintf(stdout, "%s: [%s] %s: %s\n", loc, d.RuleID, d.Severity, d.Message) _, _ = fmt.Fprintf(stdout, "%s: [%s] %s: %s\n", loc, d.RuleID, d.Severity, d.Message)
} }
} }
@@ -84,18 +84,18 @@ func cmdLint(args []string, stdin io.Reader, stdout, stderr io.Writer) int {
if len(files) == 0 { if len(files) == 0 {
src, err := io.ReadAll(stdin) src, err := io.ReadAll(stdin)
if err != nil { if err != nil {
fmt.Fprintf(stderr, "pgtidy: reading stdin: %v\n", err) _, _ = fmt.Fprintf(stderr, "pgtidy: reading stdin: %v\n", err)
return 2 return 2
} }
diags, err := check(string(src), "") diags, err := check(string(src), "")
if err != nil { if err != nil {
fmt.Fprintf(stderr, "pgtidy: %v\n", err) _, _ = fmt.Fprintf(stderr, "pgtidy: %v\n", err)
return 2 return 2
} }
if fix { if fix {
fixed := lint.ApplyFixes(string(src), diags) fixed := lint.ApplyFixes(string(src), diags)
if fixed != string(src) { if fixed != string(src) {
io.WriteString(stdout, fixed) _, _ = io.WriteString(stdout, fixed)
return 0 return 0
} }
} }
@@ -107,25 +107,25 @@ func cmdLint(args []string, stdin io.Reader, stdout, stderr io.Writer) int {
for _, path := range files { for _, path := range files {
src, err := os.ReadFile(path) src, err := os.ReadFile(path)
if err != nil { if err != nil {
fmt.Fprintf(stderr, "pgtidy: %v\n", err) _, _ = fmt.Fprintf(stderr, "pgtidy: %v\n", err)
return 2 return 2
} }
diags, err := check(string(src), path) diags, err := check(string(src), path)
if err != nil { if err != nil {
fmt.Fprintf(stderr, "pgtidy: %v\n", err) _, _ = fmt.Fprintf(stderr, "pgtidy: %v\n", err)
return 2 return 2
} }
if fix { if fix {
fixed := lint.ApplyFixes(string(src), diags) fixed := lint.ApplyFixes(string(src), diags)
if fixed != string(src) { if fixed != string(src) {
if err := os.WriteFile(path, []byte(fixed), 0o644); err != nil { if err := os.WriteFile(path, []byte(fixed), 0o644); err != nil {
fmt.Fprintf(stderr, "pgtidy: writing %s: %v\n", path, err) _, _ = fmt.Fprintf(stderr, "pgtidy: writing %s: %v\n", path, err)
return 2 return 2
} }
// Re-check to report any remaining unfixed diagnostics. // Re-check to report any remaining unfixed diagnostics.
diags, err = check(fixed, path) diags, err = check(fixed, path)
if err != nil { if err != nil {
fmt.Fprintf(stderr, "pgtidy: %v\n", err) _, _ = fmt.Fprintf(stderr, "pgtidy: %v\n", err)
return 2 return 2
} }
} }
@@ -144,7 +144,7 @@ func cmdLint(args []string, stdin io.Reader, stdout, stderr io.Writer) int {
} }
func lintUsage(w io.Writer) { func lintUsage(w io.Writer) {
fmt.Fprint(w, `Usage: pgtidy lint [flags] [file ...] _, _ = fmt.Fprint(w, `Usage: pgtidy lint [flags] [file ...]
Read SQL from files (or stdin) and report lint findings. Read SQL from files (or stdin) and report lint findings.
+2 -2
View File
@@ -12,7 +12,7 @@ import (
func cmdLsp(args []string, stdin io.Reader, stdout, stderr io.Writer) int { func cmdLsp(args []string, stdin io.Reader, stdout, stderr io.Writer) int {
for _, a := range args { for _, a := range args {
if a == "-h" || a == "--help" { if a == "-h" || a == "--help" {
fmt.Fprintln(stdout, "pgtidy lsp — start the Language Server Protocol server (stdio transport)") _, _ = fmt.Fprintln(stdout, "pgtidy lsp — start the Language Server Protocol server (stdio transport)")
return 0 return 0
} }
} }
@@ -21,7 +21,7 @@ func cmdLsp(args []string, stdin io.Reader, stdout, stderr io.Writer) int {
wd = "." wd = "."
} }
if err := lsp.Serve(context.Background(), stdin, stdout, wd); err != nil { if err := lsp.Serve(context.Background(), stdin, stdout, wd); err != nil {
fmt.Fprintf(stderr, "pgtidy lsp: %v\n", err) _, _ = fmt.Fprintf(stderr, "pgtidy lsp: %v\n", err)
return 1 return 1
} }
return 0 return 0
+3 -3
View File
@@ -29,20 +29,20 @@ func run(args []string, stdin io.Reader, stdout, stderr io.Writer) int {
case "config": case "config":
return cmdConfig(args[1:], stdin, stdout, stderr) return cmdConfig(args[1:], stdin, stdout, stderr)
case "version", "--version", "-v": case "version", "--version", "-v":
fmt.Fprintf(stdout, "pgtidy %s\n", version) _, _ = fmt.Fprintf(stdout, "pgtidy %s\n", version)
return 0 return 0
case "help", "-h", "--help": case "help", "-h", "--help":
usage(stdout) usage(stdout)
return 0 return 0
default: default:
fmt.Fprintf(stderr, "pgtidy: unknown command %q\n", args[0]) _, _ = fmt.Fprintf(stderr, "pgtidy: unknown command %q\n", args[0])
usage(stderr) usage(stderr)
return 2 return 2
} }
} }
func usage(w io.Writer) { func usage(w io.Writer) {
fmt.Fprint(w, `pgtidy — PostgreSQL formatter and linter _, _ = fmt.Fprint(w, `pgtidy — PostgreSQL formatter and linter
Usage: Usage:
pgtidy fmt [flags] [files...] Format SQL/PL-pgSQL (stdin if no files) pgtidy fmt [flags] [files...] Format SQL/PL-pgSQL (stdin if no files)
+8 -15
View File
@@ -10,18 +10,15 @@ import (
// isDMLStart reports whether toks begins with a DML statement keyword. // isDMLStart reports whether toks begins with a DML statement keyword.
func isDMLStart(toks []cst.Tok) bool { func isDMLStart(toks []cst.Tok) bool {
for _, t := range toks { if len(toks) == 0 || toks[0].Tok.Kind != lexer.Ident {
if t.Tok.Kind == lexer.Ident { return false
switch lowerASCII(t.Tok.Text) { }
switch lowerASCII(toks[0].Tok.Text) {
case "select", "insert", "update", "delete", "with": case "select", "insert", "update", "delete", "with":
return true return true
} }
return false return false
} }
return false
}
return false
}
// dmlSeg is one major clause of a DML statement. // dmlSeg is one major clause of a DML statement.
type dmlSeg struct { type dmlSeg struct {
@@ -510,13 +507,6 @@ func dmlSplitCommas(toks []cst.Tok) [][]cst.Tok {
return items return items
} }
// dmlColList formats kwText followed by a comma-separated body.
// One item: kept on the same line as the keyword.
// Multiple items: each on its own line with the configured comma style.
func dmlColList(kwText string, items [][]cst.Tok, st config.Style) string {
return dmlColListSelect(kwText, items, st)
}
// dmlColListSelect formats a SELECT / RETURNING column list with optional // dmlColListSelect formats a SELECT / RETURNING column list with optional
// align_columns and select_align_as settings. // align_columns and select_align_as settings.
func dmlColListSelect(kwText string, items [][]cst.Tok, st config.Style) string { func dmlColListSelect(kwText string, items [][]cst.Tok, st config.Style) string {
@@ -627,7 +617,10 @@ func dmlColListSet(kwText string, items [][]cst.Tok, st config.Style) string {
// alias names align vertically. // alias names align vertically.
func alignSelectItems(texts []string, st config.Style) []string { func alignSelectItems(texts []string, st config.Style) []string {
// Split each text into (expr, " AS ", alias) or keep as-is. // Split each text into (expr, " AS ", alias) or keep as-is.
type part struct{ expr, alias string; hasAs bool } type part struct {
expr, alias string
hasAs bool
}
parts := make([]part, len(texts)) parts := make([]part, len(texts))
maxExpr := 0 maxExpr := 0
for i, t := range texts { for i, t := range texts {
+7 -7
View File
@@ -89,12 +89,12 @@ func (s *server) handle(raw []byte) bool {
return true return true
case "textDocument/didOpen": case "textDocument/didOpen":
var p didOpenParams var p didOpenParams
json.Unmarshal(req.Params, &p) _ = json.Unmarshal(req.Params, &p)
s.docs[p.TextDocument.URI] = p.TextDocument.Text s.docs[p.TextDocument.URI] = p.TextDocument.Text
s.pushDiagnostics(p.TextDocument.URI, p.TextDocument.Text) s.pushDiagnostics(p.TextDocument.URI, p.TextDocument.Text)
case "textDocument/didChange": case "textDocument/didChange":
var p didChangeParams var p didChangeParams
json.Unmarshal(req.Params, &p) _ = json.Unmarshal(req.Params, &p)
if len(p.ContentChanges) > 0 { if len(p.ContentChanges) > 0 {
text := p.ContentChanges[len(p.ContentChanges)-1].Text text := p.ContentChanges[len(p.ContentChanges)-1].Text
s.docs[p.TextDocument.URI] = text s.docs[p.TextDocument.URI] = text
@@ -104,7 +104,7 @@ func (s *server) handle(raw []byte) bool {
var p struct { var p struct {
TextDocument textDocID `json:"textDocument"` TextDocument textDocID `json:"textDocument"`
} }
json.Unmarshal(req.Params, &p) _ = json.Unmarshal(req.Params, &p)
delete(s.docs, p.TextDocument.URI) delete(s.docs, p.TextDocument.URI)
delete(s.fixes, p.TextDocument.URI) delete(s.fixes, p.TextDocument.URI)
s.notify("textDocument/publishDiagnostics", publishDiagnosticsParams{ s.notify("textDocument/publishDiagnostics", publishDiagnosticsParams{
@@ -113,7 +113,7 @@ func (s *server) handle(raw []byte) bool {
}) })
case "textDocument/formatting": case "textDocument/formatting":
var p formattingParams var p formattingParams
json.Unmarshal(req.Params, &p) _ = json.Unmarshal(req.Params, &p)
text, ok := s.docs[p.TextDocument.URI] text, ok := s.docs[p.TextDocument.URI]
if !ok { if !ok {
s.reply(req.ID, []textEdit{}) s.reply(req.ID, []textEdit{})
@@ -127,7 +127,7 @@ func (s *server) handle(raw []byte) bool {
s.reply(req.ID, []textEdit{fullReplace(text, formatted)}) s.reply(req.ID, []textEdit{fullReplace(text, formatted)})
case "textDocument/rangeFormatting": case "textDocument/rangeFormatting":
var p rangeFormattingParams var p rangeFormattingParams
json.Unmarshal(req.Params, &p) _ = json.Unmarshal(req.Params, &p)
text, ok := s.docs[p.TextDocument.URI] text, ok := s.docs[p.TextDocument.URI]
if !ok { if !ok {
s.reply(req.ID, []textEdit{}) s.reply(req.ID, []textEdit{})
@@ -136,7 +136,7 @@ func (s *server) handle(raw []byte) bool {
s.reply(req.ID, s.rangeFormat(text, p.Range)) s.reply(req.ID, s.rangeFormat(text, p.Range))
case "textDocument/codeAction": case "textDocument/codeAction":
var p codeActionParams var p codeActionParams
json.Unmarshal(req.Params, &p) _ = json.Unmarshal(req.Params, &p)
s.handleCodeAction(req.ID, p) s.handleCodeAction(req.ID, p)
case "$/cancelRequest": // ignore case "$/cancelRequest": // ignore
default: default:
@@ -360,7 +360,7 @@ func (s *server) send(v interface{}) {
if err != nil { if err != nil {
return return
} }
fmt.Fprintf(s.w, "Content-Length: %d\r\n\r\n", len(data)) _, _ = fmt.Fprintf(s.w, "Content-Length: %d\r\n\r\n", len(data))
s.w.Write(data) //nolint:errcheck s.w.Write(data) //nolint:errcheck
} }
+2 -2
View File
@@ -51,7 +51,7 @@ func readResp(t *testing.T, buf *bytes.Buffer) map[string]interface{} {
} }
lenStr := string(data[idx+16 : idx+eol]) lenStr := string(data[idx+16 : idx+eol])
var n int var n int
fmt.Sscanf(lenStr, "%d", &n) _, _ = fmt.Sscanf(lenStr, "%d", &n)
sep := bytes.Index(data, []byte("\r\n\r\n")) sep := bytes.Index(data, []byte("\r\n\r\n"))
if sep < 0 || len(data) < sep+4+n { if sep < 0 || len(data) < sep+4+n {
time.Sleep(5 * time.Millisecond) time.Sleep(5 * time.Millisecond)
@@ -60,7 +60,7 @@ func readResp(t *testing.T, buf *bytes.Buffer) map[string]interface{} {
body := data[sep+4 : sep+4+n] body := data[sep+4 : sep+4+n]
buf.Next(sep + 4 + n) buf.Next(sep + 4 + n)
var result map[string]interface{} var result map[string]interface{}
json.Unmarshal(body, &result) _ = json.Unmarshal(body, &result)
return result return result
} }
t.Fatal("timeout waiting for response") t.Fatal("timeout waiting for response")
+1 -1
View File
@@ -88,7 +88,7 @@ func parseCreateFunction(stmt []cst.Tok) (*cst.CreateFunction, bool) {
if i+1 < len(stmt) && stmt[i].Is("or") && stmt[i+1].Is("replace") { if i+1 < len(stmt) && stmt[i].Is("or") && stmt[i+1].Is("replace") {
i += 2 i += 2
} }
if i >= len(stmt) || !(stmt[i].Is("function") || stmt[i].Is("procedure")) { if i >= len(stmt) || (!stmt[i].Is("function") && !stmt[i].Is("procedure")) {
return nil, false return nil, false
} }
i++ i++
+1 -1
View File
@@ -41,7 +41,7 @@ func FirstTokenOffset(sql string, start int) int {
} }
case i+1 < n && sql[i] == '/' && sql[i+1] == '*': case i+1 < n && sql[i] == '/' && sql[i+1] == '*':
i += 2 i += 2
for i+1 < n && !(sql[i] == '*' && sql[i+1] == '/') { for i+1 < n && (sql[i] != '*' || sql[i+1] != '/') {
i++ i++
} }
if i+1 < n { if i+1 < n {