From 6807a604130d62b9746875604283bb837240ab39 Mon Sep 17 00:00:00 2001 From: Steve Dignam Date: Sat, 20 Sep 2025 18:00:21 -0400 Subject: [PATCH 1/4] cli: add rendering of diffs for fix suggestions --- .github/workflows/rust.yml | 14 +- Cargo.lock | 103 ++++++++++++ Cargo.toml | 1 + README.md | 22 ++- crates/squawk/Cargo.toml | 1 + crates/squawk/src/github.rs | 3 + crates/squawk/src/reporter.rs | 48 ++++-- crates/squawk/src/snapshots/example.svg | 147 ++++++++++++++++++ ...test_reporter__display_violations_tty.snap | 22 ++- ...violations_tty_and_github_annotations.snap | 22 ++- ...reporter__test_reporter__span_offsets.snap | 54 +++++++ 11 files changed, 403 insertions(+), 34 deletions(-) create mode 100644 crates/squawk/src/snapshots/example.svg diff --git a/.github/workflows/rust.yml b/.github/workflows/rust.yml index 3ee0a496..ce0c603e 100644 --- a/.github/workflows/rust.yml +++ b/.github/workflows/rust.yml @@ -20,16 +20,6 @@ jobs: with: paths_ignore: '["docs/**", "*.md"]' - lint_pre_job: - runs-on: ubuntu-22.04 - outputs: - should_skip_lint: ${{ steps.skip_check_lint.outputs.should_skip }} - steps: - - id: skip_check_lint - uses: fkirc/skip-duplicate-actions@c449d86cf33a2a6c7a4193264cc2578e2c3266d4 # pin@v4 - with: - paths: '["**/*.rs", "**/Cargo.toml", "Cargo.lock", "s/lint"]' - build: needs: pre_job if: needs.pre_job.outputs.should_skip != 'true' || startsWith(github.ref, 'refs/tags/') @@ -283,8 +273,8 @@ jobs: NETLIFY_SITE_ID: ${{ secrets.PLAYGROUND_NETLIFY_SITE_ID }} lint: - needs: lint_pre_job - if: needs.lint_pre_job.outputs.should_skip_lint != 'true' + needs: pre_job + if: needs.pre_job.outputs.should_skip != 'true' runs-on: ubuntu-22.04 diff --git a/Cargo.lock b/Cargo.lock index c3cc54a1..7b5c5f13 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -57,6 +57,15 @@ version = "1.0.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "862ed96ca487e809f1c8e5a8447f6ee2cf102f846893800b20cebdf541fc6bbd" +[[package]] +name = "anstyle-lossy" +version = "1.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "04d3a5dc826f84d0ea11882bb8054ff7f3d482602e11bb181101303a279ea01f" +dependencies = [ + "anstyle", +] + [[package]] name = "anstyle-parse" version = "0.2.7" @@ -75,6 +84,19 @@ dependencies = [ "windows-sys 0.60.2", ] +[[package]] +name = "anstyle-svg" +version = "0.1.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "26b9ec8c976eada1b0f9747a3d7cc4eae3bef10613e443746e7487f26c872fde" +dependencies = [ + "anstyle", + "anstyle-lossy", + "anstyle-parse", + "html-escape", + "unicode-width 0.2.1", +] + [[package]] name = "anstyle-wincon" version = "3.0.10" @@ -664,6 +686,15 @@ dependencies = [ "windows-sys 0.59.0", ] +[[package]] +name = "html-escape" +version = "0.2.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6d1ad449764d627e22bfd7cd5e8868264fc9236e07c752972b4080cd351cb476" +dependencies = [ + "utf8-width", +] + [[package]] name = "http" version = "0.2.12" @@ -1119,6 +1150,12 @@ dependencies = [ "minimal-lexical", ] +[[package]] +name = "normalize-line-endings" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "61807f77802ff30975e01f4f071c8ba10c022052f98b3294119f3e615d13e5be" + [[package]] name = "num-bigint" version = "0.4.6" @@ -1237,6 +1274,16 @@ dependencies = [ "vcpkg", ] +[[package]] +name = "os_pipe" +version = "1.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "db335f4760b14ead6290116f2427bf33a14d4f0617d49f78a246de10c1831224" +dependencies = [ + "libc", + "windows-sys 0.59.0", +] + [[package]] name = "peeking_take_while" version = "0.1.2" @@ -1734,6 +1781,34 @@ dependencies = [ "serde", ] +[[package]] +name = "snapbox" +version = "0.6.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "96dcfc4581e3355d70ac2ee14cfdf81dce3d85c85f1ed9e2c1d3013f53b3436b" +dependencies = [ + "anstream", + "anstyle", + "anstyle-svg", + "libc", + "normalize-line-endings", + "os_pipe", + "serde_json", + "similar", + "snapbox-macros", + "wait-timeout", + "windows-sys 0.59.0", +] + +[[package]] +name = "snapbox-macros" +version = "0.3.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "16569f53ca23a41bb6f62e0a5084aa1661f4814a67fa33696a79073e03a664af" +dependencies = [ + "anstream", +] + [[package]] name = "socket2" version = "0.5.10" @@ -1773,6 +1848,7 @@ dependencies = [ "serde", "serde_json", "simplelog", + "snapbox", "squawk_github", "squawk_lexer", "squawk_linter", @@ -1793,6 +1869,18 @@ dependencies = [ "serde_json", ] +[[package]] +name = "squawk_ide" +version = "2.26.0" +dependencies = [ + "annotate-snippets", + "insta", + "line-index", + "log", + "rowan", + "squawk_syntax", +] + [[package]] name = "squawk_lexer" version = "2.26.0" @@ -2194,6 +2282,12 @@ dependencies = [ "serde", ] +[[package]] +name = "utf8-width" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "86bd8d4e895da8537e5315b8254664e6b769c4ff3db18321b297a1e7004392e3" + [[package]] name = "utf8_iter" version = "1.0.4" @@ -2212,6 +2306,15 @@ version = "0.2.15" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "accd4ea62f7bb7a82fe23066fb0957d48ef677f6eeb8215f372f52e48bb32426" +[[package]] +name = "wait-timeout" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09ac3b126d3914f9849036f826e054cbabdc8519970b8998ddaf3b5bd3c65f11" +dependencies = [ + "libc", +] + [[package]] name = "walkdir" version = "2.5.0" diff --git a/Cargo.toml b/Cargo.toml index e79c5184..4c35ac4c 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -51,6 +51,7 @@ ungrammar = "1.1.4" quote = "1.0.40" xshell = "0.2.7" proc-macro2 = "1.0.95" +snapbox = { version = "0.6.0", features = ["diff", "term-svg", "cmd"] } # local # we have to make the versions explicit otherwise `cargo publish` won't work diff --git a/README.md b/README.md index e8ce107b..94beb9af 100644 --- a/README.md +++ b/README.md @@ -50,28 +50,42 @@ warning[prefer-bigint-over-int]: Using 32-bit integer fields can result in hitti 6 │ "id" serial NOT NULL PRIMARY KEY, │ ━━━━━━ │ - ╰ help: Use 64-bit integer values instead to prevent hitting this limit. + ├ help: Use 64-bit integer values instead to prevent hitting this limit. + ╭╴ +6 │ "id" bigserial NOT NULL PRIMARY KEY, + ╰╴ +++ warning[prefer-identity]: Serial types make schema, dependency, and permission management difficult. ╭▸ example.sql:6:10 │ 6 │ "id" serial NOT NULL PRIMARY KEY, │ ━━━━━━ │ - ╰ help: Use an `IDENTITY` column instead. + ├ help: Use an `IDENTITY` column instead. + ╭╴ +6 - "id" serial NOT NULL PRIMARY KEY, +6 + "id" integer generated by default as identity NOT NULL PRIMARY KEY, + ╰╴ warning[prefer-text-field]: Changing the size of a `varchar` field requires an `ACCESS EXCLUSIVE` lock, that will prevent all reads and writes to the table. ╭▸ example.sql:7:13 │ 7 │ "alpha" varchar(100) NOT NULL │ ━━━━━━━━━━━━ │ - ╰ help: Use a `TEXT` field with a `CHECK` constraint. + ├ help: Use a `TEXT` field with a `CHECK` constraint. + ╭╴ +7 - "alpha" varchar(100) NOT NULL +7 + "alpha" text NOT NULL + ╰╴ warning[require-concurrent-index-creation]: During normal index creation, table updates are blocked, but reads are still allowed. ╭▸ example.sql:10:1 │ 10 │ CREATE INDEX "field_name_idx" ON "table_name" ("field_name"); │ ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ │ - ╰ help: Use `concurrently` to avoid blocking writes. + ├ help: Use `concurrently` to avoid blocking writes. + ╭╴ +10 │ CREATE INDEX concurrently "field_name_idx" ON "table_name" ("field_name"); + ╰╴ ++++++++++++ warning[constraint-missing-not-valid]: By default new constraints require a table scan and block writes to the table while that scan occurs. ╭▸ example.sql:12:24 │ diff --git a/crates/squawk/Cargo.toml b/crates/squawk/Cargo.toml index 05d26273..cc521f52 100644 --- a/crates/squawk/Cargo.toml +++ b/crates/squawk/Cargo.toml @@ -40,6 +40,7 @@ lsp-types.workspace = true [dev-dependencies] insta.workspace = true +snapbox.workspace = true [lints] workspace = true diff --git a/crates/squawk/src/github.rs b/crates/squawk/src/github.rs index 45b3bd2f..513a7165 100644 --- a/crates/squawk/src/github.rs +++ b/crates/squawk/src/github.rs @@ -394,6 +394,7 @@ mod test_github_comment { help: Some("Make the field nullable.".to_string()), column_end: 9, line_end: 1, + fix: None, }], }]; @@ -486,6 +487,7 @@ ALTER TABLE "core_recipe" ADD COLUMN "foo" integer DEFAULT 10; help: Some("Use bigint instead.".to_string()), column_end: 0, line_end: 1, + fix: None, }], }]; @@ -520,6 +522,7 @@ ALTER TABLE "core_recipe" ADD COLUMN "foo" integer DEFAULT 10; help: Some("Use bigint instead.".to_string()), column_end: 0, line_end: 1, + fix: None, }], }]; diff --git a/crates/squawk/src/reporter.rs b/crates/squawk/src/reporter.rs index 0aa1eae2..a6af7f5a 100644 --- a/crates/squawk/src/reporter.rs +++ b/crates/squawk/src/reporter.rs @@ -1,13 +1,11 @@ -use annotate_snippets::{AnnotationKind, Level, Renderer, Snippet, renderer::DecorStyle}; +use annotate_snippets::{AnnotationKind, Level, Patch, Renderer, Snippet, renderer::DecorStyle}; use anyhow::Result; use console::style; use line_index::LineIndex; use line_index::TextRange; use log::info; use serde::Serialize; -use squawk_linter::Linter; -use squawk_linter::Rule; -use squawk_linter::Version; +use squawk_linter::{Fix, Linter, Rule, Version}; use squawk_syntax::SourceFile; use std::hash::DefaultHasher; use std::hash::Hash; @@ -56,6 +54,7 @@ fn check_sql( range: e.range(), message: e.message().to_string(), rule_name: "syntax-error".to_string(), + fix: None, }) } for e in errors { @@ -74,6 +73,7 @@ fn check_sql( level: ViolationLevel::Warning, message: e.message, rule_name: e.code.to_string(), + fix: e.fix, }) } @@ -100,17 +100,30 @@ fn render_lint_error( ViolationLevel::Error => Level::ERROR, }; - let mut group = level.primary_title(title).id(error_name).element( - Snippet::source(sql) - .path(filename) - .fold(true) - .annotation(AnnotationKind::Primary.span(err.range.into())), - ); + let snippet = Snippet::source(sql) + .path(filename) + .fold(true) + .annotation(AnnotationKind::Primary.span(err.range.into())); + + let mut group = level.primary_title(title).id(error_name).element(snippet); if let Some(help) = &err.help { group = group.element(Level::HELP.message(help)); } + if let Some(fix) = &err.fix { + let mut patch_snippet = Snippet::source(sql).path(filename).fold(true); + + for edit in &fix.edits { + let start: usize = edit.text_range.start().into(); + let end: usize = edit.text_range.end().into(); + let replacement = edit.text.as_deref().unwrap_or(""); + patch_snippet = patch_snippet.patch(Patch::new(start..end, replacement)); + } + + group = group.element(patch_snippet); + } + writeln!(f, "{}", renderer.render(&[group]))?; Ok(()) } @@ -286,6 +299,8 @@ pub struct ReportViolation { pub rule_name: String, pub column_end: usize, pub line_end: usize, + #[serde(skip_serializing)] + pub fix: Option, } fn fmt_gcc(f: &mut W, reports: &[CheckReport]) -> Result<()> { @@ -642,4 +657,17 @@ SELECT 1; let filename = "main.sql"; assert_debug_snapshot!(check_sql(sql, filename, &[], None, false)); } + + #[test] + fn example_sql_svg() { + let expected = snapbox::file!["snapshots/example.svg": TermSvg]; + let bin_path = snapbox::cmd::cargo_bin("squawk"); + snapbox::cmd::Command::new(bin_path) + .env("CLICOLOR_FORCE", "1") + .arg("../../example.sql") + .assert() + .code(1) // squawk returns 1 when it finds violations + .stderr_eq("") + .stdout_eq(expected.raw()); + } } diff --git a/crates/squawk/src/snapshots/example.svg b/crates/squawk/src/snapshots/example.svg new file mode 100644 index 00000000..3498bde2 --- /dev/null +++ b/crates/squawk/src/snapshots/example.svg @@ -0,0 +1,147 @@ + + + + + + + warning[prefer-bigint-over-int]: Using 32-bit integer fields can result in hitting the max `int` limit. + + ╭▸ ../../example.sql:6:10 + + + + 6 "id" serial NOT NULL PRIMARY KEY, + + ━━━━━━ + + + + help: Use 64-bit integer values instead to prevent hitting this limit. + + ╭╴ + + 6 "id" bigserial NOT NULL PRIMARY KEY, + + ╰╴ +++ + + warning[prefer-identity]: Serial types make schema, dependency, and permission management difficult. + + ╭▸ ../../example.sql:6:10 + + + + 6 "id" serial NOT NULL PRIMARY KEY, + + ━━━━━━ + + + + help: Use an `IDENTITY` column instead. + + ╭╴ + + 6 - "id" serial NOT NULL PRIMARY KEY, + + 6 + "id" integer generated by default as identity NOT NULL PRIMARY KEY, + + ╰╴ + + warning[prefer-text-field]: Changing the size of a `varchar` field requires an `ACCESS EXCLUSIVE` lock, that will prevent all reads and writes to the table. + + ╭▸ ../../example.sql:7:13 + + + + 7 "alpha" varchar(100) NOT NULL + + ━━━━━━━━━━━━ + + + + help: Use a `TEXT` field with a `CHECK` constraint. + + ╭╴ + + 7 - "alpha" varchar(100) NOT NULL + + 7 + "alpha" text NOT NULL + + ╰╴ + + warning[require-concurrent-index-creation]: During normal index creation, table updates are blocked, but reads are still allowed. + + ╭▸ ../../example.sql:10:1 + + + + 10 CREATE INDEX "field_name_idx" ON "table_name" ("field_name"); + + ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + + + help: Use `concurrently` to avoid blocking writes. + + ╭╴ + + 10 CREATE INDEX concurrently "field_name_idx" ON "table_name" ("field_name"); + + ╰╴ ++++++++++++ + + warning[constraint-missing-not-valid]: By default new constraints require a table scan and block writes to the table while that scan occurs. + + ╭▸ ../../example.sql:12:24 + + + + 12 ALTER TABLE table_name ADD CONSTRAINT field_name_constraint UNIQUE (field_name); + + ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + + + help: Use `NOT VALID` with a later `VALIDATE CONSTRAINT` call. + + warning[disallowed-unique-constraint]: Adding a `UNIQUE` constraint requires an `ACCESS EXCLUSIVE` lock which blocks reads and writes to the table while the index is built. + + ╭▸ ../../example.sql:12:28 + + + + 12 ALTER TABLE table_name ADD CONSTRAINT field_name_constraint UNIQUE (field_name); + + ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + + + help: Create an index `CONCURRENTLY` and create the constraint using the index. + + + + Find detailed examples and solutions for each rule at https://squawkhq.com/docs/rules + + Found 6 issues in 1 file (checked 1 source file) + + + + + + diff --git a/crates/squawk/src/snapshots/squawk__reporter__test_reporter__display_violations_tty.snap b/crates/squawk/src/snapshots/squawk__reporter__test_reporter__display_violations_tty.snap index 5807fd97..03a5c73a 100644 --- a/crates/squawk/src/snapshots/squawk__reporter__test_reporter__display_violations_tty.snap +++ b/crates/squawk/src/snapshots/squawk__reporter__test_reporter__display_violations_tty.snap @@ -13,14 +13,21 @@ warning[prefer-robust-stmts]: Missing `IF NOT EXISTS`, the migration can't be re ╭▸ main.sql:2:30 │ 2 │ ALTER TABLE "core_recipe" ADD COLUMN "foo" integer NOT NULL; - ╰╴ ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + │ ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + ╭╴ +2 │ ALTER TABLE "core_recipe" ADD COLUMN if not exists "foo" integer NOT NULL; + ╰╴ +++++++++++++ warning[prefer-bigint-over-int]: Using 32-bit integer fields can result in hitting the max `int` limit. ╭▸ main.sql:2:47 │ 2 │ ALTER TABLE "core_recipe" ADD COLUMN "foo" integer NOT NULL; │ ━━━━━━━ │ - ╰ help: Use 64-bit integer values instead to prevent hitting this limit. + ├ help: Use 64-bit integer values instead to prevent hitting this limit. + ╭╴ +2 - ALTER TABLE "core_recipe" ADD COLUMN "foo" integer NOT NULL; +2 + ALTER TABLE "core_recipe" ADD COLUMN "foo" bigint NOT NULL; + ╰╴ warning[adding-required-field]: Adding a new column that is `NOT NULL` and has no default value to an existing table effectively makes it required. ╭▸ main.sql:3:24 │ @@ -32,14 +39,21 @@ warning[prefer-robust-stmts]: Missing `IF NOT EXISTS`, the migration can't be re ╭▸ main.sql:3:24 │ 3 │ ALTER TABLE "core_foo" ADD COLUMN "bar" integer NOT NULL; - ╰╴ ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + │ ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + ╭╴ +3 │ ALTER TABLE "core_foo" ADD COLUMN if not exists "bar" integer NOT NULL; + ╰╴ +++++++++++++ warning[prefer-bigint-over-int]: Using 32-bit integer fields can result in hitting the max `int` limit. ╭▸ main.sql:3:41 │ 3 │ ALTER TABLE "core_foo" ADD COLUMN "bar" integer NOT NULL; │ ━━━━━━━ │ - ╰ help: Use 64-bit integer values instead to prevent hitting this limit. + ├ help: Use 64-bit integer values instead to prevent hitting this limit. + ╭╴ +3 - ALTER TABLE "core_foo" ADD COLUMN "bar" integer NOT NULL; +3 + ALTER TABLE "core_foo" ADD COLUMN "bar" bigint NOT NULL; + ╰╴ Find detailed examples and solutions for each rule at https://squawkhq.com/docs/rules Found 6 issues in 1 file (checked 1 source file) diff --git a/crates/squawk/src/snapshots/squawk__reporter__test_reporter__display_violations_tty_and_github_annotations.snap b/crates/squawk/src/snapshots/squawk__reporter__test_reporter__display_violations_tty_and_github_annotations.snap index 292cdf55..d92952d0 100644 --- a/crates/squawk/src/snapshots/squawk__reporter__test_reporter__display_violations_tty_and_github_annotations.snap +++ b/crates/squawk/src/snapshots/squawk__reporter__test_reporter__display_violations_tty_and_github_annotations.snap @@ -19,14 +19,21 @@ warning[prefer-robust-stmts]: Missing `IF NOT EXISTS`, the migration can't be re ╭▸ main.sql:2:30 │ 2 │ ALTER TABLE "core_recipe" ADD COLUMN "foo" integer NOT NULL; - ╰╴ ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + │ ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + ╭╴ +2 │ ALTER TABLE "core_recipe" ADD COLUMN if not exists "foo" integer NOT NULL; + ╰╴ +++++++++++++ warning[prefer-bigint-over-int]: Using 32-bit integer fields can result in hitting the max `int` limit. ╭▸ main.sql:2:47 │ 2 │ ALTER TABLE "core_recipe" ADD COLUMN "foo" integer NOT NULL; │ ━━━━━━━ │ - ╰ help: Use 64-bit integer values instead to prevent hitting this limit. + ├ help: Use 64-bit integer values instead to prevent hitting this limit. + ╭╴ +2 - ALTER TABLE "core_recipe" ADD COLUMN "foo" integer NOT NULL; +2 + ALTER TABLE "core_recipe" ADD COLUMN "foo" bigint NOT NULL; + ╰╴ warning[adding-required-field]: Adding a new column that is `NOT NULL` and has no default value to an existing table effectively makes it required. ╭▸ main.sql:5:3 │ @@ -40,14 +47,21 @@ warning[prefer-robust-stmts]: Missing `IF NOT EXISTS`, the migration can't be re │ 5 │ ┏ ADD COLUMN "bar" 6 │ ┃ integer NOT NULL; - ╰╴┗━━━━━━━━━━━━━━━━━━━━┛ + │ ┗━━━━━━━━━━━━━━━━━━━━┛ + ╭╴ +5 │ ADD COLUMN if not exists "bar" + ╰╴ +++++++++++++ warning[prefer-bigint-over-int]: Using 32-bit integer fields can result in hitting the max `int` limit. ╭▸ main.sql:6:5 │ 6 │ integer NOT NULL; │ ━━━━━━━ │ - ╰ help: Use 64-bit integer values instead to prevent hitting this limit. + ├ help: Use 64-bit integer values instead to prevent hitting this limit. + ╭╴ +6 - integer NOT NULL; +6 + bigint NOT NULL; + ╰╴ Find detailed examples and solutions for each rule at https://squawkhq.com/docs/rules Found 6 issues in 1 file (checked 1 source file) diff --git a/crates/squawk/src/snapshots/squawk__reporter__test_reporter__span_offsets.snap b/crates/squawk/src/snapshots/squawk__reporter__test_reporter__span_offsets.snap index 6dcd212a..f16d8fb7 100644 --- a/crates/squawk/src/snapshots/squawk__reporter__test_reporter__span_offsets.snap +++ b/crates/squawk/src/snapshots/squawk__reporter__test_reporter__span_offsets.snap @@ -19,6 +19,7 @@ CheckReport { rule_name: "adding-required-field", column_end: 62, line_end: 2, + fix: None, }, ReportViolation { file: "main.sql", @@ -31,6 +32,19 @@ CheckReport { rule_name: "prefer-robust-stmts", column_end: 62, line_end: 2, + fix: Some( + Fix { + title: "Insert `if not exists`", + edits: [ + Edit { + text_range: 41..41, + text: Some( + " if not exists", + ), + }, + ], + }, + ), }, ReportViolation { file: "main.sql", @@ -45,6 +59,19 @@ CheckReport { rule_name: "prefer-bigint-over-int", column_end: 53, line_end: 2, + fix: Some( + Fix { + title: "Replace with a 64-bit integer type: `bigint`", + edits: [ + Edit { + text_range: 48..55, + text: Some( + "bigint", + ), + }, + ], + }, + ), }, ReportViolation { file: "main.sql", @@ -59,6 +86,7 @@ CheckReport { rule_name: "adding-required-field", column_end: 56, line_end: 3, + fix: None, }, ReportViolation { file: "main.sql", @@ -71,6 +99,19 @@ CheckReport { rule_name: "prefer-robust-stmts", column_end: 56, line_end: 3, + fix: Some( + Fix { + title: "Insert `if not exists`", + edits: [ + Edit { + text_range: 99..99, + text: Some( + " if not exists", + ), + }, + ], + }, + ), }, ReportViolation { file: "main.sql", @@ -85,6 +126,19 @@ CheckReport { rule_name: "prefer-bigint-over-int", column_end: 47, line_end: 3, + fix: Some( + Fix { + title: "Replace with a 64-bit integer type: `bigint`", + edits: [ + Edit { + text_range: 106..113, + text: Some( + "bigint", + ), + }, + ], + }, + ), }, ], } From 73ef16ad9c880a8af0da2fe54d65b32885041bf4 Mon Sep 17 00:00:00 2001 From: Steve Dignam Date: Sat, 20 Sep 2025 18:05:37 -0400 Subject: [PATCH 2/4] fix maybe? --- crates/squawk/src/reporter.rs | 12 ------------ crates/squawk/tests/example_output.rs | 14 ++++++++++++++ 2 files changed, 14 insertions(+), 12 deletions(-) create mode 100644 crates/squawk/tests/example_output.rs diff --git a/crates/squawk/src/reporter.rs b/crates/squawk/src/reporter.rs index a6af7f5a..dadf7a99 100644 --- a/crates/squawk/src/reporter.rs +++ b/crates/squawk/src/reporter.rs @@ -658,16 +658,4 @@ SELECT 1; assert_debug_snapshot!(check_sql(sql, filename, &[], None, false)); } - #[test] - fn example_sql_svg() { - let expected = snapbox::file!["snapshots/example.svg": TermSvg]; - let bin_path = snapbox::cmd::cargo_bin("squawk"); - snapbox::cmd::Command::new(bin_path) - .env("CLICOLOR_FORCE", "1") - .arg("../../example.sql") - .assert() - .code(1) // squawk returns 1 when it finds violations - .stderr_eq("") - .stdout_eq(expected.raw()); - } } diff --git a/crates/squawk/tests/example_output.rs b/crates/squawk/tests/example_output.rs new file mode 100644 index 00000000..5e2d10ff --- /dev/null +++ b/crates/squawk/tests/example_output.rs @@ -0,0 +1,14 @@ +use snapbox::{cmd::Command, file}; + +#[test] +fn example_sql_svg() { + let expected = file!["../src/snapshots/example.svg": TermSvg]; + let bin_path = snapbox::cmd::cargo_bin("squawk"); + Command::new(bin_path) + .env("CLICOLOR_FORCE", "1") + .arg("../../example.sql") + .assert() + .code(1) // squawk returns 1 when it finds violations + .stderr_eq("") + .stdout_eq(expected.raw()); +} From be5d960a498578d43c825453afcb7039d450ee26 Mon Sep 17 00:00:00 2001 From: Steve Dignam Date: Sat, 20 Sep 2025 18:10:03 -0400 Subject: [PATCH 3/4] fix lint --- crates/squawk/src/reporter.rs | 1 - 1 file changed, 1 deletion(-) diff --git a/crates/squawk/src/reporter.rs b/crates/squawk/src/reporter.rs index dadf7a99..11a27153 100644 --- a/crates/squawk/src/reporter.rs +++ b/crates/squawk/src/reporter.rs @@ -657,5 +657,4 @@ SELECT 1; let filename = "main.sql"; assert_debug_snapshot!(check_sql(sql, filename, &[], None, false)); } - } From e19da055eae7d0601bb192060cf2c4971311af36 Mon Sep 17 00:00:00 2001 From: Steve Dignam Date: Sat, 20 Sep 2025 18:21:09 -0400 Subject: [PATCH 4/4] fix --- crates/squawk/src/snapshots/example.svg | 147 ------------------------ crates/squawk/tests/example_output.rs | 14 --- 2 files changed, 161 deletions(-) delete mode 100644 crates/squawk/src/snapshots/example.svg delete mode 100644 crates/squawk/tests/example_output.rs diff --git a/crates/squawk/src/snapshots/example.svg b/crates/squawk/src/snapshots/example.svg deleted file mode 100644 index 3498bde2..00000000 --- a/crates/squawk/src/snapshots/example.svg +++ /dev/null @@ -1,147 +0,0 @@ - - - - - - - warning[prefer-bigint-over-int]: Using 32-bit integer fields can result in hitting the max `int` limit. - - ╭▸ ../../example.sql:6:10 - - - - 6 "id" serial NOT NULL PRIMARY KEY, - - ━━━━━━ - - - - help: Use 64-bit integer values instead to prevent hitting this limit. - - ╭╴ - - 6 "id" bigserial NOT NULL PRIMARY KEY, - - ╰╴ +++ - - warning[prefer-identity]: Serial types make schema, dependency, and permission management difficult. - - ╭▸ ../../example.sql:6:10 - - - - 6 "id" serial NOT NULL PRIMARY KEY, - - ━━━━━━ - - - - help: Use an `IDENTITY` column instead. - - ╭╴ - - 6 - "id" serial NOT NULL PRIMARY KEY, - - 6 + "id" integer generated by default as identity NOT NULL PRIMARY KEY, - - ╰╴ - - warning[prefer-text-field]: Changing the size of a `varchar` field requires an `ACCESS EXCLUSIVE` lock, that will prevent all reads and writes to the table. - - ╭▸ ../../example.sql:7:13 - - - - 7 "alpha" varchar(100) NOT NULL - - ━━━━━━━━━━━━ - - - - help: Use a `TEXT` field with a `CHECK` constraint. - - ╭╴ - - 7 - "alpha" varchar(100) NOT NULL - - 7 + "alpha" text NOT NULL - - ╰╴ - - warning[require-concurrent-index-creation]: During normal index creation, table updates are blocked, but reads are still allowed. - - ╭▸ ../../example.sql:10:1 - - - - 10 CREATE INDEX "field_name_idx" ON "table_name" ("field_name"); - - ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ - - - - help: Use `concurrently` to avoid blocking writes. - - ╭╴ - - 10 CREATE INDEX concurrently "field_name_idx" ON "table_name" ("field_name"); - - ╰╴ ++++++++++++ - - warning[constraint-missing-not-valid]: By default new constraints require a table scan and block writes to the table while that scan occurs. - - ╭▸ ../../example.sql:12:24 - - - - 12 ALTER TABLE table_name ADD CONSTRAINT field_name_constraint UNIQUE (field_name); - - ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ - - - - help: Use `NOT VALID` with a later `VALIDATE CONSTRAINT` call. - - warning[disallowed-unique-constraint]: Adding a `UNIQUE` constraint requires an `ACCESS EXCLUSIVE` lock which blocks reads and writes to the table while the index is built. - - ╭▸ ../../example.sql:12:28 - - - - 12 ALTER TABLE table_name ADD CONSTRAINT field_name_constraint UNIQUE (field_name); - - ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ - - - - help: Create an index `CONCURRENTLY` and create the constraint using the index. - - - - Find detailed examples and solutions for each rule at https://squawkhq.com/docs/rules - - Found 6 issues in 1 file (checked 1 source file) - - - - - - diff --git a/crates/squawk/tests/example_output.rs b/crates/squawk/tests/example_output.rs deleted file mode 100644 index 5e2d10ff..00000000 --- a/crates/squawk/tests/example_output.rs +++ /dev/null @@ -1,14 +0,0 @@ -use snapbox::{cmd::Command, file}; - -#[test] -fn example_sql_svg() { - let expected = file!["../src/snapshots/example.svg": TermSvg]; - let bin_path = snapbox::cmd::cargo_bin("squawk"); - Command::new(bin_path) - .env("CLICOLOR_FORCE", "1") - .arg("../../example.sql") - .assert() - .code(1) // squawk returns 1 when it finds violations - .stderr_eq("") - .stdout_eq(expected.raw()); -}