* feat(#3464): widen no-source-grep to detect regex.exec() on tracked text Adds an execCall kind alongside the existing regexTest detection -- regex.exec(tracked) was invisible to the rule while regex.test(tracked) was already caught, despite both reading a source-derived string through a regex. Measured: 4 previously-invisible sites across 2 files. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> * test(#3464): migrate 4 sites newly flagged by the exec() widening docs-hooks-table-parity.test.cjs's three regex-extraction loops are site-scoped marked (source-text-is-the-product) -- the dynamic preToolEvent/postToolEvent dialect branching they mirror is explicitly documented as not statically parseable, so a literal-pattern mirror is the practical minimum-cost check. no-bare-gsd-tools-command-position.test.cjs's readRouterVerbs() now requires HOST_COMMAND_ROUTERS directly instead of regex-walking gsd-tools.cjs's source text -- the same accessor three other suites already use. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> * fix(#3464): pay down 6 grandfathered uncited allow-test-rule markers Two were genuinely load-bearing (suppressing a real detected violation) and just needed a citation added -- phase6-capstone-conformance.test.cjs, runtime-name-policy.test.cjs, both now (#3464). Four were dead-weight file-header markers suppressing nothing -- each file's real effective sites are covered by separate, already-cited markers elsewhere in the same file. Deleted outright rather than cited, per Phase 1's own precedent (remove non-load-bearing markers instead of grandfathering them forever) -- codex-config.test.cjs (two copies), gsd-check-update-worker-platform-gate.test.cjs, orphaned-hooks.test.cjs, settings-jsonc.test.cjs. allowlist.json: 134 -> 128 entries. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> * chore(#3464): re-baseline effective-exemption ceiling to 84 The exec() widening's 3 newly-marked sites are now suppressed and counted; ceiling rises 81 -> 84, the exact measured high-water mark. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> * fix(#3464): correct citation and restore a wrongly-deleted marker Two review corrections, both found by the orthogonal review pass: - docs-hooks-table-parity.test.cjs's 3 new exec() markers cited #3464 (mechanically "the phase that widened the rule") when the file's own established, correct reference is #3839 (the issue this whole test exists to enforce, already cited in its file header) -- fixed to match. - gsd-check-update-worker-platform-gate.test.cjs's deleted file-header marker was NOT dead weight: its codeOnly() helper wraps readFileSync and is called inline as an assert argument, a genuine source-grep pattern on real .cjs/.js source that the rule cannot currently see (helper-function indirection is a distinct blind spot from anything Phase 7/8 measured) -- CONTRIBUTING.md is explicit that "unverified" is not the same as "vestigial." Restored, site-scoped this time (directly above codeOnly(), not as an inert file-header comment) and cited (#3103, the issue the file's own docstring already references). codex-config.test.cjs's two deletions and orphaned-hooks.test.cjs's / settings-jsonc.test.cjs's deletions were independently re-verified and stand: their flagged lines read generated .toml/.json OUTPUT, not source, or have no residual pattern at all. 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:
@@ -6,7 +6,8 @@
|
||||
* Flags variables bound to readFileSync() of a .cjs/.cts/.js/.mjs/.mts/.ts
|
||||
* source path that later have a text-search method called on them, whether
|
||||
* directly, via a bounded chain of derived bindings (`const b = f(a)`,
|
||||
* `b = a`, ...), or via `regex.test(tracked)` / `/lit/.test(tracked)`.
|
||||
* `b = a`, ...), or via `regex.test(tracked)` / `/lit/.test(tracked)` /
|
||||
* `regex.exec(tracked)` / `/lit/.exec(tracked)` (#3464 phase 8).
|
||||
*
|
||||
* Variable identity is resolved through real lexical scope (ESLint
|
||||
* `Variable` objects via `sourceCode.scopeManager`/`getScope`), not by name
|
||||
@@ -235,7 +236,7 @@ const rule = {
|
||||
],
|
||||
messages: {
|
||||
noSourceGrep:
|
||||
'Source-grep test: do not read source .cjs/.cts/.js/.mjs/.mts/.ts files with readFileSync and call .includes/.match/.matchAll/.startsWith/.indexOf/.split/.replace/.search (or regex.test()) on the result. Use require() to run the module instead. Add // allow-test-rule: <reason> (#NNN) directly above (or trailing) the flagged line to suppress just that site.',
|
||||
'Source-grep test: do not read source .cjs/.cts/.js/.mjs/.mts/.ts files with readFileSync and call .includes/.match/.matchAll/.startsWith/.indexOf/.split/.replace/.search (or regex.test() / regex.exec()) on the result. Use require() to run the module instead. Add // allow-test-rule: <reason> (#NNN) directly above (or trailing) the flagged line to suppress just that site.',
|
||||
// Diagnostic-only companion to `noSourceGrep`, emitted ONLY when the
|
||||
// `neutralizeSuppression` schema option is set (see its doc comment
|
||||
// and `reportUnlessSuppressed` above) -- never fires with the real
|
||||
@@ -600,6 +601,8 @@ const rule = {
|
||||
pendingCalls.push({ node, kind: 'textMethod' });
|
||||
} else if (propName === 'test') {
|
||||
pendingCalls.push({ node, kind: 'regexTest' });
|
||||
} else if (propName === 'exec') {
|
||||
pendingCalls.push({ node, kind: 'execCall' });
|
||||
}
|
||||
},
|
||||
'Program:exit'() {
|
||||
@@ -711,8 +714,13 @@ const rule = {
|
||||
continue;
|
||||
}
|
||||
|
||||
// kind === 'regexTest': re.test(tracked) or /lit/.test(tracked).
|
||||
// The tracked variable is the ARGUMENT here, not the callee object.
|
||||
// kind === 'regexTest' / 'execCall': re.test(tracked) or
|
||||
// /lit/.test(tracked), and identically re.exec(tracked) or
|
||||
// /lit/.exec(tracked) (#3464 phase 8) -- both return a
|
||||
// regex-shaped result, but what matters here is only that the
|
||||
// ARGUMENT (not the callee object) may carry the tracked source
|
||||
// text, so the receiver/argument classification is shared
|
||||
// byte-for-byte between the two kinds.
|
||||
const looksLikeRegexReceiver =
|
||||
obj.type === 'Identifier' || (obj.type === 'Literal' && !!obj.regex);
|
||||
if (!looksLikeRegexReceiver) continue;
|
||||
|
||||
Reference in New Issue
Block a user