@ttt-productions/repo-guards 0.1.11 → 0.1.12
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import type { GenericGuardDefinition } from '../../node/suite/config.js';
|
|
2
2
|
import type { Reviewed } from '../../node/layout.js';
|
|
3
3
|
import type { FileView } from '../../reader/view.js';
|
|
4
|
+
import type { SharedFakeFacts } from '../repo/shared-fakes.js';
|
|
4
5
|
import { type AllowlistEntry, type Hit } from '../../shapes/shapes.js';
|
|
5
6
|
export interface AppConfigReaderFacts {
|
|
6
7
|
/** The one client module that reads the app config document. */
|
|
@@ -61,6 +62,11 @@ export interface PartialMockFacts {
|
|
|
61
62
|
module: string;
|
|
62
63
|
why: string;
|
|
63
64
|
}>;
|
|
65
|
+
/**
|
|
66
|
+
* The app's shared fakes: a mock whose factory loads a listed root's shared fake keeps the root's
|
|
67
|
+
* real exports when that fake does.
|
|
68
|
+
*/
|
|
69
|
+
sharedFakes?: Pick<SharedFakeFacts, 'importAliases' | 'fakes'>;
|
|
64
70
|
}
|
|
65
71
|
/** Each `vi.mock` / `vi.doMock` call on exactly `module`, as its balanced source slice. */
|
|
66
72
|
export declare function moduleMockCalls(code: string, module: string): Array<{
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"client.d.ts","sourceRoot":"","sources":["../../../src/modules/frontend-actions/client.ts"],"names":[],"mappings":"AAKA,OAAO,KAAK,EAAE,sBAAsB,EAAE,MAAM,4BAA4B,CAAC;AAEzE,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,sBAAsB,CAAC;AAErD,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,sBAAsB,CAAC;
|
|
1
|
+
{"version":3,"file":"client.d.ts","sourceRoot":"","sources":["../../../src/modules/frontend-actions/client.ts"],"names":[],"mappings":"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"}
|
|
@@ -7,6 +7,7 @@ import { allowlistProblems, namesProblem, reviewedProblems, textProblem, } from
|
|
|
7
7
|
import { fromStatements, namesModule } from '../../support/imports.js';
|
|
8
8
|
import { escapeRegExp, isUnder } from '../../support/text.js';
|
|
9
9
|
import { isPlainString } from '../../support/ast.js';
|
|
10
|
+
import { aliasList, resolveSpecifier } from '../../support/resolve-specifier.js';
|
|
10
11
|
const APP_CODE = { roots: ['app'], kinds: ['code'], tests: false };
|
|
11
12
|
/** A dotted code spelling (`A.b`) as a pattern that allows whitespace around each dot. */
|
|
12
13
|
const dottedPattern = (spelling) => spelling
|
|
@@ -399,6 +400,52 @@ export function spreadsImportOriginal(call) {
|
|
|
399
400
|
const binding = /const\s+([A-Za-z_$][\w$]*)\s*=\s*await\s+importOriginal\b/.exec(call);
|
|
400
401
|
return binding !== null && new RegExp(`\\.\\.\\.\\s*${escapeRegExp(binding[1])}\\b`).test(call);
|
|
401
402
|
}
|
|
403
|
+
/** The specifiers a mock call's factory loads with `import()`. */
|
|
404
|
+
function factoryLoads(call) {
|
|
405
|
+
const loads = [];
|
|
406
|
+
const load = /\bimport\(\s*(['"`])([^'"`]+)\1\s*\)/g;
|
|
407
|
+
for (let m = load.exec(call); m; m = load.exec(call))
|
|
408
|
+
loads.push(m[2]);
|
|
409
|
+
return loads;
|
|
410
|
+
}
|
|
411
|
+
/** The source of a function `name` declares, as a declaration or a variable holding a function. */
|
|
412
|
+
function functionText(file, name) {
|
|
413
|
+
for (const statement of file.sourceFile.statements) {
|
|
414
|
+
if (ts.isFunctionDeclaration(statement) && statement.name?.text === name)
|
|
415
|
+
return statement.getText(file.sourceFile);
|
|
416
|
+
if (ts.isVariableStatement(statement))
|
|
417
|
+
for (const d of statement.declarationList.declarations)
|
|
418
|
+
if (ts.isIdentifier(d.name) && d.name.text === name && d.initializer)
|
|
419
|
+
return d.initializer.getText(file.sourceFile);
|
|
420
|
+
}
|
|
421
|
+
return null;
|
|
422
|
+
}
|
|
423
|
+
/**
|
|
424
|
+
* True when a shared fake keeps its module's real exports: it spreads `importOriginal` itself, or it
|
|
425
|
+
* calls a helper from a local file whose own body does.
|
|
426
|
+
*/
|
|
427
|
+
function ownerKeepsRealExports(context, owner, aliases) {
|
|
428
|
+
if (!context.reader.exists(owner))
|
|
429
|
+
return false;
|
|
430
|
+
const file = context.file(owner);
|
|
431
|
+
if (spreadsImportOriginal(file.code))
|
|
432
|
+
return true;
|
|
433
|
+
for (const site of file.imports) {
|
|
434
|
+
if (site.typeOnly)
|
|
435
|
+
continue;
|
|
436
|
+
const target = resolveSpecifier(context.reader, owner, site.spec, aliases);
|
|
437
|
+
if (target === site.spec || !context.reader.exists(target))
|
|
438
|
+
continue;
|
|
439
|
+
for (const name of site.named) {
|
|
440
|
+
if (name.typeOnly || !new RegExp(`\\b${escapeRegExp(name.local)}\\s*[<(]`).test(file.code))
|
|
441
|
+
continue;
|
|
442
|
+
const helper = functionText(context.file(target), name.imported);
|
|
443
|
+
if (helper !== null && spreadsImportOriginal(helper))
|
|
444
|
+
return true;
|
|
445
|
+
}
|
|
446
|
+
}
|
|
447
|
+
return false;
|
|
448
|
+
}
|
|
402
449
|
export const partialMockGuard = {
|
|
403
450
|
id: 'partialMocks',
|
|
404
451
|
absentWhen: false,
|
|
@@ -410,6 +457,13 @@ export const partialMockGuard = {
|
|
|
410
457
|
textProblem(r?.module, 'a package root', problems);
|
|
411
458
|
textProblem(r?.why, `package root ${String(r?.module)}'s why`, problems);
|
|
412
459
|
}
|
|
460
|
+
if (facts.sharedFakes !== undefined) {
|
|
461
|
+
if (typeof facts.sharedFakes?.importAliases !== 'object' ||
|
|
462
|
+
facts.sharedFakes.importAliases === null)
|
|
463
|
+
problems.push('sharedFakes.importAliases is missing');
|
|
464
|
+
if (!Array.isArray(facts.sharedFakes?.fakes))
|
|
465
|
+
problems.push('sharedFakes.fakes is missing');
|
|
466
|
+
}
|
|
413
467
|
return problems;
|
|
414
468
|
},
|
|
415
469
|
build(facts) {
|
|
@@ -419,11 +473,21 @@ export const partialMockGuard = {
|
|
|
419
473
|
description: 'A `vi.mock` of a listed package root spreads the real module: ' +
|
|
420
474
|
"`vi.mock('<root>', async (importOriginal) => ({ ...(await importOriginal()), <override> }))`. A factory that " +
|
|
421
475
|
'returns only the exports one test steers deletes the rest, and code that needs them fails away from the ' +
|
|
422
|
-
|
|
476
|
+
"assertion. A mock whose factory loads the root's shared fake passes when that fake spreads the real module. " +
|
|
423
477
|
facts.packageRoots.map((r) => `${r.module}: ${r.why}.`).join(' '),
|
|
424
478
|
run(context) {
|
|
425
479
|
const findings = [];
|
|
426
480
|
const mocked = new Set();
|
|
481
|
+
const aliases = aliasList(facts.sharedFakes?.importAliases ?? {});
|
|
482
|
+
const ownerOf = (module, rootName) => facts.sharedFakes?.fakes.find((f) => f.module === module &&
|
|
483
|
+
context.reader.exists(f.owner) &&
|
|
484
|
+
context.file(f.owner).rootName === rootName)?.owner;
|
|
485
|
+
const loadsKeepingOwner = (file, module, call) => {
|
|
486
|
+
const owner = ownerOf(module, file.rootName);
|
|
487
|
+
return (owner !== undefined &&
|
|
488
|
+
factoryLoads(call).some((spec) => resolveSpecifier(context.reader, file.path, spec, aliases) === owner) &&
|
|
489
|
+
ownerKeepsRealExports(context, owner, aliases));
|
|
490
|
+
};
|
|
427
491
|
const files = context.files({
|
|
428
492
|
roots: ['app', 'functions'],
|
|
429
493
|
kinds: ['code'],
|
|
@@ -434,7 +498,7 @@ export const partialMockGuard = {
|
|
|
434
498
|
for (const { module } of facts.packageRoots) {
|
|
435
499
|
for (const { call, index } of moduleMockCalls(file.code, module)) {
|
|
436
500
|
mocked.add(module);
|
|
437
|
-
if (!spreadsImportOriginal(call))
|
|
501
|
+
if (!spreadsImportOriginal(call) && !loadsKeepingOwner(file, module, call))
|
|
438
502
|
findings.push({
|
|
439
503
|
file: file.path,
|
|
440
504
|
line: file.lineOf(index),
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"client.js","sourceRoot":"","sources":["../../../src/modules/frontend-actions/client.ts"],"names":[],"mappings":"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","sourcesContent":["// The guards over client resources with one owner each: the app config document, browser storage,\n// upload-progress listeners, media rendering, media classification, motion, age derivation, and the\n// test mocks of pure package roots.\n\nimport ts from 'typescript';\nimport type { GenericGuardDefinition } from '../../node/suite/config.js';\nimport type { Finding, Guard } from '../../guard.js';\nimport type { Reviewed } from '../../node/layout.js';\nimport type { Scope } from '../../node/reader.js';\nimport type { FileView } from '../../reader/view.js';\nimport {\n matchPattern,\n ownerOnly,\n reviewedAllowlist,\n type AllowlistEntry,\n type Hit,\n} from '../../shapes/shapes.js';\nimport {\n allowlistProblems,\n namesProblem,\n reviewedProblems,\n textProblem,\n} from '../../support/facts.js';\nimport { fromStatements, namesModule } from '../../support/imports.js';\nimport { escapeRegExp, isUnder } from '../../support/text.js';\nimport { isPlainString } from '../../support/ast.js';\n\nconst APP_CODE: Scope = { roots: ['app'], kinds: ['code'], tests: false };\n\n/** A dotted code spelling (`A.b`) as a pattern that allows whitespace around each dot. */\nconst dottedPattern = (spelling: string) =>\n spelling\n .split('.')\n .map((part) => escapeRegExp(part.trim()))\n .join('\\\\s*\\\\.\\\\s*');\n\n/** A name in any quote: `'x'`, `\"x\"`, or a backtick literal. */\nconst quotedPattern = (name: string) => `(['\"\\`])${escapeRegExp(name)}\\\\1`;\n\n// ---------------------------------------------------------------------------------------------\n// App config: one client module names the app config document.\n\nexport interface AppConfigReaderFacts {\n /** The one client module that reads the app config document. */\n provider: string;\n /** The code spellings that name the document (`PATHS.appConfig`). */\n docSpellings: readonly string[];\n /** The document's collection name, matched when quoted. */\n collectionName: string;\n /** Server-side folders that read the document as the backend, each with why (declare [] when none). */\n serverPaths: readonly Reviewed[];\n}\n\nexport const appConfigReaderGuard: GenericGuardDefinition<AppConfigReaderFacts> = {\n id: 'appConfigReader',\n absentWhen: 'the app has no app config document',\n factProblems(facts) {\n const problems: string[] = [];\n textProblem(facts?.provider, 'provider', problems);\n namesProblem(facts?.docSpellings, 'docSpellings', problems);\n textProblem(facts?.collectionName, 'collectionName', problems);\n reviewedProblems(facts?.serverPaths, 'serverPaths', problems);\n return problems;\n },\n build(facts) {\n const pattern = new RegExp(\n [\n ...facts.docSpellings.map((s) => `\\\\b${dottedPattern(s)}\\\\b`),\n quotedPattern(facts.collectionName),\n ].join('|'),\n 'g',\n );\n const names = matchPattern(pattern);\n const guard = ownerOnly({\n title: 'one client module reads the app config document',\n description:\n `${facts.provider} is the one client source that names the app config document: it reads every value through ` +\n 'its checked readers and hands consumers the checked state. A second client read would see the raw, untrusted ' +\n \"value and bypass the provider's status. Read it through the provider's accessor; server code under the declared \" +\n 'server paths reads it as the backend.',\n scope: APP_CODE,\n match: (file) =>\n facts.serverPaths.some((p) => isUnder(file.path, p.path)) ? [] : names(file),\n owners: [facts.provider],\n samples: {\n flags: [\n `const ref = doc(db, ${facts.docSpellings[0]}());`,\n `doc(db, '${facts.collectionName}', 'app')`,\n ],\n passes: [\n `// reads ${facts.docSpellings[0]}() live`,\n `const hint = 'the field on ${facts.collectionName}/app is invalid';`,\n ],\n },\n });\n return [\n {\n ...guard,\n async run(context) {\n const findings = await guard.run(context);\n for (const p of facts.serverPaths)\n if (!context.reader.exists(p.path))\n findings.push({ file: p.path, detail: 'stale server path: it no longer exists' });\n return findings;\n },\n },\n ];\n },\n};\n\n// ---------------------------------------------------------------------------------------------\n// Browser storage: touched only through the guarded helper.\n\nexport interface BrowserStorageFacts {\n /** The guarded helper every read and write goes through. */\n helper: string;\n allowlist: readonly AllowlistEntry[];\n}\n\nexport const browserStorageGuard: GenericGuardDefinition<BrowserStorageFacts> = {\n id: 'browserStorage',\n absentWhen: false,\n factProblems(facts) {\n const problems: string[] = [];\n textProblem(facts?.helper, 'helper', problems);\n allowlistProblems(facts?.allowlist, 'allowlist', problems);\n return problems;\n },\n build(facts) {\n return [\n reviewedAllowlist({\n title: 'browser storage is touched only through the guarded helper',\n description:\n `Read and write browser storage through ${facts.helper}. A raw \\`localStorage\\` / \\`sessionStorage\\` access ` +\n 'throws for a viewer whose browser blocks site data, and inside a store the shell mounts that takes down every route.',\n scope: APP_CODE,\n match: matchPattern(/\\b(?:localStorage|sessionStorage)\\s*(?:\\.|\\[)/),\n allowlist: facts.allowlist,\n samples: {\n flags: [\n 'window.localStorage.getItem(KEY)',\n \"sessionStorage.removeItem('k')\",\n 'localStorage[KEY] = v',\n ],\n passes: [\n \"readStoredValue(KEY, 'session')\",\n '`stored in localStorage: ${v}`',\n '// localStorage.getItem(KEY)',\n ],\n },\n }),\n ];\n },\n};\n\n// ---------------------------------------------------------------------------------------------\n// Upload listeners: no component subscribes to the upload-processing collection itself.\n\nexport interface UploadListenerFacts {\n /** The upload-processing collection's name, matched when quoted. */\n collectionName: string;\n /** Code spellings that name the collection (`COLLECTIONS.UPLOADS`). */\n collectionSpellings: readonly string[];\n /** The one global subscriber and the hooks feature code reads instead. */\n subscriber: string;\n}\n\nexport const uploadListenerGuard: GenericGuardDefinition<UploadListenerFacts> = {\n id: 'uploadListeners',\n absentWhen: 'the app has no upload-processing collection',\n factProblems(facts) {\n const problems: string[] = [];\n textProblem(facts?.collectionName, 'collectionName', problems);\n namesProblem(facts?.collectionSpellings, 'collectionSpellings', problems, { nonEmpty: false });\n textProblem(facts?.subscriber, 'subscriber', problems);\n return problems;\n },\n build(facts) {\n const names = new RegExp(\n [\n quotedPattern(facts.collectionName),\n ...facts.collectionSpellings.map((s) => `\\\\b${dottedPattern(s)}\\\\b`),\n ].join('|'),\n );\n const subscribe = matchPattern(/\\bonSnapshot\\b/, () => 'onSnapshot');\n return [\n reviewedAllowlist({\n title: 'no component subscribes to upload processing itself',\n description:\n `Upload processing has one global subscriber (${facts.subscriber}). No file that names the ` +\n `${facts.collectionName} collection calls onSnapshot: read an upload's progress through the subscriber's hooks.`,\n scope: APP_CODE,\n match: (file) => (names.test(file.code) ? subscribe(file) : []),\n allowlist: [],\n samples: {\n flags: [`onSnapshot(doc(db, '${facts.collectionName}', id), cb);`],\n passes: [\n `onSnapshot(doc(db, 'otherThings', id), cb);`,\n `getDoc(doc(db, '${facts.collectionName}', id));`,\n ],\n },\n }),\n ];\n },\n};\n\n// ---------------------------------------------------------------------------------------------\n// Media renderer: every stored picture draws through the app's renderer owners.\n\nexport interface MediaRendererFacts {\n /** Each renderer owner: the one file that may import the listed names (or every import) from a module. */\n renderers: ReadonlyArray<{ owner: string; module: string; imports: readonly string[] | 'all' }>;\n /** Files that may draw a raw image, each with why (declare [] when none). */\n rawImageAllowlist: readonly AllowlistEntry[];\n}\n\nconst RAW_IMAGE =\n /<img\\b|from\\s*['\"]next\\/image['\"]|\\bbackgroundImage\\s*:|background(?:-image)?\\s*:\\s*url\\(/g;\n\n/** Each import of a renderer's names (or of anything, for `all`) from its module or a subpath. */\nexport function rendererImports(\n file: FileView,\n renderer: MediaRendererFacts['renderers'][number],\n): Hit[] {\n const hits: Hit[] = [];\n for (const site of fromStatements(file.sourceFile)) {\n if (!namesModule(site.spec, renderer.module)) continue;\n const index = site.node.getStart(file.sourceFile);\n if (renderer.imports === 'all') {\n hits.push({ index, detail: `imports from ${site.spec}` });\n continue;\n }\n for (const name of renderer.imports) {\n if (site.named.some((n) => n.imported === name || n.local === name))\n hits.push({ index, detail: `imports ${name} from ${site.spec}` });\n }\n }\n return hits;\n}\n\nexport const mediaRendererGuard: GenericGuardDefinition<MediaRendererFacts> = {\n id: 'mediaRenderer',\n absentWhen: false,\n factProblems(facts) {\n const problems: string[] = [];\n if (!Array.isArray(facts?.renderers) || !facts.renderers.length)\n problems.push('renderers is missing or empty');\n else {\n for (const r of facts.renderers) {\n textProblem(r?.owner, 'a renderer owner', problems);\n textProblem(r?.module, `renderer ${String(r?.owner)}'s module`, problems);\n if (r?.imports !== 'all')\n namesProblem(r?.imports, `renderer ${String(r?.owner)}'s imports (or 'all')`, problems);\n }\n }\n allowlistProblems(facts?.rawImageAllowlist, 'rawImageAllowlist', problems);\n return problems;\n },\n build(facts) {\n const raw = reviewedAllowlist({\n title: 'a stored picture draws only through its renderer owners',\n description:\n 'No component draws a raw `<img>`, a `next/image`, or a background image: a stored picture renders through the ' +\n `app's renderer owners (${facts.renderers.map((r) => r.owner).join(', ')}), and each owner is the one file that ` +\n 'imports what it wraps from its module — a second caller is a second render path. A file that must draw a raw ' +\n 'image is a reviewed entry with its why.',\n scope: APP_CODE,\n match: matchPattern(RAW_IMAGE),\n allowlist: facts.rawImageAllowlist,\n samples: {\n flags: [\n '<img src={url} alt=\"\" />',\n \"import Image from 'next/image';\",\n '<div style={{ backgroundImage: `url(${url})` }} />',\n ],\n passes: [\n \"import { Image } from 'lucide-react';\",\n '<Image className=\"icon-sm\" />',\n '// never an <img>',\n \"const tag = 'img';\",\n ],\n },\n });\n return [\n {\n ...raw,\n async run(context) {\n const findings: Finding[] = await raw.run(context);\n const held = new Set<string>();\n for (const file of context.files(APP_CODE)) {\n for (const renderer of facts.renderers) {\n const hits = rendererImports(file, renderer);\n if (!hits.length) continue;\n if (file.path === renderer.owner) held.add(renderer.owner);\n else\n for (const hit of hits)\n findings.push({\n file: file.path,\n line: file.lineOf(hit.index),\n detail: `outside its renderer owner ${renderer.owner}: ${hit.detail}`,\n });\n }\n }\n for (const renderer of facts.renderers) {\n if (!context.reader.exists(renderer.owner))\n findings.push({\n file: renderer.owner,\n detail: 'the renderer owner no longer exists',\n });\n else if (!held.has(renderer.owner))\n findings.push({\n file: renderer.owner,\n detail: `stale owner: it no longer imports from ${renderer.module}`,\n });\n }\n return findings;\n },\n },\n ];\n },\n};\n\n// ---------------------------------------------------------------------------------------------\n// MIME prefix classification: what a file IS comes from the server's inspection, never a MIME prefix.\n\nexport type MimePrefixFacts = Record<string, never>;\n\nconst MIME_PREFIX = /^(image|video|audio)\\/$/;\nconst MIME_PREFIX_REGEX = /\\^\\(?\\??:?(image|video|audio)/;\n\n/** Each MIME-prefix classification: a bare prefix literal, a regex anchored on a media type, or `split('/')[0]`. */\nexport function mimePrefixClassifications(file: FileView): Hit[] {\n const sf = file.sourceFile;\n const hits: Hit[] = [];\n const visit = (node: ts.Node) => {\n if (isPlainString(node) && MIME_PREFIX.test(node.text)) {\n hits.push({ index: node.getStart(sf), detail: `'${node.text}'` });\n }\n if (ts.isRegularExpressionLiteral(node) && MIME_PREFIX_REGEX.test(node.text))\n hits.push({ index: node.getStart(sf), detail: node.text });\n if (\n ts.isElementAccessExpression(node) &&\n ts.isNumericLiteral(node.argumentExpression) &&\n node.argumentExpression.text === '0' &&\n ts.isCallExpression(node.expression) &&\n ts.isPropertyAccessExpression(node.expression.expression) &&\n node.expression.expression.name.text === 'split' &&\n node.expression.arguments.length === 1 &&\n ts.isStringLiteral(node.expression.arguments[0]) &&\n node.expression.arguments[0].text === '/'\n ) {\n hits.push({ index: node.getStart(sf), detail: \"split('/')[0]\" });\n }\n ts.forEachChild(node, visit);\n };\n visit(sf);\n return hits;\n}\n\nexport const mimePrefixGuard: GenericGuardDefinition<MimePrefixFacts> = {\n id: 'mimePrefixClassification',\n absentWhen: false,\n factProblems(facts) {\n if (!facts || typeof facts !== 'object') return ['the facts are missing (declare {})'];\n return Object.keys(facts).length ? [`unknown facts: ${Object.keys(facts).join(', ')}`] : [];\n },\n build() {\n return [\n reviewedAllowlist({\n title: 'no code classifies media from a MIME prefix',\n description:\n \"Code never decides what a stored or uploaded file is from a MIME prefix (`startsWith('image/')`, a regex \" +\n \"anchored on a media type, `type.split('/')[0]`): a client-declared type may be neutral, so the kind a surface \" +\n \"renders or routes on is the server's inspection of the bytes.\",\n scope: APP_CODE,\n match: mimePrefixClassifications,\n allowlist: [],\n samples: {\n flags: [\n \"const a = contentType.startsWith('video/');\",\n 'const b = /^audio\\\\//.test(contentType);',\n \"const c = contentType.split('/')[0];\",\n 'const e = mime.includes(`image/`);',\n ],\n passes: [\n \"// contentType.startsWith('image/')\",\n \"const d = new File([blob], 'x.jpg', { type: 'image/jpeg' });\",\n ],\n },\n }),\n ];\n },\n};\n\n// ---------------------------------------------------------------------------------------------\n// Effective motion: one store reads the device's reduced-motion request.\n\nexport interface EffectiveMotionFacts {\n /** The store that owns the effective-motion value. */\n store: string;\n}\n\nexport const effectiveMotionGuard: GenericGuardDefinition<EffectiveMotionFacts> = {\n id: 'effectiveMotion',\n absentWhen: false,\n factProblems(facts) {\n const problems: string[] = [];\n textProblem(facts?.store, 'store', problems);\n return problems;\n },\n build(facts) {\n const query = matchPattern(/matchMedia\\([^)]*prefers-reduced-motion/);\n // The store may hold the media query itself or build on a package store that does, so it is\n // exempt rather than required to hold it; it must still exist.\n const guard = reviewedAllowlist({\n title: 'one store reads the reduced-motion request',\n description:\n `The effective motion value comes from ${facts.store}, which combines the device request with the saved ` +\n 'choice. No other code checks the `prefers-reduced-motion` media query itself: read the store.',\n scope: APP_CODE,\n match: (file) => (file.path === facts.store ? [] : query(file)),\n allowlist: [],\n samples: {\n flags: [\"const reduce = window.matchMedia('(prefers-reduced-motion: reduce)').matches;\"],\n passes: [\n '// the store owns prefers-reduced-motion',\n \"window.matchMedia('(min-width: 768px)')\",\n ],\n },\n });\n return [\n {\n ...guard,\n async run(context) {\n const findings = await guard.run(context);\n if (!context.reader.exists(facts.store))\n findings.push({ file: facts.store, detail: 'the motion store no longer exists' });\n return findings;\n },\n },\n ];\n },\n};\n\n// ---------------------------------------------------------------------------------------------\n// Date-of-birth derivation: one owner computes whole years from a date.\n\nexport interface DateOfBirthFacts {\n /** The one file that derives an age from a date of birth (may be in the core package's build). */\n owner: string;\n}\n\nexport const dateOfBirthGuard: GenericGuardDefinition<DateOfBirthFacts> = {\n id: 'dateOfBirthDerivation',\n absentWhen: 'the app never derives an age from a date of birth',\n factProblems(facts) {\n const problems: string[] = [];\n textProblem(facts?.owner, 'owner', problems);\n return problems;\n },\n build(facts) {\n return [\n ownerOnly({\n title: 'a date of birth becomes an age in one place',\n description:\n `Whole years are computed from a date only in ${facts.owner}. A second year subtraction anywhere else is a ` +\n 'copy that drifts from the owner at a boundary birthday: call the owner.',\n scope: {\n roots: ['app', 'functions', 'coreDist'],\n kinds: ['code'],\n tests: false,\n optionalRoots: ['functions', 'coreDist'],\n },\n match: matchPattern(/\\.get(?:UTC)?FullYear\\(\\)\\s*-/),\n owners: [facts.owner],\n samples: {\n flags: [\n 'const age = now.getFullYear() - dob.getFullYear();',\n 'let years = asOf.getUTCFullYear() - dob.year;',\n ],\n passes: [\n 'const year = now.getUTCFullYear();',\n '// now.getFullYear() - dob.getFullYear()',\n ],\n },\n }),\n ];\n },\n};\n\n// ---------------------------------------------------------------------------------------------\n// Partial mocks: a test mock of a listed package root keeps the root's real exports.\n\nexport interface PartialMockFacts {\n /** Package roots whose mocks must spread the real module, each with what breaks when one does not. */\n packageRoots: ReadonlyArray<{ module: string; why: string }>;\n}\n\n/** Each `vi.mock` / `vi.doMock` call on exactly `module`, as its balanced source slice. */\nexport function moduleMockCalls(\n code: string,\n module: string,\n): Array<{ call: string; index: number }> {\n const opener = new RegExp(`vi\\\\.(?:mock|doMock)\\\\(\\\\s*(['\"\\`])${escapeRegExp(module)}\\\\1`, 'g');\n const calls: Array<{ call: string; index: number }> = [];\n for (let m = opener.exec(code); m; m = opener.exec(code)) {\n const open = code.indexOf('(', m.index);\n let depth = 0;\n let end = open;\n for (; end < code.length; end++) {\n if (code[end] === '(') depth++;\n else if (code[end] === ')' && --depth === 0) break;\n }\n calls.push({ call: code.slice(m.index, end + 1), index: m.index });\n }\n return calls;\n}\n\n/** True when a mock call spreads the real module, inline or through a binding. */\nexport function spreadsImportOriginal(call: string): boolean {\n if (/\\.\\.\\.\\s*\\(\\s*await\\s+importOriginal\\b/.test(call)) return true;\n const binding = /const\\s+([A-Za-z_$][\\w$]*)\\s*=\\s*await\\s+importOriginal\\b/.exec(call);\n return binding !== null && new RegExp(`\\\\.\\\\.\\\\.\\\\s*${escapeRegExp(binding[1])}\\\\b`).test(call);\n}\n\nexport const partialMockGuard: GenericGuardDefinition<PartialMockFacts> = {\n id: 'partialMocks',\n absentWhen: false,\n factProblems(facts) {\n const problems: string[] = [];\n if (!Array.isArray(facts?.packageRoots) || !facts.packageRoots.length)\n return ['packageRoots is missing or empty'];\n for (const r of facts.packageRoots) {\n textProblem(r?.module, 'a package root', problems);\n textProblem(r?.why, `package root ${String(r?.module)}'s why`, problems);\n }\n return problems;\n },\n build(facts): Guard[] {\n return [\n {\n title: \"a mock of a listed package root keeps the root's real exports\",\n description:\n 'A `vi.mock` of a listed package root spreads the real module: ' +\n \"`vi.mock('<root>', async (importOriginal) => ({ ...(await importOriginal()), <override> }))`. A factory that \" +\n 'returns only the exports one test steers deletes the rest, and code that needs them fails away from the ' +\n 'assertion. ' +\n facts.packageRoots.map((r) => `${r.module}: ${r.why}.`).join(' '),\n run(context) {\n const findings: Finding[] = [];\n const mocked = new Set<string>();\n const files = context.files({\n roots: ['app', 'functions'],\n kinds: ['code'],\n tests: true,\n optionalRoots: ['functions'],\n });\n for (const file of files) {\n for (const { module } of facts.packageRoots) {\n for (const { call, index } of moduleMockCalls(file.code, module)) {\n mocked.add(module);\n if (!spreadsImportOriginal(call))\n findings.push({\n file: file.path,\n line: file.lineOf(index),\n detail: `a full-module mock of ${module}`,\n });\n }\n }\n }\n for (const { module } of facts.packageRoots) {\n if (!mocked.has(module))\n findings.push({\n file: '(packageRoots)',\n detail: `stale entry: nothing mocks ${module}`,\n });\n }\n return findings;\n },\n },\n ];\n },\n};\n"]}
|
|
1
|
+
{"version":3,"file":"client.js","sourceRoot":"","sources":["../../../src/modules/frontend-actions/client.ts"],"names":[],"mappings":"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","sourcesContent":["// The guards over client resources with one owner each: the app config document, browser storage,\n// upload-progress listeners, media rendering, media classification, motion, age derivation, and the\n// test mocks of pure package roots.\n\nimport ts from 'typescript';\nimport type { GenericGuardDefinition } from '../../node/suite/config.js';\nimport type { Finding, Guard } from '../../guard.js';\nimport type { Reviewed } from '../../node/layout.js';\nimport type { Scope } from '../../node/reader.js';\nimport type { FileView } from '../../reader/view.js';\nimport type { GuardContext } from '../../guard.js';\nimport type { SharedFakeFacts } from '../repo/shared-fakes.js';\nimport {\n matchPattern,\n ownerOnly,\n reviewedAllowlist,\n type AllowlistEntry,\n type Hit,\n} from '../../shapes/shapes.js';\nimport {\n allowlistProblems,\n namesProblem,\n reviewedProblems,\n textProblem,\n} from '../../support/facts.js';\nimport { fromStatements, namesModule } from '../../support/imports.js';\nimport { escapeRegExp, isUnder } from '../../support/text.js';\nimport { isPlainString } from '../../support/ast.js';\nimport { aliasList, resolveSpecifier } from '../../support/resolve-specifier.js';\n\nconst APP_CODE: Scope = { roots: ['app'], kinds: ['code'], tests: false };\n\n/** A dotted code spelling (`A.b`) as a pattern that allows whitespace around each dot. */\nconst dottedPattern = (spelling: string) =>\n spelling\n .split('.')\n .map((part) => escapeRegExp(part.trim()))\n .join('\\\\s*\\\\.\\\\s*');\n\n/** A name in any quote: `'x'`, `\"x\"`, or a backtick literal. */\nconst quotedPattern = (name: string) => `(['\"\\`])${escapeRegExp(name)}\\\\1`;\n\n// ---------------------------------------------------------------------------------------------\n// App config: one client module names the app config document.\n\nexport interface AppConfigReaderFacts {\n /** The one client module that reads the app config document. */\n provider: string;\n /** The code spellings that name the document (`PATHS.appConfig`). */\n docSpellings: readonly string[];\n /** The document's collection name, matched when quoted. */\n collectionName: string;\n /** Server-side folders that read the document as the backend, each with why (declare [] when none). */\n serverPaths: readonly Reviewed[];\n}\n\nexport const appConfigReaderGuard: GenericGuardDefinition<AppConfigReaderFacts> = {\n id: 'appConfigReader',\n absentWhen: 'the app has no app config document',\n factProblems(facts) {\n const problems: string[] = [];\n textProblem(facts?.provider, 'provider', problems);\n namesProblem(facts?.docSpellings, 'docSpellings', problems);\n textProblem(facts?.collectionName, 'collectionName', problems);\n reviewedProblems(facts?.serverPaths, 'serverPaths', problems);\n return problems;\n },\n build(facts) {\n const pattern = new RegExp(\n [\n ...facts.docSpellings.map((s) => `\\\\b${dottedPattern(s)}\\\\b`),\n quotedPattern(facts.collectionName),\n ].join('|'),\n 'g',\n );\n const names = matchPattern(pattern);\n const guard = ownerOnly({\n title: 'one client module reads the app config document',\n description:\n `${facts.provider} is the one client source that names the app config document: it reads every value through ` +\n 'its checked readers and hands consumers the checked state. A second client read would see the raw, untrusted ' +\n \"value and bypass the provider's status. Read it through the provider's accessor; server code under the declared \" +\n 'server paths reads it as the backend.',\n scope: APP_CODE,\n match: (file) =>\n facts.serverPaths.some((p) => isUnder(file.path, p.path)) ? [] : names(file),\n owners: [facts.provider],\n samples: {\n flags: [\n `const ref = doc(db, ${facts.docSpellings[0]}());`,\n `doc(db, '${facts.collectionName}', 'app')`,\n ],\n passes: [\n `// reads ${facts.docSpellings[0]}() live`,\n `const hint = 'the field on ${facts.collectionName}/app is invalid';`,\n ],\n },\n });\n return [\n {\n ...guard,\n async run(context) {\n const findings = await guard.run(context);\n for (const p of facts.serverPaths)\n if (!context.reader.exists(p.path))\n findings.push({ file: p.path, detail: 'stale server path: it no longer exists' });\n return findings;\n },\n },\n ];\n },\n};\n\n// ---------------------------------------------------------------------------------------------\n// Browser storage: touched only through the guarded helper.\n\nexport interface BrowserStorageFacts {\n /** The guarded helper every read and write goes through. */\n helper: string;\n allowlist: readonly AllowlistEntry[];\n}\n\nexport const browserStorageGuard: GenericGuardDefinition<BrowserStorageFacts> = {\n id: 'browserStorage',\n absentWhen: false,\n factProblems(facts) {\n const problems: string[] = [];\n textProblem(facts?.helper, 'helper', problems);\n allowlistProblems(facts?.allowlist, 'allowlist', problems);\n return problems;\n },\n build(facts) {\n return [\n reviewedAllowlist({\n title: 'browser storage is touched only through the guarded helper',\n description:\n `Read and write browser storage through ${facts.helper}. A raw \\`localStorage\\` / \\`sessionStorage\\` access ` +\n 'throws for a viewer whose browser blocks site data, and inside a store the shell mounts that takes down every route.',\n scope: APP_CODE,\n match: matchPattern(/\\b(?:localStorage|sessionStorage)\\s*(?:\\.|\\[)/),\n allowlist: facts.allowlist,\n samples: {\n flags: [\n 'window.localStorage.getItem(KEY)',\n \"sessionStorage.removeItem('k')\",\n 'localStorage[KEY] = v',\n ],\n passes: [\n \"readStoredValue(KEY, 'session')\",\n '`stored in localStorage: ${v}`',\n '// localStorage.getItem(KEY)',\n ],\n },\n }),\n ];\n },\n};\n\n// ---------------------------------------------------------------------------------------------\n// Upload listeners: no component subscribes to the upload-processing collection itself.\n\nexport interface UploadListenerFacts {\n /** The upload-processing collection's name, matched when quoted. */\n collectionName: string;\n /** Code spellings that name the collection (`COLLECTIONS.UPLOADS`). */\n collectionSpellings: readonly string[];\n /** The one global subscriber and the hooks feature code reads instead. */\n subscriber: string;\n}\n\nexport const uploadListenerGuard: GenericGuardDefinition<UploadListenerFacts> = {\n id: 'uploadListeners',\n absentWhen: 'the app has no upload-processing collection',\n factProblems(facts) {\n const problems: string[] = [];\n textProblem(facts?.collectionName, 'collectionName', problems);\n namesProblem(facts?.collectionSpellings, 'collectionSpellings', problems, { nonEmpty: false });\n textProblem(facts?.subscriber, 'subscriber', problems);\n return problems;\n },\n build(facts) {\n const names = new RegExp(\n [\n quotedPattern(facts.collectionName),\n ...facts.collectionSpellings.map((s) => `\\\\b${dottedPattern(s)}\\\\b`),\n ].join('|'),\n );\n const subscribe = matchPattern(/\\bonSnapshot\\b/, () => 'onSnapshot');\n return [\n reviewedAllowlist({\n title: 'no component subscribes to upload processing itself',\n description:\n `Upload processing has one global subscriber (${facts.subscriber}). No file that names the ` +\n `${facts.collectionName} collection calls onSnapshot: read an upload's progress through the subscriber's hooks.`,\n scope: APP_CODE,\n match: (file) => (names.test(file.code) ? subscribe(file) : []),\n allowlist: [],\n samples: {\n flags: [`onSnapshot(doc(db, '${facts.collectionName}', id), cb);`],\n passes: [\n `onSnapshot(doc(db, 'otherThings', id), cb);`,\n `getDoc(doc(db, '${facts.collectionName}', id));`,\n ],\n },\n }),\n ];\n },\n};\n\n// ---------------------------------------------------------------------------------------------\n// Media renderer: every stored picture draws through the app's renderer owners.\n\nexport interface MediaRendererFacts {\n /** Each renderer owner: the one file that may import the listed names (or every import) from a module. */\n renderers: ReadonlyArray<{ owner: string; module: string; imports: readonly string[] | 'all' }>;\n /** Files that may draw a raw image, each with why (declare [] when none). */\n rawImageAllowlist: readonly AllowlistEntry[];\n}\n\nconst RAW_IMAGE =\n /<img\\b|from\\s*['\"]next\\/image['\"]|\\bbackgroundImage\\s*:|background(?:-image)?\\s*:\\s*url\\(/g;\n\n/** Each import of a renderer's names (or of anything, for `all`) from its module or a subpath. */\nexport function rendererImports(\n file: FileView,\n renderer: MediaRendererFacts['renderers'][number],\n): Hit[] {\n const hits: Hit[] = [];\n for (const site of fromStatements(file.sourceFile)) {\n if (!namesModule(site.spec, renderer.module)) continue;\n const index = site.node.getStart(file.sourceFile);\n if (renderer.imports === 'all') {\n hits.push({ index, detail: `imports from ${site.spec}` });\n continue;\n }\n for (const name of renderer.imports) {\n if (site.named.some((n) => n.imported === name || n.local === name))\n hits.push({ index, detail: `imports ${name} from ${site.spec}` });\n }\n }\n return hits;\n}\n\nexport const mediaRendererGuard: GenericGuardDefinition<MediaRendererFacts> = {\n id: 'mediaRenderer',\n absentWhen: false,\n factProblems(facts) {\n const problems: string[] = [];\n if (!Array.isArray(facts?.renderers) || !facts.renderers.length)\n problems.push('renderers is missing or empty');\n else {\n for (const r of facts.renderers) {\n textProblem(r?.owner, 'a renderer owner', problems);\n textProblem(r?.module, `renderer ${String(r?.owner)}'s module`, problems);\n if (r?.imports !== 'all')\n namesProblem(r?.imports, `renderer ${String(r?.owner)}'s imports (or 'all')`, problems);\n }\n }\n allowlistProblems(facts?.rawImageAllowlist, 'rawImageAllowlist', problems);\n return problems;\n },\n build(facts) {\n const raw = reviewedAllowlist({\n title: 'a stored picture draws only through its renderer owners',\n description:\n 'No component draws a raw `<img>`, a `next/image`, or a background image: a stored picture renders through the ' +\n `app's renderer owners (${facts.renderers.map((r) => r.owner).join(', ')}), and each owner is the one file that ` +\n 'imports what it wraps from its module — a second caller is a second render path. A file that must draw a raw ' +\n 'image is a reviewed entry with its why.',\n scope: APP_CODE,\n match: matchPattern(RAW_IMAGE),\n allowlist: facts.rawImageAllowlist,\n samples: {\n flags: [\n '<img src={url} alt=\"\" />',\n \"import Image from 'next/image';\",\n '<div style={{ backgroundImage: `url(${url})` }} />',\n ],\n passes: [\n \"import { Image } from 'lucide-react';\",\n '<Image className=\"icon-sm\" />',\n '// never an <img>',\n \"const tag = 'img';\",\n ],\n },\n });\n return [\n {\n ...raw,\n async run(context) {\n const findings: Finding[] = await raw.run(context);\n const held = new Set<string>();\n for (const file of context.files(APP_CODE)) {\n for (const renderer of facts.renderers) {\n const hits = rendererImports(file, renderer);\n if (!hits.length) continue;\n if (file.path === renderer.owner) held.add(renderer.owner);\n else\n for (const hit of hits)\n findings.push({\n file: file.path,\n line: file.lineOf(hit.index),\n detail: `outside its renderer owner ${renderer.owner}: ${hit.detail}`,\n });\n }\n }\n for (const renderer of facts.renderers) {\n if (!context.reader.exists(renderer.owner))\n findings.push({\n file: renderer.owner,\n detail: 'the renderer owner no longer exists',\n });\n else if (!held.has(renderer.owner))\n findings.push({\n file: renderer.owner,\n detail: `stale owner: it no longer imports from ${renderer.module}`,\n });\n }\n return findings;\n },\n },\n ];\n },\n};\n\n// ---------------------------------------------------------------------------------------------\n// MIME prefix classification: what a file IS comes from the server's inspection, never a MIME prefix.\n\nexport type MimePrefixFacts = Record<string, never>;\n\nconst MIME_PREFIX = /^(image|video|audio)\\/$/;\nconst MIME_PREFIX_REGEX = /\\^\\(?\\??:?(image|video|audio)/;\n\n/** Each MIME-prefix classification: a bare prefix literal, a regex anchored on a media type, or `split('/')[0]`. */\nexport function mimePrefixClassifications(file: FileView): Hit[] {\n const sf = file.sourceFile;\n const hits: Hit[] = [];\n const visit = (node: ts.Node) => {\n if (isPlainString(node) && MIME_PREFIX.test(node.text)) {\n hits.push({ index: node.getStart(sf), detail: `'${node.text}'` });\n }\n if (ts.isRegularExpressionLiteral(node) && MIME_PREFIX_REGEX.test(node.text))\n hits.push({ index: node.getStart(sf), detail: node.text });\n if (\n ts.isElementAccessExpression(node) &&\n ts.isNumericLiteral(node.argumentExpression) &&\n node.argumentExpression.text === '0' &&\n ts.isCallExpression(node.expression) &&\n ts.isPropertyAccessExpression(node.expression.expression) &&\n node.expression.expression.name.text === 'split' &&\n node.expression.arguments.length === 1 &&\n ts.isStringLiteral(node.expression.arguments[0]) &&\n node.expression.arguments[0].text === '/'\n ) {\n hits.push({ index: node.getStart(sf), detail: \"split('/')[0]\" });\n }\n ts.forEachChild(node, visit);\n };\n visit(sf);\n return hits;\n}\n\nexport const mimePrefixGuard: GenericGuardDefinition<MimePrefixFacts> = {\n id: 'mimePrefixClassification',\n absentWhen: false,\n factProblems(facts) {\n if (!facts || typeof facts !== 'object') return ['the facts are missing (declare {})'];\n return Object.keys(facts).length ? [`unknown facts: ${Object.keys(facts).join(', ')}`] : [];\n },\n build() {\n return [\n reviewedAllowlist({\n title: 'no code classifies media from a MIME prefix',\n description:\n \"Code never decides what a stored or uploaded file is from a MIME prefix (`startsWith('image/')`, a regex \" +\n \"anchored on a media type, `type.split('/')[0]`): a client-declared type may be neutral, so the kind a surface \" +\n \"renders or routes on is the server's inspection of the bytes.\",\n scope: APP_CODE,\n match: mimePrefixClassifications,\n allowlist: [],\n samples: {\n flags: [\n \"const a = contentType.startsWith('video/');\",\n 'const b = /^audio\\\\//.test(contentType);',\n \"const c = contentType.split('/')[0];\",\n 'const e = mime.includes(`image/`);',\n ],\n passes: [\n \"// contentType.startsWith('image/')\",\n \"const d = new File([blob], 'x.jpg', { type: 'image/jpeg' });\",\n ],\n },\n }),\n ];\n },\n};\n\n// ---------------------------------------------------------------------------------------------\n// Effective motion: one store reads the device's reduced-motion request.\n\nexport interface EffectiveMotionFacts {\n /** The store that owns the effective-motion value. */\n store: string;\n}\n\nexport const effectiveMotionGuard: GenericGuardDefinition<EffectiveMotionFacts> = {\n id: 'effectiveMotion',\n absentWhen: false,\n factProblems(facts) {\n const problems: string[] = [];\n textProblem(facts?.store, 'store', problems);\n return problems;\n },\n build(facts) {\n const query = matchPattern(/matchMedia\\([^)]*prefers-reduced-motion/);\n // The store may hold the media query itself or build on a package store that does, so it is\n // exempt rather than required to hold it; it must still exist.\n const guard = reviewedAllowlist({\n title: 'one store reads the reduced-motion request',\n description:\n `The effective motion value comes from ${facts.store}, which combines the device request with the saved ` +\n 'choice. No other code checks the `prefers-reduced-motion` media query itself: read the store.',\n scope: APP_CODE,\n match: (file) => (file.path === facts.store ? [] : query(file)),\n allowlist: [],\n samples: {\n flags: [\"const reduce = window.matchMedia('(prefers-reduced-motion: reduce)').matches;\"],\n passes: [\n '// the store owns prefers-reduced-motion',\n \"window.matchMedia('(min-width: 768px)')\",\n ],\n },\n });\n return [\n {\n ...guard,\n async run(context) {\n const findings = await guard.run(context);\n if (!context.reader.exists(facts.store))\n findings.push({ file: facts.store, detail: 'the motion store no longer exists' });\n return findings;\n },\n },\n ];\n },\n};\n\n// ---------------------------------------------------------------------------------------------\n// Date-of-birth derivation: one owner computes whole years from a date.\n\nexport interface DateOfBirthFacts {\n /** The one file that derives an age from a date of birth (may be in the core package's build). */\n owner: string;\n}\n\nexport const dateOfBirthGuard: GenericGuardDefinition<DateOfBirthFacts> = {\n id: 'dateOfBirthDerivation',\n absentWhen: 'the app never derives an age from a date of birth',\n factProblems(facts) {\n const problems: string[] = [];\n textProblem(facts?.owner, 'owner', problems);\n return problems;\n },\n build(facts) {\n return [\n ownerOnly({\n title: 'a date of birth becomes an age in one place',\n description:\n `Whole years are computed from a date only in ${facts.owner}. A second year subtraction anywhere else is a ` +\n 'copy that drifts from the owner at a boundary birthday: call the owner.',\n scope: {\n roots: ['app', 'functions', 'coreDist'],\n kinds: ['code'],\n tests: false,\n optionalRoots: ['functions', 'coreDist'],\n },\n match: matchPattern(/\\.get(?:UTC)?FullYear\\(\\)\\s*-/),\n owners: [facts.owner],\n samples: {\n flags: [\n 'const age = now.getFullYear() - dob.getFullYear();',\n 'let years = asOf.getUTCFullYear() - dob.year;',\n ],\n passes: [\n 'const year = now.getUTCFullYear();',\n '// now.getFullYear() - dob.getFullYear()',\n ],\n },\n }),\n ];\n },\n};\n\n// ---------------------------------------------------------------------------------------------\n// Partial mocks: a test mock of a listed package root keeps the root's real exports.\n\nexport interface PartialMockFacts {\n /** Package roots whose mocks must spread the real module, each with what breaks when one does not. */\n packageRoots: ReadonlyArray<{ module: string; why: string }>;\n /**\n * The app's shared fakes: a mock whose factory loads a listed root's shared fake keeps the root's\n * real exports when that fake does.\n */\n sharedFakes?: Pick<SharedFakeFacts, 'importAliases' | 'fakes'>;\n}\n\n/** Each `vi.mock` / `vi.doMock` call on exactly `module`, as its balanced source slice. */\nexport function moduleMockCalls(\n code: string,\n module: string,\n): Array<{ call: string; index: number }> {\n const opener = new RegExp(`vi\\\\.(?:mock|doMock)\\\\(\\\\s*(['\"\\`])${escapeRegExp(module)}\\\\1`, 'g');\n const calls: Array<{ call: string; index: number }> = [];\n for (let m = opener.exec(code); m; m = opener.exec(code)) {\n const open = code.indexOf('(', m.index);\n let depth = 0;\n let end = open;\n for (; end < code.length; end++) {\n if (code[end] === '(') depth++;\n else if (code[end] === ')' && --depth === 0) break;\n }\n calls.push({ call: code.slice(m.index, end + 1), index: m.index });\n }\n return calls;\n}\n\n/** True when a mock call spreads the real module, inline or through a binding. */\nexport function spreadsImportOriginal(call: string): boolean {\n if (/\\.\\.\\.\\s*\\(\\s*await\\s+importOriginal\\b/.test(call)) return true;\n const binding = /const\\s+([A-Za-z_$][\\w$]*)\\s*=\\s*await\\s+importOriginal\\b/.exec(call);\n return binding !== null && new RegExp(`\\\\.\\\\.\\\\.\\\\s*${escapeRegExp(binding[1])}\\\\b`).test(call);\n}\n\n/** The specifiers a mock call's factory loads with `import()`. */\nfunction factoryLoads(call: string): string[] {\n const loads: string[] = [];\n const load = /\\bimport\\(\\s*(['\"`])([^'\"`]+)\\1\\s*\\)/g;\n for (let m = load.exec(call); m; m = load.exec(call)) loads.push(m[2]);\n return loads;\n}\n\n/** The source of a function `name` declares, as a declaration or a variable holding a function. */\nfunction functionText(file: FileView, name: string): string | null {\n for (const statement of file.sourceFile.statements) {\n if (ts.isFunctionDeclaration(statement) && statement.name?.text === name)\n return statement.getText(file.sourceFile);\n if (ts.isVariableStatement(statement))\n for (const d of statement.declarationList.declarations)\n if (ts.isIdentifier(d.name) && d.name.text === name && d.initializer)\n return d.initializer.getText(file.sourceFile);\n }\n return null;\n}\n\n/**\n * True when a shared fake keeps its module's real exports: it spreads `importOriginal` itself, or it\n * calls a helper from a local file whose own body does.\n */\nfunction ownerKeepsRealExports(\n context: GuardContext,\n owner: string,\n aliases: ReadonlyArray<[string, string]>,\n): boolean {\n if (!context.reader.exists(owner)) return false;\n const file = context.file(owner);\n if (spreadsImportOriginal(file.code)) return true;\n for (const site of file.imports) {\n if (site.typeOnly) continue;\n const target = resolveSpecifier(context.reader, owner, site.spec, aliases);\n if (target === site.spec || !context.reader.exists(target)) continue;\n for (const name of site.named) {\n if (name.typeOnly || !new RegExp(`\\\\b${escapeRegExp(name.local)}\\\\s*[<(]`).test(file.code))\n continue;\n const helper = functionText(context.file(target), name.imported);\n if (helper !== null && spreadsImportOriginal(helper)) return true;\n }\n }\n return false;\n}\n\nexport const partialMockGuard: GenericGuardDefinition<PartialMockFacts> = {\n id: 'partialMocks',\n absentWhen: false,\n factProblems(facts) {\n const problems: string[] = [];\n if (!Array.isArray(facts?.packageRoots) || !facts.packageRoots.length)\n return ['packageRoots is missing or empty'];\n for (const r of facts.packageRoots) {\n textProblem(r?.module, 'a package root', problems);\n textProblem(r?.why, `package root ${String(r?.module)}'s why`, problems);\n }\n if (facts.sharedFakes !== undefined) {\n if (\n typeof facts.sharedFakes?.importAliases !== 'object' ||\n facts.sharedFakes.importAliases === null\n )\n problems.push('sharedFakes.importAliases is missing');\n if (!Array.isArray(facts.sharedFakes?.fakes)) problems.push('sharedFakes.fakes is missing');\n }\n return problems;\n },\n build(facts): Guard[] {\n return [\n {\n title: \"a mock of a listed package root keeps the root's real exports\",\n description:\n 'A `vi.mock` of a listed package root spreads the real module: ' +\n \"`vi.mock('<root>', async (importOriginal) => ({ ...(await importOriginal()), <override> }))`. A factory that \" +\n 'returns only the exports one test steers deletes the rest, and code that needs them fails away from the ' +\n \"assertion. A mock whose factory loads the root's shared fake passes when that fake spreads the real module. \" +\n facts.packageRoots.map((r) => `${r.module}: ${r.why}.`).join(' '),\n run(context) {\n const findings: Finding[] = [];\n const mocked = new Set<string>();\n const aliases = aliasList(facts.sharedFakes?.importAliases ?? {});\n const ownerOf = (module: string, rootName: string): string | undefined =>\n facts.sharedFakes?.fakes.find(\n (f) =>\n f.module === module &&\n context.reader.exists(f.owner) &&\n context.file(f.owner).rootName === rootName,\n )?.owner;\n const loadsKeepingOwner = (file: FileView, module: string, call: string): boolean => {\n const owner = ownerOf(module, file.rootName);\n return (\n owner !== undefined &&\n factoryLoads(call).some(\n (spec) => resolveSpecifier(context.reader, file.path, spec, aliases) === owner,\n ) &&\n ownerKeepsRealExports(context, owner, aliases)\n );\n };\n const files = context.files({\n roots: ['app', 'functions'],\n kinds: ['code'],\n tests: true,\n optionalRoots: ['functions'],\n });\n for (const file of files) {\n for (const { module } of facts.packageRoots) {\n for (const { call, index } of moduleMockCalls(file.code, module)) {\n mocked.add(module);\n if (!spreadsImportOriginal(call) && !loadsKeepingOwner(file, module, call))\n findings.push({\n file: file.path,\n line: file.lineOf(index),\n detail: `a full-module mock of ${module}`,\n });\n }\n }\n }\n for (const { module } of facts.packageRoots) {\n if (!mocked.has(module))\n findings.push({\n file: '(packageRoots)',\n detail: `stale entry: nothing mocks ${module}`,\n });\n }\n return findings;\n },\n },\n ];\n },\n};\n"]}
|