sqlanvil-sqlx-lint 0.2.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,22 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Acuantia (original dataform-sqlx-lint)
4
+ Copyright (c) 2026 Ivan Histand / SQLAnvil (sqlanvil-sqlx-lint fork)
5
+
6
+ Permission is hereby granted, free of charge, to any person obtaining a copy
7
+ of this software and associated documentation files (the "Software"), to deal
8
+ in the Software without restriction, including without limitation the rights
9
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10
+ copies of the Software, and to permit persons to whom the Software is
11
+ furnished to do so, subject to the following conditions:
12
+
13
+ The above copyright notice and this permission notice shall be included in all
14
+ copies or substantial portions of the Software.
15
+
16
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
22
+ SOFTWARE.
@@ -0,0 +1,187 @@
1
+ Metadata-Version: 2.4
2
+ Name: sqlanvil-sqlx-lint
3
+ Version: 0.2.0
4
+ Summary: Convention linter for SQLAnvil .sqlx files — config-block, project-convention, and sqlanvil-delta checks that SQL linters and compile cannot see
5
+ Author-email: Ivan Histand <ivan@histand.net>
6
+ License-Expression: MIT
7
+ Project-URL: Homepage, https://github.com/SQLAnvil/sqlanvil-sqlx-lint
8
+ Project-URL: Documentation, https://sqlanvil.com/docs
9
+ Project-URL: Upstream, https://github.com/acuantia/dataform-sqlx-lint
10
+ Keywords: sqlanvil,sqlx,lint,postgres,supabase,mysql,bigquery,dataform,pre-commit
11
+ Classifier: Development Status :: 4 - Beta
12
+ Classifier: Environment :: Console
13
+ Classifier: Programming Language :: Python :: 3
14
+ Classifier: Topic :: Software Development :: Quality Assurance
15
+ Requires-Python: >=3.11
16
+ Description-Content-Type: text/markdown
17
+ License-File: LICENSE
18
+ Dynamic: license-file
19
+
20
+ # sqlanvil-sqlx-lint
21
+
22
+ A convention linter for [SQLAnvil](https://sqlanvil.com) `.sqlx` files. SQL
23
+ linters (sqlfluff) check the SQL body; `sqlanvil compile` checks syntax and
24
+ `sqlanvil validate` checks against the warehouse. None of them see the
25
+ **config-block and project conventions** that keep a sqlanvil repo healthy, and
26
+ several Dataform habits that sqlanvil silently ignores or fails on at run time.
27
+ This tool does, in milliseconds, with no warehouse connection.
28
+
29
+ Zero dependencies (Python ≥ 3.11 standard library only). Designed for
30
+ [pre-commit](https://pre-commit.com). Warehouse-aware: PostgreSQL, Supabase,
31
+ MySQL/MariaDB, and BigQuery-target projects.
32
+
33
+ ```bash
34
+ pip install sqlanvil-sqlx-lint
35
+ ```
36
+
37
+ Forked from [acuantia/dataform-sqlx-lint](https://github.com/acuantia/dataform-sqlx-lint)
38
+ (MIT), which was extracted from a production Dataform repository.
39
+
40
+ ## Rules
41
+
42
+ ### Conventions
43
+
44
+ | Code | Default | Checks |
45
+ |------|---------|--------|
46
+ | E001 | on | `config {}` block present and balanced |
47
+ | E002 | on | non-empty `columns: {}` on tables/views/incrementals/declarations (declarations may carry `columnTypes: {}` from `sqlanvil introspect` instead) |
48
+ | E003 | on | `schema:` must not hardcode an environment suffix (`_prod`/`_dev`/`_test` by default) — `--schema-suffix` and `environments.<name>.schemaSuffix` append it, so a literal doubles up (`analytics_test_test`) |
49
+ | E004 | on | `name:` matching the filename is redundant (declarations exempt) |
50
+ | E005 | opt-in | operations/assertions must not set `schema:` (`hasOutput: true` operations exempt — schema+name define `${self()}`) |
51
+ | E006 | on | hardcoded table paths instead of `${ref()}` — these silently break the dependency graph. Warehouse-aware: `public.orders`, `"schema"."table"`, `` `db`.`table` ``, `` `project.dataset.table` `` |
52
+ | E007 | on* | configurable per-directory naming/type policies (*no-op until policies are configured) |
53
+ | W008 | opt-in | `post_operations {}` placed before the main SELECT (style preference) |
54
+ | E010 | on | every determinable output column appears in `columns: {}` — parses the main SELECT conservatively (unparseable expressions are skipped, never false-flagged) and follows `select *` through a single plain `${ref()}` into the upstream file |
55
+
56
+ Why E002/E010 matter: sqlanvil writes `description:` and `columns: {}` into the
57
+ warehouse catalog (`COMMENT ON TABLE` / `COMMENT ON COLUMN` on Postgres, table
58
+ and column descriptions on BigQuery). That is the metadata data catalogs, BI
59
+ tools, and AI analytics agents read. Partial blocks leave silent gaps.
60
+
61
+ ### sqlanvil deltas
62
+
63
+ The places where a Dataform/BigQuery habit produces a sqlanvil project that
64
+ compiles but does the wrong thing. The first three are failures no compiler
65
+ catches.
66
+
67
+ | Code | Default | Checks | `sqlanvil compile` catches it? |
68
+ |------|---------|--------|-------------------------------|
69
+ | S101 | on | `bigquery: {}`, `partitionBy`, `clusterBy`, `bigqueryPolicyTags` on a non-BigQuery warehouse — ignored silently, never applied | no |
70
+ | S102 | on | `;` separating statements in `operations` / `pre_operations` / `post_operations` — sqlanvil splits on a `---` line, so the block runs as one statement and fails at run time | no |
71
+ | S105 | on | `ADD PRIMARY KEY` / `ADD CONSTRAINT` in an incremental's operations block not wrapped in `${when(!incremental(), …)}` — runs on every append and errors the second time | no |
72
+ | S103 | on | `postgres.indexes[].method` given as a string — it is a numeric enum (`BTREE=0, HASH=1, GIN=2, GIST=3, BRIN=4`) | yes |
73
+ | S104 | on | `incrementalStrategy` on a non-BigQuery warehouse | yes (≥1.29) |
74
+ | S106 | on | `assertions:` sets both `uniqueKey` and `uniqueKeys` | yes |
75
+ | S108 | on | `.jitCode()` / `jitData()` — no runtime in sqlanvil | yes (≥1.30) |
76
+
77
+ ## Usage
78
+
79
+ ```bash
80
+ sqlanvil-sqlx-lint definitions/outputs/my_table.sqlx [...]
81
+ # exit 0 = clean or warnings only; 1 = errors; 2 = bad config
82
+ ```
83
+
84
+ Run from the project root. The target warehouse is read from
85
+ `workflow_settings.yaml` there (`warehouse: postgres|supabase|mysql|bigquery`),
86
+ and `--definitions-root` (default `./definitions`) is indexed so `${ref()}`
87
+ targets resolve for E010's star-resolution. Override with `--warehouse` or the
88
+ `warehouse` config key; the default with nothing configured is `postgres`.
89
+
90
+ ### pre-commit
91
+
92
+ ```yaml
93
+ repos:
94
+ - repo: https://github.com/SQLAnvil/sqlanvil-sqlx-lint
95
+ rev: v0.2.0
96
+ hooks:
97
+ - id: sqlanvil-sqlx-lint
98
+ ```
99
+
100
+ ### Configuration
101
+
102
+ `.sqlx-lint.toml` in the project root, or a `[tool.sqlx-lint]` table in
103
+ `pyproject.toml` (the standalone file wins). All keys optional:
104
+
105
+ ```toml
106
+ warehouse = "postgres" # else workflow_settings.yaml, else postgres
107
+ schema_suffixes = ["_prod", "_dev", "_test"] # E003 suffix list ([] disables)
108
+ documented_types = ["table", "view", "incremental", "declaration"] # E002
109
+ coverage_paths = ["definitions/outputs/"] # E010 scope; empty = everywhere
110
+ allowed_schemas = ["extensions"] # E006 may reference these directly
111
+ enable = ["E005", "W008"] # switch on opt-in rules
112
+ disable = ["E004"] # switch off default rules
113
+
114
+ [[dir_policies]] # E007 (repeatable)
115
+ path_contains = "definitions/outputs/"
116
+ require_prefix = "rpt_"
117
+ require_types = ["table", "incremental"]
118
+ severity = "error" # or "warning"
119
+ ```
120
+
121
+ See [`examples/sqlx-lint.toml`](examples/sqlx-lint.toml) for a complete
122
+ sources → intermediate → outputs layout. System catalogs (`pg_catalog`,
123
+ `information_schema`, `mysql`, `sys`, …) are always allowed.
124
+
125
+ ### Adopting on an existing project
126
+
127
+ A migrated repository will be loud on the first run: E002 on every undocumented
128
+ model and E010 wherever documentation is partial. Adopt in layers rather than
129
+ suppressing:
130
+
131
+ 1. Start with `disable = ["E002", "E010"]` so the S-series and E006 findings,
132
+ which are actual defects, land first.
133
+ 2. Re-enable E002 and scope E010 with `coverage_paths` to the BI-facing layer.
134
+ 3. Widen `coverage_paths` as documentation catches up.
135
+
136
+ ### Suppressing findings
137
+
138
+ ```sql
139
+ from public.legacy_events -- sqlx-lint: disable=E006 (declaration repoints at cutover)
140
+ ```
141
+
142
+ or file-wide, anywhere in the file:
143
+
144
+ ```sql
145
+ -- sqlx-lint: disable-file=E006
146
+ ```
147
+
148
+ Suppress with a reason, sparingly — the convention is usually the fix.
149
+
150
+ ## Design notes
151
+
152
+ - **Conservative by construction**: the SQL projection parser only claims
153
+ column names it can determine (aliases, simple identifiers, resolvable
154
+ `select *`); anything ambiguous is skipped, so E010 never false-flags.
155
+ - **E006 looks only at table positions** (`FROM`, `JOIN`, `INTO`, `UPDATE`),
156
+ ignores `FROM` used as function syntax (`extract(dow from d.date)`), function
157
+ calls (`public.my_func(1)`), and anything inside `${…}`.
158
+ - **S102 understands sqlanvil's text**: `;` inside string literals, `$$`
159
+ dollar-quoted PL/pgSQL bodies, and `${…}` JavaScript regions never count; a
160
+ single trailing `;` is fine.
161
+ - **Declarations are exempt** from E003/E004 deliberately: raw source schemas
162
+ legitimately carry environment-suffixed names, and `name:` is required.
163
+ - Rule codes are stable; gaps in the numbering are historical.
164
+
165
+ ## Agent Skill
166
+
167
+ The `sqlanvil-sqlx-lint` Agent Skill teaches AI coding agents (Claude Code,
168
+ Codex CLI, Cursor, or any tool supporting the open
169
+ [Agent Skills](https://agentskills.io/) format) to run this linter on every
170
+ `.sqlx` file they create or modify. It lives with the other SQLAnvil skills:
171
+
172
+ ```bash
173
+ npx skills add SQLAnvil/agent-skills
174
+ ```
175
+
176
+ Source: https://github.com/SQLAnvil/agent-skills
177
+
178
+ ## Development
179
+
180
+ ```bash
181
+ python3 -m venv .venv && .venv/bin/pip install -e . pytest
182
+ .venv/bin/pytest # 103 tests
183
+ ```
184
+
185
+ ## License
186
+
187
+ MIT — see [LICENSE](https://github.com/SQLAnvil/sqlanvil-sqlx-lint/blob/main/LICENSE).
@@ -0,0 +1,168 @@
1
+ # sqlanvil-sqlx-lint
2
+
3
+ A convention linter for [SQLAnvil](https://sqlanvil.com) `.sqlx` files. SQL
4
+ linters (sqlfluff) check the SQL body; `sqlanvil compile` checks syntax and
5
+ `sqlanvil validate` checks against the warehouse. None of them see the
6
+ **config-block and project conventions** that keep a sqlanvil repo healthy, and
7
+ several Dataform habits that sqlanvil silently ignores or fails on at run time.
8
+ This tool does, in milliseconds, with no warehouse connection.
9
+
10
+ Zero dependencies (Python ≥ 3.11 standard library only). Designed for
11
+ [pre-commit](https://pre-commit.com). Warehouse-aware: PostgreSQL, Supabase,
12
+ MySQL/MariaDB, and BigQuery-target projects.
13
+
14
+ ```bash
15
+ pip install sqlanvil-sqlx-lint
16
+ ```
17
+
18
+ Forked from [acuantia/dataform-sqlx-lint](https://github.com/acuantia/dataform-sqlx-lint)
19
+ (MIT), which was extracted from a production Dataform repository.
20
+
21
+ ## Rules
22
+
23
+ ### Conventions
24
+
25
+ | Code | Default | Checks |
26
+ |------|---------|--------|
27
+ | E001 | on | `config {}` block present and balanced |
28
+ | E002 | on | non-empty `columns: {}` on tables/views/incrementals/declarations (declarations may carry `columnTypes: {}` from `sqlanvil introspect` instead) |
29
+ | E003 | on | `schema:` must not hardcode an environment suffix (`_prod`/`_dev`/`_test` by default) — `--schema-suffix` and `environments.<name>.schemaSuffix` append it, so a literal doubles up (`analytics_test_test`) |
30
+ | E004 | on | `name:` matching the filename is redundant (declarations exempt) |
31
+ | E005 | opt-in | operations/assertions must not set `schema:` (`hasOutput: true` operations exempt — schema+name define `${self()}`) |
32
+ | E006 | on | hardcoded table paths instead of `${ref()}` — these silently break the dependency graph. Warehouse-aware: `public.orders`, `"schema"."table"`, `` `db`.`table` ``, `` `project.dataset.table` `` |
33
+ | E007 | on* | configurable per-directory naming/type policies (*no-op until policies are configured) |
34
+ | W008 | opt-in | `post_operations {}` placed before the main SELECT (style preference) |
35
+ | E010 | on | every determinable output column appears in `columns: {}` — parses the main SELECT conservatively (unparseable expressions are skipped, never false-flagged) and follows `select *` through a single plain `${ref()}` into the upstream file |
36
+
37
+ Why E002/E010 matter: sqlanvil writes `description:` and `columns: {}` into the
38
+ warehouse catalog (`COMMENT ON TABLE` / `COMMENT ON COLUMN` on Postgres, table
39
+ and column descriptions on BigQuery). That is the metadata data catalogs, BI
40
+ tools, and AI analytics agents read. Partial blocks leave silent gaps.
41
+
42
+ ### sqlanvil deltas
43
+
44
+ The places where a Dataform/BigQuery habit produces a sqlanvil project that
45
+ compiles but does the wrong thing. The first three are failures no compiler
46
+ catches.
47
+
48
+ | Code | Default | Checks | `sqlanvil compile` catches it? |
49
+ |------|---------|--------|-------------------------------|
50
+ | S101 | on | `bigquery: {}`, `partitionBy`, `clusterBy`, `bigqueryPolicyTags` on a non-BigQuery warehouse — ignored silently, never applied | no |
51
+ | S102 | on | `;` separating statements in `operations` / `pre_operations` / `post_operations` — sqlanvil splits on a `---` line, so the block runs as one statement and fails at run time | no |
52
+ | S105 | on | `ADD PRIMARY KEY` / `ADD CONSTRAINT` in an incremental's operations block not wrapped in `${when(!incremental(), …)}` — runs on every append and errors the second time | no |
53
+ | S103 | on | `postgres.indexes[].method` given as a string — it is a numeric enum (`BTREE=0, HASH=1, GIN=2, GIST=3, BRIN=4`) | yes |
54
+ | S104 | on | `incrementalStrategy` on a non-BigQuery warehouse | yes (≥1.29) |
55
+ | S106 | on | `assertions:` sets both `uniqueKey` and `uniqueKeys` | yes |
56
+ | S108 | on | `.jitCode()` / `jitData()` — no runtime in sqlanvil | yes (≥1.30) |
57
+
58
+ ## Usage
59
+
60
+ ```bash
61
+ sqlanvil-sqlx-lint definitions/outputs/my_table.sqlx [...]
62
+ # exit 0 = clean or warnings only; 1 = errors; 2 = bad config
63
+ ```
64
+
65
+ Run from the project root. The target warehouse is read from
66
+ `workflow_settings.yaml` there (`warehouse: postgres|supabase|mysql|bigquery`),
67
+ and `--definitions-root` (default `./definitions`) is indexed so `${ref()}`
68
+ targets resolve for E010's star-resolution. Override with `--warehouse` or the
69
+ `warehouse` config key; the default with nothing configured is `postgres`.
70
+
71
+ ### pre-commit
72
+
73
+ ```yaml
74
+ repos:
75
+ - repo: https://github.com/SQLAnvil/sqlanvil-sqlx-lint
76
+ rev: v0.2.0
77
+ hooks:
78
+ - id: sqlanvil-sqlx-lint
79
+ ```
80
+
81
+ ### Configuration
82
+
83
+ `.sqlx-lint.toml` in the project root, or a `[tool.sqlx-lint]` table in
84
+ `pyproject.toml` (the standalone file wins). All keys optional:
85
+
86
+ ```toml
87
+ warehouse = "postgres" # else workflow_settings.yaml, else postgres
88
+ schema_suffixes = ["_prod", "_dev", "_test"] # E003 suffix list ([] disables)
89
+ documented_types = ["table", "view", "incremental", "declaration"] # E002
90
+ coverage_paths = ["definitions/outputs/"] # E010 scope; empty = everywhere
91
+ allowed_schemas = ["extensions"] # E006 may reference these directly
92
+ enable = ["E005", "W008"] # switch on opt-in rules
93
+ disable = ["E004"] # switch off default rules
94
+
95
+ [[dir_policies]] # E007 (repeatable)
96
+ path_contains = "definitions/outputs/"
97
+ require_prefix = "rpt_"
98
+ require_types = ["table", "incremental"]
99
+ severity = "error" # or "warning"
100
+ ```
101
+
102
+ See [`examples/sqlx-lint.toml`](examples/sqlx-lint.toml) for a complete
103
+ sources → intermediate → outputs layout. System catalogs (`pg_catalog`,
104
+ `information_schema`, `mysql`, `sys`, …) are always allowed.
105
+
106
+ ### Adopting on an existing project
107
+
108
+ A migrated repository will be loud on the first run: E002 on every undocumented
109
+ model and E010 wherever documentation is partial. Adopt in layers rather than
110
+ suppressing:
111
+
112
+ 1. Start with `disable = ["E002", "E010"]` so the S-series and E006 findings,
113
+ which are actual defects, land first.
114
+ 2. Re-enable E002 and scope E010 with `coverage_paths` to the BI-facing layer.
115
+ 3. Widen `coverage_paths` as documentation catches up.
116
+
117
+ ### Suppressing findings
118
+
119
+ ```sql
120
+ from public.legacy_events -- sqlx-lint: disable=E006 (declaration repoints at cutover)
121
+ ```
122
+
123
+ or file-wide, anywhere in the file:
124
+
125
+ ```sql
126
+ -- sqlx-lint: disable-file=E006
127
+ ```
128
+
129
+ Suppress with a reason, sparingly — the convention is usually the fix.
130
+
131
+ ## Design notes
132
+
133
+ - **Conservative by construction**: the SQL projection parser only claims
134
+ column names it can determine (aliases, simple identifiers, resolvable
135
+ `select *`); anything ambiguous is skipped, so E010 never false-flags.
136
+ - **E006 looks only at table positions** (`FROM`, `JOIN`, `INTO`, `UPDATE`),
137
+ ignores `FROM` used as function syntax (`extract(dow from d.date)`), function
138
+ calls (`public.my_func(1)`), and anything inside `${…}`.
139
+ - **S102 understands sqlanvil's text**: `;` inside string literals, `$$`
140
+ dollar-quoted PL/pgSQL bodies, and `${…}` JavaScript regions never count; a
141
+ single trailing `;` is fine.
142
+ - **Declarations are exempt** from E003/E004 deliberately: raw source schemas
143
+ legitimately carry environment-suffixed names, and `name:` is required.
144
+ - Rule codes are stable; gaps in the numbering are historical.
145
+
146
+ ## Agent Skill
147
+
148
+ The `sqlanvil-sqlx-lint` Agent Skill teaches AI coding agents (Claude Code,
149
+ Codex CLI, Cursor, or any tool supporting the open
150
+ [Agent Skills](https://agentskills.io/) format) to run this linter on every
151
+ `.sqlx` file they create or modify. It lives with the other SQLAnvil skills:
152
+
153
+ ```bash
154
+ npx skills add SQLAnvil/agent-skills
155
+ ```
156
+
157
+ Source: https://github.com/SQLAnvil/agent-skills
158
+
159
+ ## Development
160
+
161
+ ```bash
162
+ python3 -m venv .venv && .venv/bin/pip install -e . pytest
163
+ .venv/bin/pytest # 103 tests
164
+ ```
165
+
166
+ ## License
167
+
168
+ MIT — see [LICENSE](https://github.com/SQLAnvil/sqlanvil-sqlx-lint/blob/main/LICENSE).
@@ -0,0 +1,34 @@
1
+ [build-system]
2
+ requires = ["setuptools>=77"]
3
+ build-backend = "setuptools.build_meta"
4
+
5
+ [project]
6
+ name = "sqlanvil-sqlx-lint"
7
+ version = "0.2.0"
8
+ description = "Convention linter for SQLAnvil .sqlx files — config-block, project-convention, and sqlanvil-delta checks that SQL linters and compile cannot see"
9
+ readme = "README.md"
10
+ requires-python = ">=3.11"
11
+ license = "MIT"
12
+ license-files = ["LICENSE"]
13
+ authors = [{ name = "Ivan Histand", email = "ivan@histand.net" }]
14
+ keywords = ["sqlanvil", "sqlx", "lint", "postgres", "supabase", "mysql", "bigquery", "dataform", "pre-commit"]
15
+ classifiers = [
16
+ "Development Status :: 4 - Beta",
17
+ "Environment :: Console",
18
+ "Programming Language :: Python :: 3",
19
+ "Topic :: Software Development :: Quality Assurance",
20
+ ]
21
+
22
+ [project.scripts]
23
+ sqlanvil-sqlx-lint = "sqlanvil_sqlx_lint.cli:entrypoint"
24
+
25
+ [project.urls]
26
+ Homepage = "https://github.com/SQLAnvil/sqlanvil-sqlx-lint"
27
+ Documentation = "https://sqlanvil.com/docs"
28
+ Upstream = "https://github.com/acuantia/dataform-sqlx-lint"
29
+
30
+ [tool.setuptools.packages.find]
31
+ where = ["src"]
32
+
33
+ [tool.pytest.ini_options]
34
+ testpaths = ["tests"]
@@ -0,0 +1,4 @@
1
+ [egg_info]
2
+ tag_build =
3
+ tag_date = 0
4
+
@@ -0,0 +1,16 @@
1
+ """sqlanvil-sqlx-lint: convention linter for SQLAnvil .sqlx files."""
2
+
3
+ from .config import Config, DirPolicy, detect_warehouse, load_config, load_config_file
4
+ from .linter import Finding, lint_file, lint_text
5
+
6
+ __all__ = [
7
+ "Config",
8
+ "DirPolicy",
9
+ "Finding",
10
+ "detect_warehouse",
11
+ "lint_file",
12
+ "lint_text",
13
+ "load_config",
14
+ "load_config_file",
15
+ ]
16
+ __version__ = "0.2.0"
@@ -0,0 +1,93 @@
1
+ """Command-line interface.
2
+
3
+ Usage: sqlanvil-sqlx-lint [--config PATH] [--warehouse NAME]
4
+ [--definitions-root DIR] FILE [FILE ...]
5
+ Exit codes: 0 = clean or warnings only, 1 = errors found, 2 = usage error.
6
+ """
7
+
8
+ from __future__ import annotations
9
+
10
+ import argparse
11
+ import sys
12
+ from dataclasses import replace
13
+ from pathlib import Path
14
+
15
+ from .config import WAREHOUSES, Config, load_config, load_config_file, with_detected_warehouse
16
+ from .linter import lint_file
17
+
18
+
19
+ def _repo_resolver(root: Path):
20
+ """Resolve an action name to the text of <root>/**/<name>.sqlx.
21
+ Used to follow `select *` through ${ref()} for E010."""
22
+ if not root.is_dir():
23
+ return lambda name: None
24
+ index = {p.stem: p for p in root.rglob("*.sqlx")}
25
+
26
+ def resolve(name):
27
+ p = index.get(name)
28
+ try:
29
+ return p.read_text(encoding="utf-8") if p else None
30
+ except OSError:
31
+ return None
32
+
33
+ return resolve
34
+
35
+
36
+ def main(argv: list[str] | None = None) -> int:
37
+ parser = argparse.ArgumentParser(
38
+ prog="sqlanvil-sqlx-lint",
39
+ description="Convention linter for SQLAnvil .sqlx files",
40
+ )
41
+ parser.add_argument("files", nargs="+", help=".sqlx files to lint")
42
+ parser.add_argument(
43
+ "--config",
44
+ help="path to a TOML config file (default: .sqlx-lint.toml or "
45
+ "[tool.sqlx-lint] in ./pyproject.toml)",
46
+ )
47
+ parser.add_argument(
48
+ "--warehouse",
49
+ choices=WAREHOUSES,
50
+ help="target warehouse (default: `warehouse` in the config, else "
51
+ "workflow_settings.yaml in the working directory, else postgres)",
52
+ )
53
+ parser.add_argument(
54
+ "--definitions-root",
55
+ default="definitions",
56
+ help="directory indexed to resolve ${ref()} targets for the E010 "
57
+ "coverage rule (default: ./definitions)",
58
+ )
59
+ args = parser.parse_args(argv)
60
+
61
+ try:
62
+ if args.config:
63
+ cfg: Config = with_detected_warehouse(load_config_file(args.config), ".")
64
+ else:
65
+ cfg = load_config(".")
66
+ except (OSError, ValueError) as exc:
67
+ print(f"sqlx-lint: bad config: {exc}", file=sys.stderr)
68
+ return 2
69
+ if args.warehouse:
70
+ cfg = replace(cfg, warehouse=args.warehouse)
71
+ resolver = _repo_resolver(Path(args.definitions_root))
72
+
73
+ errors = warnings = 0
74
+ for path in args.files:
75
+ try:
76
+ findings = lint_file(path, config=cfg, resolver=resolver)
77
+ except OSError as exc:
78
+ print(f"{path}: cannot read: {exc}")
79
+ errors += 1
80
+ continue
81
+ for f in sorted(findings, key=lambda f: f.line):
82
+ print(f"{path}:{f.line}: {f.code} [{f.severity}] {f.message}")
83
+ if f.severity == "error":
84
+ errors += 1
85
+ else:
86
+ warnings += 1
87
+ if errors or warnings:
88
+ print(f"sqlx-lint: {errors} error(s), {warnings} warning(s)")
89
+ return 1 if errors else 0
90
+
91
+
92
+ def entrypoint() -> None:
93
+ sys.exit(main())