From e3a93531b8bb9f3c390a93d167e865053771ab4a Mon Sep 17 00:00:00 2001 From: Steve Dignam Date: Thu, 16 Apr 2026 23:12:59 -0400 Subject: [PATCH] cli: include param for default disabled rules --- README.md | 89 +++++----- crates/squawk/src/cmd.rs | 1 + crates/squawk/src/config.rs | 22 +++ crates/squawk/src/github.rs | 1 + crates/squawk/src/main.rs | 14 ++ crates/squawk/src/reporter.rs | 24 +-- ...st_config__load_assume_in_transaction.snap | 1 + ...k__config__test_config__load_cfg_full.snap | 1 + ...fig__test_config__load_excluded_paths.snap | 1 + ...fig__test_config__load_excluded_rules.snap | 1 + ...onfig__load_excluded_rules_with_alias.snap | 1 + ..._test_config__load_fail_on_violations.snap | 1 + ...fig__test_config__load_included_rules.snap | 20 +++ ..._config__test_config__load_pg_version.snap | 1 + crates/squawk_linter/src/ignore.rs | 18 +- crates/squawk_linter/src/lib.rs | 57 +++++-- .../src/rules/require_table_schema.rs | 13 +- ...e_table_schema__test__alter_table_err.snap | 2 +- ...ble_schema__test__create_table_as_err.snap | 2 +- ..._table_schema__test__create_table_err.snap | 2 +- ...re_table_schema__test__drop_table_err.snap | 2 +- crates/squawk_server/src/lint.rs | 2 +- crates/squawk_wasm/src/lib.rs | 2 +- docs/docs/cli.md | 157 +++++++++++------- 24 files changed, 287 insertions(+), 148 deletions(-) create mode 100644 crates/squawk/src/snapshots/squawk__config__test_config__load_included_rules.snap diff --git a/README.md b/README.md index 8a36b95bc..be6b3b5bb 100644 --- a/README.md +++ b/README.md @@ -108,65 +108,78 @@ Found 6 issues in 1 file (checked 1 source file) ### `squawk --help` ``` -squawk Find problems in your SQL -USAGE: - squawk [FLAGS] [OPTIONS] [path]... [SUBCOMMAND] +Usage: squawk [OPTIONS] [path]... [COMMAND] -FLAGS: - --assume-in-transaction - Assume that a transaction will wrap each SQL file when run by a migration tool +Commands: + server Run the language server + upload-to-github Comment on a PR with Squawk's results + help Print this message or the help of the given subcommand(s) - Use --no-assume-in-transaction to override this setting in any config file that exists - -h, --help - Prints help information +Arguments: + [path]... + Paths or patterns to search - -V, --version - Prints version information +Options: + --exclude-path + Paths to exclude - --verbose - Enable debug logging output + For example: + `--exclude-path=005_user_ids.sql --exclude-path=009_account_emails.sql` -OPTIONS: - -c, --config - Path to the squawk config file (.squawk.toml) + `--exclude-path='*user_ids.sql'` - --debug - Output debug info [possible values: Lex, Parse] + -e, --exclude + Exclude specific warnings - --exclude-path ... - Paths to exclude + For example: --exclude=require-concurrent-index-creation,ban-drop-database - For example: --exclude-path=005_user_ids.sql --exclude-path=009_account_emails.sql + -i, --include + Include opt-in rules that are disabled by default - --exclude-path='*user_ids.sql' + Rules listed in --exclude take precedence over --include. - -e, --exclude ... - Exclude specific warnings + For example: --include=require-table-schema - For example: --exclude=require-concurrent-index-creation,ban-drop-database + --pg-version + Specify postgres version - --pg-version - Specify postgres version + For example: --pg-version=13.0 - For example: --pg-version=13.0 - --reporter - Style of error reporting [possible values: Tty, Gcc, Json] + --debug + Output debug format - --stdin-filepath - Path to use in reporting for stdin + [possible values: lex, parse, ast] + --reporter + Style of error reporting -ARGS: - ... - Paths to search + [possible values: tty, gcc, json, gitlab] + --stdin-filepath + Path to use in reporting for stdin -SUBCOMMANDS: - help Prints this message or the help of the given subcommand(s) - upload-to-github Comment on a PR with Squawk's results + --verbose + Enable debug logging output + + -c, --config + Path to the squawk config file (.squawk.toml) + + --assume-in-transaction + Assume that a transaction will wrap each SQL file when run by a migration tool + + Use --no-assume-in-transaction to override any config file that sets this + + --no-error-on-unmatched-pattern + Do not exit with an error when provided path patterns do not match any files + + -h, --help + Print help (see a summary with '-h') + + -V, --version + Print version ``` ## Rules diff --git a/crates/squawk/src/cmd.rs b/crates/squawk/src/cmd.rs index f2eaece7e..63c5af40f 100644 --- a/crates/squawk/src/cmd.rs +++ b/crates/squawk/src/cmd.rs @@ -58,6 +58,7 @@ impl Cmd { return Cmd::Lint(LintArgs { input, excluded_rules: conf.excluded_rules, + included_rules: conf.included_rules, pg_version: conf.pg_version, assume_in_transaction: conf.assume_in_transaction, reporter: conf.reporter, diff --git a/crates/squawk/src/config.rs b/crates/squawk/src/config.rs index 91cb77c0e..e7312bef3 100644 --- a/crates/squawk/src/config.rs +++ b/crates/squawk/src/config.rs @@ -26,6 +26,8 @@ pub struct ConfigFile { #[serde(default)] pub excluded_rules: Vec, #[serde(default)] + pub included_rules: Vec, + #[serde(default)] pub pg_version: Option, #[serde(default)] pub assume_in_transaction: Option, @@ -56,6 +58,7 @@ impl ConfigFile { pub struct Config { pub excluded_paths: Vec, pub excluded_rules: Vec, + pub included_rules: Vec, pub pg_version: Option, pub assume_in_transaction: bool, pub upload_to_github: UploadToGitHubConfig, @@ -86,6 +89,13 @@ impl Config { conf.excluded_rules.clone() }; + // the --include flag completely overrides the configuration file. + let included_rules = if let Some(included_rules) = opts.included_rules { + included_rules + } else { + conf.included_rules.clone() + }; + // the --exclude-path flag completely overrides the configuration file. let excluded_paths = if let Some(excluded_paths) = opts.excluded_path { excluded_paths @@ -115,6 +125,7 @@ impl Config { info!("pg version: {pg_version:?}"); info!("excluded rules: {:?}", &excluded_rules); + info!("included rules: {:?}", &included_rules); info!("excluded paths: {:?}", &excluded_paths); info!("assume in a transaction: {assume_in_transaction:?}"); info!("no error on unmatched pattern: {no_error_on_unmatched_pattern:?}"); @@ -138,6 +149,7 @@ impl Config { Config { excluded_paths, excluded_rules, + included_rules, pg_version, assume_in_transaction, upload_to_github, @@ -246,6 +258,16 @@ fail_on_violations = true assert_debug_snapshot!(ConfigFile::parse(Some(squawk_toml.path().to_path_buf()))); } #[test] + fn load_included_rules() { + let squawk_toml = NamedTempFile::new().expect("generate tempFile"); + let file = r#" +included_rules = ["require-table-schema"] + + "#; + fs::write(&squawk_toml, file).expect("Unable to write file"); + assert_debug_snapshot!(ConfigFile::parse(Some(squawk_toml.path().to_path_buf()))); + } + #[test] fn load_excluded_rules_with_alias() { let squawk_toml = NamedTempFile::new().expect("generate tempFile"); let file = r#" diff --git a/crates/squawk/src/github.rs b/crates/squawk/src/github.rs index 627c13f65..59ef6711f 100644 --- a/crates/squawk/src/github.rs +++ b/crates/squawk/src/github.rs @@ -121,6 +121,7 @@ pub fn check_and_comment_on_pr(cfg: Config) -> Result<()> { let file_results = lint_files(&LintArgs { input: Input::Paths(found_paths), excluded_rules: cfg.excluded_rules, + included_rules: cfg.included_rules, pg_version: cfg.pg_version, assume_in_transaction: cfg.assume_in_transaction, reporter: cfg.reporter, diff --git a/crates/squawk/src/main.rs b/crates/squawk/src/main.rs index a1b93b72f..41e89a57c 100644 --- a/crates/squawk/src/main.rs +++ b/crates/squawk/src/main.rs @@ -108,6 +108,20 @@ struct Opts { global = true )] excluded_rules: Option>, + /// Include opt-in rules that are disabled by default + /// + /// Rules listed in --exclude take precedence over --include. + /// + /// For example: + /// --include=require-table-schema + #[arg( + short = 'i', + long = "include", + value_name = "rule", + value_delimiter = ',', + global = true + )] + included_rules: Option>, /// Specify postgres version /// /// For example: diff --git a/crates/squawk/src/reporter.rs b/crates/squawk/src/reporter.rs index 14a8ddbd6..f840e7bc6 100644 --- a/crates/squawk/src/reporter.rs +++ b/crates/squawk/src/reporter.rs @@ -22,11 +22,12 @@ use crate::{ fn check_sql( sql: &str, path: &str, + included_rules: &[Rule], excluded_rules: &[Rule], pg_version: Option, assume_in_transaction: bool, ) -> CheckReport { - let mut linter = Linter::without_rules(excluded_rules); + let mut linter = Linter::with_rules(included_rules, excluded_rules); if let Some(pg_version) = pg_version { linter.settings.pg_version = pg_version; } @@ -142,6 +143,7 @@ fn render_lint_error( pub(crate) struct LintArgs { pub(crate) input: Input, pub(crate) excluded_rules: Vec, + pub(crate) included_rules: Vec, pub(crate) pg_version: Option, pub(crate) assume_in_transaction: bool, pub(crate) reporter: Reporter, @@ -162,6 +164,7 @@ pub fn lint_files(args: &LintArgs) -> Result> { let content = check_sql( &sql, &path, + &args.included_rules, &args.excluded_rules, args.pg_version, args.assume_in_transaction, @@ -176,6 +179,7 @@ pub fn lint_files(args: &LintArgs) -> Result> { let content = check_sql( &sql, path.to_str().unwrap(), + &args.included_rules, &args.excluded_rules, args.pg_version, args.assume_in_transaction, @@ -491,7 +495,7 @@ mod test_check_files { select \; "; let mut buff = Vec::new(); - let res = check_sql(sql, "test.sql", &[], None, false); + let res = check_sql(sql, "test.sql", &[], &[], None, false); fmt_json(&mut buff, vec![res]).unwrap(); let val: Value = serde_json::from_slice(&buff).unwrap(); @@ -502,7 +506,7 @@ select \; fn skip_lint_on_syntax_error() { let error_sql = "ALTER TABLE foo ALTER CONSTRAINT bar RENAME TO quux;"; let mut buff = vec![]; - let res = check_sql(error_sql, "test.sql", &[], None, false); + let res = check_sql(error_sql, "test.sql", &[], &[], None, false); fmt_json(&mut buff, vec![res]).unwrap(); assert_snapshot!(String::from_utf8_lossy(&buff), @r#"[{"file":"test.sql","line":0,"column":36,"level":"Error","message":"missing comma","help":null,"rule_name":"syntax-error","column_end":36,"line_end":0}]"#); } @@ -529,7 +533,7 @@ SELECT 1; let res = print_violations( &mut buff, - vec![check_sql(sql, filename, &[], None, false)], + vec![check_sql(sql, filename, &[], &[], None, false)], &Reporter::Gcc, false, ); @@ -562,7 +566,7 @@ SELECT 1; let res = print_violations( &mut buff, - vec![check_sql(sql, filename, &[], None, false)], + vec![check_sql(sql, filename, &[], &[], None, false)], &Reporter::Tty, true, ); @@ -584,7 +588,7 @@ SELECT 1; let res = print_violations( &mut buff, - vec![check_sql(sql, filename, &[], None, false)], + vec![check_sql(sql, filename, &[], &[], None, false)], &Reporter::Tty, false, ); @@ -600,7 +604,7 @@ SELECT 1; let res = print_violations( &mut buff, - vec![check_sql(sql, "main.sql", &[], None, false)], + vec![check_sql(sql, "main.sql", &[], &[], None, false)], &Reporter::Tty, false, ); @@ -622,7 +626,7 @@ SELECT 1; let res = print_violations( &mut buff, - vec![check_sql(sql, filename, &[], None, false)], + vec![check_sql(sql, filename, &[], &[], None, false)], &Reporter::Json, false, ); @@ -643,7 +647,7 @@ SELECT 1; let res = print_violations( &mut buff, - vec![check_sql(sql, filename, &[], None, false)], + vec![check_sql(sql, filename, &[], &[], None, false)], &Reporter::Gitlab, false, ); @@ -662,6 +666,6 @@ ALTER TABLE "core_foo" ADD COLUMN "bar" integer NOT NULL; SELECT 1; "#; let filename = "main.sql"; - assert_debug_snapshot!(check_sql(sql, filename, &[], None, false)); + assert_debug_snapshot!(check_sql(sql, filename, &[], &[], None, false)); } } diff --git a/crates/squawk/src/snapshots/squawk__config__test_config__load_assume_in_transaction.snap b/crates/squawk/src/snapshots/squawk__config__test_config__load_assume_in_transaction.snap index 5fb18c872..b8a89dea3 100644 --- a/crates/squawk/src/snapshots/squawk__config__test_config__load_assume_in_transaction.snap +++ b/crates/squawk/src/snapshots/squawk__config__test_config__load_assume_in_transaction.snap @@ -7,6 +7,7 @@ Ok( ConfigFile { excluded_paths: [], excluded_rules: [], + included_rules: [], pg_version: None, assume_in_transaction: Some( false, diff --git a/crates/squawk/src/snapshots/squawk__config__test_config__load_cfg_full.snap b/crates/squawk/src/snapshots/squawk__config__test_config__load_cfg_full.snap index 35945b317..cc6e930be 100644 --- a/crates/squawk/src/snapshots/squawk__config__test_config__load_cfg_full.snap +++ b/crates/squawk/src/snapshots/squawk__config__test_config__load_cfg_full.snap @@ -11,6 +11,7 @@ Ok( excluded_rules: [ RequireConcurrentIndexCreation, ], + included_rules: [], pg_version: Some( Version { major: 19, diff --git a/crates/squawk/src/snapshots/squawk__config__test_config__load_excluded_paths.snap b/crates/squawk/src/snapshots/squawk__config__test_config__load_excluded_paths.snap index 0f70a3d3b..183c81821 100644 --- a/crates/squawk/src/snapshots/squawk__config__test_config__load_excluded_paths.snap +++ b/crates/squawk/src/snapshots/squawk__config__test_config__load_excluded_paths.snap @@ -9,6 +9,7 @@ Ok( "example.sql", ], excluded_rules: [], + included_rules: [], pg_version: None, assume_in_transaction: None, upload_to_github: UploadToGitHubConfig { diff --git a/crates/squawk/src/snapshots/squawk__config__test_config__load_excluded_rules.snap b/crates/squawk/src/snapshots/squawk__config__test_config__load_excluded_rules.snap index 7848a7eda..9aabf2381 100644 --- a/crates/squawk/src/snapshots/squawk__config__test_config__load_excluded_rules.snap +++ b/crates/squawk/src/snapshots/squawk__config__test_config__load_excluded_rules.snap @@ -9,6 +9,7 @@ Ok( excluded_rules: [ RequireConcurrentIndexCreation, ], + included_rules: [], pg_version: None, assume_in_transaction: None, upload_to_github: UploadToGitHubConfig { diff --git a/crates/squawk/src/snapshots/squawk__config__test_config__load_excluded_rules_with_alias.snap b/crates/squawk/src/snapshots/squawk__config__test_config__load_excluded_rules_with_alias.snap index 2155d8b7b..01910f516 100644 --- a/crates/squawk/src/snapshots/squawk__config__test_config__load_excluded_rules_with_alias.snap +++ b/crates/squawk/src/snapshots/squawk__config__test_config__load_excluded_rules_with_alias.snap @@ -10,6 +10,7 @@ Ok( PreferTimestampTz, PreferTimestampTz, ], + included_rules: [], pg_version: None, assume_in_transaction: None, upload_to_github: UploadToGitHubConfig { diff --git a/crates/squawk/src/snapshots/squawk__config__test_config__load_fail_on_violations.snap b/crates/squawk/src/snapshots/squawk__config__test_config__load_fail_on_violations.snap index 8337825ed..994c15fb0 100644 --- a/crates/squawk/src/snapshots/squawk__config__test_config__load_fail_on_violations.snap +++ b/crates/squawk/src/snapshots/squawk__config__test_config__load_fail_on_violations.snap @@ -7,6 +7,7 @@ Ok( ConfigFile { excluded_paths: [], excluded_rules: [], + included_rules: [], pg_version: None, assume_in_transaction: None, upload_to_github: UploadToGitHubConfig { diff --git a/crates/squawk/src/snapshots/squawk__config__test_config__load_included_rules.snap b/crates/squawk/src/snapshots/squawk__config__test_config__load_included_rules.snap new file mode 100644 index 000000000..1977a09b8 --- /dev/null +++ b/crates/squawk/src/snapshots/squawk__config__test_config__load_included_rules.snap @@ -0,0 +1,20 @@ +--- +source: crates/squawk/src/config.rs +expression: "ConfigFile::parse(Some(squawk_toml.path().to_path_buf()))" +--- +Ok( + Some( + ConfigFile { + excluded_paths: [], + excluded_rules: [], + included_rules: [ + RequireTableSchema, + ], + pg_version: None, + assume_in_transaction: None, + upload_to_github: UploadToGitHubConfig { + fail_on_violations: None, + }, + }, + ), +) diff --git a/crates/squawk/src/snapshots/squawk__config__test_config__load_pg_version.snap b/crates/squawk/src/snapshots/squawk__config__test_config__load_pg_version.snap index aeeec4970..c380224bf 100644 --- a/crates/squawk/src/snapshots/squawk__config__test_config__load_pg_version.snap +++ b/crates/squawk/src/snapshots/squawk__config__test_config__load_pg_version.snap @@ -7,6 +7,7 @@ Ok( ConfigFile { excluded_paths: [], excluded_rules: [], + included_rules: [], pg_version: Some( Version { major: 19, diff --git a/crates/squawk_linter/src/ignore.rs b/crates/squawk_linter/src/ignore.rs index 9e7cffc40..3dc19cd21 100644 --- a/crates/squawk_linter/src/ignore.rs +++ b/crates/squawk_linter/src/ignore.rs @@ -164,7 +164,7 @@ select 1; "; let parse = squawk_syntax::SourceFile::parse(sql); - let mut linter = Linter::with_all_rules(); + let mut linter = Linter::with_default_rules(); find_ignores(&mut linter, &parse.syntax_node()); assert_eq!(linter.ignores.len(), 1); @@ -240,7 +240,7 @@ alter table t drop column c cascade; #[test] fn ignore_multiple_stmts() { - let mut linter = Linter::with_all_rules(); + let mut linter = Linter::with_default_rules(); let sql = r#" -- squawk-ignore ban-char-field,prefer-robust-stmts,require-timeout-settings alter table t add column c char; @@ -262,7 +262,7 @@ create table users ( #[test] fn starting_line_aka_zero() { - let mut linter = Linter::with_all_rules(); + let mut linter = Linter::with_default_rules(); let sql = r#"alter table t add column c char;"#; let parse = squawk_syntax::SourceFile::parse(sql); @@ -355,7 +355,7 @@ create table users ( #[test] fn regression_unknown_name() { - let mut linter = Linter::with_all_rules(); + let mut linter = Linter::with_default_rules(); let sql = r#" -- squawk-ignore prefer-robust-stmts, require-timeout-settings create table test_table ( @@ -467,7 +467,7 @@ alter table t drop column c cascade; #[test] fn file_level_only_ignores_specific_rules() { - let mut linter = Linter::with_all_rules(); + let mut linter = Linter::with_default_rules(); let sql = r#" -- squawk-ignore-file ban-drop-column alter table t drop column c cascade; @@ -493,7 +493,7 @@ alter table t2 drop column c2 cascade; #[test] fn file_ignore_at_end_of_file_is_fine() { - let mut linter = Linter::with_all_rules(); + let mut linter = Linter::with_default_rules(); let sql = r#" alter table t drop column c cascade; alter table t2 drop column c2 cascade; @@ -519,7 +519,7 @@ alter table t2 drop column c2 cascade; #[test] fn file_ignore_with_invalid_rules() { - let mut linter = Linter::with_all_rules(); + let mut linter = Linter::with_default_rules(); let sql = r#" -- squawk-ignore-file ban-ban-ban-drop-column ignore-something hmm alter table t drop column c cascade; @@ -566,7 +566,7 @@ alter table t2 drop column c2 cascade; #[test] fn file_ignore_with_trailing_comment() { - let mut linter = Linter::with_all_rules(); + let mut linter = Linter::with_default_rules(); let sql = r#" -- squawk-ignore-file ban-drop-column -- some comment here alter table t drop column c cascade; @@ -592,7 +592,7 @@ alter table t2 drop column c2 cascade; #[test] fn line_ignore_with_trailing_comment() { - let mut linter = Linter::with_all_rules(); + let mut linter = Linter::with_default_rules(); let sql = r#" -- squawk-ignore ban-drop-column,prefer-robust-stmts -- drop is intentional alter table t drop column c cascade; diff --git a/crates/squawk_linter/src/lib.rs b/crates/squawk_linter/src/lib.rs index eac2c3ca3..2fda9d047 100644 --- a/crates/squawk_linter/src/lib.rs +++ b/crates/squawk_linter/src/lib.rs @@ -1,4 +1,4 @@ -use rustc_hash::{FxBuildHasher, FxHashSet}; +use rustc_hash::FxHashSet; use std::fmt; use enum_iterator::Sequence; @@ -466,28 +466,29 @@ impl Linter { errors } - pub fn with_all_rules() -> Self { - let rules = all::() + fn default_rules() -> FxHashSet { + all::() .filter(|r| !r.is_opt_in()) - .collect::>(); + .collect::>() + } + + pub fn with_default_rules() -> Self { + let rules = Linter::default_rules(); Linter::from(rules) } - pub fn without_rules(exclude: &[Rule]) -> Self { - let all_rules = all::() - .filter(|r| !r.is_opt_in()) - .collect::>(); - let mut exclude_set = FxHashSet::with_capacity_and_hasher(exclude.len(), FxBuildHasher); - for e in exclude { - exclude_set.insert(e); + pub fn with_rules(include: &[Rule], exclude: &[Rule]) -> Self { + let mut default_rules = Linter::default_rules(); + + for rule in include { + default_rules.insert(*rule); } - let rules = all_rules - .into_iter() - .filter(|x| !exclude_set.contains(x)) - .collect::>(); + for rule in exclude { + default_rules.remove(rule); + } - Linter::from(rules) + Linter::from(default_rules) } pub fn from(rules: impl IntoIterator) -> Self { @@ -519,4 +520,28 @@ mod tests { let result: Result = "invalid-rule-name".parse(); assert!(result.is_err()); } + + #[test] + fn with_rules_opt_in_disabled_by_default() { + let linter = Linter::with_rules(&[], &[]); + assert!(!linter.rules.contains(&Rule::RequireTableSchema)); + } + + #[test] + fn with_rules_opt_in_enabled_via_include() { + let linter = Linter::with_rules(&[Rule::RequireTableSchema], &[]); + assert!(linter.rules.contains(&Rule::RequireTableSchema)); + } + + #[test] + fn with_rules_exclude_takes_precedence_over_include() { + let linter = Linter::with_rules(&[Rule::RequireTableSchema], &[Rule::RequireTableSchema]); + assert!(!linter.rules.contains(&Rule::RequireTableSchema)); + } + + #[test] + fn with_rules_exclude_removes_default_rule() { + let linter = Linter::with_rules(&[], &[Rule::BanDropTable]); + assert!(!linter.rules.contains(&Rule::BanDropTable)); + } } diff --git a/crates/squawk_linter/src/rules/require_table_schema.rs b/crates/squawk_linter/src/rules/require_table_schema.rs index 79b1ffbbd..753bafec8 100644 --- a/crates/squawk_linter/src/rules/require_table_schema.rs +++ b/crates/squawk_linter/src/rules/require_table_schema.rs @@ -10,31 +10,30 @@ pub(crate) fn require_table_schema(ctx: &mut Linter, parse: &Parse) for stmt in file.stmts() { match stmt { ast::Stmt::CreateTable(create_table) => { - check_path(ctx, create_table.path(), create_table.syntax()); + check_path(ctx, create_table.path()); } ast::Stmt::CreateTableAs(create_table_as) => { - check_path(ctx, create_table_as.path(), create_table_as.syntax()); + check_path(ctx, create_table_as.path()); } ast::Stmt::AlterTable(alter_table) => { - let path = alter_table.relation_name().and_then(|r| r.path()); - check_path(ctx, path, alter_table.syntax()); + check_path(ctx, alter_table.relation_name().and_then(|r| r.path())); } ast::Stmt::DropTable(drop_table) => { - check_path(ctx, drop_table.path(), drop_table.syntax()); + check_path(ctx, drop_table.path()); } _ => (), } } } -fn check_path(ctx: &mut Linter, path: Option, syntax: &squawk_syntax::SyntaxNode) { +fn check_path(ctx: &mut Linter, path: Option) { if let Some(path) = path && path.qualifier().is_none() { ctx.report(Violation::for_node( Rule::RequireTableSchema, "Table name is not schema-qualified. Use schema.table (e.g., public.my_table).".into(), - syntax, + path.syntax(), )); } } diff --git a/crates/squawk_linter/src/rules/snapshots/squawk_linter__rules__require_table_schema__test__alter_table_err.snap b/crates/squawk_linter/src/rules/snapshots/squawk_linter__rules__require_table_schema__test__alter_table_err.snap index 3923ab72d..f28975ce0 100644 --- a/crates/squawk_linter/src/rules/snapshots/squawk_linter__rules__require_table_schema__test__alter_table_err.snap +++ b/crates/squawk_linter/src/rules/snapshots/squawk_linter__rules__require_table_schema__test__alter_table_err.snap @@ -5,4 +5,4 @@ expression: "lint_errors(sql, Rule::RequireTableSchema)" warning[require-table-schema]: Table name is not schema-qualified. Use schema.table (e.g., public.my_table). ╭▸ 2 │ ALTER TABLE my_table ADD COLUMN name text; - ╰╴━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + ╰╴ ━━━━━━━━ diff --git a/crates/squawk_linter/src/rules/snapshots/squawk_linter__rules__require_table_schema__test__create_table_as_err.snap b/crates/squawk_linter/src/rules/snapshots/squawk_linter__rules__require_table_schema__test__create_table_as_err.snap index 740a37cde..ba936feb0 100644 --- a/crates/squawk_linter/src/rules/snapshots/squawk_linter__rules__require_table_schema__test__create_table_as_err.snap +++ b/crates/squawk_linter/src/rules/snapshots/squawk_linter__rules__require_table_schema__test__create_table_as_err.snap @@ -5,4 +5,4 @@ expression: "lint_errors(sql, Rule::RequireTableSchema)" warning[require-table-schema]: Table name is not schema-qualified. Use schema.table (e.g., public.my_table). ╭▸ 2 │ CREATE TABLE my_table AS SELECT 1; - ╰╴━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + ╰╴ ━━━━━━━━ diff --git a/crates/squawk_linter/src/rules/snapshots/squawk_linter__rules__require_table_schema__test__create_table_err.snap b/crates/squawk_linter/src/rules/snapshots/squawk_linter__rules__require_table_schema__test__create_table_err.snap index b35836353..51aaf31bc 100644 --- a/crates/squawk_linter/src/rules/snapshots/squawk_linter__rules__require_table_schema__test__create_table_err.snap +++ b/crates/squawk_linter/src/rules/snapshots/squawk_linter__rules__require_table_schema__test__create_table_err.snap @@ -5,4 +5,4 @@ expression: "lint_errors(sql, Rule::RequireTableSchema)" warning[require-table-schema]: Table name is not schema-qualified. Use schema.table (e.g., public.my_table). ╭▸ 2 │ CREATE TABLE my_table (id int); - ╰╴━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + ╰╴ ━━━━━━━━ diff --git a/crates/squawk_linter/src/rules/snapshots/squawk_linter__rules__require_table_schema__test__drop_table_err.snap b/crates/squawk_linter/src/rules/snapshots/squawk_linter__rules__require_table_schema__test__drop_table_err.snap index a38c54b45..e54c28543 100644 --- a/crates/squawk_linter/src/rules/snapshots/squawk_linter__rules__require_table_schema__test__drop_table_err.snap +++ b/crates/squawk_linter/src/rules/snapshots/squawk_linter__rules__require_table_schema__test__drop_table_err.snap @@ -5,4 +5,4 @@ expression: "lint_errors(sql, Rule::RequireTableSchema)" warning[require-table-schema]: Table name is not schema-qualified. Use schema.table (e.g., public.my_table). ╭▸ 2 │ DROP TABLE my_table; - ╰╴━━━━━━━━━━━━━━━━━━━ + ╰╴ ━━━━━━━━ diff --git a/crates/squawk_server/src/lint.rs b/crates/squawk_server/src/lint.rs index ab52490b8..39d8340b5 100644 --- a/crates/squawk_server/src/lint.rs +++ b/crates/squawk_server/src/lint.rs @@ -24,7 +24,7 @@ pub(crate) fn lint(db: &dyn Db, file: File) -> Vec { let parse = parse(db, file); let content = file.content(db); let parse_errors = parse.errors(); - let mut linter = Linter::with_all_rules(); + let mut linter = Linter::with_default_rules(); let violations = linter.lint(&parse, content); let line_index = file_line_index(db, file); diff --git a/crates/squawk_wasm/src/lib.rs b/crates/squawk_wasm/src/lib.rs index 539330a3a..10f145716 100644 --- a/crates/squawk_wasm/src/lib.rs +++ b/crates/squawk_wasm/src/lib.rs @@ -175,7 +175,7 @@ impl SquawkDatabase { pub fn lint(&self) -> Result { let file = self.file()?; let content = file.content(&self.db); - let mut linter = squawk_linter::Linter::with_all_rules(); + let mut linter = squawk_linter::Linter::with_default_rules(); let parse = db::parse(&self.db, file); let parse_errors = parse.errors(); diff --git a/docs/docs/cli.md b/docs/docs/cli.md index 208e1af4e..cd85894b6 100644 --- a/docs/docs/cli.md +++ b/docs/docs/cli.md @@ -21,6 +21,14 @@ Individual rules can be disabled via the `--exclude` flag squawk --exclude=adding-field-with-default,disallowed-unique-constraint example.sql ``` +Rules that are disabled by default can be enabled via the `--include` flag + +```shell +squawk --include=require-table-schema example.sql +``` + +Note: `--exclude` takes precedence over `--include`. + ### Disabling rules via comments Rule violations can be ignored via the `squawk-ignore` comment: @@ -72,7 +80,7 @@ By default, Squawk will traverse up from the current directory to find a `.squaw squawk --config=~/.squawk.toml example.sql ``` -The `--exclude`, `--exclude-path`, and `--pg-version` flags will always be prioritized over the configuration file. +The `--exclude`, `--include`, `--exclude-path`, and `--pg-version` flags will always be prioritized over the configuration file. ## Example `.squawk.toml` configurations @@ -86,6 +94,19 @@ excluded_rules = [ ] ``` +### Including rules + +Rules that are disabled by default can be enabled via `included_rules`. + +```toml +# .squawk.toml +included_rules = [ + "require-table-schema", +] +``` + +Note: `excluded_rules` takes precedence over `included_rules`. + ### Specifying postgres version ```toml @@ -109,6 +130,9 @@ excluded_rules = [ "require-concurrent-index-creation", "require-concurrent-index-deletion", ] +included_rules = [ + "require-table-schema", +] assume_in_transaction = true excluded_paths = [ "005_user_ids.sql", @@ -123,67 +147,76 @@ See the [Squawk website](https://squawkhq.com/docs/rules) for documentation on e ## `squawk --help` ``` -squawk Find problems in your SQL -USAGE: - squawk [FLAGS] [OPTIONS] [path]... [SUBCOMMAND] - -FLAGS: - --assume-in-transaction - Assume that a transaction will wrap each SQL file when run by a migration tool - - Use --no-assume-in-transaction to override any config file that sets this - -h, --help - Prints help information - - --no-error-on-unmatched-pattern - Do not exit with an error when provided path patterns do not match any files - - -V, --version - Prints version information - - --verbose - Enable debug logging output - - -OPTIONS: - -c, --config - Path to the squawk config file (.squawk.toml) - - --debug - Output debug format [possible values: Lex, Parse, Ast] - - --exclude-path ... - Paths to exclude - - For example: - - `--exclude-path=005_user_ids.sql --exclude-path=009_account_emails.sql` - - `--exclude-path='*user_ids.sql'` - -e, --exclude ... - Exclude specific warnings - - For example: --exclude=require-concurrent-index-creation,ban-drop-database - --pg-version - Specify postgres version - - For example: --pg-version=13.0 - --reporter - Style of error reporting [possible values: Tty, Gcc, Json, Gitlab] - - --stdin-filepath - Path to use in reporting for stdin - - -ARGS: - ... - Paths or patterns to search - - -SUBCOMMANDS: - help Prints this message or the help of the given subcommand(s) - server Run the language server - upload-to-github Comment on a PR with Squawk's results +Usage: squawk [OPTIONS] [path]... [COMMAND] + +Commands: + server Run the language server + upload-to-github Comment on a PR with Squawk's results + help Print this message or the help of the given subcommand(s) + +Arguments: + [path]... + Paths or patterns to search + +Options: + --exclude-path + Paths to exclude + + For example: + + `--exclude-path=005_user_ids.sql --exclude-path=009_account_emails.sql` + + `--exclude-path='*user_ids.sql'` + + -e, --exclude + Exclude specific warnings + + For example: --exclude=require-concurrent-index-creation,ban-drop-database + + -i, --include + Include opt-in rules that are disabled by default + + Rules listed in --exclude take precedence over --include. + + For example: --include=require-table-schema + + --pg-version + Specify postgres version + + For example: --pg-version=13.0 + + --debug + Output debug format + + [possible values: lex, parse, ast] + + --reporter + Style of error reporting + + [possible values: tty, gcc, json, gitlab] + + --stdin-filepath + Path to use in reporting for stdin + + --verbose + Enable debug logging output + + -c, --config + Path to the squawk config file (.squawk.toml) + + --assume-in-transaction + Assume that a transaction will wrap each SQL file when run by a migration tool + + Use --no-assume-in-transaction to override any config file that sets this + + --no-error-on-unmatched-pattern + Do not exit with an error when provided path patterns do not match any files + + -h, --help + Print help (see a summary with '-h') + + -V, --version + Print version ```