Skip to content
Merged
Show file tree
Hide file tree
Changes from 11 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -24,3 +24,5 @@ node_modules/

**/dist/
.claude-session-id

squawk/
90 changes: 90 additions & 0 deletions agentic/port_squawk_rules.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
The goal is to port all missing rules from Squawk to our analyser.

Our analyser lives in the `pgt_analyser` crate. There is a `CONTRIBUTING.md` guide in that crate which explains how to add new rules. Please also read existing rules to see how it all works.

Then, I want you to check the rules in the squawk project which I copied here for convenience. The rules are in `squawk/linter/src/rules`. The implementation should be very similar to what we have, and porting them straightforward. Here a few things to watch out for though:

- although both libraries are using `libpg_query` to parse the SQL, the bindings can be different. Ours is in the `pgt_query` crate of you need a reference. The `protobuf.rs` file contains the full thing.
- the context for each rule is different, but you can get the same information out of it:
```rust
pub struct RuleContext<'a, R: Rule> {
// the ast of the target statement
stmt: &'a pgt_query::NodeEnum,
// options for that specific rule
options: &'a R::Options,
// the schema cache - also includes the postgres version
schema_cache: Option<&'a SchemaCache>,
// the file context which contains other statements in that file in case you need them
file_context: &'a AnalysedFileContext,
}

pub struct AnalysedFileContext<'a> {
// all other statements in this file
pub all_stmts: &'a Vec<pgt_query::NodeEnum>,
// total count of statements in this file
pub stmt_count: usize,
// all statements before the currently analysed one
pub previous_stmts: Vec<&'a pgt_query::NodeEnum>,
}
```

In squawk, you will see:
```rust
// all statements of that file -> our analyser goes statement by statement but has access to the files content via `file_context`
tree: &[RawStmt],
// the postgres version -> we store it in the schema cache
_pg_version: Option<Version>,
// for us, this is always true
_assume_in_transaction: bool,

```

Please always write idiomatic code!
Only add comments to explain WHY the code is doing something. DO NOT write comments to explain WHAT the code is doing.

If you learn something new that might help in porting all the rules, please update this document.

LEARNINGS:
- Use `cargo clippy` to check your code after writing it
- The `just new-lintrule` command expects severity to be "info", "warn", or "error" (not "warning")
- RuleDiagnostic methods: `detail(span, msg)` takes two parameters, `note(msg)` takes only one parameter
- To check Postgres version: access `ctx.schema_cache().is_some_and(|sc| sc.version.major_version)` which gives e.g. 17
- NEVER skip anything, or use a subset of something. ALWAYS do the full thing. For example, copy the entire non-volatile functions list from Squawk, not just a subset.
- If you are missing features from our rule context to be able to properly implement a rule, DO NOT DO IT. Instead, add that rule to the NEEDS FEATURES list below. The node enum is generated from the same source as it is in squawk, so they have feature parity.
- Remember to run `just gen-lint` after creating a new rule to generate all necessary files

Please update the list below with the rules that we need to migrate, and the ones that are already migrated. Keep the list up-to-date.

NEEDS FEATURES:

TODO:

DONE:
- adding_field_with_default ✓ (ported from Squawk)
- adding_foreign_key_constraint ✓ (ported from Squawk)
- adding_not_null_field ✓ (ported from Squawk)
- adding_primary_key_constraint ✓ (ported from Squawk)
- adding_required_field (already exists in pgt_analyser)
- ban_char_field ✓ (ported from Squawk)
- ban_concurrent_index_creation_in_transaction ✓ (ported from Squawk)
- ban_drop_column (already exists in pgt_analyser)
- changing_column_type ✓ (ported from Squawk)
- constraint_missing_not_valid ✓ (ported from Squawk)
- ban_drop_database (already exists in pgt_analyser, as bad_drop_database in squawk)
- ban_drop_not_null (already exists in pgt_analyser)
- ban_drop_table (already exists in pgt_analyser)
- prefer_big_int ✓ (ported from Squawk)
- prefer_bigint_over_int ✓ (ported from Squawk)
- prefer_bigint_over_smallint ✓ (ported from Squawk)
- prefer_identity ✓ (ported from Squawk)
- prefer_text_field ✓ (ported from Squawk)
- prefer_timestamptz ✓ (ported from Squawk)
- disallow_unique_constraint ✓ (ported from Squawk)
- renaming_column ✓ (ported from Squawk)
- renaming_table ✓ (ported from Squawk)
- require_concurrent_index_creation ✓ (ported from Squawk)
- require_concurrent_index_deletion ✓ (ported from Squawk)
- transaction_nesting ✓ (ported from Squawk)
- prefer_robust_stmts ✓ (ported from Squawk - simplified version)


22 changes: 17 additions & 5 deletions crates/pgt_analyse/src/analysed_file_context.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,19 @@
#[derive(Default)]
pub struct AnalysedFileContext {}
pub struct AnalysedFileContext<'a> {
pub all_stmts: &'a Vec<pgt_query::NodeEnum>,
pub stmt_count: usize,
pub previous_stmts: Vec<&'a pgt_query::NodeEnum>,
}

impl<'a> AnalysedFileContext<'a> {
pub fn new(stmts: &'a Vec<pgt_query::NodeEnum>) -> Self {
Self {
all_stmts: stmts,
stmt_count: stmts.len(),
previous_stmts: Vec::new(),
}
}

impl AnalysedFileContext {
#[allow(unused)]
pub fn update_from(&mut self, stmt_root: &pgt_query::NodeEnum) {}
pub fn update_from(&mut self, stmt_root: &'a pgt_query::NodeEnum) {
self.previous_stmts.push(stmt_root);
}
}
2 changes: 1 addition & 1 deletion crates/pgt_analyse/src/context.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ pub struct RuleContext<'a, R: Rule> {
stmt: &'a pgt_query::NodeEnum,
options: &'a R::Options,
schema_cache: Option<&'a SchemaCache>,
file_context: &'a AnalysedFileContext,
file_context: &'a AnalysedFileContext<'a>,
}

impl<'a, R> RuleContext<'a, R>
Expand Down
2 changes: 1 addition & 1 deletion crates/pgt_analyse/src/registry.rs
Original file line number Diff line number Diff line change
Expand Up @@ -159,7 +159,7 @@ impl RuleRegistry {
pub struct RegistryRuleParams<'a> {
pub root: &'a pgt_query::NodeEnum,
pub options: &'a AnalyserOptions,
pub analysed_file_context: &'a AnalysedFileContext,
pub analysed_file_context: &'a AnalysedFileContext<'a>,
pub schema_cache: Option<&'a pgt_schema_cache::SchemaCache>,
}

Expand Down
Loading
Loading