mirror of
https://github.com/pbakaus/impeccable.git
synced 2026-09-15 23:56:29 +03:00
Upstream sha fa44839f72.
Advisory handling. `severity` becomes the canonical registry field: the
`advisory` bool leaves `Antipattern`, `advisory_rule_ids` filters on
`severity == "advisory"`, and `derive_advisory_flag` stamps the finding's
`advisory: true` from the effective severity, so a per-finding promotion or
demotion carries the flag. The html and browser engines call it after their
severity override; the detect CLI and the hook accept either spelling; the
driver's serializer and the wasm registry exports derive it the same way.
em-dash-overuse moves from `advisory: true` to `severity: "advisory"`.
URL scans. `expand_joined_url_targets` splits an argv value that is entirely
whitespace-separated URLs and leaves paths with spaces alone. The browser
driver reads the readable linked-stylesheet corpus into the HTML pattern
corpora and resolves a finding's selector with `selector_nodes_for_live_dom`
/ `pseudo_element_host_selector`, so an unresolvable selector drops the
finding instead of keeping it page-level. The CSSOM walk itself is page JS:
`browser-bundle/15-snapshot.js` gains `__snapLinkedStylesheetText` (grouping
rules flattened, container-query probes, effective keyframes) and puts it in
the snapshot as `linkedCss`; `10-probe.js` exposes the same for the in-page
route, and the Dom trait carries `linked_stylesheet_text`.
Also `enclosing_css_selector` blanks comments before hunting the previous
declaration delimiter, and `check_typography` reports the uniquely most-used
family instead of every family over a 15% share.
Verified: `impeccable detect --no-config --json tests/fixtures/antipatterns`
is now byte-identical to `node cli/bin/cli.js` on an origin/main worktree
over the shared corpus (432 findings). The two changed lines in
tests/oracle/vectors/calls/rules.checks/checkHtmlPatterns.jsonl were
re-recorded by running origin/main's `checkHtmlPatterns` over the frozen
args; only the comment-polluted selector changed. Goldens re-recorded for
the advisory partition (config-*, fixture gemini/gpt-tells,
numbered-section-labels, scoped-ignore, shape-assembled-illustration,
color, em-dash-entities) and the help text, each cross-checked against the
JS on origin/main.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Vau2X53xGTjjTCXWMVBoNY
127 lines
3.9 KiB
Rust
127 lines
3.9 KiB
Rust
//! The text engine's half of the rule-pack extension point: the pack's
|
|
//! `check_text` hook runs on every scanned source file after the built-in
|
|
//! matchers, its findings are waivable with `impeccable-disable`, and a run
|
|
//! with no pack is identical to the built-in output.
|
|
|
|
use impeccable_core::findings::{finding_for, Finding};
|
|
use impeccable_core::registry::Antipattern;
|
|
use impeccable_core::rule_pack::RulePack;
|
|
use impeccable_detect::detect_text::{detect_text, TextOptions};
|
|
|
|
const MARKER: &str = "TODO(pack)";
|
|
|
|
static ROWS: &[Antipattern] = &[Antipattern {
|
|
id: "testpack/todo-marker",
|
|
category: "quality",
|
|
scopes: None,
|
|
severity: Some("warning"),
|
|
name: "Unfinished copy marker",
|
|
description: "Text still carries a TODO marker from drafting.",
|
|
skill_section: None,
|
|
skill_guideline: None,
|
|
}];
|
|
|
|
#[derive(Debug)]
|
|
struct TestPack;
|
|
static PACK: TestPack = TestPack;
|
|
|
|
impl RulePack for TestPack {
|
|
fn registry(&self) -> &'static [Antipattern] {
|
|
ROWS
|
|
}
|
|
fn check_text(&self, content: &str, file_path: &str, ext: &str) -> Vec<Finding> {
|
|
// `ext` proves the hook is told what kind of file this is.
|
|
assert!(ext.is_empty() || ext.starts_with('.'), "ext = {ext:?}");
|
|
content
|
|
.split('\n')
|
|
.enumerate()
|
|
.filter(|(_, line)| line.contains(MARKER))
|
|
.map(|(i, line)| finding_for(&ROWS[0], file_path, line.trim(), (i + 1) as f64))
|
|
.collect()
|
|
}
|
|
}
|
|
|
|
/// A file with one built-in finding (the CSS-in-JS side stripe on line 2) and
|
|
/// one line for the pack.
|
|
const SOURCE: &str = "const Card = styled.div`\n border-left: 4px solid red;\n`;\nexport const copy = \"TODO(pack) write the real headline\";\n";
|
|
|
|
fn scan(rule_pack: Option<&'static dyn RulePack>) -> Vec<Finding> {
|
|
detect_text(
|
|
SOURCE,
|
|
"/app/Card.tsx",
|
|
&TextOptions {
|
|
inline_ignores: true,
|
|
rule_pack,
|
|
..Default::default()
|
|
},
|
|
)
|
|
}
|
|
|
|
#[test]
|
|
fn text_hook_fires_and_no_pack_is_unchanged() {
|
|
let built_in = scan(None);
|
|
assert!(
|
|
!built_in.is_empty(),
|
|
"the fixture must trip a built-in rule"
|
|
);
|
|
assert!(built_in
|
|
.iter()
|
|
.all(|f| !f.antipattern.starts_with("testpack/")));
|
|
|
|
impeccable_core::rule_pack::install(&PACK);
|
|
let with_pack = scan(Some(&PACK));
|
|
|
|
// Built-in findings come first, unchanged, in the same order.
|
|
assert_eq!(&with_pack[..built_in.len()], &built_in[..]);
|
|
|
|
let extra = &with_pack[built_in.len()..];
|
|
assert_eq!(extra.len(), 1);
|
|
assert_eq!(extra[0].antipattern, "testpack/todo-marker");
|
|
assert_eq!(extra[0].name, "Unfinished copy marker");
|
|
assert_eq!(extra[0].severity, "warning");
|
|
assert_eq!(extra[0].category.as_deref(), Some("quality"));
|
|
assert_eq!(extra[0].file, "/app/Card.tsx");
|
|
assert_eq!(extra[0].line, 4.0);
|
|
assert_eq!(
|
|
extra[0].snippet,
|
|
"export const copy = \"TODO(pack) write the real headline\";"
|
|
);
|
|
|
|
// The pack's rows serialize like built-in rows.
|
|
let json = serde_json::to_string(&extra[0]).unwrap();
|
|
assert!(
|
|
json.starts_with("{\"antipattern\":\"testpack/todo-marker\""),
|
|
"{json}"
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn pack_findings_are_waivable_inline() {
|
|
impeccable_core::rule_pack::install(&PACK);
|
|
let source = format!(
|
|
"// impeccable-disable-next-line testpack/todo-marker\nconst copy = \"{MARKER} later\";\n"
|
|
);
|
|
let waived = detect_text(
|
|
&source,
|
|
"/app/copy.ts",
|
|
&TextOptions {
|
|
inline_ignores: true,
|
|
rule_pack: Some(&PACK),
|
|
..Default::default()
|
|
},
|
|
);
|
|
assert!(waived.is_empty(), "{waived:?}");
|
|
|
|
let unwaived = detect_text(
|
|
&source,
|
|
"/app/copy.ts",
|
|
&TextOptions {
|
|
inline_ignores: false,
|
|
rule_pack: Some(&PACK),
|
|
..Default::default()
|
|
},
|
|
);
|
|
assert_eq!(unwaived.len(), 1);
|
|
assert_eq!(unwaived[0].antipattern, "testpack/todo-marker");
|
|
}
|