A developer site reached through concatenation is still a developer site
`throw new Error(`head ${n} tail` + detail)` puts a BinaryExpression between
the literal and the NewExpression, so the parent-only check saw the `+` and
not the throw, and reported a thrown message as a reader-visible leak.
Found while measuring the .js scan on legend-tree.js:392, and my first
report of it was wrong in a way worth recording: I told dx-shepherd the
developer-site exemption "is not firing on the .js path". It fires fine.
The gap is concatenation, and a .vue file concatenating inside a throw hits
it identically — the .js scan only surfaced a shape that is rarer in
templates. Three .vue findings cleared when this landed, which is the proof
it was never .js-specific: 92 -> 89.
Only `+` chains are walked. A string built with + is still the same argument
to the same call; any other expression type between the two is a different
construct and should be judged on its own site.
Two-sided: a thrown message built by concatenation goes quiet, and a
concatenated sentence that is NOT at a developer site still counts.
The .js scan itself is NOT in this commit. It needs the data-map exemption
first, or it hands two owners ~60 findings that are lookup keys they have
already classified.
(cherry picked from commit f96381481b)
This commit is contained in:
parent
404b2c866e
commit
fb2cad2efc
1 changed files with 35 additions and 1 deletions
|
|
@ -189,6 +189,26 @@ function isDomQuerySite(parent) {
|
|||
}
|
||||
|
||||
// A literal that is an argument to one of these never reaches a reader, whatever it says.
|
||||
// ★ CONCATENATION HIDES A DEVELOPER SITE FROM ITS OWN GUARD. `throw new Error(\`head\` + rest)`
|
||||
// puts a BinaryExpression between the literal and the NewExpression, so the parent check below sees
|
||||
// the `+` and not the `new Error`. Measured on legend-tree.js:392, a thrown message reported as a
|
||||
// leak. It is NOT a .js-path bug — a .vue file concatenating inside a throw hits it identically; the
|
||||
// .js scan just surfaced a shape that is rarer in templates.
|
||||
//
|
||||
// Only `+` chains are walked: a string built with + is still the same argument to the same call,
|
||||
// whereas any other expression type between the two is a different construct and should be judged
|
||||
// on its own site.
|
||||
function developerSiteAncestor(parent, stack) {
|
||||
if (isDeveloperSite(parent)) return true
|
||||
if (!parent || parent.type !== 'BinaryExpression' || parent.operator !== '+') return false
|
||||
for (let i = stack.length - 1; i >= 0; i--) {
|
||||
const n = stack[i]
|
||||
if (n.type === 'BinaryExpression' && n.operator === '+') continue
|
||||
return isDeveloperSite(n)
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
function isDeveloperSite(parent) {
|
||||
if (!parent) return false
|
||||
if (parent.type === 'CallExpression' && parent.callee?.object?.name === 'console') return true
|
||||
|
|
@ -513,7 +533,7 @@ function scanScript(code, file, out, lineBase = 0) {
|
|||
// ★ DEVELOPER STRINGS ARE EXEMPT BY CALL SITE, NOT BY CONTENT (upg100). `throw new Error('…')`
|
||||
// and console.* never reach a reader, and judging them by their text means inventing a rule for
|
||||
// every sentence a developer might write. The SITE is the fact; the wording is not.
|
||||
if (isDeveloperSite(parent) || isDomQuerySite(parent)) return
|
||||
if (developerSiteAncestor(parent, stack) || isDomQuerySite(parent)) return
|
||||
if (parent && /Import|Export/.test(parent.type)) return
|
||||
if (parent && parent.type === 'TaggedTemplateExpression') return
|
||||
if (guardedFallback(stack, names)) return
|
||||
|
|
@ -944,6 +964,20 @@ const a = greeting + 'Welcome to the prayer map'
|
|||
'an HTML attribute seam on dir/lang is allowed')
|
||||
ok(!allowedBy('Direction: left to right'),
|
||||
'prose ABOUT direction is NOT — the shape needs an attribute name followed by =')
|
||||
// ★ A DEVELOPER SITE REACHED THROUGH CONCATENATION. `throw new Error(\`head\` + rest)` puts a
|
||||
// BinaryExpression between the literal and the NewExpression, so the parent-only check missed it
|
||||
// and reported a thrown message as a leak (legend-tree.js:392). Not a .js-path bug — a .vue file
|
||||
// concatenating inside a throw hits it identically.
|
||||
const DEV = `<template><p>{{ x }}</p></template>
|
||||
<script setup>
|
||||
throw new Error(\`legend-tree L15: \${n} leaf nodes violate the shape\` + detail)
|
||||
const shown = 'A sentence a reader actually sees' + suffix
|
||||
</script>`
|
||||
const dev = scanSfc(DEV, 'dev.vue').map((f) => f.text)
|
||||
ok(!dev.some((t) => /leaf nodes violate/.test(t)),
|
||||
'QUIET on a thrown developer message built by CONCATENATION')
|
||||
ok(dev.some((t) => /A sentence a reader actually sees/.test(t)),
|
||||
'COUNTS a concatenated sentence that is NOT at a developer site')
|
||||
// the catalogue-side rule, tested directly: the two facts that make a fallback safe
|
||||
ok(fallbackFullyResolved('dashboard.map_loading'),
|
||||
'a key present and really translated in all 11 locales counts as resolved')
|
||||
|
|
|
|||
Loading…
Reference in a new issue