* test(#4590): add no-rendered-text-length-assert ESLint rule Enforces ADR-456's typed-surface mandate for one specific bug shape: a test assertion whose pass/fail depends on the length/substring content of a template literal that interpolates an OS-derived path (os.tmpdir(), os.homedir(), path.join/resolve/..., or a PATH_RETURNING_FNS resolver). Because macOS's default tmpdir prefix is longer than Linux's, such an assertion can pass on one runner and fail on another -- the defect class behind #4421's incident (git show 4e75b836e9), already fixed there by pinning to a typed field per ADR-456 Sec(c) before this rule existed to catch a recurrence. Two repo-wide sweeps against the real tests/ tree narrowed the rule to a sound scope: an initial design that traced call arguments (to approximate the historical incident's cross-file render-function shape) produced false positives on ordinary fs.readFileSync(path.join(...)) + assert.match patterns; a second design that matched any bare direct path-returning call produced 45 false positives on path suffix/prefix/non-emptiness checks. The shipped rule matches only a path-returning expression interpolated into a template literal, directly or via one identifier hop -- disclosed in the rule's own "Known boundaries" as not covering the literal cross-file incident shape, which would require tracing into a callee's body. Phase 1 of epic #4589 (CI test-matrix Linux-primary migration) -- Phase 2's safety argument depends on this class of OS-dependent test assertion being enforced going forward, not merely fixed once. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> * fix(#4590): address code-review findings on no-rendered-text-length-assert Reletter the "Known boundaries" doc-comment list (a)-(e), fixing a gap left by an earlier edit pass and every stale cross-reference to it. Collapse isDirectPathTaint/isTaintedInterpolation's duplicated TemplateLiteral-walk into one recursive relationship (isTaintedInterpolation now delegates a nested-template-literal case back to isDirectPathTaint instead of re-implementing the .some() traversal) -- behavior unchanged, confirmed by re-running the repo-wide sweep (still zero false positives). Found by the Standards-axis /code-review pass on this PR. 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:
@@ -39,6 +39,7 @@ import requireRegisteredExit from './eslint-rules/require-registered-exit.cjs';
|
||||
import noSwallowedPrecondition from './eslint-rules/no-swallowed-precondition.cjs';
|
||||
import noExactCaseEnvAccess from './eslint-rules/no-exact-case-env-access.cjs';
|
||||
import noAdhocTimeoutLiteral from './eslint-rules/no-adhoc-timeout-literal.cjs';
|
||||
import noRenderedTextLengthAssert from './eslint-rules/no-rendered-text-length-assert.cjs';
|
||||
|
||||
const adhocTimeoutLiteralAllowlist = require('./eslint-rules/no-adhoc-timeout-literal.allowlist.json');
|
||||
|
||||
@@ -72,6 +73,7 @@ const localPlugin = {
|
||||
'no-swallowed-precondition': noSwallowedPrecondition,
|
||||
'no-exact-case-env-access': noExactCaseEnvAccess,
|
||||
'no-adhoc-timeout-literal': noAdhocTimeoutLiteral,
|
||||
'no-rendered-text-length-assert': noRenderedTextLengthAssert,
|
||||
},
|
||||
};
|
||||
|
||||
@@ -717,6 +719,13 @@ export default tseslint.config(
|
||||
// Require a fixed-point termination guard on any dirname() ancestor walk —
|
||||
// a length/equality-only bound spins forever at a Windows drive root (#4020 / #4220).
|
||||
'local/no-unbounded-dirname-walk': 'error',
|
||||
// #4590 (epic #4589 Phase 1): ban length/substring assertions on a
|
||||
// template literal that interpolates an OS-derived path (tmpdir/homedir
|
||||
// length differs by OS — the #4421 incident shape). Does NOT flag a bare
|
||||
// path-returning call probed directly (e.g. `.endsWith('.md')`,
|
||||
// `.length > 0`) — only path-in-rendered-text embedding. See ADR-456
|
||||
// §(c) typed-surface mandate.
|
||||
'local/no-rendered-text-length-assert': 'error',
|
||||
// Ban unbounded sync child_process spawns in tests (DEFECT.UNBOUNDED-SUBPROCESS).
|
||||
// No allowlist: the epic (#3064) migrated every site; the rule runs with no
|
||||
// exemption surface. The only sanctioned escapes are an explicit `timeout` on
|
||||
|
||||
Reference in New Issue
Block a user