keywordcase

package module
v0.4.36 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Aug 24, 2026 License: MIT Imports: 8 Imported by: 0

README

Documentation

Overview

Package keywordcase reports PostgreSQL keywords that are not written in lowercase, per the gomatic SQL standard (keywords are lowercase; uppercase and title-case are the error). It tokenizes SQL with the shared gomatic/go-sql library — a thin wrapper over libpg_query, PostgreSQL's own lexer — and flags any keyword token whose text is not already its lowercase form. Quoted identifiers and keyword text inside string literals are not keyword tokens, so they are never flagged.

The scanner classifies keywords by table lookup, context-free, so an unquoted identifier that spells an unreserved or column-name keyword (a column named Version, say) scans as a keyword token. The parse tree disambiguates: tokens it proves to be identifiers are never flagged, and when a statement does not parse as SQL at all only the reserved and type/function-name keyword classes — which can never be bare identifiers — are flagged, and only within that statement.

A type name in type position

A type name is treated as an identifier, so `create table t (c TEXT)` is not reported while `create table t (c INT)` is. That reads like an inconsistency and is a real limit, so here is exactly where it comes from.

PostgreSQL resolves a SQL-standard-syntax type to its catalog name — INT becomes `pg_catalog.int4`, VARCHAR becomes `pg_catalog.varchar` — so the tree's name no longer matches the token and the clearing does not apply. TEXT resolves to the bare name `text`, which does match, so it is cleared.

It cannot be fixed by looking harder at the tree. `create table t (c TEXT)` and `create table t (c Value)`, where Value is a type this schema declares, produce the SAME tree shape — one unqualified lowercase name — and the SAME token class, UNRESERVED_KEYWORD. A parser does no catalog resolution, so nothing here can tell a built-in type keyword from a user-defined type whose name happens to be one. Reporting the first would report the second, and this rule prefers a missed keyword to calling an identifier a keyword and blocking an innocent build.

Types PostgreSQL does not class as keywords at all — UUID, JSONB, TIMESTAMPTZ are NO_KEYWORD — are outside this rule's subject entirely, whatever their case.

A function or DO body written as a dollar-quoted string is scanned recursively so the SQL inside it is held to the same standard; a body that is not lexable SQL (a PL/Python body, say) is skipped silently, and recursion stops after [nestingLimit] levels of dollar quoting.

The TAGGED spelling ($body$…$body$) is the fleet's form. This comment used to call the bare $$…$$ "the fleet-standard form", which is the spelling sql-standards forbids in as many words -- "ALWAYS use explicit dollar-quote tags instead of `$$`", and again under a heading reading "Bad: Bare dollar quotes". An analyzer cannot be right against a standard this fleet owns, and a package doc is what teaches the next reader and the next agent, so the sentence was not a stale comment but an active instruction to write the forbidden thing. Both spellings are SCANNED, because the rule's subject is the SQL inside a body and not how the body is delimited; only the claim about which is standard was wrong.

A literal is a BODY because of where the grammar puts it, never because of how it is quoted, and the parse tree is what says so — see [bodies]. This rule used to enter every dollar-quoted literal, which made the sentence above about string literals false: `insert into help (body) values ($$Use END to finish$$)` was reported at END, and applying that remedy edits the row the statement stores.

Index

Constants

View Source
const Category = "sql"

Category is the language group this analyzer belongs to, used by the yze suite to run it only when processing SQL.

View Source
const ErrReadFile errs.Const = "cannot read SQL file"

ErrReadFile reports that a SQL source file could not be read.

View Source
const Name = "keywordcase"

Name is the analyzer's stable identifier — the suffix of its flat rule id and the key the yze suite catalogs it under.

View Source
const Rule = Tool + "/" + Name

Rule is the stable, flat rule id every diagnostic carries: "yze/" + Name.

View Source
const Tool = "yze"

Tool is the suite name stamped on every diagnostic. The analyzer is bundled into the yze suite (the language group lives in the repo path and Category, not the rule id), so it reports as "yze", not a separate "yze-sql" tool.

Variables

This section is empty.

Functions

func Diagnostics

func Diagnostics(path Path, source sql.SQL) ([]goyze.Diagnostic, error)

Diagnostics reports every keyword token in source whose text is not its canonical lowercase spelling, and reports the file itself where the scanner cannot read all of it — see [truncation]. A lexical error scanning source is returned (wrapped in sql.ErrScan) so the caller can surface it as a tool failure rather than a clean pass. path is stamped on each diagnostic's location. Positions are computed in a single forward pass over source, so a file of any size costs O(n).

func Report

func Report(read FileReader, files []string) (goyze.Report, error)

Report runs the keyword-case check over each file and aggregates the diagnostics into the lean stickler-json report. A read failure surfaces as ErrReadFile and a lexical scan failure as the wrapped sql.ErrScan — and in both cases the report still carries every diagnostic the other files produced.

It used to return an EMPTY report alongside the error, so one unlexable file threw away every real violation found in the files beside it, whichever order they were given in. A caller that surfaced the error and dropped the report — the obvious way to use this signature — reported nothing for a tree that had findings, which is the all-or-nothing failure go-yze's own walk documents itself as having removed: "a directory the walk cannot enter destroyed every other directory's findings".

The first failure is the one returned, and later files are still read: a caller fixing one unlexable file should not then discover a second, and a third. What the rule found is not contingent on what it could not read.

Types

type FileReader

type FileReader func(path string) ([]byte, error)

FileReader reads a file's bytes; injected so aggregation is testable without the filesystem.

type Path added in v0.3.4

type Path string

Path is the file path stamped on each diagnostic's location.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL