* feat(#3545): widen no-source-grep with one-hop path-fold and hooks dir Resolve a readFileSync() path argument that is a bare Identifier one hop back to its VariableDeclarator initializer before classification, and recognize `hooks` as a source directory alongside bin/lib/gsd-core/src. Measured (epic #3464 phase 7): fold+hooks together newly flag 76 unsuppressed sites across 18 files that were previously invisible to identifier-indirected or hooks/-rooted source reads. Neither widening alone is sufficient — hooks-only surfaces 0 new sites, confirming #3520's prior finding that the identifier-indirection gap must close first. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> * test(#3545): migrate 76 sites newly flagged by the fold+hooks widening Per-site classification: rewrite behaviorally (require() the real module, assert on its actual exported behavior) wherever the read was a proxy for code behavior; add a site-scoped `// allow-test-rule: <reason> (#3545)` marker only where the raw source text genuinely is the product under test (codex-config.test.cjs's adapter-header-contract checks, install.js structural-wiring guards with no exported symbol, AST-parse fixture inputs, etc.) — each marker cites an existing repo-sanctioned category from CONTRIBUTING.md's allow-test-rule exception table. Also converts two try/finally test bodies (introduced during this same migration) to the required t.after() cleanup pattern per CONTRIBUTING.md. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> * chore(#3545): re-baseline effective-exemption ceiling to 81 The fold+hooks widening's own newly-detected sites are now suppressed by site-scoped markers, moving them from invisible into the tightly-ratcheted effective-exemption count. Ceiling rises from 10 to 81 (the exact measured high-water mark, grace unchanged at 2) — a deliberate, measured re-baseline per the widening working as intended, not an ordinary ceiling bump. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> * fix(#3545): use canonical allow-test-rule category tokens 4 markers added during migration cited an issue ref correctly but didn't use one of CONTRIBUTING.md's seven recognized category tokens, unlike every other marker in this change. Cosmetic only — same suppression lines, same effective/live counts (81/81, 0 live). Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> * fix(#3545): correct stale phase-artifact path in test comment Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> --------- Co-authored-by: sim <sim@local> Co-authored-by: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
@@ -21,6 +21,12 @@
|
||||
* call that originated the tracked value -- so annotating the read directly
|
||||
* (the intuitive placement) suppresses the violation just as well as
|
||||
* annotating the search call (adversarial-review fix, epic #3464 phase 4).
|
||||
*
|
||||
* A `readFileSync(...)` path argument that is a bare Identifier is resolved
|
||||
* ONE hop back to its `VariableDeclarator` initializer before classification,
|
||||
* so a path built once and passed by reference is recognized the same as an
|
||||
* inline path expression; `hooks` is also a recognized source directory
|
||||
* alongside `bin`/`lib`/`gsd-core`/`src` (#3545 / epic #3464 phase 7).
|
||||
*/
|
||||
|
||||
// How many derivation hops from the original readFileSync() binding to
|
||||
@@ -326,9 +332,27 @@ const rule = {
|
||||
return identifierVariableMap.get(identifierNode) || null;
|
||||
}
|
||||
|
||||
// WIDENING (fold, #3545/epic #3464 phase 7): resolve a bare Identifier
|
||||
// path argument back to its variable initializer, ONE hop, before
|
||||
// text-matching it -- so `const p = path.join(__dirname, '..', 'src',
|
||||
// 'x.cjs'); readFileSync(p)` is classified the same as an inline
|
||||
// `readFileSync(path.join(__dirname, '..', 'src', 'x.cjs'))`. Only
|
||||
// resolves a `VariableDeclarator`'s `init` (not an `AssignmentExpression`
|
||||
// — an assignment-bound identifier is a documented, deliberate miss, see
|
||||
// 40-design.md "Known limits"), and only ONE hop (a chain of two or more
|
||||
// indirections stays invisible -- also documented).
|
||||
function resolveOneHopText(node) {
|
||||
if (node.type !== 'Identifier') return sourceCode.getText(node);
|
||||
const v = resolveVariable(node);
|
||||
if (!v) return sourceCode.getText(node);
|
||||
const def = v.defs.find((d) => d.type === 'Variable' && d.node && d.node.init);
|
||||
if (!def) return sourceCode.getText(node);
|
||||
return sourceCode.getText(def.node.init);
|
||||
}
|
||||
|
||||
// Detect if a node represents a readFileSync call on a source file
|
||||
// (.cjs/.cts/.js/.mjs/.mts/.ts) that lives in a source directory
|
||||
// (bin, lib, gsd-core, src).
|
||||
// (bin, lib, gsd-core, hooks, src).
|
||||
function isSourceReadFileSync(node) {
|
||||
if (!node || node.type !== 'CallExpression') return false;
|
||||
|
||||
@@ -345,7 +369,7 @@ const rule = {
|
||||
if (!args || args.length === 0) return false;
|
||||
|
||||
const firstArg = args[0];
|
||||
const fullSrc = sourceCode.getText(firstArg);
|
||||
const fullSrc = resolveOneHopText(firstArg);
|
||||
|
||||
return looksLikeSourcePath(fullSrc);
|
||||
}
|
||||
@@ -359,8 +383,9 @@ const rule = {
|
||||
const hasSourceExt = /['"`.][^'"`.]*\.(?:cts|mts|mjs|cjs|js|ts)['"`)]/i.test(src);
|
||||
if (!hasSourceExt) return false;
|
||||
|
||||
// Must reference a source directory indicator somewhere in the expression
|
||||
const hasSourceDir = /['"](?:bin|lib|gsd-core|src)['"]/i.test(src);
|
||||
// Must reference a source directory indicator somewhere in the
|
||||
// expression (bin, lib, gsd-core, hooks, src).
|
||||
const hasSourceDir = /['"](?:bin|lib|gsd-core|hooks|src)['"]/i.test(src);
|
||||
return hasSourceDir;
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user