Add facts and fact_filter to Piranha rules - #780
Conversation
Introduces two new features:
**Facts**: Rules can record structured String->String metadata about matched
nodes without modifying code. Facts are associated with the matched node's range,
stored per-file in SourceCodeUnit, and included in PiranhaOutputSummary output.
When a rewrite overlaps a fact's range the fact is marked voided; rewrites that
precede a fact's range shift its byte offsets.
**Fact filter**: Rule filters gain a fact_filter field. A filter with a non-empty
fact_filter only passes when at least one non-voided recorded fact in the file
contains all the specified key-value pairs. This lets rewrite rules be gated on
information recorded by earlier fact rules.
TOML usage:
[[rules]]
name = "record_api"
query = "..."
replace_node = "call"
fact = { type = "deprecated", method = "@name" }
[[rules.filters]]
fact_filter = { type = "deprecated" }
Also adds 11 unit tests and 2 integration tests covering: basic fact recording,
@tag substitution, multi-match ordering, voiding by rewrite, byte-offset shifting,
two facts on the same range, fact_filter blocking/allowing rewrites, and
voided-fact semantics end-to-end.
Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>
614ceae to
5947b61
Compare
|
|
||
| /// Checks if a rule is a `fact` rule i.e. it records facts without rewriting code | ||
| pub(crate) fn is_fact_rule(&self) -> bool { | ||
| !self.fact().is_empty() |
There was a problem hiding this comment.
if a user sets both fact and replace in TOML, this check fires first and the replace is silently ignored. should validate() reject rules that have both?
There was a problem hiding this comment.
I think rules with both is weird. Do u mean, we want toads facts to the rewritten stuff ? In that case we can always have a self edge to add the fact
There was a problem hiding this comment.
Im just thinking whether we should panic when we read a rule that;s supposed to generate facts, but then sets rewrite?
|
|
||
| /// Shift byte offsets by `delta` after a preceding edit. | ||
| /// Point (row/col) offsets are not adjusted since we don't have source code available here. | ||
| pub(crate) fn shift_range(&mut self, delta: isize) { |
There was a problem hiding this comment.
this shifts byte offsets but leaves start_point / end_point stale. since Range exposes both, consumers could read wrong row/col from shifted facts, right?
- validate(): reject rules that set both `fact` and `replace` — they are mutually exclusive. Previously the fact branch silently won and `replace` was ignored; now a clear error is surfaced at rule construction time. - shift_range(): was only updating start_byte/end_byte but left start_point and end_point stale, so consumers reading row/col from a shifted fact would get incorrect values. Now propagates row/col deltas from the InputEdit: facts on a later row shift by row_delta; facts on the same row as the edit end also shift their column. Adds two new tests: - test_validate_rejects_fact_and_replace_together - test_fact_shift_updates_row_col_points Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>
danieltrt
left a comment
There was a problem hiding this comment.
LGTM overall, cargo fmt is failing
Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>
The hook ran `cargo clippy --fix --allow-staged` which refused to apply fixes whenever any file had unstaged changes. Adding `--allow-dirty` lets it proceed regardless of unstaged changes in other files. Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>
Introduces two new features:
Facts: Rules can record structured String->String metadata about matched nodes without modifying code. Facts are associated with the matched node's range, stored per-file in SourceCodeUnit, and included in PiranhaOutputSummary output. When a rewrite overlaps a fact's range the fact is marked voided; rewrites that precede a fact's range shift its byte offsets.
Fact filter: Rule filters gain a fact_filter field. A filter with a non-empty fact_filter only passes when at least one non-voided recorded fact in the file contains all the specified key-value pairs. This lets rewrite rules be gated on information recorded by earlier fact rules.
TOML usage:
[[rules]]
name = "record_api"
query = "..."
replace_node = "call"
fact = { type = "deprecated", method = "@name" }
[[rules.filters]]
fact_filter = { type = "deprecated" }
Also adds 11 unit tests and 2 integration tests covering: basic fact recording, @tag substitution, multi-match ordering, voiding by rewrite, byte-offset shifting, two facts on the same range, fact_filter blocking/allowing rewrites, and voided-fact semantics end-to-end.