Compare every silently-failing side effect, not just strategy keys
Some checks are pending
build-gate / build (push) Waiting to run
Some checks are pending
build-gate / build (push) Waiting to run
The gate caught one of the four things a baked composite must repeat. The
other three were left to luck, and one of them had already gone wrong: the
composite shipped mapbox-gl-rtl-text zero times while both leaf bundles
shipped it once, so Arabic, Hebrew and Persian labels rendered reversed in the
cap-5 shell. Arab World has 99 rows and Persian-Median 49, which makes that a
wrong name rather than a style issue.
Now compares createPinia, createAppI18n and installRtlTextPlugin alongside the
registerStrategies keys: whatever the leaf entry calls, the composite that
bakes its .vue must call too.
provide('profileModules') is deliberately excluded. It is the fifth side
effect and the only one that throws when missing, and it is also the only one
safe to omit, because a baked composite does not use ProfileLoader. Requiring
it would fail a composite that is correct. The rule is "repeat what silently
disappears", not "repeat everything".
Matched as calls -- name followed by "(" with the import line for that name
stripped first -- because an indexOf on the bare name reads the import instead
of the invocation, which is the trap that made the strategy parser return
nothing and report a confident empty result.
Negative-tested twice, and the first case reproduces the real historical bug:
removing installRtlTextPlugin() fails by name and prints both sides' lists;
removing createPinia() does the same. Pairs compared went 1 to 2.
This commit is contained in:
parent
e94253ed58
commit
9cae7fe16a
1 changed files with 70 additions and 9 deletions
|
|
@ -118,6 +118,43 @@ const bundles = readdirSync(DESIGNS, { withFileTypes: true })
|
|||
.filter((e) => e.isDirectory() && !e.name.startsWith('.'))
|
||||
.map((e) => e.name)
|
||||
|
||||
/**
|
||||
* ★ THE SIDE EFFECTS A BAKED COMPOSITE MUST REPEAT, beyond the strategy keys.
|
||||
*
|
||||
* An entry file does five things. FOUR OF THEM FAIL SILENTLY if a composite forgets them, which is
|
||||
* why counting on "it renders" is no evidence the entry's work was done:
|
||||
*
|
||||
* createPinia() silent — state simply is not shared
|
||||
* createAppI18n() silent — an empty SyntaxError, and the subtree vanishes
|
||||
* registerStrategies() silent — getColorStrategy() FALLS BACK instead of throwing, so the
|
||||
* charts paint grey and read as a data problem
|
||||
* installRtlTextPlugin() silent — Arabic/Hebrew/Persian labels render REVERSED. Measured: the
|
||||
* composite shipped mapbox-gl-rtl-text 0 times while both leaf
|
||||
* bundles shipped it once. Arab World (99 rows) and
|
||||
* Persian-Median (49) are in scope, so that is a WRONG NAME.
|
||||
* provide('profileModules') LOUD — an explicit throw, and the ONLY one safe to omit, because a
|
||||
* baked composite does not use ProfileLoader.vue at all.
|
||||
*
|
||||
* So `provide` is deliberately NOT in this list: requiring it would fail a composite that is
|
||||
* correct. The rule is "repeat what silently disappears", not "repeat everything".
|
||||
*
|
||||
* Matched as CALLS (name followed by `(`) so an import line does not count as a use — the same trap
|
||||
* that made the strategy parser read the import instead of the invocation.
|
||||
*/
|
||||
const SILENT_SIDE_EFFECTS = ['createPinia', 'createAppI18n', 'installRtlTextPlugin']
|
||||
|
||||
function calledEffects(file) {
|
||||
if (!existsSync(file)) return null
|
||||
const src = readFileSync(file, 'utf8')
|
||||
const out = new Set()
|
||||
for (const name of SILENT_SIDE_EFFECTS) {
|
||||
// Strip the import line for THIS name, then look for a real call.
|
||||
const withoutImport = src.replace(new RegExp(`^import[^\\n]*\\b${name}\\b[^\\n]*$`, 'gm'), '')
|
||||
if (new RegExp(`\\b${name}\\s*\\(`).test(withoutImport)) out.add(name)
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
const errs = []
|
||||
let comparisons = 0
|
||||
|
||||
|
|
@ -137,17 +174,41 @@ for (const b of bundles) {
|
|||
const mine = strategyKeys(join(dir, 'index.js'))
|
||||
if (mine === null) continue // no entry file of its own; nothing to compare
|
||||
|
||||
const myEffects = calledEffects(join(dir, 'index.js')) || new Set()
|
||||
|
||||
for (const s of siblings) {
|
||||
const theirs = strategyKeys(join(DESIGNS, s, 'index.js'))
|
||||
if (theirs === null || !theirs.size) continue // sibling registers nothing -> nothing to inherit
|
||||
comparisons++
|
||||
const missing = [...theirs].filter((k) => !mine.has(k))
|
||||
if (missing.length) {
|
||||
errs.push(
|
||||
`${b} bakes ${s}/${s}.vue but does not register: ${missing.join(', ')}\n` +
|
||||
` ${s}/index.js registers [${[...theirs].join(', ')}]\n` +
|
||||
` ${b}/index.js registers [${[...mine].join(', ')} ]`
|
||||
)
|
||||
const theirEffects = calledEffects(join(DESIGNS, s, 'index.js')) || new Set()
|
||||
if (theirs === null) continue // sibling has no entry file; nothing to inherit
|
||||
|
||||
// 1 — strategy keys
|
||||
if (theirs.size) {
|
||||
comparisons++
|
||||
const missing = [...theirs].filter((k) => !mine.has(k))
|
||||
if (missing.length) {
|
||||
errs.push(
|
||||
`${b} bakes ${s}/${s}.vue but does not register: ${missing.join(', ')}\n` +
|
||||
` ${s}/index.js registers [${[...theirs].join(', ')}]\n` +
|
||||
` ${b}/index.js registers [${[...mine].join(', ')} ]`
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
// 2 — the silent side effects. Same rule, wider surface: whatever the LEAF entry calls and
|
||||
// whose absence would not announce itself, the composite must call too.
|
||||
if (theirEffects.size) {
|
||||
comparisons++
|
||||
const missingFx = [...theirEffects].filter((k) => !myEffects.has(k))
|
||||
if (missingFx.length) {
|
||||
errs.push(
|
||||
`${b} bakes ${s}/${s}.vue but never calls: ${missingFx.map((f) => f + '()').join(', ')}\n` +
|
||||
` ${s}/index.js calls [${[...theirEffects].join(', ')}]\n` +
|
||||
` ${b}/index.js calls [${[...myEffects].join(', ')} ]\n` +
|
||||
' Each of these fails SILENTLY when missing — no error, no warning, just a wrong\n' +
|
||||
' picture. installRtlTextPlugin was absent exactly this way and reversed every\n' +
|
||||
' Arabic, Hebrew and Persian label in the cap-5 shell.'
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in a new issue