@ttt-productions/repo-guards 0.1.27 → 0.1.29
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.
- package/dist/modules/firestore/client-guards.d.ts +5 -0
- package/dist/modules/firestore/client-guards.d.ts.map +1 -1
- package/dist/modules/firestore/client-guards.js +69 -0
- package/dist/modules/firestore/client-guards.js.map +1 -1
- package/dist/modules/firestore/guards.d.ts +2 -1
- package/dist/modules/firestore/guards.d.ts.map +1 -1
- package/dist/modules/firestore/guards.js +2 -1
- package/dist/modules/firestore/guards.js.map +1 -1
- package/dist/modules/frontend-reads/read-failures.d.ts +3 -0
- package/dist/modules/frontend-reads/read-failures.d.ts.map +1 -1
- package/dist/modules/frontend-reads/read-failures.js +170 -6
- package/dist/modules/frontend-reads/read-failures.js.map +1 -1
- package/dist/modules/repo/typed-mocks.d.ts +5 -0
- package/dist/modules/repo/typed-mocks.d.ts.map +1 -1
- package/dist/modules/repo/typed-mocks.js +41 -3
- package/dist/modules/repo/typed-mocks.js.map +1 -1
- package/package.json +1 -1
|
@@ -13,4 +13,9 @@ export interface FirestoreRuntimeInUiFacts {
|
|
|
13
13
|
}
|
|
14
14
|
export declare function firestoreRuntimeImports(file: FileView): Hit[];
|
|
15
15
|
export declare const firestoreRuntimeInUiGuard: GenericGuardDefinition<FirestoreRuntimeInUiFacts>;
|
|
16
|
+
export interface FirestoreClientWritesFacts {
|
|
17
|
+
sanctioned: readonly Reviewed[];
|
|
18
|
+
}
|
|
19
|
+
export declare function firestoreClientWriteHits(file: FileView): Hit[];
|
|
20
|
+
export declare const firestoreClientWritesGuard: GenericGuardDefinition<FirestoreClientWritesFacts>;
|
|
16
21
|
//# sourceMappingURL=client-guards.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"client-guards.d.ts","sourceRoot":"","sources":["../../../src/modules/firestore/client-guards.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,sBAAsB,EAAE,MAAM,4BAA4B,CAAC;AAEzE,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,sBAAsB,CAAC;AACrD,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,sBAAsB,CAAC;AACrD,OAAO,EAAqB,KAAK,GAAG,EAAE,MAAM,wBAAwB,CAAC;AASrE,wBAAgB,oBAAoB,CAAC,IAAI,EAAE,QAAQ,GAAG,GAAG,EAAE,CA4B1D;AAED,MAAM,WAAW,qBAAqB;IACpC,SAAS,EAAE,SAAS,QAAQ,EAAE,CAAC;CAChC;AAED,eAAO,MAAM,qBAAqB,EAAE,sBAAsB,CAAC,qBAAqB,CAwC/E,CAAC;AAEF,MAAM,WAAW,yBAAyB;IACxC,SAAS,EAAE,SAAS,MAAM,EAAE,CAAC;IAC7B,UAAU,EAAE,SAAS,QAAQ,EAAE,CAAC;CACjC;AAED,wBAAgB,uBAAuB,CAAC,IAAI,EAAE,QAAQ,GAAG,GAAG,EAAE,CAoB7D;AAED,eAAO,MAAM,yBAAyB,EAAE,sBAAsB,CAAC,yBAAyB,CA+CvF,CAAC"}
|
|
1
|
+
{"version":3,"file":"client-guards.d.ts","sourceRoot":"","sources":["../../../src/modules/firestore/client-guards.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,sBAAsB,EAAE,MAAM,4BAA4B,CAAC;AAEzE,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,sBAAsB,CAAC;AACrD,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,sBAAsB,CAAC;AACrD,OAAO,EAAqB,KAAK,GAAG,EAAE,MAAM,wBAAwB,CAAC;AASrE,wBAAgB,oBAAoB,CAAC,IAAI,EAAE,QAAQ,GAAG,GAAG,EAAE,CA4B1D;AAED,MAAM,WAAW,qBAAqB;IACpC,SAAS,EAAE,SAAS,QAAQ,EAAE,CAAC;CAChC;AAED,eAAO,MAAM,qBAAqB,EAAE,sBAAsB,CAAC,qBAAqB,CAwC/E,CAAC;AAEF,MAAM,WAAW,yBAAyB;IACxC,SAAS,EAAE,SAAS,MAAM,EAAE,CAAC;IAC7B,UAAU,EAAE,SAAS,QAAQ,EAAE,CAAC;CACjC;AAED,wBAAgB,uBAAuB,CAAC,IAAI,EAAE,QAAQ,GAAG,GAAG,EAAE,CAoB7D;AAED,eAAO,MAAM,yBAAyB,EAAE,sBAAsB,CAAC,yBAAyB,CA+CvF,CAAC;AAWF,MAAM,WAAW,0BAA0B;IACzC,UAAU,EAAE,SAAS,QAAQ,EAAE,CAAC;CACjC;AAED,wBAAgB,wBAAwB,CAAC,IAAI,EAAE,QAAQ,GAAG,GAAG,EAAE,CAwB9D;AAED,eAAO,MAAM,0BAA0B,EAAE,sBAAsB,CAAC,0BAA0B,CAoCzF,CAAC"}
|
|
@@ -153,4 +153,73 @@ export const firestoreRuntimeInUiGuard = {
|
|
|
153
153
|
];
|
|
154
154
|
},
|
|
155
155
|
};
|
|
156
|
+
const CLIENT_WRITES = new Set([
|
|
157
|
+
'setDoc',
|
|
158
|
+
'updateDoc',
|
|
159
|
+
'deleteDoc',
|
|
160
|
+
'addDoc',
|
|
161
|
+
'writeBatch',
|
|
162
|
+
'runTransaction',
|
|
163
|
+
]);
|
|
164
|
+
export function firestoreClientWriteHits(file) {
|
|
165
|
+
const source = file.sourceFile;
|
|
166
|
+
const hits = [];
|
|
167
|
+
const namespaces = new Set();
|
|
168
|
+
for (const site of moduleImports(source, ['import', 'export'])) {
|
|
169
|
+
if (!FIRESTORE_SDK.test(site.spec) || site.typeOnly)
|
|
170
|
+
continue;
|
|
171
|
+
if (site.namespace)
|
|
172
|
+
namespaces.add(site.namespace);
|
|
173
|
+
for (const name of site.named)
|
|
174
|
+
if (!name.typeOnly && CLIENT_WRITES.has(name.imported))
|
|
175
|
+
hits.push({
|
|
176
|
+
index: site.node.getStart(source),
|
|
177
|
+
detail: `${name.imported} from '${site.spec}'`,
|
|
178
|
+
});
|
|
179
|
+
}
|
|
180
|
+
forEachNode(source, (node) => {
|
|
181
|
+
if (ts.isPropertyAccessExpression(node) &&
|
|
182
|
+
ts.isIdentifier(node.expression) &&
|
|
183
|
+
namespaces.has(node.expression.text) &&
|
|
184
|
+
CLIENT_WRITES.has(node.name.text))
|
|
185
|
+
hits.push({ index: node.getStart(source), detail: node.getText(source) });
|
|
186
|
+
});
|
|
187
|
+
return hits;
|
|
188
|
+
}
|
|
189
|
+
export const firestoreClientWritesGuard = {
|
|
190
|
+
id: 'firestoreClientWrites',
|
|
191
|
+
absentWhen: 'the app stores nothing in Firestore',
|
|
192
|
+
factProblems(facts) {
|
|
193
|
+
const problems = [];
|
|
194
|
+
reviewedProblems(facts?.sanctioned, 'sanctioned', problems);
|
|
195
|
+
return problems;
|
|
196
|
+
},
|
|
197
|
+
build(facts) {
|
|
198
|
+
return [
|
|
199
|
+
reviewedAllowlist({
|
|
200
|
+
title: 'the client never writes Firestore',
|
|
201
|
+
description: 'Every write goes through a callable that checks it on the server; a client SDK write skips that check and ' +
|
|
202
|
+
'every rule the callable enforces. Flagged: a runtime import of a Firestore write function (' +
|
|
203
|
+
`${[...CLIENT_WRITES].join(', ')}) from the Firestore SDK, or a call to one through a namespace import, in ` +
|
|
204
|
+
"the app's client source. Fix: call the callable that owns the write. A sanctioned site is a reviewed entry " +
|
|
205
|
+
'with its why; one that no longer writes is stale.',
|
|
206
|
+
scope: APP_SOURCE,
|
|
207
|
+
match: firestoreClientWriteHits,
|
|
208
|
+
allowlist: facts.sanctioned,
|
|
209
|
+
samples: {
|
|
210
|
+
flags: [
|
|
211
|
+
"import { doc, setDoc } from 'firebase/firestore';",
|
|
212
|
+
"import { writeBatch as batchOf } from 'firebase/firestore';",
|
|
213
|
+
"import * as fs from 'firebase/firestore';\nawait fs.updateDoc(ref, patch);",
|
|
214
|
+
],
|
|
215
|
+
passes: [
|
|
216
|
+
"import { doc, getDoc, onSnapshot } from 'firebase/firestore';",
|
|
217
|
+
"import { type WriteBatch } from 'firebase/firestore';",
|
|
218
|
+
'await callable({ id });',
|
|
219
|
+
],
|
|
220
|
+
},
|
|
221
|
+
}),
|
|
222
|
+
];
|
|
223
|
+
},
|
|
224
|
+
};
|
|
156
225
|
//# sourceMappingURL=client-guards.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"client-guards.js","sourceRoot":"","sources":["../../../src/modules/firestore/client-guards.ts"],"names":[],"mappings":"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","sourcesContent":["import ts from 'typescript';\nimport type { GenericGuardDefinition } from '../../node/suite/config.js';\nimport type { Scope } from '../../node/reader.js';\nimport type { Reviewed } from '../../node/layout.js';\nimport type { FileView } from '../../reader/view.js';\nimport { reviewedAllowlist, type Hit } from '../../shapes/shapes.js';\nimport { forEachNode } from '../../support/ast.js';\nimport { namesProblem, reviewedProblems } from '../../support/facts.js';\nimport { moduleImports } from '../../support/imports.js';\nimport { isUnder } from '../../support/text.js';\n\nconst FIRESTORE_SDK = /^firebase\\/firestore(?:\\/|$)/;\nconst APP_SOURCE: Scope = { roots: ['app'], kinds: ['code'], tests: false };\n\nexport function liveSubscriptionHits(file: FileView): Hit[] {\n const source = file.sourceFile;\n const snapshotNames = new Set(['onSnapshot']);\n const namespaces = new Set<string>();\n for (const site of moduleImports(source, ['import'])) {\n if (!site.topLevel || !FIRESTORE_SDK.test(site.spec)) continue;\n if (site.namespace) namespaces.add(site.namespace);\n for (const name of site.named)\n if (name.imported === 'onSnapshot') snapshotNames.add(name.local);\n }\n const hits: Hit[] = [];\n const at = (node: ts.Node, detail: string) => hits.push({ index: node.getStart(source), detail });\n forEachNode(source, (node) => {\n if (ts.isPropertyAssignment(node) && ts.isObjectLiteralExpression(node.parent)) {\n const key =\n ts.isIdentifier(node.name) || ts.isStringLiteral(node.name) ? node.name.text : null;\n if (key === 'subscribe' && node.initializer.kind !== ts.SyntaxKind.FalseKeyword)\n at(node, `subscribe: ${node.initializer.getText(source)}`);\n } else if (ts.isShorthandPropertyAssignment(node) && node.name.text === 'subscribe') {\n at(node, '{ subscribe }');\n } else if (ts.isCallExpression(node)) {\n const callee = node.expression;\n if (ts.isIdentifier(callee) && snapshotNames.has(callee.text)) at(node, `${callee.text}(…)`);\n else if (ts.isPropertyAccessExpression(callee) && callee.name.text === 'onSnapshot')\n at(node, `${callee.getText(source)}(…)`);\n }\n });\n return hits;\n}\n\nexport interface LiveSubscriptionFacts {\n allowlist: readonly Reviewed[];\n}\n\nexport const liveSubscriptionGuard: GenericGuardDefinition<LiveSubscriptionFacts> = {\n id: 'firestoreLiveSubscriptions',\n absentWhen: 'the app stores nothing in Firestore',\n factProblems(facts) {\n const problems: string[] = [];\n reviewedProblems(facts?.allowlist, 'allowlist', problems);\n return problems;\n },\n build(facts) {\n return [\n reviewedAllowlist({\n title: 'every live Firestore subscription is on the reviewed list',\n description:\n 'A live read bills a document read on every change for as long as it is mounted, so it is a reviewed exception: ' +\n 'a file that declares live mode — a subscribe option set to anything but a literal false, the { subscribe } ' +\n \"shorthand, or an onSnapshot call under any name the file bound the Firestore SDK's to — is on the reviewed \" +\n 'list with why it needs real-time delivery. Fix: read once and keep it fresh through the cache, or add the ' +\n 'reviewed entry. An entry whose file no longer subscribes is stale.',\n scope: APP_SOURCE,\n match: liveSubscriptionHits,\n allowlist: facts.allowlist,\n samples: {\n flags: [\n 'useFirestoreDoc({ docPath, queryKey, subscribe: true });',\n 'useBatchFirestoreDocs({ ids, subscribe: isLive });',\n 'useFirestoreCollection({ collectionPath, subscribe, enabled });',\n \"import { doc, onSnapshot as listen } from 'firebase/firestore';\\nconst stop = listen(doc(db, p), render);\",\n \"import * as fs from 'firebase/firestore';\\nconst stop = fs.onSnapshot(ref, handle);\",\n ],\n passes: [\n \"useBatchFirestoreDocs({ db, collectionPath, ids, transport: 'get' });\",\n 'useFirestoreDoc({ docPath, queryKey, subscribe: false });',\n 'const unsubscribe = cache.subscribe((event) => handle(event));',\n 'return useSyncExternalStore(subscribe, readValue, getServerValue);',\n '// subscribe: true is a reviewed exception\\nconst x = 1;',\n ],\n },\n }),\n ];\n },\n};\n\nexport interface FirestoreRuntimeInUiFacts {\n uiFolders: readonly string[];\n sanctioned: readonly Reviewed[];\n}\n\nexport function firestoreRuntimeImports(file: FileView): Hit[] {\n const source = file.sourceFile;\n const hits: Hit[] = [];\n for (const site of moduleImports(source, ['import', 'export', 'dynamic'])) {\n if (!FIRESTORE_SDK.test(site.spec)) continue;\n const at = site.node.getStart(source);\n if (site.kind === 'import') {\n const typeOnly =\n !site.sideEffect &&\n (site.typeOnly ||\n (!site.defaultName &&\n !site.namespace &&\n site.named.length > 0 &&\n site.named.every((n) => n.typeOnly)));\n if (!typeOnly) hits.push({ index: at, detail: `import from '${site.spec}'` });\n } else if (site.kind === 'export') {\n if (!site.typeOnly) hits.push({ index: at, detail: `re-export from '${site.spec}'` });\n } else hits.push({ index: at, detail: `import('${site.spec}')` });\n }\n return hits;\n}\n\nexport const firestoreRuntimeInUiGuard: GenericGuardDefinition<FirestoreRuntimeInUiFacts> = {\n id: 'firestoreRuntimeInUi',\n absentWhen: 'the app stores nothing in Firestore',\n factProblems(facts) {\n const problems: string[] = [];\n namesProblem(facts?.uiFolders, 'uiFolders', problems);\n reviewedProblems(facts?.sanctioned, 'sanctioned', problems);\n return problems;\n },\n build(facts) {\n const inUi = (path: string) => facts.uiFolders.some((folder) => isUnder(path, folder));\n return [\n reviewedAllowlist({\n title: 'no component or route imports the Firestore runtime',\n description:\n 'A component or route that reaches into Firestore bypasses the cache, the query keys, and every server check ' +\n 'at once. Flagged: a runtime import, re-export, or dynamic import of the Firestore SDK (a type-only import ' +\n 'passes) in the UI folders. Fix: read through a hook, write through a callable. A sanctioned root provider is ' +\n 'a reviewed entry with its why; one that no longer imports the runtime is stale.',\n scope: APP_SOURCE,\n match: (file) => (inUi(file.path) ? firestoreRuntimeImports(file) : []),\n allowlist: facts.sanctioned,\n samples: {\n flags: [\n {\n path: `${facts.uiFolders[0]}/probe.tsx`,\n text: \"import { doc, getDoc } from 'firebase/firestore';\",\n },\n {\n path: `${facts.uiFolders[0]}/probe.tsx`,\n text: 'import {\\n updateDoc,\\n} from \"firebase/firestore\";',\n },\n ],\n passes: [\n {\n path: `${facts.uiFolders[0]}/typed.tsx`,\n text: \"import type { Timestamp } from 'firebase/firestore';\",\n },\n {\n path: `${facts.uiFolders[0]}/typed.tsx`,\n text: \"import { type Timestamp } from 'firebase/firestore';\",\n },\n ],\n },\n }),\n ];\n },\n};\n"]}
|
|
1
|
+
{"version":3,"file":"client-guards.js","sourceRoot":"","sources":["../../../src/modules/firestore/client-guards.ts"],"names":[],"mappings":"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","sourcesContent":["import ts from 'typescript';\nimport type { GenericGuardDefinition } from '../../node/suite/config.js';\nimport type { Scope } from '../../node/reader.js';\nimport type { Reviewed } from '../../node/layout.js';\nimport type { FileView } from '../../reader/view.js';\nimport { reviewedAllowlist, type Hit } from '../../shapes/shapes.js';\nimport { forEachNode } from '../../support/ast.js';\nimport { namesProblem, reviewedProblems } from '../../support/facts.js';\nimport { moduleImports } from '../../support/imports.js';\nimport { isUnder } from '../../support/text.js';\n\nconst FIRESTORE_SDK = /^firebase\\/firestore(?:\\/|$)/;\nconst APP_SOURCE: Scope = { roots: ['app'], kinds: ['code'], tests: false };\n\nexport function liveSubscriptionHits(file: FileView): Hit[] {\n const source = file.sourceFile;\n const snapshotNames = new Set(['onSnapshot']);\n const namespaces = new Set<string>();\n for (const site of moduleImports(source, ['import'])) {\n if (!site.topLevel || !FIRESTORE_SDK.test(site.spec)) continue;\n if (site.namespace) namespaces.add(site.namespace);\n for (const name of site.named)\n if (name.imported === 'onSnapshot') snapshotNames.add(name.local);\n }\n const hits: Hit[] = [];\n const at = (node: ts.Node, detail: string) => hits.push({ index: node.getStart(source), detail });\n forEachNode(source, (node) => {\n if (ts.isPropertyAssignment(node) && ts.isObjectLiteralExpression(node.parent)) {\n const key =\n ts.isIdentifier(node.name) || ts.isStringLiteral(node.name) ? node.name.text : null;\n if (key === 'subscribe' && node.initializer.kind !== ts.SyntaxKind.FalseKeyword)\n at(node, `subscribe: ${node.initializer.getText(source)}`);\n } else if (ts.isShorthandPropertyAssignment(node) && node.name.text === 'subscribe') {\n at(node, '{ subscribe }');\n } else if (ts.isCallExpression(node)) {\n const callee = node.expression;\n if (ts.isIdentifier(callee) && snapshotNames.has(callee.text)) at(node, `${callee.text}(…)`);\n else if (ts.isPropertyAccessExpression(callee) && callee.name.text === 'onSnapshot')\n at(node, `${callee.getText(source)}(…)`);\n }\n });\n return hits;\n}\n\nexport interface LiveSubscriptionFacts {\n allowlist: readonly Reviewed[];\n}\n\nexport const liveSubscriptionGuard: GenericGuardDefinition<LiveSubscriptionFacts> = {\n id: 'firestoreLiveSubscriptions',\n absentWhen: 'the app stores nothing in Firestore',\n factProblems(facts) {\n const problems: string[] = [];\n reviewedProblems(facts?.allowlist, 'allowlist', problems);\n return problems;\n },\n build(facts) {\n return [\n reviewedAllowlist({\n title: 'every live Firestore subscription is on the reviewed list',\n description:\n 'A live read bills a document read on every change for as long as it is mounted, so it is a reviewed exception: ' +\n 'a file that declares live mode — a subscribe option set to anything but a literal false, the { subscribe } ' +\n \"shorthand, or an onSnapshot call under any name the file bound the Firestore SDK's to — is on the reviewed \" +\n 'list with why it needs real-time delivery. Fix: read once and keep it fresh through the cache, or add the ' +\n 'reviewed entry. An entry whose file no longer subscribes is stale.',\n scope: APP_SOURCE,\n match: liveSubscriptionHits,\n allowlist: facts.allowlist,\n samples: {\n flags: [\n 'useFirestoreDoc({ docPath, queryKey, subscribe: true });',\n 'useBatchFirestoreDocs({ ids, subscribe: isLive });',\n 'useFirestoreCollection({ collectionPath, subscribe, enabled });',\n \"import { doc, onSnapshot as listen } from 'firebase/firestore';\\nconst stop = listen(doc(db, p), render);\",\n \"import * as fs from 'firebase/firestore';\\nconst stop = fs.onSnapshot(ref, handle);\",\n ],\n passes: [\n \"useBatchFirestoreDocs({ db, collectionPath, ids, transport: 'get' });\",\n 'useFirestoreDoc({ docPath, queryKey, subscribe: false });',\n 'const unsubscribe = cache.subscribe((event) => handle(event));',\n 'return useSyncExternalStore(subscribe, readValue, getServerValue);',\n '// subscribe: true is a reviewed exception\\nconst x = 1;',\n ],\n },\n }),\n ];\n },\n};\n\nexport interface FirestoreRuntimeInUiFacts {\n uiFolders: readonly string[];\n sanctioned: readonly Reviewed[];\n}\n\nexport function firestoreRuntimeImports(file: FileView): Hit[] {\n const source = file.sourceFile;\n const hits: Hit[] = [];\n for (const site of moduleImports(source, ['import', 'export', 'dynamic'])) {\n if (!FIRESTORE_SDK.test(site.spec)) continue;\n const at = site.node.getStart(source);\n if (site.kind === 'import') {\n const typeOnly =\n !site.sideEffect &&\n (site.typeOnly ||\n (!site.defaultName &&\n !site.namespace &&\n site.named.length > 0 &&\n site.named.every((n) => n.typeOnly)));\n if (!typeOnly) hits.push({ index: at, detail: `import from '${site.spec}'` });\n } else if (site.kind === 'export') {\n if (!site.typeOnly) hits.push({ index: at, detail: `re-export from '${site.spec}'` });\n } else hits.push({ index: at, detail: `import('${site.spec}')` });\n }\n return hits;\n}\n\nexport const firestoreRuntimeInUiGuard: GenericGuardDefinition<FirestoreRuntimeInUiFacts> = {\n id: 'firestoreRuntimeInUi',\n absentWhen: 'the app stores nothing in Firestore',\n factProblems(facts) {\n const problems: string[] = [];\n namesProblem(facts?.uiFolders, 'uiFolders', problems);\n reviewedProblems(facts?.sanctioned, 'sanctioned', problems);\n return problems;\n },\n build(facts) {\n const inUi = (path: string) => facts.uiFolders.some((folder) => isUnder(path, folder));\n return [\n reviewedAllowlist({\n title: 'no component or route imports the Firestore runtime',\n description:\n 'A component or route that reaches into Firestore bypasses the cache, the query keys, and every server check ' +\n 'at once. Flagged: a runtime import, re-export, or dynamic import of the Firestore SDK (a type-only import ' +\n 'passes) in the UI folders. Fix: read through a hook, write through a callable. A sanctioned root provider is ' +\n 'a reviewed entry with its why; one that no longer imports the runtime is stale.',\n scope: APP_SOURCE,\n match: (file) => (inUi(file.path) ? firestoreRuntimeImports(file) : []),\n allowlist: facts.sanctioned,\n samples: {\n flags: [\n {\n path: `${facts.uiFolders[0]}/probe.tsx`,\n text: \"import { doc, getDoc } from 'firebase/firestore';\",\n },\n {\n path: `${facts.uiFolders[0]}/probe.tsx`,\n text: 'import {\\n updateDoc,\\n} from \"firebase/firestore\";',\n },\n ],\n passes: [\n {\n path: `${facts.uiFolders[0]}/typed.tsx`,\n text: \"import type { Timestamp } from 'firebase/firestore';\",\n },\n {\n path: `${facts.uiFolders[0]}/typed.tsx`,\n text: \"import { type Timestamp } from 'firebase/firestore';\",\n },\n ],\n },\n }),\n ];\n },\n};\n\nconst CLIENT_WRITES = new Set([\n 'setDoc',\n 'updateDoc',\n 'deleteDoc',\n 'addDoc',\n 'writeBatch',\n 'runTransaction',\n]);\n\nexport interface FirestoreClientWritesFacts {\n sanctioned: readonly Reviewed[];\n}\n\nexport function firestoreClientWriteHits(file: FileView): Hit[] {\n const source = file.sourceFile;\n const hits: Hit[] = [];\n const namespaces = new Set<string>();\n for (const site of moduleImports(source, ['import', 'export'])) {\n if (!FIRESTORE_SDK.test(site.spec) || site.typeOnly) continue;\n if (site.namespace) namespaces.add(site.namespace);\n for (const name of site.named)\n if (!name.typeOnly && CLIENT_WRITES.has(name.imported))\n hits.push({\n index: site.node.getStart(source),\n detail: `${name.imported} from '${site.spec}'`,\n });\n }\n forEachNode(source, (node) => {\n if (\n ts.isPropertyAccessExpression(node) &&\n ts.isIdentifier(node.expression) &&\n namespaces.has(node.expression.text) &&\n CLIENT_WRITES.has(node.name.text)\n )\n hits.push({ index: node.getStart(source), detail: node.getText(source) });\n });\n return hits;\n}\n\nexport const firestoreClientWritesGuard: GenericGuardDefinition<FirestoreClientWritesFacts> = {\n id: 'firestoreClientWrites',\n absentWhen: 'the app stores nothing in Firestore',\n factProblems(facts) {\n const problems: string[] = [];\n reviewedProblems(facts?.sanctioned, 'sanctioned', problems);\n return problems;\n },\n build(facts) {\n return [\n reviewedAllowlist({\n title: 'the client never writes Firestore',\n description:\n 'Every write goes through a callable that checks it on the server; a client SDK write skips that check and ' +\n 'every rule the callable enforces. Flagged: a runtime import of a Firestore write function (' +\n `${[...CLIENT_WRITES].join(', ')}) from the Firestore SDK, or a call to one through a namespace import, in ` +\n \"the app's client source. Fix: call the callable that owns the write. A sanctioned site is a reviewed entry \" +\n 'with its why; one that no longer writes is stale.',\n scope: APP_SOURCE,\n match: firestoreClientWriteHits,\n allowlist: facts.sanctioned,\n samples: {\n flags: [\n \"import { doc, setDoc } from 'firebase/firestore';\",\n \"import { writeBatch as batchOf } from 'firebase/firestore';\",\n \"import * as fs from 'firebase/firestore';\\nawait fs.updateDoc(ref, patch);\",\n ],\n passes: [\n \"import { doc, getDoc, onSnapshot } from 'firebase/firestore';\",\n \"import { type WriteBatch } from 'firebase/firestore';\",\n 'await callable({ id });',\n ],\n },\n }),\n ];\n },\n};\n"]}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { Absent, GenericGuardDefinition } from '../../node/suite/config.js';
|
|
2
|
-
import { type FirestoreRuntimeInUiFacts, type LiveSubscriptionFacts } from './client-guards.js';
|
|
2
|
+
import { type FirestoreClientWritesFacts, type FirestoreRuntimeInUiFacts, type LiveSubscriptionFacts } from './client-guards.js';
|
|
3
3
|
import { type TestPathFacts, type WriteGuardFacts } from './guard.js';
|
|
4
4
|
import { type CollectionGroupIndexFacts } from './index-guard.js';
|
|
5
5
|
import { type PathAssemblyFacts, type RetiredSegmentFacts } from './path-guards.js';
|
|
@@ -21,6 +21,7 @@ export type FirestoreGuardFacts = {
|
|
|
21
21
|
firestoreCounterDecrements: NoFacts | Absent;
|
|
22
22
|
firestoreLiveSubscriptions: LiveSubscriptionFacts | Absent;
|
|
23
23
|
firestoreRuntimeInUi: FirestoreRuntimeInUiFacts | Absent;
|
|
24
|
+
firestoreClientWrites: FirestoreClientWritesFacts | Absent;
|
|
24
25
|
firestoreRandomUuidSites: RandomUuidFacts | Absent;
|
|
25
26
|
firestoreTimestampNow: NoFacts | Absent;
|
|
26
27
|
rulesClaimNames: RulesClaimFacts;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"guards.d.ts","sourceRoot":"","sources":["../../../src/modules/firestore/guards.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,MAAM,EAAE,sBAAsB,EAAE,MAAM,4BAA4B,CAAC;AACjF,OAAO,
|
|
1
|
+
{"version":3,"file":"guards.d.ts","sourceRoot":"","sources":["../../../src/modules/firestore/guards.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,MAAM,EAAE,sBAAsB,EAAE,MAAM,4BAA4B,CAAC;AACjF,OAAO,EAIL,KAAK,0BAA0B,EAC/B,KAAK,yBAAyB,EAC9B,KAAK,qBAAqB,EAC3B,MAAM,oBAAoB,CAAC;AAC5B,OAAO,EAA6B,KAAK,aAAa,EAAE,KAAK,eAAe,EAAE,MAAM,YAAY,CAAC;AACjG,OAAO,EAA6B,KAAK,yBAAyB,EAAE,MAAM,kBAAkB,CAAC;AAC7F,OAAO,EAIL,KAAK,iBAAiB,EACtB,KAAK,mBAAmB,EACzB,MAAM,kBAAkB,CAAC;AAC1B,OAAO,EAAmB,KAAK,eAAe,EAAE,MAAM,wBAAwB,CAAC;AAC/E,OAAO,EAAoB,KAAK,gBAAgB,EAAE,MAAM,yBAAyB,CAAC;AAClF,OAAO,EAKL,KAAK,eAAe,EACrB,MAAM,oBAAoB,CAAC;AAC5B,OAAO,EAGL,KAAK,0BAA0B,EAC/B,KAAK,wBAAwB,EAC9B,MAAM,qBAAqB,CAAC;AAC7B,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,cAAc,CAAC;AAE5C,MAAM,MAAM,mBAAmB,GAAG;IAChC,eAAe,EAAE,eAAe,GAAG,MAAM,CAAC;IAC1C,kBAAkB,EAAE,aAAa,GAAG,MAAM,CAAC;IAC3C,wBAAwB,EAAE,OAAO,GAAG,MAAM,CAAC;IAC3C,qBAAqB,EAAE,iBAAiB,GAAG,MAAM,CAAC;IAClD,wBAAwB,EAAE,mBAAmB,GAAG,MAAM,CAAC;IACvD,+BAA+B,EAAE,yBAAyB,GAAG,MAAM,CAAC;IACpE,oBAAoB,EAAE,wBAAwB,GAAG,MAAM,CAAC;IACxD,qBAAqB,EAAE,0BAA0B,GAAG,MAAM,CAAC;IAC3D,yBAAyB,EAAE,OAAO,GAAG,MAAM,CAAC;IAC5C,0BAA0B,EAAE,OAAO,GAAG,MAAM,CAAC;IAC7C,0BAA0B,EAAE,qBAAqB,GAAG,MAAM,CAAC;IAC3D,oBAAoB,EAAE,yBAAyB,GAAG,MAAM,CAAC;IACzD,qBAAqB,EAAE,0BAA0B,GAAG,MAAM,CAAC;IAC3D,wBAAwB,EAAE,eAAe,GAAG,MAAM,CAAC;IACnD,qBAAqB,EAAE,OAAO,GAAG,MAAM,CAAC;IACxC,eAAe,EAAE,eAAe,CAAC;IACjC,eAAe,EAAE,gBAAgB,CAAC;CACnC,CAAC;AAEF,eAAO,MAAM,gBAAgB,EAAE,aAAa,CAAC,sBAAsB,CAAC,KAAK,CAAC,CAkBxB,CAAC"}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { firestoreRuntimeInUiGuard, liveSubscriptionGuard, } from './client-guards.js';
|
|
1
|
+
import { firestoreClientWritesGuard, firestoreRuntimeInUiGuard, liveSubscriptionGuard, } from './client-guards.js';
|
|
2
2
|
import { testPathGuard, writeGuard } from './guard.js';
|
|
3
3
|
import { collectionGroupIndexGuard } from './index-guard.js';
|
|
4
4
|
import { literalSegmentGuard, pathAssemblyGuard, retiredSegmentGuard, } from './path-guards.js';
|
|
@@ -19,6 +19,7 @@ export const FIRESTORE_GUARDS = Object.freeze([
|
|
|
19
19
|
counterDecrementGuard,
|
|
20
20
|
liveSubscriptionGuard,
|
|
21
21
|
firestoreRuntimeInUiGuard,
|
|
22
|
+
firestoreClientWritesGuard,
|
|
22
23
|
randomUuidGuard,
|
|
23
24
|
timestampNowGuard,
|
|
24
25
|
rulesClaimGuard,
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"guards.js","sourceRoot":"","sources":["../../../src/modules/firestore/guards.ts"],"names":[],"mappings":"AACA,OAAO,EACL,yBAAyB,EACzB,qBAAqB,
|
|
1
|
+
{"version":3,"file":"guards.js","sourceRoot":"","sources":["../../../src/modules/firestore/guards.ts"],"names":[],"mappings":"AACA,OAAO,EACL,0BAA0B,EAC1B,yBAAyB,EACzB,qBAAqB,GAItB,MAAM,oBAAoB,CAAC;AAC5B,OAAO,EAAE,aAAa,EAAE,UAAU,EAA4C,MAAM,YAAY,CAAC;AACjG,OAAO,EAAE,yBAAyB,EAAkC,MAAM,kBAAkB,CAAC;AAC7F,OAAO,EACL,mBAAmB,EACnB,iBAAiB,EACjB,mBAAmB,GAGpB,MAAM,kBAAkB,CAAC;AAC1B,OAAO,EAAE,eAAe,EAAwB,MAAM,wBAAwB,CAAC;AAC/E,OAAO,EAAE,gBAAgB,EAAyB,MAAM,yBAAyB,CAAC;AAClF,OAAO,EACL,qBAAqB,EACrB,eAAe,EACf,iBAAiB,EACjB,oBAAoB,GAErB,MAAM,oBAAoB,CAAC;AAC5B,OAAO,EACL,0BAA0B,EAC1B,wBAAwB,GAGzB,MAAM,qBAAqB,CAAC;AAuB7B,MAAM,CAAC,MAAM,gBAAgB,GAAiD,MAAM,CAAC,MAAM,CAAC;IAC1F,UAAU;IACV,aAAa;IACb,mBAAmB;IACnB,iBAAiB;IACjB,mBAAmB;IACnB,yBAAyB;IACzB,wBAAwB;IACxB,0BAA0B;IAC1B,oBAAoB;IACpB,qBAAqB;IACrB,qBAAqB;IACrB,yBAAyB;IACzB,0BAA0B;IAC1B,eAAe;IACf,iBAAiB;IACjB,eAAe;IACf,gBAAgB;CAC+B,CAAC,CAAC","sourcesContent":["import type { Absent, GenericGuardDefinition } from '../../node/suite/config.js';\nimport {\n firestoreClientWritesGuard,\n firestoreRuntimeInUiGuard,\n liveSubscriptionGuard,\n type FirestoreClientWritesFacts,\n type FirestoreRuntimeInUiFacts,\n type LiveSubscriptionFacts,\n} from './client-guards.js';\nimport { testPathGuard, writeGuard, type TestPathFacts, type WriteGuardFacts } from './guard.js';\nimport { collectionGroupIndexGuard, type CollectionGroupIndexFacts } from './index-guard.js';\nimport {\n literalSegmentGuard,\n pathAssemblyGuard,\n retiredSegmentGuard,\n type PathAssemblyFacts,\n type RetiredSegmentFacts,\n} from './path-guards.js';\nimport { rulesClaimGuard, type RulesClaimFacts } from './rules-claim-guard.js';\nimport { rulesMirrorGuard, type RulesMirrorFacts } from './rules-mirror-guard.js';\nimport {\n counterDecrementGuard,\n randomUuidGuard,\n timestampNowGuard,\n transactionReadGuard,\n type RandomUuidFacts,\n} from './server-guards.js';\nimport {\n storageObjectMetadataGuard,\n storageUploadOriginGuard,\n type StorageObjectMetadataFacts,\n type StorageUploadOriginFacts,\n} from './storage-guards.js';\nimport type { NoFacts } from './support.js';\n\nexport type FirestoreGuardFacts = {\n firestoreWrites: WriteGuardFacts | Absent;\n firestoreTestPaths: TestPathFacts | Absent;\n firestoreLiteralSegments: NoFacts | Absent;\n firestorePathAssembly: PathAssemblyFacts | Absent;\n firestoreRetiredSegments: RetiredSegmentFacts | Absent;\n firestoreCollectionGroupIndexes: CollectionGroupIndexFacts | Absent;\n storageUploadOrigins: StorageUploadOriginFacts | Absent;\n storageObjectMetadata: StorageObjectMetadataFacts | Absent;\n firestoreTransactionReads: NoFacts | Absent;\n firestoreCounterDecrements: NoFacts | Absent;\n firestoreLiveSubscriptions: LiveSubscriptionFacts | Absent;\n firestoreRuntimeInUi: FirestoreRuntimeInUiFacts | Absent;\n firestoreClientWrites: FirestoreClientWritesFacts | Absent;\n firestoreRandomUuidSites: RandomUuidFacts | Absent;\n firestoreTimestampNow: NoFacts | Absent;\n rulesClaimNames: RulesClaimFacts;\n rulesMirrorCore: RulesMirrorFacts;\n};\n\nexport const FIRESTORE_GUARDS: ReadonlyArray<GenericGuardDefinition<never>> = Object.freeze([\n writeGuard,\n testPathGuard,\n literalSegmentGuard,\n pathAssemblyGuard,\n retiredSegmentGuard,\n collectionGroupIndexGuard,\n storageUploadOriginGuard,\n storageObjectMetadataGuard,\n transactionReadGuard,\n counterDecrementGuard,\n liveSubscriptionGuard,\n firestoreRuntimeInUiGuard,\n firestoreClientWritesGuard,\n randomUuidGuard,\n timestampNowGuard,\n rulesClaimGuard,\n rulesMirrorGuard,\n] as ReadonlyArray<GenericGuardDefinition<never>>);\n"]}
|
|
@@ -15,7 +15,10 @@ export interface ReadStateOwnerFacts {
|
|
|
15
15
|
export declare const readStateOwnerGuard: GenericGuardDefinition<ReadStateOwnerFacts>;
|
|
16
16
|
export interface ReadRetryStateFacts {
|
|
17
17
|
seat: FailureSeat;
|
|
18
|
+
controlPendingProp: string;
|
|
18
19
|
}
|
|
20
|
+
export declare function failureBranchLivePending(file: FileView, controlPendingProp: string): Hit[];
|
|
21
|
+
export declare function liveLoadingBeforeFailure(file: FileView): Hit[];
|
|
19
22
|
export declare function liveFlagRetryStates(file: FileView, seat: FailureSeat): Hit[];
|
|
20
23
|
export declare function failedAnswersWithoutRetrying(file: FileView): Hit[];
|
|
21
24
|
export declare function seatRetriesWithoutPending(file: FileView, seat: FailureSeat): Hit[];
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"read-failures.d.ts","sourceRoot":"","sources":["../../../src/modules/frontend-reads/read-failures.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,sBAAsB,EAAE,MAAM,4BAA4B,CAAC;AAEzE,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,sBAAsB,CAAC;AACrD,OAAO,EAA8C,KAAK,GAAG,EAAE,MAAM,wBAAwB,CAAC;AAC9F,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,sBAAsB,CAAC;AAiBrD,MAAM,WAAW,WAAW;IAC1B,SAAS,EAAE,MAAM,CAAC;IAClB,SAAS,EAAE,MAAM,CAAC;IAClB,WAAW,EAAE,MAAM,CAAC;CACrB;AAWD,MAAM,WAAW,mBAAmB;IAClC,MAAM,EAAE,MAAM,CAAC;IACf,aAAa,EAAE,MAAM,CAAC;IACtB,cAAc,EAAE,MAAM,CAAC;CACxB;AAED,eAAO,MAAM,mBAAmB,EAAE,sBAAsB,CAAC,mBAAmB,CAoC3E,CAAC;AAEF,MAAM,WAAW,mBAAmB;IAClC,IAAI,EAAE,WAAW,CAAC;
|
|
1
|
+
{"version":3,"file":"read-failures.d.ts","sourceRoot":"","sources":["../../../src/modules/frontend-reads/read-failures.ts"],"names":[],"mappings":"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"}
|
|
@@ -75,13 +75,17 @@ function retryStateExpressions(file, fields) {
|
|
|
75
75
|
});
|
|
76
76
|
return found;
|
|
77
77
|
}
|
|
78
|
-
|
|
78
|
+
const FAILURE_NAME = /^(?:isError|error)$/;
|
|
79
|
+
function readsFetchingFlag(expression, file) {
|
|
80
|
+
return readsFlag(expression, file, FETCHING_FLAG);
|
|
81
|
+
}
|
|
82
|
+
function readsFlag(expression, file, flag, seen = new Set()) {
|
|
79
83
|
let reads = false;
|
|
80
84
|
const bound = [];
|
|
81
85
|
forEachNode(expression, (node) => {
|
|
82
86
|
if (!ts.isIdentifier(node) || reads)
|
|
83
87
|
return;
|
|
84
|
-
if (
|
|
88
|
+
if (flag.test(node.text))
|
|
85
89
|
reads = true;
|
|
86
90
|
const parent = node.parent;
|
|
87
91
|
const receiverOrCallee = (ts.isPropertyAccessExpression(parent) &&
|
|
@@ -103,7 +107,7 @@ function readsFetchingFlag(expression, file, seen = new Set()) {
|
|
|
103
107
|
ts.isIdentifier(node.name) &&
|
|
104
108
|
node.name.text === name &&
|
|
105
109
|
node.propertyName &&
|
|
106
|
-
|
|
110
|
+
flag.test(fieldName(node.propertyName) ?? '')) {
|
|
107
111
|
renamed = true;
|
|
108
112
|
}
|
|
109
113
|
else if (!declared &&
|
|
@@ -122,8 +126,160 @@ function readsFetchingFlag(expression, file, seen = new Set()) {
|
|
|
122
126
|
const fromHelper = ts.isCallExpression(value) &&
|
|
123
127
|
ts.isIdentifier(value.expression) &&
|
|
124
128
|
RETRY_STATE_HELPERS.includes(value.expression.text);
|
|
125
|
-
return !fromHelper &&
|
|
129
|
+
return !fromHelper && readsFlag(value, file, flag, seen);
|
|
130
|
+
});
|
|
131
|
+
}
|
|
132
|
+
function branchConditionsAbove(node) {
|
|
133
|
+
const conditions = [];
|
|
134
|
+
for (let child = node, parent = node.parent; parent; child = parent, parent = parent.parent) {
|
|
135
|
+
if (ts.isIfStatement(parent) && parent.thenStatement === child)
|
|
136
|
+
conditions.push(parent.expression);
|
|
137
|
+
else if (ts.isConditionalExpression(parent) && parent.whenTrue === child)
|
|
138
|
+
conditions.push(parent.condition);
|
|
139
|
+
else if (ts.isBinaryExpression(parent) &&
|
|
140
|
+
parent.operatorToken.kind === ts.SyntaxKind.AmpersandAmpersandToken &&
|
|
141
|
+
parent.right === child)
|
|
142
|
+
conditions.push(parent.left);
|
|
143
|
+
}
|
|
144
|
+
return conditions;
|
|
145
|
+
}
|
|
146
|
+
export function failureBranchLivePending(file, controlPendingProp) {
|
|
147
|
+
const sf = file.sourceFile;
|
|
148
|
+
const hits = [];
|
|
149
|
+
forEachNode(sf, (node) => {
|
|
150
|
+
if (!ts.isJsxAttribute(node) ||
|
|
151
|
+
fieldName(node.name) !== controlPendingProp ||
|
|
152
|
+
!node.initializer ||
|
|
153
|
+
!ts.isJsxExpression(node.initializer) ||
|
|
154
|
+
!node.initializer.expression)
|
|
155
|
+
return;
|
|
156
|
+
const pending = node.initializer.expression;
|
|
157
|
+
if (!readsFetchingFlag(pending, file))
|
|
158
|
+
return;
|
|
159
|
+
if (!branchConditionsAbove(node).some((c) => readsFlag(c, file, FAILURE_NAME)))
|
|
160
|
+
return;
|
|
161
|
+
hits.push({
|
|
162
|
+
index: node.getStart(sf),
|
|
163
|
+
detail: `a failure's retry pending from a fetching flag: ${pending.getText(sf).replace(/\s+/g, ' ').slice(0, 120)}`,
|
|
164
|
+
});
|
|
165
|
+
});
|
|
166
|
+
return hits;
|
|
167
|
+
}
|
|
168
|
+
const LOADING_FIELD = 'isLoading';
|
|
169
|
+
const READ_FAILURE_FIELDS = new Set(['error', 'isError', 'retryState']);
|
|
170
|
+
const HELD_RECEIVER = /retry/i;
|
|
171
|
+
function scopeOf(node) {
|
|
172
|
+
return enclosingFunctionOf(node) ?? node.getSourceFile();
|
|
173
|
+
}
|
|
174
|
+
function destructuredBindings(sf) {
|
|
175
|
+
const bindings = new Map();
|
|
176
|
+
forEachNode(sf, (node) => {
|
|
177
|
+
if (!ts.isVariableDeclaration(node) ||
|
|
178
|
+
!ts.isObjectBindingPattern(node.name) ||
|
|
179
|
+
!node.initializer ||
|
|
180
|
+
!ts.isIdentifier(node.initializer))
|
|
181
|
+
return;
|
|
182
|
+
const receiver = node.initializer.text;
|
|
183
|
+
const scope = scopeOf(node);
|
|
184
|
+
for (const element of node.name.elements) {
|
|
185
|
+
if (!ts.isIdentifier(element.name))
|
|
186
|
+
continue;
|
|
187
|
+
const field = element.propertyName ? fieldName(element.propertyName) : element.name.text;
|
|
188
|
+
if (!field)
|
|
189
|
+
continue;
|
|
190
|
+
const named = bindings.get(element.name.text) ?? [];
|
|
191
|
+
named.push({ field, receiver, scope });
|
|
192
|
+
bindings.set(element.name.text, named);
|
|
193
|
+
}
|
|
194
|
+
});
|
|
195
|
+
return bindings;
|
|
196
|
+
}
|
|
197
|
+
function declaresParameter(fn, name) {
|
|
198
|
+
let declares = false;
|
|
199
|
+
for (const parameter of fn.parameters)
|
|
200
|
+
forEachNode(parameter.name, (node) => {
|
|
201
|
+
if (ts.isIdentifier(node) && node.text === name)
|
|
202
|
+
declares = true;
|
|
203
|
+
});
|
|
204
|
+
return declares;
|
|
205
|
+
}
|
|
206
|
+
function bindingFor(use, bindings) {
|
|
207
|
+
const named = bindings.get(use.text);
|
|
208
|
+
if (!named)
|
|
209
|
+
return undefined;
|
|
210
|
+
for (let scope = scopeOf(use); scope;) {
|
|
211
|
+
const found = named.find((binding) => binding.scope === scope);
|
|
212
|
+
if (found)
|
|
213
|
+
return found;
|
|
214
|
+
if (ts.isFunctionLike(scope) && declaresParameter(scope, use.text))
|
|
215
|
+
return undefined;
|
|
216
|
+
scope = ts.isSourceFile(scope) ? undefined : scopeOf(scope);
|
|
217
|
+
}
|
|
218
|
+
return undefined;
|
|
219
|
+
}
|
|
220
|
+
function rootIdentifier(node) {
|
|
221
|
+
let current = node;
|
|
222
|
+
while (ts.isPropertyAccessExpression(current) || ts.isNonNullExpression(current))
|
|
223
|
+
current = current.expression;
|
|
224
|
+
return ts.isIdentifier(current) ? current.text : null;
|
|
225
|
+
}
|
|
226
|
+
function readFieldsIn(scope, bindings, wanted) {
|
|
227
|
+
const sf = scope.getSourceFile();
|
|
228
|
+
const found = [];
|
|
229
|
+
forEachNode(scope, (node) => {
|
|
230
|
+
if (ts.isPropertyAccessExpression(node) && wanted(node.name.text)) {
|
|
231
|
+
const receiver = rootIdentifier(node.expression);
|
|
232
|
+
if (receiver && !HELD_RECEIVER.test(node.expression.getText(sf)))
|
|
233
|
+
found.push({ receiver, index: node.getStart(sf) });
|
|
234
|
+
}
|
|
235
|
+
else if (ts.isIdentifier(node) &&
|
|
236
|
+
!(ts.isPropertyAccessExpression(node.parent) && node.parent.name === node)) {
|
|
237
|
+
const bound = bindingFor(node, bindings);
|
|
238
|
+
if (bound && wanted(bound.field) && !HELD_RECEIVER.test(bound.receiver))
|
|
239
|
+
found.push({ receiver: bound.receiver, index: node.getStart(sf) });
|
|
240
|
+
}
|
|
241
|
+
});
|
|
242
|
+
return found;
|
|
243
|
+
}
|
|
244
|
+
function enclosingFunctionOf(node) {
|
|
245
|
+
for (let p = node.parent; p; p = p.parent)
|
|
246
|
+
if (ts.isFunctionLike(p))
|
|
247
|
+
return p;
|
|
248
|
+
return undefined;
|
|
249
|
+
}
|
|
250
|
+
function branchCondition(node) {
|
|
251
|
+
if (ts.isIfStatement(node))
|
|
252
|
+
return node.expression;
|
|
253
|
+
if (ts.isConditionalExpression(node))
|
|
254
|
+
return node.condition;
|
|
255
|
+
if (ts.isBinaryExpression(node) &&
|
|
256
|
+
node.operatorToken.kind === ts.SyntaxKind.AmpersandAmpersandToken &&
|
|
257
|
+
ts.isJsxExpression(node.parent))
|
|
258
|
+
return node.left;
|
|
259
|
+
return undefined;
|
|
260
|
+
}
|
|
261
|
+
export function liveLoadingBeforeFailure(file) {
|
|
262
|
+
const sf = file.sourceFile;
|
|
263
|
+
const bindings = destructuredBindings(sf);
|
|
264
|
+
const isLoading = (field) => field === LOADING_FIELD;
|
|
265
|
+
const isFailure = (field) => READ_FAILURE_FIELDS.has(field);
|
|
266
|
+
const hits = [];
|
|
267
|
+
forEachNode(sf, (node) => {
|
|
268
|
+
const condition = branchCondition(node);
|
|
269
|
+
const fn = condition && enclosingFunctionOf(node);
|
|
270
|
+
if (!condition || !fn)
|
|
271
|
+
return;
|
|
272
|
+
const tested = new Set(readFieldsIn(condition, bindings, isFailure).map((r) => r.receiver));
|
|
273
|
+
const end = condition.getEnd();
|
|
274
|
+
const later = readFieldsIn(fn, bindings, isFailure).filter((r) => r.index >= end);
|
|
275
|
+
const loading = readFieldsIn(condition, bindings, isLoading).find((r) => !tested.has(r.receiver) && later.some((f) => f.receiver === r.receiver));
|
|
276
|
+
if (loading)
|
|
277
|
+
hits.push({
|
|
278
|
+
index: loading.index,
|
|
279
|
+
detail: `a live loading flag tested before the read's failure: ${condition.getText(sf).replace(/\s+/g, ' ').slice(0, 120)}`,
|
|
280
|
+
});
|
|
126
281
|
});
|
|
282
|
+
return hits;
|
|
127
283
|
}
|
|
128
284
|
export function liveFlagRetryStates(file, seat) {
|
|
129
285
|
const code = file.code;
|
|
@@ -210,7 +366,11 @@ export function seatRetriesWithoutPending(file, seat) {
|
|
|
210
366
|
export const readRetryStateGuard = {
|
|
211
367
|
id: 'readRetryState',
|
|
212
368
|
absentWhen: false,
|
|
213
|
-
factProblems
|
|
369
|
+
factProblems(facts) {
|
|
370
|
+
const problems = seatProblems(facts?.seat);
|
|
371
|
+
identifierProblem(facts?.controlPendingProp, 'controlPendingProp', problems);
|
|
372
|
+
return problems;
|
|
373
|
+
},
|
|
214
374
|
build: (facts) => [
|
|
215
375
|
{
|
|
216
376
|
title: "a read's retry state comes from its one owner, never the live fetching flags",
|
|
@@ -220,7 +380,9 @@ export const readRetryStateGuard = {
|
|
|
220
380
|
`(exposed from the read's hook) and pass its ${RETRY_STATE_FIELD}; a lookup hands out its held ${HELD_ANSWER_FIELD}, and a hook ` +
|
|
221
381
|
`that answers with a status gives its failed answer the held ${HELD_ANSWER_FIELD} beside the error, never one built from a ` +
|
|
222
382
|
`fetching flag; and every <${facts.seat.component}> given ${facts.seat.retryProp} also takes ${facts.seat.pendingProp}, so the ` +
|
|
223
|
-
|
|
383
|
+
`pressed retry shows it is running. The same live flags decide no failure surface: a control's ${facts.controlPendingProp} ` +
|
|
384
|
+
"inside a failure branch reads the held isRetrying, never a fetching flag, and a branch never tests a read's live isLoading " +
|
|
385
|
+
"ahead of that read's failure — the held retryState decides the failure first, then its isLoading.",
|
|
224
386
|
run(context) {
|
|
225
387
|
const findings = [];
|
|
226
388
|
for (const file of context.files(APP_CODE)) {
|
|
@@ -228,6 +390,8 @@ export const readRetryStateGuard = {
|
|
|
228
390
|
...liveFlagRetryStates(file, facts.seat),
|
|
229
391
|
...seatRetriesWithoutPending(file, facts.seat),
|
|
230
392
|
...failedAnswersWithoutRetrying(file),
|
|
393
|
+
...failureBranchLivePending(file, facts.controlPendingProp),
|
|
394
|
+
...liveLoadingBeforeFailure(file),
|
|
231
395
|
];
|
|
232
396
|
for (const hit of hits) {
|
|
233
397
|
findings.push({
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"read-failures.js","sourceRoot":"","sources":["../../../src/modules/frontend-reads/read-failures.ts"],"names":[],"mappings":"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","sourcesContent":["import ts from 'typescript';\nimport type { GenericGuardDefinition } from '../../node/suite/config.js';\nimport type { Finding, GuardContext } from '../../guard.js';\nimport type { FileView } from '../../reader/view.js';\nimport { matchPattern, ownerOnly, reviewedAllowlist, type Hit } from '../../shapes/shapes.js';\nimport type { Reviewed } from '../../node/layout.js';\nimport { forEachNode, lineOfNode } from '../../support/ast.js';\nimport { identifierProblem, pathProblem, reviewedProblems, isList } from '../../support/facts.js';\nimport { escapeRegExp, isUnder } from '../../support/text.js';\nimport {\n APP_CODE,\n filesUnder,\n identifierListProblem,\n isHookName,\n pathListProblem,\n requiredFile,\n} from './shared.js';\n\nconst RETRY_STATE_HELPERS = ['useReadRetryState', 'useReadRetryStates'] as const;\nconst RETRY_STATE_FIELD = 'isRetrying';\nconst HELD_ANSWER_FIELD = 'retrying';\n\nexport interface FailureSeat {\n component: string;\n retryProp: string;\n pendingProp: string;\n}\n\nfunction seatProblems(seat: FailureSeat | undefined): string[] {\n if (!seat || typeof seat !== 'object') return ['seat is missing'];\n const problems: string[] = [];\n identifierProblem(seat.component, 'seat.component', problems);\n identifierProblem(seat.retryProp, 'seat.retryProp', problems);\n identifierProblem(seat.pendingProp, 'seat.pendingProp', problems);\n return problems;\n}\n\nexport interface ReadStateOwnerFacts {\n module: string;\n seatComponent: string;\n laterPageRetry: string;\n}\n\nexport const readStateOwnerGuard: GenericGuardDefinition<ReadStateOwnerFacts> = {\n id: 'readStateOwner',\n absentWhen: false,\n factProblems(facts) {\n const problems: string[] = [];\n pathProblem(facts?.module, 'module', problems);\n identifierProblem(facts?.seatComponent, 'seatComponent', problems);\n identifierProblem(facts?.laterPageRetry, 'laterPageRetry', problems);\n return problems;\n },\n build: (facts) => [\n ownerOnly({\n title: 'read failures render only through the read-state module',\n description:\n `Every read's pending, failed, and empty states render through ${facts.module}, so every failure offers Try again ` +\n `held through its retry. Nothing outside it renders <${facts.seatComponent}> or calls ${facts.laterPageRetry}(); ` +\n 'render the read through the module instead.',\n scope: APP_CODE,\n match: matchPattern(\n new RegExp(\n `<${escapeRegExp(facts.seatComponent)}\\\\b|\\\\b${escapeRegExp(facts.laterPageRetry)}\\\\s*\\\\(`,\n ),\n ),\n owners: [facts.module],\n samples: {\n flags: [\n `return <${facts.seatComponent} thing=\"rows\" />;`,\n `const retry = ${facts.laterPageRetry}(list);`,\n ],\n passes: [\n `// <${facts.seatComponent} /> named in prose only\\nconst rows = 1;`,\n `import { ${facts.seatComponent} } from './seat';`,\n ],\n },\n }),\n ],\n};\n\nexport interface ReadRetryStateFacts {\n seat: FailureSeat;\n}\n\nconst FAILURE_FLAG = String.raw`[\\w$.?]*\\b(?:isError|error)\\b`;\nconst FETCHING = String.raw`[\\w$.?]*\\bisFetching\\b`;\nconst LIVE_FLAG_PAIRS: ReadonlyArray<{ name: string; re: RegExp }> = [\n { name: 'failure flag && isFetching', re: new RegExp(`${FAILURE_FLAG}\\\\s*&&\\\\s*${FETCHING}`) },\n { name: 'isFetching && failure flag', re: new RegExp(`${FETCHING}\\\\s*&&\\\\s*${FAILURE_FLAG}`) },\n];\n\nconst FETCHING_FLAG =\n /^(?:is(?:Re)?[Ff]etching|fetchingCount|useIsFetching|fetchStatus|sourceState)$/;\n\nconst fieldName = (name: ts.Node): string | null =>\n ts.isIdentifier(name) || ts.isStringLiteralLike(name) ? name.text : null;\n\nfunction retryStateExpressions(\n file: FileView,\n fields: readonly string[],\n): Array<{ index: number; expression: ts.Expression }> {\n const sf = file.sourceFile;\n const wanted = new Set(fields);\n const found: Array<{ index: number; expression: ts.Expression }> = [];\n const add = (name: ts.Node, expression: ts.Expression | undefined) => {\n if (expression && wanted.has(fieldName(name) ?? ''))\n found.push({ index: name.getStart(sf), expression });\n };\n forEachNode(sf, (node) => {\n if (ts.isPropertyAssignment(node) || ts.isVariableDeclaration(node))\n add(node.name, node.initializer);\n else if (ts.isJsxAttribute(node) && node.initializer && ts.isJsxExpression(node.initializer))\n add(node.name, node.initializer.expression);\n else if (ts.isBinaryExpression(node) && node.operatorToken.kind === ts.SyntaxKind.EqualsToken) {\n add(ts.isPropertyAccessExpression(node.left) ? node.left.name : node.left, node.right);\n }\n });\n return found;\n}\n\nfunction readsFetchingFlag(\n expression: ts.Node,\n file: FileView,\n seen: Set<string> = new Set(),\n): boolean {\n let reads = false;\n const bound: string[] = [];\n forEachNode(expression, (node) => {\n if (!ts.isIdentifier(node) || reads) return;\n if (FETCHING_FLAG.test(node.text)) reads = true;\n const parent = node.parent;\n const receiverOrCallee =\n (ts.isPropertyAccessExpression(parent) &&\n (parent.expression === node || parent.name === node)) ||\n (ts.isCallExpression(parent) && parent.expression === node);\n if (!receiverOrCallee) bound.push(node.text);\n });\n if (reads) return true;\n return bound.some((name) => {\n if (seen.has(name)) return false;\n seen.add(name);\n let declared: ts.Expression | undefined;\n let renamed = false;\n forEachNode(file.sourceFile, (node) => {\n if (\n ts.isBindingElement(node) &&\n ts.isIdentifier(node.name) &&\n node.name.text === name &&\n node.propertyName &&\n FETCHING_FLAG.test(fieldName(node.propertyName) ?? '')\n ) {\n renamed = true;\n } else if (\n !declared &&\n ts.isVariableDeclaration(node) &&\n ts.isIdentifier(node.name) &&\n node.name.text === name &&\n node.initializer\n ) {\n declared = node.initializer;\n }\n });\n if (renamed) return true;\n if (!declared) return false;\n const value = declared;\n const fromHelper =\n ts.isCallExpression(value) &&\n ts.isIdentifier(value.expression) &&\n (RETRY_STATE_HELPERS as readonly string[]).includes(value.expression.text);\n return !fromHelper && readsFetchingFlag(value, file, seen);\n });\n}\n\nexport function liveFlagRetryStates(file: FileView, seat: FailureSeat): Hit[] {\n const code = file.code;\n const hits = new Map<number, Hit>();\n let offset = 0;\n for (const line of code.split('\\n')) {\n for (const shape of LIVE_FLAG_PAIRS) {\n const m = shape.re.exec(line);\n if (m)\n hits.set(file.lineOf(offset + m.index), {\n index: offset + m.index,\n detail: `${shape.name}: ${line.trim().slice(0, 120)}`,\n });\n }\n offset += line.length + 1;\n }\n for (const { index, expression } of retryStateExpressions(file, [\n RETRY_STATE_FIELD,\n HELD_ANSWER_FIELD,\n seat.pendingProp,\n ])) {\n if (!readsFetchingFlag(expression, file)) continue;\n const line = file.lineOf(index);\n if (!hits.has(line))\n hits.set(line, {\n index,\n detail: `retry state from a fetching flag: ${expression.getText(file.sourceFile).replace(/\\s+/g, ' ').slice(0, 120)}`,\n });\n }\n return [...hits.values()].sort((a, b) => a.index - b.index);\n}\n\nconst isHookFunction = (node: ts.Node, sf: ts.SourceFile): boolean => {\n if ((ts.isFunctionDeclaration(node) || ts.isFunctionExpression(node)) && node.name)\n return isHookName(node.name.text);\n if (\n (ts.isArrowFunction(node) || ts.isFunctionExpression(node)) &&\n ts.isVariableDeclaration(node.parent)\n )\n return isHookName(node.parent.name.getText(sf));\n return false;\n};\n\nexport function failedAnswersWithoutRetrying(file: FileView): Hit[] {\n const sf = file.sourceFile;\n const hits: Hit[] = [];\n forEachNode(sf, (node) => {\n if (!ts.isObjectLiteralExpression(node)) return;\n const named = (name: string) =>\n node.properties.find(\n (p) =>\n (ts.isPropertyAssignment(p) || ts.isShorthandPropertyAssignment(p)) &&\n p.name.getText(sf) === name,\n );\n const status = named('status');\n if (\n !status ||\n !ts.isPropertyAssignment(status) ||\n !ts.isStringLiteralLike(status.initializer) ||\n status.initializer.text !== 'failed'\n )\n return;\n if (!named('error') || named(HELD_ANSWER_FIELD) || node.properties.some(ts.isSpreadAssignment))\n return;\n let inHook = false;\n for (let p: ts.Node | undefined = node.parent; p && !inHook; p = p.parent)\n inHook = isHookFunction(p, sf);\n if (inHook)\n hits.push({\n index: node.getStart(sf),\n detail: `a failed answer with no ${HELD_ANSWER_FIELD}: ${node.getText(sf).replace(/\\s+/g, ' ').slice(0, 120)}`,\n });\n });\n return hits;\n}\n\nexport function seatRetriesWithoutPending(file: FileView, seat: FailureSeat): Hit[] {\n const hits: Hit[] = [];\n const element = new RegExp(`<${escapeRegExp(seat.component)}\\\\b[\\\\s\\\\S]*?\\\\/>`, 'g');\n for (const match of file.code.matchAll(element)) {\n const text = match[0];\n if (\n new RegExp(`\\\\b${escapeRegExp(seat.retryProp)}=`).test(text) &&\n !new RegExp(`\\\\b${escapeRegExp(seat.pendingProp)}=`).test(text) &&\n !/\\{\\s*\\.\\.\\./.test(text)\n ) {\n hits.push({\n index: match.index ?? 0,\n detail: `<${seat.component} ${seat.retryProp}=…> without ${seat.pendingProp}`,\n });\n }\n }\n return hits;\n}\n\nexport const readRetryStateGuard: GenericGuardDefinition<ReadRetryStateFacts> = {\n id: 'readRetryState',\n absentWhen: false,\n factProblems: (facts) => seatProblems(facts?.seat),\n build: (facts) => [\n {\n title: \"a read's retry state comes from its one owner, never the live fetching flags\",\n description:\n 'TanStack Query puts a query that never succeeded back to pending and clears its error the moment a refetch starts, so a retry ' +\n 'state built from isFetching / isRefetching / fetchStatus with isError or error reads false for the retry of a failed first read, ' +\n `and the failure turns into a loading state under the pressed retry. Hold the failure with query-core's ${RETRY_STATE_HELPERS.join(' / ')} ` +\n `(exposed from the read's hook) and pass its ${RETRY_STATE_FIELD}; a lookup hands out its held ${HELD_ANSWER_FIELD}, and a hook ` +\n `that answers with a status gives its failed answer the held ${HELD_ANSWER_FIELD} beside the error, never one built from a ` +\n `fetching flag; and every <${facts.seat.component}> given ${facts.seat.retryProp} also takes ${facts.seat.pendingProp}, so the ` +\n 'pressed retry shows it is running.',\n run(context: GuardContext): Finding[] {\n const findings: Finding[] = [];\n for (const file of context.files(APP_CODE)) {\n const hits = [\n ...liveFlagRetryStates(file, facts.seat),\n ...seatRetriesWithoutPending(file, facts.seat),\n ...failedAnswersWithoutRetrying(file),\n ];\n for (const hit of hits) {\n findings.push({\n file: file.path,\n line: file.lineOf(hit.index),\n detail: hit.detail ?? '',\n });\n }\n }\n return findings;\n },\n },\n ],\n};\n\nexport interface HookReadDefaultFacts {\n hooksTree: string;\n readCollapsers: readonly string[];\n}\n\nconst FAILURE_FIELDS = new Set(['error', 'isError', 'status']);\n\nfunction isEmptyAnswer(node: ts.Expression): boolean {\n return (\n (ts.isArrayLiteralExpression(node) && node.elements.length === 0) ||\n (ts.isObjectLiteralExpression(node) && node.properties.length === 0) ||\n node.kind === ts.SyntaxKind.FalseKeyword ||\n node.kind === ts.SyntaxKind.NullKeyword ||\n (ts.isNumericLiteral(node) && node.text === '0') ||\n (ts.isStringLiteral(node) && node.text === '')\n );\n}\n\nfunction enclosingFunction(node: ts.Node): ts.Node | undefined {\n let current: ts.Node | undefined = node.parent;\n while (current && !ts.isFunctionLike(current)) current = current.parent;\n return current;\n}\n\nfunction readsFailureOf(scope: ts.Node, receiver: string, sf: ts.SourceFile): boolean {\n let found = false;\n forEachNode(scope, (n) => {\n if (\n ts.isPropertyAccessExpression(n) &&\n FAILURE_FIELDS.has(n.name.text) &&\n n.expression.getText(sf) === receiver\n )\n found = true;\n if (\n ts.isCallExpression(n) &&\n n.expression.getText(sf) === RETRY_STATE_HELPERS[0] &&\n n.arguments[0]?.getText(sf) === receiver\n )\n found = true;\n });\n return found;\n}\n\nfunction exportedHookBody(\n node: ts.Node,\n sf: ts.SourceFile,\n): { name: string; body: ts.ConciseBody } | undefined {\n const isExported = (n: ts.Node) =>\n ts.canHaveModifiers(n) &&\n (ts.getModifiers(n) ?? []).some((m) => m.kind === ts.SyntaxKind.ExportKeyword);\n if (\n ts.isFunctionDeclaration(node) &&\n node.name &&\n isHookName(node.name.text) &&\n isExported(node) &&\n node.body\n ) {\n return { name: node.name.text, body: node.body };\n }\n if (\n ts.isVariableDeclaration(node) &&\n ts.isIdentifier(node.name) &&\n isHookName(node.name.text) &&\n node.initializer &&\n (ts.isArrowFunction(node.initializer) || ts.isFunctionExpression(node.initializer)) &&\n isExported(node.parent.parent)\n ) {\n return { name: node.name.getText(sf), body: node.initializer.body };\n }\n return undefined;\n}\n\nfunction returnedExpressions(body: ts.ConciseBody): ts.Expression[] {\n if (!ts.isBlock(body)) return [body];\n const out: ts.Expression[] = [];\n const visit = (n: ts.Node) => {\n if (ts.isFunctionLike(n)) return;\n if (ts.isReturnStatement(n) && n.expression) out.push(n.expression);\n ts.forEachChild(n, visit);\n };\n ts.forEachChild(body, visit);\n return out;\n}\n\nfunction readsData(node: ts.Node): boolean {\n let found = false;\n forEachNode(node, (n) => {\n if (ts.isPropertyAccessExpression(n) && n.name.text === 'data') found = true;\n });\n return found;\n}\n\nconst COMPARISONS = new Set([\n ts.SyntaxKind.EqualsEqualsEqualsToken,\n ts.SyntaxKind.ExclamationEqualsEqualsToken,\n ts.SyntaxKind.GreaterThanToken,\n ts.SyntaxKind.LessThanToken,\n ts.SyntaxKind.GreaterThanEqualsToken,\n ts.SyntaxKind.LessThanEqualsToken,\n]);\n\nfunction collapsesReadToBoolean(\n expr: ts.Expression,\n sf: ts.SourceFile,\n collapsers: ReadonlySet<string>,\n): boolean {\n let e = expr;\n while (ts.isParenthesizedExpression(e)) e = e.expression;\n if (ts.isCallExpression(e) && collapsers.has(e.expression.getText(sf))) return true;\n if (ts.isPrefixUnaryExpression(e) && e.operator === ts.SyntaxKind.ExclamationToken)\n return readsData(e);\n if (ts.isBinaryExpression(e) && COMPARISONS.has(e.operatorToken.kind)) return readsData(e);\n if (ts.isCallExpression(e) && e.expression.getText(sf) === 'Boolean') return readsData(e);\n return false;\n}\n\nexport function hookReadDefaults(\n file: FileView,\n collapsers: readonly string[],\n): Array<{ line: number; detail: string }> {\n const sf = file.sourceFile;\n const names = new Set(collapsers);\n const hits: Array<{ line: number; detail: string }> = [];\n forEachNode(sf, (node) => {\n if (\n ts.isBinaryExpression(node) &&\n (node.operatorToken.kind === ts.SyntaxKind.QuestionQuestionToken ||\n node.operatorToken.kind === ts.SyntaxKind.BarBarToken) &&\n ts.isPropertyAccessExpression(node.left) &&\n node.left.name.text === 'data' &&\n isEmptyAnswer(node.right)\n ) {\n const scope = enclosingFunction(node);\n if (!scope || !readsFailureOf(scope, node.left.expression.getText(sf), sf)) {\n hits.push({\n line: lineOfNode(file, node),\n detail: `a defaulted read with no failure beside it: ${node.getText(sf)}`,\n });\n }\n }\n if (\n ts.isVariableDeclaration(node) &&\n node.initializer &&\n ts.isObjectBindingPattern(node.name)\n ) {\n const init = node.initializer;\n if (ts.isCallExpression(init) && isHookName(init.expression.getText(sf))) {\n const keyOf = (el: ts.BindingElement) => (el.propertyName ?? el.name).getText(sf);\n const elements = node.name.elements;\n const defaultsData = elements.some(\n (el) => keyOf(el) === 'data' && el.initializer !== undefined,\n );\n const carriesFailure = elements.some(\n (el) => el.dotDotDotToken || FAILURE_FIELDS.has(keyOf(el)),\n );\n if (defaultsData && !carriesFailure)\n hits.push({\n line: lineOfNode(file, node),\n detail: `a defaulted read with no failure beside it: ${node.name.getText(sf)}`,\n });\n }\n }\n const hook = exportedHookBody(node, sf);\n if (hook) {\n for (const returned of returnedExpressions(hook.body)) {\n if (collapsesReadToBoolean(returned, sf, names))\n hits.push({\n line: lineOfNode(file, returned),\n detail: `${hook.name} returns a boolean from a read`,\n });\n }\n }\n });\n return hits;\n}\n\nexport const hookReadDefaultGuard: GenericGuardDefinition<HookReadDefaultFacts> = {\n id: 'hookReadDefault',\n absentWhen: false,\n factProblems(facts) {\n const problems: string[] = [];\n pathProblem(facts?.hooksTree, 'hooksTree', problems);\n identifierListProblem(facts?.readCollapsers, 'readCollapsers', problems, { nonEmpty: false });\n return problems;\n },\n build: (facts) => [\n {\n title: 'a hook never returns a default its consumers cannot tell from data',\n description:\n `In ${facts.hooksTree}, a read's data defaulted to an empty answer (q.data ?? [], const { data = [] } = useQ()) is allowed only ` +\n `where the same function also carries that read's failure (error / isError / status, or the read handed to ${RETRY_STATE_HELPERS[0]}), ` +\n 'and no exported hook collapses a read into a bare boolean, where an unknown read would read as \"no\". Return the read state ' +\n '(pending / failed / answered) or carry the failure beside the defaulted data.',\n run(context) {\n const { files, findings } = filesUnder(context, [facts.hooksTree]);\n for (const file of files)\n for (const hit of hookReadDefaults(file, facts.readCollapsers))\n findings.push({ file: file.path, ...hit });\n return findings;\n },\n },\n ],\n};\n\nexport interface DefaultedReadBindingFacts {\n surfaceTrees: readonly string[];\n heldFailureFields: readonly string[];\n allowlist: ReadonlyArray<Reviewed & { gate: ReadGate }>;\n}\n\nexport interface ReadGate {\n file: string;\n code: string;\n}\n\nconst squashed = (text: string) => text.replace(/\\s+/g, ' ').trim();\n\nfunction gateProblems(allowlist: unknown): string[] {\n if (!isList(allowlist)) return [];\n const problems: string[] = [];\n for (const entry of allowlist as Array<{ path?: string; gate?: Partial<ReadGate> }>) {\n const what = `allowlist entry ${String(entry?.path)}`;\n if (!entry?.gate || typeof entry.gate !== 'object')\n problems.push(\n `${what} names no gate (the file and the code that hold its surface back until the read answered)`,\n );\n else {\n pathProblem(entry.gate.file, `${what}'s gate file`, problems);\n if (typeof entry.gate.code !== 'string' || !entry.gate.code.trim())\n problems.push(`${what}'s gate names no code`);\n }\n }\n return problems;\n}\n\nfunction missingGates(\n context: GuardContext,\n allowlist: DefaultedReadBindingFacts['allowlist'],\n): Finding[] {\n const findings: Finding[] = [];\n for (const { path, gate } of allowlist) {\n if (!context.reader.exists(gate.file))\n findings.push({\n file: gate.file,\n detail: `the gate ${path} relies on is gone: the file no longer exists`,\n });\n else if (!squashed(context.file(gate.file).code).includes(squashed(gate.code))) {\n findings.push({\n file: gate.file,\n detail: `the gate ${path} relies on is gone: no longer holds ${squashed(gate.code)}`,\n });\n }\n }\n return findings;\n}\n\nexport function unguardedDataDefaults(file: FileView, heldFailureFields: readonly string[]): Hit[] {\n const sf = file.sourceFile;\n const failureBindings = new Set([...FAILURE_FIELDS, ...heldFailureFields]);\n const hits: Hit[] = [];\n forEachNode(sf, (node) => {\n if (\n !ts.isVariableDeclaration(node) ||\n !node.initializer ||\n !ts.isObjectBindingPattern(node.name)\n )\n return;\n let init: ts.Expression = node.initializer;\n if (ts.isAwaitExpression(init)) init = init.expression;\n if (!ts.isCallExpression(init)) return;\n const callee = init.expression.getText(sf);\n if (!isHookName(callee)) return;\n const elements = node.name.elements;\n if (elements.some((el) => el.dotDotDotToken)) return;\n const keyOf = (el: ts.BindingElement) => (el.propertyName ?? el.name).getText(sf);\n if (!elements.some((el) => keyOf(el) === 'data' && el.initializer !== undefined)) return;\n if (elements.some((el) => failureBindings.has(keyOf(el)))) return;\n hits.push({\n index: node.getStart(sf),\n detail: `${callee}() defaults data with no failure bound`,\n });\n });\n return hits;\n}\n\nexport const defaultedReadBindingGuard: GenericGuardDefinition<DefaultedReadBindingFacts> = {\n id: 'defaultedReadBinding',\n absentWhen: false,\n factProblems(facts) {\n const problems: string[] = [];\n pathListProblem(facts?.surfaceTrees, 'surfaceTrees', problems);\n identifierListProblem(facts?.heldFailureFields, 'heldFailureFields', problems, {\n nonEmpty: false,\n });\n reviewedProblems(facts?.allowlist, 'allowlist', problems);\n return [...problems, ...gateProblems(facts?.allowlist)];\n },\n build: (facts) => {\n const held = facts.heldFailureFields.length ? ` / ${facts.heldFailureFields.join(' / ')}` : '';\n const guard = reviewedAllowlist({\n title: 'a render surface that defaults a read binds its failure',\n description:\n `A render surface (${facts.surfaceTrees.join(', ')}) that defaults a read's data (const { data: rows = [] } = useRows()) binds ` +\n `that same read's failure (error / isError / status${held}) in the same destructure, and renders the pending and failed states ` +\n 'before the default is used; otherwise a failed read renders as an empty list, a zero, or a negative answer that decides a control. ' +\n 'A reviewed default names the gate that holds its surface back until the read answered, and that gate must still be there.',\n scope: APP_CODE,\n match: (file) =>\n facts.surfaceTrees.some((tree) => isUnder(file.path, tree))\n ? unguardedDataDefaults(file, facts.heldFailureFields)\n : [],\n allowlist: facts.allowlist,\n samples: {\n flags: [\n {\n path: `${facts.surfaceTrees[0]}/sample.tsx`,\n text: 'const { data: rows = [] } = useRows(id);',\n },\n ],\n passes: [\n {\n path: `${facts.surfaceTrees[0]}/sample.tsx`,\n text: 'const { data: rows = [], isError } = useRows(id);',\n },\n ],\n },\n });\n return [\n {\n ...guard,\n run: async (context) => [\n ...(await guard.run(context)),\n ...missingGates(context, facts.allowlist),\n ],\n },\n ];\n },\n};\n\nexport interface LaterPageFailureFacts {\n hooksTree: string;\n readStateModule: string;\n listRoutes: ReadonlyArray<{ component: string; prop?: string }>;\n handOffProp: string;\n}\n\nconst PAGES = /\\b(?:fetchNextPage|hasNextPage)\\b/;\n\nfunction routedPattern(routes: LaterPageFailureFacts['listRoutes']): RegExp {\n return new RegExp(\n routes\n .map(({ component, prop }) =>\n prop\n ? `<${escapeRegExp(component)}\\\\s[^>]*?\\\\b${escapeRegExp(prop)}=`\n : `<${escapeRegExp(component)}\\\\b`,\n )\n .join('|'),\n );\n}\n\nexport function unroutedPagingSurfaces(\n files: readonly FileView[],\n facts: LaterPageFailureFacts,\n): FileView[] {\n const routed = routedPattern(facts.listRoutes);\n const routing = new Set(\n files\n .filter((f) => routed.test(f.code))\n .flatMap((f) => [...f.code.matchAll(/export function ([A-Z]\\w*)/g)].map((m) => m[1])),\n );\n const handOff = new RegExp(`<([A-Z]\\\\w*)\\\\s[^>]*?\\\\b${escapeRegExp(facts.handOffProp)}=\\\\{`, 'g');\n return files.filter((f) => {\n if (!PAGES.test(f.code) || routed.test(f.code)) return false;\n return ![...f.code.matchAll(handOff)].some((m) => routing.has(m[1]));\n });\n}\n\nexport const laterPageFailureGuard: GenericGuardDefinition<LaterPageFailureFacts> = {\n id: 'laterPageFailure',\n absentWhen: false,\n factProblems(facts) {\n const problems: string[] = [];\n pathProblem(facts?.hooksTree, 'hooksTree', problems);\n pathProblem(facts?.readStateModule, 'readStateModule', problems);\n identifierProblem(facts?.handOffProp, 'handOffProp', problems);\n if (!isList(facts?.listRoutes) || !facts.listRoutes.length)\n problems.push('listRoutes names no component');\n else\n facts.listRoutes.forEach((r, i) => {\n identifierProblem(r?.component, `listRoutes[${i}].component`, problems);\n if (r?.prop !== undefined) identifierProblem(r.prop, `listRoutes[${i}].prop`, problems);\n });\n return problems;\n },\n build: (facts) => [\n {\n title: \"an infinite list's later-page failure routes through the read-state module\",\n description:\n `Every surface that pages an infinite list (fetchNextPage / hasNextPage) renders it through ` +\n `${facts.listRoutes.map((r) => `<${r.component}${r.prop ? ` ${r.prop}=…` : ''}>`).join(' or ')} from ${facts.readStateModule}, ` +\n `or hands it (${facts.handOffProp}=…) to a component whose own file does, so a failed load-more retries that page instead of ` +\n 'refetching the whole list.',\n run(context) {\n const { findings, file: module } = requiredFile(\n context,\n facts.readStateModule,\n 'the read-state module',\n );\n if (!module) return findings;\n const surfaces = context\n .files(APP_CODE)\n .filter((f) => !isUnder(f.path, facts.hooksTree) && f.path !== facts.readStateModule);\n for (const file of unroutedPagingSurfaces(surfaces, facts)) {\n findings.push({\n file: file.path,\n detail:\n 'pages an infinite list without routing its later-page failure through the read-state module',\n });\n }\n return findings;\n },\n },\n ],\n};\n\nexport interface ReadFailureCopyFacts {\n owner: string;\n allowlist: readonly Reviewed[];\n}\n\nexport const READ_FAILURE_SENTENCE =\n /(?:could not|couldn['’]?t|failed to|unable to)\\s+(?:load|search|fetch|read|retrieve|check|find)\\b|error\\s+(?:loading|searching|fetching|reading)\\b/i;\n\nexport const readFailureCopyGuard: GenericGuardDefinition<ReadFailureCopyFacts> = {\n id: 'readFailureCopy',\n absentWhen: false,\n factProblems(facts) {\n const problems: string[] = [];\n pathProblem(facts?.owner, 'owner', problems);\n reviewedProblems(facts?.allowlist, 'allowlist', problems);\n if (isList(facts?.allowlist) && facts.allowlist.some((e) => e?.path === facts.owner))\n problems.push('the owner is listed in the allowlist');\n return problems;\n },\n build: (facts) => [\n reviewedAllowlist({\n title: \"a read failure's copy comes from its one owner\",\n description:\n `Every read-failure sentence a person sees is built by ${facts.owner} from the short noun the surface names; no other file ` +\n 'hand-writes one (\"could not load\", \"failed to load\", \"error loading\", or the same around another read verb, in any casing). ' +\n 'Render the owner (with surface-specific guidance as its hint), or — for text never shown to a person — add a reviewed entry with its why.',\n scope: APP_CODE,\n match: matchPattern(READ_FAILURE_SENTENCE),\n allowlist: [\n { path: facts.owner, why: 'the one owner of read-failure copy' },\n ...facts.allowlist,\n ],\n samples: {\n flags: [\n '<p>Could not load the rows.</p>',\n '<AlertTitle>Error Loading Rows</AlertTitle>',\n 'const m = \"Couldn\\'t fetch the list.\";',\n ],\n passes: [\n 'hint=\"a list that could not be read is not an empty list.\"',\n '<LoadingState label=\"Loading rows\" />',\n ],\n },\n }),\n ],\n};\n\nexport const READ_FAILURE_GUARDS = [\n readStateOwnerGuard,\n readRetryStateGuard,\n hookReadDefaultGuard,\n defaultedReadBindingGuard,\n laterPageFailureGuard,\n readFailureCopyGuard,\n] as const;\n"]}
|
|
1
|
+
{"version":3,"file":"read-failures.js","sourceRoot":"","sources":["../../../src/modules/frontend-reads/read-failures.ts"],"names":[],"mappings":"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","sourcesContent":["import ts from 'typescript';\nimport type { GenericGuardDefinition } from '../../node/suite/config.js';\nimport type { Finding, GuardContext } from '../../guard.js';\nimport type { FileView } from '../../reader/view.js';\nimport { matchPattern, ownerOnly, reviewedAllowlist, type Hit } from '../../shapes/shapes.js';\nimport type { Reviewed } from '../../node/layout.js';\nimport { forEachNode, lineOfNode } from '../../support/ast.js';\nimport { identifierProblem, pathProblem, reviewedProblems, isList } from '../../support/facts.js';\nimport { escapeRegExp, isUnder } from '../../support/text.js';\nimport {\n APP_CODE,\n filesUnder,\n identifierListProblem,\n isHookName,\n pathListProblem,\n requiredFile,\n} from './shared.js';\n\nconst RETRY_STATE_HELPERS = ['useReadRetryState', 'useReadRetryStates'] as const;\nconst RETRY_STATE_FIELD = 'isRetrying';\nconst HELD_ANSWER_FIELD = 'retrying';\n\nexport interface FailureSeat {\n component: string;\n retryProp: string;\n pendingProp: string;\n}\n\nfunction seatProblems(seat: FailureSeat | undefined): string[] {\n if (!seat || typeof seat !== 'object') return ['seat is missing'];\n const problems: string[] = [];\n identifierProblem(seat.component, 'seat.component', problems);\n identifierProblem(seat.retryProp, 'seat.retryProp', problems);\n identifierProblem(seat.pendingProp, 'seat.pendingProp', problems);\n return problems;\n}\n\nexport interface ReadStateOwnerFacts {\n module: string;\n seatComponent: string;\n laterPageRetry: string;\n}\n\nexport const readStateOwnerGuard: GenericGuardDefinition<ReadStateOwnerFacts> = {\n id: 'readStateOwner',\n absentWhen: false,\n factProblems(facts) {\n const problems: string[] = [];\n pathProblem(facts?.module, 'module', problems);\n identifierProblem(facts?.seatComponent, 'seatComponent', problems);\n identifierProblem(facts?.laterPageRetry, 'laterPageRetry', problems);\n return problems;\n },\n build: (facts) => [\n ownerOnly({\n title: 'read failures render only through the read-state module',\n description:\n `Every read's pending, failed, and empty states render through ${facts.module}, so every failure offers Try again ` +\n `held through its retry. Nothing outside it renders <${facts.seatComponent}> or calls ${facts.laterPageRetry}(); ` +\n 'render the read through the module instead.',\n scope: APP_CODE,\n match: matchPattern(\n new RegExp(\n `<${escapeRegExp(facts.seatComponent)}\\\\b|\\\\b${escapeRegExp(facts.laterPageRetry)}\\\\s*\\\\(`,\n ),\n ),\n owners: [facts.module],\n samples: {\n flags: [\n `return <${facts.seatComponent} thing=\"rows\" />;`,\n `const retry = ${facts.laterPageRetry}(list);`,\n ],\n passes: [\n `// <${facts.seatComponent} /> named in prose only\\nconst rows = 1;`,\n `import { ${facts.seatComponent} } from './seat';`,\n ],\n },\n }),\n ],\n};\n\nexport interface ReadRetryStateFacts {\n seat: FailureSeat;\n controlPendingProp: string;\n}\n\nconst FAILURE_FLAG = String.raw`[\\w$.?]*\\b(?:isError|error)\\b`;\nconst FETCHING = String.raw`[\\w$.?]*\\bisFetching\\b`;\nconst LIVE_FLAG_PAIRS: ReadonlyArray<{ name: string; re: RegExp }> = [\n { name: 'failure flag && isFetching', re: new RegExp(`${FAILURE_FLAG}\\\\s*&&\\\\s*${FETCHING}`) },\n { name: 'isFetching && failure flag', re: new RegExp(`${FETCHING}\\\\s*&&\\\\s*${FAILURE_FLAG}`) },\n];\n\nconst FETCHING_FLAG =\n /^(?:is(?:Re)?[Ff]etching|fetchingCount|useIsFetching|fetchStatus|sourceState)$/;\n\nconst fieldName = (name: ts.Node): string | null =>\n ts.isIdentifier(name) || ts.isStringLiteralLike(name) ? name.text : null;\n\nfunction retryStateExpressions(\n file: FileView,\n fields: readonly string[],\n): Array<{ index: number; expression: ts.Expression }> {\n const sf = file.sourceFile;\n const wanted = new Set(fields);\n const found: Array<{ index: number; expression: ts.Expression }> = [];\n const add = (name: ts.Node, expression: ts.Expression | undefined) => {\n if (expression && wanted.has(fieldName(name) ?? ''))\n found.push({ index: name.getStart(sf), expression });\n };\n forEachNode(sf, (node) => {\n if (ts.isPropertyAssignment(node) || ts.isVariableDeclaration(node))\n add(node.name, node.initializer);\n else if (ts.isJsxAttribute(node) && node.initializer && ts.isJsxExpression(node.initializer))\n add(node.name, node.initializer.expression);\n else if (ts.isBinaryExpression(node) && node.operatorToken.kind === ts.SyntaxKind.EqualsToken) {\n add(ts.isPropertyAccessExpression(node.left) ? node.left.name : node.left, node.right);\n }\n });\n return found;\n}\n\nconst FAILURE_NAME = /^(?:isError|error)$/;\n\nfunction readsFetchingFlag(expression: ts.Node, file: FileView): boolean {\n return readsFlag(expression, file, FETCHING_FLAG);\n}\n\nfunction readsFlag(\n expression: ts.Node,\n file: FileView,\n flag: RegExp,\n seen: Set<string> = new Set(),\n): boolean {\n let reads = false;\n const bound: string[] = [];\n forEachNode(expression, (node) => {\n if (!ts.isIdentifier(node) || reads) return;\n if (flag.test(node.text)) reads = true;\n const parent = node.parent;\n const receiverOrCallee =\n (ts.isPropertyAccessExpression(parent) &&\n (parent.expression === node || parent.name === node)) ||\n (ts.isCallExpression(parent) && parent.expression === node);\n if (!receiverOrCallee) bound.push(node.text);\n });\n if (reads) return true;\n return bound.some((name) => {\n if (seen.has(name)) return false;\n seen.add(name);\n let declared: ts.Expression | undefined;\n let renamed = false;\n forEachNode(file.sourceFile, (node) => {\n if (\n ts.isBindingElement(node) &&\n ts.isIdentifier(node.name) &&\n node.name.text === name &&\n node.propertyName &&\n flag.test(fieldName(node.propertyName) ?? '')\n ) {\n renamed = true;\n } else if (\n !declared &&\n ts.isVariableDeclaration(node) &&\n ts.isIdentifier(node.name) &&\n node.name.text === name &&\n node.initializer\n ) {\n declared = node.initializer;\n }\n });\n if (renamed) return true;\n if (!declared) return false;\n const value = declared;\n const fromHelper =\n ts.isCallExpression(value) &&\n ts.isIdentifier(value.expression) &&\n (RETRY_STATE_HELPERS as readonly string[]).includes(value.expression.text);\n return !fromHelper && readsFlag(value, file, flag, seen);\n });\n}\n\nfunction branchConditionsAbove(node: ts.Node): ts.Expression[] {\n const conditions: ts.Expression[] = [];\n for (let child = node, parent = node.parent; parent; child = parent, parent = parent.parent) {\n if (ts.isIfStatement(parent) && parent.thenStatement === child)\n conditions.push(parent.expression);\n else if (ts.isConditionalExpression(parent) && parent.whenTrue === child)\n conditions.push(parent.condition);\n else if (\n ts.isBinaryExpression(parent) &&\n parent.operatorToken.kind === ts.SyntaxKind.AmpersandAmpersandToken &&\n parent.right === child\n )\n conditions.push(parent.left);\n }\n return conditions;\n}\n\nexport function failureBranchLivePending(file: FileView, controlPendingProp: string): Hit[] {\n const sf = file.sourceFile;\n const hits: Hit[] = [];\n forEachNode(sf, (node) => {\n if (\n !ts.isJsxAttribute(node) ||\n fieldName(node.name) !== controlPendingProp ||\n !node.initializer ||\n !ts.isJsxExpression(node.initializer) ||\n !node.initializer.expression\n )\n return;\n const pending = node.initializer.expression;\n if (!readsFetchingFlag(pending, file)) return;\n if (!branchConditionsAbove(node).some((c) => readsFlag(c, file, FAILURE_NAME))) return;\n hits.push({\n index: node.getStart(sf),\n detail: `a failure's retry pending from a fetching flag: ${pending.getText(sf).replace(/\\s+/g, ' ').slice(0, 120)}`,\n });\n });\n return hits;\n}\n\nconst LOADING_FIELD = 'isLoading';\nconst READ_FAILURE_FIELDS = new Set(['error', 'isError', 'retryState']);\nconst HELD_RECEIVER = /retry/i;\n\ninterface Destructured {\n field: string;\n receiver: string;\n scope: ts.Node;\n}\n\ntype Bindings = Map<string, Destructured[]>;\n\nfunction scopeOf(node: ts.Node): ts.Node {\n return enclosingFunctionOf(node) ?? node.getSourceFile();\n}\n\nfunction destructuredBindings(sf: ts.SourceFile): Bindings {\n const bindings: Bindings = new Map();\n forEachNode(sf, (node) => {\n if (\n !ts.isVariableDeclaration(node) ||\n !ts.isObjectBindingPattern(node.name) ||\n !node.initializer ||\n !ts.isIdentifier(node.initializer)\n )\n return;\n const receiver = node.initializer.text;\n const scope = scopeOf(node);\n for (const element of node.name.elements) {\n if (!ts.isIdentifier(element.name)) continue;\n const field = element.propertyName ? fieldName(element.propertyName) : element.name.text;\n if (!field) continue;\n const named = bindings.get(element.name.text) ?? [];\n named.push({ field, receiver, scope });\n bindings.set(element.name.text, named);\n }\n });\n return bindings;\n}\n\nfunction declaresParameter(fn: ts.SignatureDeclaration, name: string): boolean {\n let declares = false;\n for (const parameter of fn.parameters)\n forEachNode(parameter.name, (node) => {\n if (ts.isIdentifier(node) && node.text === name) declares = true;\n });\n return declares;\n}\n\nfunction bindingFor(use: ts.Identifier, bindings: Bindings): Destructured | undefined {\n const named = bindings.get(use.text);\n if (!named) return undefined;\n for (let scope: ts.Node | undefined = scopeOf(use); scope;) {\n const found = named.find((binding) => binding.scope === scope);\n if (found) return found;\n if (ts.isFunctionLike(scope) && declaresParameter(scope, use.text)) return undefined;\n scope = ts.isSourceFile(scope) ? undefined : scopeOf(scope);\n }\n return undefined;\n}\n\nfunction rootIdentifier(node: ts.Expression): string | null {\n let current: ts.Expression = node;\n while (ts.isPropertyAccessExpression(current) || ts.isNonNullExpression(current))\n current = current.expression;\n return ts.isIdentifier(current) ? current.text : null;\n}\n\ninterface ReadField {\n receiver: string;\n index: number;\n}\n\nfunction readFieldsIn(\n scope: ts.Node,\n bindings: Bindings,\n wanted: (field: string) => boolean,\n): ReadField[] {\n const sf = scope.getSourceFile();\n const found: ReadField[] = [];\n forEachNode(scope, (node) => {\n if (ts.isPropertyAccessExpression(node) && wanted(node.name.text)) {\n const receiver = rootIdentifier(node.expression);\n if (receiver && !HELD_RECEIVER.test(node.expression.getText(sf)))\n found.push({ receiver, index: node.getStart(sf) });\n } else if (\n ts.isIdentifier(node) &&\n !(ts.isPropertyAccessExpression(node.parent) && node.parent.name === node)\n ) {\n const bound = bindingFor(node, bindings);\n if (bound && wanted(bound.field) && !HELD_RECEIVER.test(bound.receiver))\n found.push({ receiver: bound.receiver, index: node.getStart(sf) });\n }\n });\n return found;\n}\n\nfunction enclosingFunctionOf(node: ts.Node): ts.Node | undefined {\n for (let p = node.parent; p; p = p.parent) if (ts.isFunctionLike(p)) return p;\n return undefined;\n}\n\nfunction branchCondition(node: ts.Node): ts.Expression | undefined {\n if (ts.isIfStatement(node)) return node.expression;\n if (ts.isConditionalExpression(node)) return node.condition;\n if (\n ts.isBinaryExpression(node) &&\n node.operatorToken.kind === ts.SyntaxKind.AmpersandAmpersandToken &&\n ts.isJsxExpression(node.parent)\n )\n return node.left;\n return undefined;\n}\n\nexport function liveLoadingBeforeFailure(file: FileView): Hit[] {\n const sf = file.sourceFile;\n const bindings = destructuredBindings(sf);\n const isLoading = (field: string) => field === LOADING_FIELD;\n const isFailure = (field: string) => READ_FAILURE_FIELDS.has(field);\n const hits: Hit[] = [];\n forEachNode(sf, (node) => {\n const condition = branchCondition(node);\n const fn = condition && enclosingFunctionOf(node);\n if (!condition || !fn) return;\n const tested = new Set(readFieldsIn(condition, bindings, isFailure).map((r) => r.receiver));\n const end = condition.getEnd();\n const later = readFieldsIn(fn, bindings, isFailure).filter((r) => r.index >= end);\n const loading = readFieldsIn(condition, bindings, isLoading).find(\n (r) => !tested.has(r.receiver) && later.some((f) => f.receiver === r.receiver),\n );\n if (loading)\n hits.push({\n index: loading.index,\n detail: `a live loading flag tested before the read's failure: ${condition.getText(sf).replace(/\\s+/g, ' ').slice(0, 120)}`,\n });\n });\n return hits;\n}\n\nexport function liveFlagRetryStates(file: FileView, seat: FailureSeat): Hit[] {\n const code = file.code;\n const hits = new Map<number, Hit>();\n let offset = 0;\n for (const line of code.split('\\n')) {\n for (const shape of LIVE_FLAG_PAIRS) {\n const m = shape.re.exec(line);\n if (m)\n hits.set(file.lineOf(offset + m.index), {\n index: offset + m.index,\n detail: `${shape.name}: ${line.trim().slice(0, 120)}`,\n });\n }\n offset += line.length + 1;\n }\n for (const { index, expression } of retryStateExpressions(file, [\n RETRY_STATE_FIELD,\n HELD_ANSWER_FIELD,\n seat.pendingProp,\n ])) {\n if (!readsFetchingFlag(expression, file)) continue;\n const line = file.lineOf(index);\n if (!hits.has(line))\n hits.set(line, {\n index,\n detail: `retry state from a fetching flag: ${expression.getText(file.sourceFile).replace(/\\s+/g, ' ').slice(0, 120)}`,\n });\n }\n return [...hits.values()].sort((a, b) => a.index - b.index);\n}\n\nconst isHookFunction = (node: ts.Node, sf: ts.SourceFile): boolean => {\n if ((ts.isFunctionDeclaration(node) || ts.isFunctionExpression(node)) && node.name)\n return isHookName(node.name.text);\n if (\n (ts.isArrowFunction(node) || ts.isFunctionExpression(node)) &&\n ts.isVariableDeclaration(node.parent)\n )\n return isHookName(node.parent.name.getText(sf));\n return false;\n};\n\nexport function failedAnswersWithoutRetrying(file: FileView): Hit[] {\n const sf = file.sourceFile;\n const hits: Hit[] = [];\n forEachNode(sf, (node) => {\n if (!ts.isObjectLiteralExpression(node)) return;\n const named = (name: string) =>\n node.properties.find(\n (p) =>\n (ts.isPropertyAssignment(p) || ts.isShorthandPropertyAssignment(p)) &&\n p.name.getText(sf) === name,\n );\n const status = named('status');\n if (\n !status ||\n !ts.isPropertyAssignment(status) ||\n !ts.isStringLiteralLike(status.initializer) ||\n status.initializer.text !== 'failed'\n )\n return;\n if (!named('error') || named(HELD_ANSWER_FIELD) || node.properties.some(ts.isSpreadAssignment))\n return;\n let inHook = false;\n for (let p: ts.Node | undefined = node.parent; p && !inHook; p = p.parent)\n inHook = isHookFunction(p, sf);\n if (inHook)\n hits.push({\n index: node.getStart(sf),\n detail: `a failed answer with no ${HELD_ANSWER_FIELD}: ${node.getText(sf).replace(/\\s+/g, ' ').slice(0, 120)}`,\n });\n });\n return hits;\n}\n\nexport function seatRetriesWithoutPending(file: FileView, seat: FailureSeat): Hit[] {\n const hits: Hit[] = [];\n const element = new RegExp(`<${escapeRegExp(seat.component)}\\\\b[\\\\s\\\\S]*?\\\\/>`, 'g');\n for (const match of file.code.matchAll(element)) {\n const text = match[0];\n if (\n new RegExp(`\\\\b${escapeRegExp(seat.retryProp)}=`).test(text) &&\n !new RegExp(`\\\\b${escapeRegExp(seat.pendingProp)}=`).test(text) &&\n !/\\{\\s*\\.\\.\\./.test(text)\n ) {\n hits.push({\n index: match.index ?? 0,\n detail: `<${seat.component} ${seat.retryProp}=…> without ${seat.pendingProp}`,\n });\n }\n }\n return hits;\n}\n\nexport const readRetryStateGuard: GenericGuardDefinition<ReadRetryStateFacts> = {\n id: 'readRetryState',\n absentWhen: false,\n factProblems(facts) {\n const problems = seatProblems(facts?.seat);\n identifierProblem(facts?.controlPendingProp, 'controlPendingProp', problems);\n return problems;\n },\n build: (facts) => [\n {\n title: \"a read's retry state comes from its one owner, never the live fetching flags\",\n description:\n 'TanStack Query puts a query that never succeeded back to pending and clears its error the moment a refetch starts, so a retry ' +\n 'state built from isFetching / isRefetching / fetchStatus with isError or error reads false for the retry of a failed first read, ' +\n `and the failure turns into a loading state under the pressed retry. Hold the failure with query-core's ${RETRY_STATE_HELPERS.join(' / ')} ` +\n `(exposed from the read's hook) and pass its ${RETRY_STATE_FIELD}; a lookup hands out its held ${HELD_ANSWER_FIELD}, and a hook ` +\n `that answers with a status gives its failed answer the held ${HELD_ANSWER_FIELD} beside the error, never one built from a ` +\n `fetching flag; and every <${facts.seat.component}> given ${facts.seat.retryProp} also takes ${facts.seat.pendingProp}, so the ` +\n `pressed retry shows it is running. The same live flags decide no failure surface: a control's ${facts.controlPendingProp} ` +\n \"inside a failure branch reads the held isRetrying, never a fetching flag, and a branch never tests a read's live isLoading \" +\n \"ahead of that read's failure — the held retryState decides the failure first, then its isLoading.\",\n run(context: GuardContext): Finding[] {\n const findings: Finding[] = [];\n for (const file of context.files(APP_CODE)) {\n const hits = [\n ...liveFlagRetryStates(file, facts.seat),\n ...seatRetriesWithoutPending(file, facts.seat),\n ...failedAnswersWithoutRetrying(file),\n ...failureBranchLivePending(file, facts.controlPendingProp),\n ...liveLoadingBeforeFailure(file),\n ];\n for (const hit of hits) {\n findings.push({\n file: file.path,\n line: file.lineOf(hit.index),\n detail: hit.detail ?? '',\n });\n }\n }\n return findings;\n },\n },\n ],\n};\n\nexport interface HookReadDefaultFacts {\n hooksTree: string;\n readCollapsers: readonly string[];\n}\n\nconst FAILURE_FIELDS = new Set(['error', 'isError', 'status']);\n\nfunction isEmptyAnswer(node: ts.Expression): boolean {\n return (\n (ts.isArrayLiteralExpression(node) && node.elements.length === 0) ||\n (ts.isObjectLiteralExpression(node) && node.properties.length === 0) ||\n node.kind === ts.SyntaxKind.FalseKeyword ||\n node.kind === ts.SyntaxKind.NullKeyword ||\n (ts.isNumericLiteral(node) && node.text === '0') ||\n (ts.isStringLiteral(node) && node.text === '')\n );\n}\n\nfunction enclosingFunction(node: ts.Node): ts.Node | undefined {\n let current: ts.Node | undefined = node.parent;\n while (current && !ts.isFunctionLike(current)) current = current.parent;\n return current;\n}\n\nfunction readsFailureOf(scope: ts.Node, receiver: string, sf: ts.SourceFile): boolean {\n let found = false;\n forEachNode(scope, (n) => {\n if (\n ts.isPropertyAccessExpression(n) &&\n FAILURE_FIELDS.has(n.name.text) &&\n n.expression.getText(sf) === receiver\n )\n found = true;\n if (\n ts.isCallExpression(n) &&\n n.expression.getText(sf) === RETRY_STATE_HELPERS[0] &&\n n.arguments[0]?.getText(sf) === receiver\n )\n found = true;\n });\n return found;\n}\n\nfunction exportedHookBody(\n node: ts.Node,\n sf: ts.SourceFile,\n): { name: string; body: ts.ConciseBody } | undefined {\n const isExported = (n: ts.Node) =>\n ts.canHaveModifiers(n) &&\n (ts.getModifiers(n) ?? []).some((m) => m.kind === ts.SyntaxKind.ExportKeyword);\n if (\n ts.isFunctionDeclaration(node) &&\n node.name &&\n isHookName(node.name.text) &&\n isExported(node) &&\n node.body\n ) {\n return { name: node.name.text, body: node.body };\n }\n if (\n ts.isVariableDeclaration(node) &&\n ts.isIdentifier(node.name) &&\n isHookName(node.name.text) &&\n node.initializer &&\n (ts.isArrowFunction(node.initializer) || ts.isFunctionExpression(node.initializer)) &&\n isExported(node.parent.parent)\n ) {\n return { name: node.name.getText(sf), body: node.initializer.body };\n }\n return undefined;\n}\n\nfunction returnedExpressions(body: ts.ConciseBody): ts.Expression[] {\n if (!ts.isBlock(body)) return [body];\n const out: ts.Expression[] = [];\n const visit = (n: ts.Node) => {\n if (ts.isFunctionLike(n)) return;\n if (ts.isReturnStatement(n) && n.expression) out.push(n.expression);\n ts.forEachChild(n, visit);\n };\n ts.forEachChild(body, visit);\n return out;\n}\n\nfunction readsData(node: ts.Node): boolean {\n let found = false;\n forEachNode(node, (n) => {\n if (ts.isPropertyAccessExpression(n) && n.name.text === 'data') found = true;\n });\n return found;\n}\n\nconst COMPARISONS = new Set([\n ts.SyntaxKind.EqualsEqualsEqualsToken,\n ts.SyntaxKind.ExclamationEqualsEqualsToken,\n ts.SyntaxKind.GreaterThanToken,\n ts.SyntaxKind.LessThanToken,\n ts.SyntaxKind.GreaterThanEqualsToken,\n ts.SyntaxKind.LessThanEqualsToken,\n]);\n\nfunction collapsesReadToBoolean(\n expr: ts.Expression,\n sf: ts.SourceFile,\n collapsers: ReadonlySet<string>,\n): boolean {\n let e = expr;\n while (ts.isParenthesizedExpression(e)) e = e.expression;\n if (ts.isCallExpression(e) && collapsers.has(e.expression.getText(sf))) return true;\n if (ts.isPrefixUnaryExpression(e) && e.operator === ts.SyntaxKind.ExclamationToken)\n return readsData(e);\n if (ts.isBinaryExpression(e) && COMPARISONS.has(e.operatorToken.kind)) return readsData(e);\n if (ts.isCallExpression(e) && e.expression.getText(sf) === 'Boolean') return readsData(e);\n return false;\n}\n\nexport function hookReadDefaults(\n file: FileView,\n collapsers: readonly string[],\n): Array<{ line: number; detail: string }> {\n const sf = file.sourceFile;\n const names = new Set(collapsers);\n const hits: Array<{ line: number; detail: string }> = [];\n forEachNode(sf, (node) => {\n if (\n ts.isBinaryExpression(node) &&\n (node.operatorToken.kind === ts.SyntaxKind.QuestionQuestionToken ||\n node.operatorToken.kind === ts.SyntaxKind.BarBarToken) &&\n ts.isPropertyAccessExpression(node.left) &&\n node.left.name.text === 'data' &&\n isEmptyAnswer(node.right)\n ) {\n const scope = enclosingFunction(node);\n if (!scope || !readsFailureOf(scope, node.left.expression.getText(sf), sf)) {\n hits.push({\n line: lineOfNode(file, node),\n detail: `a defaulted read with no failure beside it: ${node.getText(sf)}`,\n });\n }\n }\n if (\n ts.isVariableDeclaration(node) &&\n node.initializer &&\n ts.isObjectBindingPattern(node.name)\n ) {\n const init = node.initializer;\n if (ts.isCallExpression(init) && isHookName(init.expression.getText(sf))) {\n const keyOf = (el: ts.BindingElement) => (el.propertyName ?? el.name).getText(sf);\n const elements = node.name.elements;\n const defaultsData = elements.some(\n (el) => keyOf(el) === 'data' && el.initializer !== undefined,\n );\n const carriesFailure = elements.some(\n (el) => el.dotDotDotToken || FAILURE_FIELDS.has(keyOf(el)),\n );\n if (defaultsData && !carriesFailure)\n hits.push({\n line: lineOfNode(file, node),\n detail: `a defaulted read with no failure beside it: ${node.name.getText(sf)}`,\n });\n }\n }\n const hook = exportedHookBody(node, sf);\n if (hook) {\n for (const returned of returnedExpressions(hook.body)) {\n if (collapsesReadToBoolean(returned, sf, names))\n hits.push({\n line: lineOfNode(file, returned),\n detail: `${hook.name} returns a boolean from a read`,\n });\n }\n }\n });\n return hits;\n}\n\nexport const hookReadDefaultGuard: GenericGuardDefinition<HookReadDefaultFacts> = {\n id: 'hookReadDefault',\n absentWhen: false,\n factProblems(facts) {\n const problems: string[] = [];\n pathProblem(facts?.hooksTree, 'hooksTree', problems);\n identifierListProblem(facts?.readCollapsers, 'readCollapsers', problems, { nonEmpty: false });\n return problems;\n },\n build: (facts) => [\n {\n title: 'a hook never returns a default its consumers cannot tell from data',\n description:\n `In ${facts.hooksTree}, a read's data defaulted to an empty answer (q.data ?? [], const { data = [] } = useQ()) is allowed only ` +\n `where the same function also carries that read's failure (error / isError / status, or the read handed to ${RETRY_STATE_HELPERS[0]}), ` +\n 'and no exported hook collapses a read into a bare boolean, where an unknown read would read as \"no\". Return the read state ' +\n '(pending / failed / answered) or carry the failure beside the defaulted data.',\n run(context) {\n const { files, findings } = filesUnder(context, [facts.hooksTree]);\n for (const file of files)\n for (const hit of hookReadDefaults(file, facts.readCollapsers))\n findings.push({ file: file.path, ...hit });\n return findings;\n },\n },\n ],\n};\n\nexport interface DefaultedReadBindingFacts {\n surfaceTrees: readonly string[];\n heldFailureFields: readonly string[];\n allowlist: ReadonlyArray<Reviewed & { gate: ReadGate }>;\n}\n\nexport interface ReadGate {\n file: string;\n code: string;\n}\n\nconst squashed = (text: string) => text.replace(/\\s+/g, ' ').trim();\n\nfunction gateProblems(allowlist: unknown): string[] {\n if (!isList(allowlist)) return [];\n const problems: string[] = [];\n for (const entry of allowlist as Array<{ path?: string; gate?: Partial<ReadGate> }>) {\n const what = `allowlist entry ${String(entry?.path)}`;\n if (!entry?.gate || typeof entry.gate !== 'object')\n problems.push(\n `${what} names no gate (the file and the code that hold its surface back until the read answered)`,\n );\n else {\n pathProblem(entry.gate.file, `${what}'s gate file`, problems);\n if (typeof entry.gate.code !== 'string' || !entry.gate.code.trim())\n problems.push(`${what}'s gate names no code`);\n }\n }\n return problems;\n}\n\nfunction missingGates(\n context: GuardContext,\n allowlist: DefaultedReadBindingFacts['allowlist'],\n): Finding[] {\n const findings: Finding[] = [];\n for (const { path, gate } of allowlist) {\n if (!context.reader.exists(gate.file))\n findings.push({\n file: gate.file,\n detail: `the gate ${path} relies on is gone: the file no longer exists`,\n });\n else if (!squashed(context.file(gate.file).code).includes(squashed(gate.code))) {\n findings.push({\n file: gate.file,\n detail: `the gate ${path} relies on is gone: no longer holds ${squashed(gate.code)}`,\n });\n }\n }\n return findings;\n}\n\nexport function unguardedDataDefaults(file: FileView, heldFailureFields: readonly string[]): Hit[] {\n const sf = file.sourceFile;\n const failureBindings = new Set([...FAILURE_FIELDS, ...heldFailureFields]);\n const hits: Hit[] = [];\n forEachNode(sf, (node) => {\n if (\n !ts.isVariableDeclaration(node) ||\n !node.initializer ||\n !ts.isObjectBindingPattern(node.name)\n )\n return;\n let init: ts.Expression = node.initializer;\n if (ts.isAwaitExpression(init)) init = init.expression;\n if (!ts.isCallExpression(init)) return;\n const callee = init.expression.getText(sf);\n if (!isHookName(callee)) return;\n const elements = node.name.elements;\n if (elements.some((el) => el.dotDotDotToken)) return;\n const keyOf = (el: ts.BindingElement) => (el.propertyName ?? el.name).getText(sf);\n if (!elements.some((el) => keyOf(el) === 'data' && el.initializer !== undefined)) return;\n if (elements.some((el) => failureBindings.has(keyOf(el)))) return;\n hits.push({\n index: node.getStart(sf),\n detail: `${callee}() defaults data with no failure bound`,\n });\n });\n return hits;\n}\n\nexport const defaultedReadBindingGuard: GenericGuardDefinition<DefaultedReadBindingFacts> = {\n id: 'defaultedReadBinding',\n absentWhen: false,\n factProblems(facts) {\n const problems: string[] = [];\n pathListProblem(facts?.surfaceTrees, 'surfaceTrees', problems);\n identifierListProblem(facts?.heldFailureFields, 'heldFailureFields', problems, {\n nonEmpty: false,\n });\n reviewedProblems(facts?.allowlist, 'allowlist', problems);\n return [...problems, ...gateProblems(facts?.allowlist)];\n },\n build: (facts) => {\n const held = facts.heldFailureFields.length ? ` / ${facts.heldFailureFields.join(' / ')}` : '';\n const guard = reviewedAllowlist({\n title: 'a render surface that defaults a read binds its failure',\n description:\n `A render surface (${facts.surfaceTrees.join(', ')}) that defaults a read's data (const { data: rows = [] } = useRows()) binds ` +\n `that same read's failure (error / isError / status${held}) in the same destructure, and renders the pending and failed states ` +\n 'before the default is used; otherwise a failed read renders as an empty list, a zero, or a negative answer that decides a control. ' +\n 'A reviewed default names the gate that holds its surface back until the read answered, and that gate must still be there.',\n scope: APP_CODE,\n match: (file) =>\n facts.surfaceTrees.some((tree) => isUnder(file.path, tree))\n ? unguardedDataDefaults(file, facts.heldFailureFields)\n : [],\n allowlist: facts.allowlist,\n samples: {\n flags: [\n {\n path: `${facts.surfaceTrees[0]}/sample.tsx`,\n text: 'const { data: rows = [] } = useRows(id);',\n },\n ],\n passes: [\n {\n path: `${facts.surfaceTrees[0]}/sample.tsx`,\n text: 'const { data: rows = [], isError } = useRows(id);',\n },\n ],\n },\n });\n return [\n {\n ...guard,\n run: async (context) => [\n ...(await guard.run(context)),\n ...missingGates(context, facts.allowlist),\n ],\n },\n ];\n },\n};\n\nexport interface LaterPageFailureFacts {\n hooksTree: string;\n readStateModule: string;\n listRoutes: ReadonlyArray<{ component: string; prop?: string }>;\n handOffProp: string;\n}\n\nconst PAGES = /\\b(?:fetchNextPage|hasNextPage)\\b/;\n\nfunction routedPattern(routes: LaterPageFailureFacts['listRoutes']): RegExp {\n return new RegExp(\n routes\n .map(({ component, prop }) =>\n prop\n ? `<${escapeRegExp(component)}\\\\s[^>]*?\\\\b${escapeRegExp(prop)}=`\n : `<${escapeRegExp(component)}\\\\b`,\n )\n .join('|'),\n );\n}\n\nexport function unroutedPagingSurfaces(\n files: readonly FileView[],\n facts: LaterPageFailureFacts,\n): FileView[] {\n const routed = routedPattern(facts.listRoutes);\n const routing = new Set(\n files\n .filter((f) => routed.test(f.code))\n .flatMap((f) => [...f.code.matchAll(/export function ([A-Z]\\w*)/g)].map((m) => m[1])),\n );\n const handOff = new RegExp(`<([A-Z]\\\\w*)\\\\s[^>]*?\\\\b${escapeRegExp(facts.handOffProp)}=\\\\{`, 'g');\n return files.filter((f) => {\n if (!PAGES.test(f.code) || routed.test(f.code)) return false;\n return ![...f.code.matchAll(handOff)].some((m) => routing.has(m[1]));\n });\n}\n\nexport const laterPageFailureGuard: GenericGuardDefinition<LaterPageFailureFacts> = {\n id: 'laterPageFailure',\n absentWhen: false,\n factProblems(facts) {\n const problems: string[] = [];\n pathProblem(facts?.hooksTree, 'hooksTree', problems);\n pathProblem(facts?.readStateModule, 'readStateModule', problems);\n identifierProblem(facts?.handOffProp, 'handOffProp', problems);\n if (!isList(facts?.listRoutes) || !facts.listRoutes.length)\n problems.push('listRoutes names no component');\n else\n facts.listRoutes.forEach((r, i) => {\n identifierProblem(r?.component, `listRoutes[${i}].component`, problems);\n if (r?.prop !== undefined) identifierProblem(r.prop, `listRoutes[${i}].prop`, problems);\n });\n return problems;\n },\n build: (facts) => [\n {\n title: \"an infinite list's later-page failure routes through the read-state module\",\n description:\n `Every surface that pages an infinite list (fetchNextPage / hasNextPage) renders it through ` +\n `${facts.listRoutes.map((r) => `<${r.component}${r.prop ? ` ${r.prop}=…` : ''}>`).join(' or ')} from ${facts.readStateModule}, ` +\n `or hands it (${facts.handOffProp}=…) to a component whose own file does, so a failed load-more retries that page instead of ` +\n 'refetching the whole list.',\n run(context) {\n const { findings, file: module } = requiredFile(\n context,\n facts.readStateModule,\n 'the read-state module',\n );\n if (!module) return findings;\n const surfaces = context\n .files(APP_CODE)\n .filter((f) => !isUnder(f.path, facts.hooksTree) && f.path !== facts.readStateModule);\n for (const file of unroutedPagingSurfaces(surfaces, facts)) {\n findings.push({\n file: file.path,\n detail:\n 'pages an infinite list without routing its later-page failure through the read-state module',\n });\n }\n return findings;\n },\n },\n ],\n};\n\nexport interface ReadFailureCopyFacts {\n owner: string;\n allowlist: readonly Reviewed[];\n}\n\nexport const READ_FAILURE_SENTENCE =\n /(?:could not|couldn['’]?t|failed to|unable to)\\s+(?:load|search|fetch|read|retrieve|check|find)\\b|error\\s+(?:loading|searching|fetching|reading)\\b/i;\n\nexport const readFailureCopyGuard: GenericGuardDefinition<ReadFailureCopyFacts> = {\n id: 'readFailureCopy',\n absentWhen: false,\n factProblems(facts) {\n const problems: string[] = [];\n pathProblem(facts?.owner, 'owner', problems);\n reviewedProblems(facts?.allowlist, 'allowlist', problems);\n if (isList(facts?.allowlist) && facts.allowlist.some((e) => e?.path === facts.owner))\n problems.push('the owner is listed in the allowlist');\n return problems;\n },\n build: (facts) => [\n reviewedAllowlist({\n title: \"a read failure's copy comes from its one owner\",\n description:\n `Every read-failure sentence a person sees is built by ${facts.owner} from the short noun the surface names; no other file ` +\n 'hand-writes one (\"could not load\", \"failed to load\", \"error loading\", or the same around another read verb, in any casing). ' +\n 'Render the owner (with surface-specific guidance as its hint), or — for text never shown to a person — add a reviewed entry with its why.',\n scope: APP_CODE,\n match: matchPattern(READ_FAILURE_SENTENCE),\n allowlist: [\n { path: facts.owner, why: 'the one owner of read-failure copy' },\n ...facts.allowlist,\n ],\n samples: {\n flags: [\n '<p>Could not load the rows.</p>',\n '<AlertTitle>Error Loading Rows</AlertTitle>',\n 'const m = \"Couldn\\'t fetch the list.\";',\n ],\n passes: [\n 'hint=\"a list that could not be read is not an empty list.\"',\n '<LoadingState label=\"Loading rows\" />',\n ],\n },\n }),\n ],\n};\n\nexport const READ_FAILURE_GUARDS = [\n readStateOwnerGuard,\n readRetryStateGuard,\n hookReadDefaultGuard,\n defaultedReadBindingGuard,\n laterPageFailureGuard,\n readFailureCopyGuard,\n] as const;\n"]}
|
|
@@ -4,10 +4,15 @@ export interface TypedMockHelper {
|
|
|
4
4
|
owner: string;
|
|
5
5
|
names: readonly string[];
|
|
6
6
|
}
|
|
7
|
+
export interface FakeCall {
|
|
8
|
+
name: string;
|
|
9
|
+
overridesArgument: number;
|
|
10
|
+
}
|
|
7
11
|
export interface ForbiddenOverride {
|
|
8
12
|
module: string;
|
|
9
13
|
names: readonly string[];
|
|
10
14
|
why: string;
|
|
15
|
+
fakeCalls?: readonly FakeCall[];
|
|
11
16
|
}
|
|
12
17
|
export interface TypedMockFacts {
|
|
13
18
|
importAliases: Readonly<Record<string, string>>;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"typed-mocks.d.ts","sourceRoot":"","sources":["../../../src/modules/repo/typed-mocks.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,sBAAsB,EAAE,MAAM,4BAA4B,CAAC;AAwBzE,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,mBAAmB,CAAC;AAWpD,MAAM,WAAW,eAAe;IAC9B,KAAK,EAAE,MAAM,CAAC;IACd,KAAK,EAAE,SAAS,MAAM,EAAE,CAAC;CAC1B;AAED,MAAM,WAAW,iBAAiB;IAChC,MAAM,EAAE,MAAM,CAAC;IACf,KAAK,EAAE,SAAS,MAAM,EAAE,CAAC;IACzB,GAAG,EAAE,MAAM,CAAC;
|
|
1
|
+
{"version":3,"file":"typed-mocks.d.ts","sourceRoot":"","sources":["../../../src/modules/repo/typed-mocks.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,sBAAsB,EAAE,MAAM,4BAA4B,CAAC;AAwBzE,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,mBAAmB,CAAC;AAWpD,MAAM,WAAW,eAAe;IAC9B,KAAK,EAAE,MAAM,CAAC;IACd,KAAK,EAAE,SAAS,MAAM,EAAE,CAAC;CAC1B;AAED,MAAM,WAAW,QAAQ;IACvB,IAAI,EAAE,MAAM,CAAC;IACb,iBAAiB,EAAE,MAAM,CAAC;CAC3B;AAED,MAAM,WAAW,iBAAiB;IAChC,MAAM,EAAE,MAAM,CAAC;IACf,KAAK,EAAE,SAAS,MAAM,EAAE,CAAC;IACzB,GAAG,EAAE,MAAM,CAAC;IACZ,SAAS,CAAC,EAAE,SAAS,QAAQ,EAAE,CAAC;CACjC;AAED,MAAM,WAAW,cAAc;IAC7B,aAAa,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC,CAAC;IAChD,KAAK,EAAE,SAAS,UAAU,EAAE,CAAC;IAC7B,YAAY,EAAE,SAAS,eAAe,EAAE,CAAC;IACzC,kBAAkB,EAAE,SAAS,iBAAiB,EAAE,CAAC;CAClD;AAuTD,eAAO,MAAM,cAAc,EAAE,sBAAsB,CAAC,cAAc,CAqCjE,CAAC"}
|
|
@@ -153,12 +153,32 @@ const isOriginalSpread = (expression, original) => {
|
|
|
153
153
|
callee.expression.text === 'vi' &&
|
|
154
154
|
callee.name.text === 'importActual'));
|
|
155
155
|
};
|
|
156
|
-
function
|
|
156
|
+
function calleeName(call) {
|
|
157
|
+
const callee = unwrap(call.expression);
|
|
158
|
+
if (ts.isIdentifier(callee))
|
|
159
|
+
return callee.text;
|
|
160
|
+
if (ts.isPropertyAccessExpression(callee))
|
|
161
|
+
return callee.name.text;
|
|
162
|
+
return null;
|
|
163
|
+
}
|
|
164
|
+
function overridesOf(returned, original, fakeCalls) {
|
|
157
165
|
const value = awaited(returned);
|
|
158
166
|
const objects = [];
|
|
159
167
|
const out = [];
|
|
168
|
+
const name = ts.isCallExpression(value) ? calleeName(value) : null;
|
|
169
|
+
const overridesAt = name === null ? undefined : fakeCalls.get(name);
|
|
160
170
|
if (ts.isObjectLiteralExpression(value))
|
|
161
171
|
objects.push(value);
|
|
172
|
+
else if (ts.isCallExpression(value) && overridesAt !== undefined) {
|
|
173
|
+
const argument = value.arguments[overridesAt];
|
|
174
|
+
if (argument) {
|
|
175
|
+
const arg = unwrap(argument);
|
|
176
|
+
if (ts.isObjectLiteralExpression(arg))
|
|
177
|
+
objects.push(arg);
|
|
178
|
+
else
|
|
179
|
+
out.push({ unread: arg });
|
|
180
|
+
}
|
|
181
|
+
}
|
|
162
182
|
else if (ts.isCallExpression(value))
|
|
163
183
|
for (const argument of value.arguments) {
|
|
164
184
|
const arg = unwrap(argument);
|
|
@@ -195,10 +215,11 @@ function checkForbidden(reader, facts, files) {
|
|
|
195
215
|
if (!fn)
|
|
196
216
|
continue;
|
|
197
217
|
const forbidden = new Set(entry.names);
|
|
218
|
+
const fakeCalls = new Map((entry.fakeCalls ?? []).map((call) => [call.name, call.overridesArgument]));
|
|
198
219
|
for (const returned of returnedExpressions(fn)) {
|
|
199
220
|
if (!returned)
|
|
200
221
|
continue;
|
|
201
|
-
for (const read of overridesOf(returned, parameterName(fn))) {
|
|
222
|
+
for (const read of overridesOf(returned, parameterName(fn), fakeCalls)) {
|
|
202
223
|
if ('unread' in read) {
|
|
203
224
|
findings.push({
|
|
204
225
|
file: file.path,
|
|
@@ -256,6 +277,21 @@ function factProblems(facts) {
|
|
|
256
277
|
seen.add(entry.module);
|
|
257
278
|
namesProblem(entry.names, `forbiddenOverrides entry ${entry.module}'s names`, problems);
|
|
258
279
|
textProblem(entry.why, `forbiddenOverrides entry ${entry.module}'s why`, problems);
|
|
280
|
+
if (entry.fakeCalls === undefined)
|
|
281
|
+
continue;
|
|
282
|
+
if (!Array.isArray(entry.fakeCalls)) {
|
|
283
|
+
problems.push(`forbiddenOverrides entry ${entry.module}'s fakeCalls is not a list`);
|
|
284
|
+
continue;
|
|
285
|
+
}
|
|
286
|
+
for (const call of entry.fakeCalls) {
|
|
287
|
+
const fake = (call && typeof call === 'object' ? call : {});
|
|
288
|
+
if (!isText(fake.name))
|
|
289
|
+
problems.push(`a fakeCalls entry of ${entry.module} names no fake`);
|
|
290
|
+
if (typeof fake.overridesArgument !== 'number' ||
|
|
291
|
+
!Number.isInteger(fake.overridesArgument) ||
|
|
292
|
+
fake.overridesArgument < 1)
|
|
293
|
+
problems.push(`fakeCalls entry ${String(fake.name)} of ${entry.module} needs overridesArgument, the position of its override object after importOriginal`);
|
|
294
|
+
}
|
|
259
295
|
}
|
|
260
296
|
}
|
|
261
297
|
return problems;
|
|
@@ -285,7 +321,9 @@ export const typedMockGuard = {
|
|
|
285
321
|
'a stand-in that behaves differently on the path a case proves lets the case pass on behavior the app ' +
|
|
286
322
|
'never has. The guard reads the names each override object sets at its top level; a spread of anything ' +
|
|
287
323
|
'but the real module, a computed name, or an override object it cannot see fails, since it could hide a ' +
|
|
288
|
-
'listed one. Each entry names its module, the exports, and why the real one runs.'
|
|
324
|
+
'listed one. Each entry names its module, the exports, and why the real one runs. An entry may name ' +
|
|
325
|
+
'its shared fake’s calls with the position of their override object: for a call to that fake only that ' +
|
|
326
|
+
'argument is read, and the arguments before it are the fake’s state.',
|
|
289
327
|
timeoutMs: REPOSITORY_GUARD_TIMEOUT_MS,
|
|
290
328
|
run(context) {
|
|
291
329
|
return checkForbidden(context.reader, facts, context.files(SCOPE));
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"typed-mocks.js","sourceRoot":"","sources":["../../../src/modules/repo/typed-mocks.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,YAAY,CAAC;AAC5B,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AAKjC,OAAO,EAAE,UAAU,EAAE,MAAM,EAAE,MAAM,sBAAsB,CAAC;AAC1D,OAAO,EACL,OAAO,EACP,UAAU,EACV,YAAY,EACZ,eAAe,EACf,eAAe,EACf,aAAa,EACb,mBAAmB,GAGpB,MAAM,iCAAiC,CAAC;AACzC,OAAO,EACL,oBAAoB,EACpB,MAAM,EACN,YAAY,EACZ,aAAa,EACb,WAAW,EACX,WAAW,GACZ,MAAM,wBAAwB,CAAC;AAChC,OAAO,EAAE,SAAS,EAAE,aAAa,EAAE,gBAAgB,EAAE,MAAM,oCAAoC,CAAC;AAChG,OAAO,EAAE,2BAA2B,EAAE,MAAM,2BAA2B,CAAC;AAGxE,MAAM,UAAU,GAAwB,IAAI,GAAG,CAAC,CAAC,SAAS,EAAE,WAAW,CAAC,CAAC,CAAC;AAE1E,MAAM,KAAK,GAAU;IACnB,KAAK,EAAE,CAAC,KAAK,EAAE,WAAW,EAAE,SAAS,EAAE,SAAS,CAAC;IACjD,KAAK,EAAE,CAAC,MAAM,CAAC;IACf,KAAK,EAAE,MAAM;IACb,aAAa,EAAE,CAAC,WAAW,EAAE,SAAS,EAAE,SAAS,CAAC;CACnD,CAAC;AA2BF,SAAS,OAAO,CAAC,MAAkB,EAAE,IAAc,EAAE,OAAgC;IACnF,MAAM,GAAG,GAAW,EAAE,CAAC;IACvB,KAAK,MAAM,IAAI,IAAI,IAAI,CAAC,OAAO,EAAE,CAAC;QAChC,IAAI,IAAI,CAAC,IAAI,KAAK,MAAM,IAAI,CAAC,UAAU,CAAC,GAAG,CAAC,IAAI,CAAC,MAAM,IAAI,EAAE,CAAC;YAAE,SAAS;QACzE,IAAI,CAAC,EAAE,CAAC,gBAAgB,CAAC,IAAI,CAAC,IAAI,CAAC;YAAE,SAAS;QAC9C,GAAG,CAAC,IAAI,CAAC;YACP,IAAI,EAAE,IAAI,CAAC,IAAI;YACf,MAAM,EAAE,gBAAgB,CAAC,MAAM,EAAE,IAAI,CAAC,IAAI,EAAE,IAAI,CAAC,IAAI,EAAE,OAAO,CAAC;YAC/D,QAAQ,EAAE,IAAI,CAAC,QAAQ;YACvB,OAAO,EAAE,IAAI,CAAC,IAAI,CAAC,SAAS,CAAC,CAAC,CAAC;SAChC,CAAC,CAAC;IACL,CAAC;IACD,OAAO,GAAG,CAAC;AACb,CAAC;AAQD,SAAS,SAAS,CAAC,MAAc,EAAE,IAAY,EAAE,IAAY;IAC3D,MAAM,KAAK,GAAG,MAAM,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC;IAC/B,IAAI,CAAC,KAAK;QAAE,OAAO,IAAI,CAAC;IACxB,IAAI,KAAK,CAAC,IAAI,KAAK,MAAM;QAAE,OAAO,MAAM,CAAC;IACzC,OAAO,KAAK,CAAC,KAAK,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,QAAQ,CAAC,CAAC,CAAC,IAAI,CAAC;AACjD,CAAC;AAED,SAAS,UAAU,CACjB,IAAuB,EACvB,MAAc,EACd,QAA8B,EAC9B,OAAiC;IAEjC,MAAM,MAAM,GAAG,YAAY,CAAC,IAAI,EAAE,QAAQ,EAAE,OAAO,CAAC,CAAC;IACrD,OAAO,MAAM,CAAC,CAAC,CAAC,SAAS,CAAC,MAAM,EAAE,MAAM,CAAC,IAAI,EAAE,MAAM,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC;AACrE,CAAC;AAED,SAAS,aAAa,CACpB,UAAgC,EAChC,EAAW,EACX,MAAc,EACd,QAA8B,EAC9B,OAAiC;IAEjC,MAAM,IAAI,GAAG,UAAU,CAAC,CAAC,CAAC,OAAO,CAAC,UAAU,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC;IACrD,IAAI,CAAC,IAAI,IAAI,CAAC,EAAE,CAAC,gBAAgB,CAAC,IAAI,CAAC;QACrC,OAAO,mEAAmE,CAAC;IAC7E,MAAM,IAAI,GAAG,UAAU,CAAC,IAAI,EAAE,MAAM,EAAE,QAAQ,EAAE,OAAO,CAAC,CAAC;IACzD,IAAI,CAAC,IAAI;QAAE,OAAO,+EAA+E,CAAC;IAClG,IAAI,IAAI,KAAK,MAAM;QAAE,OAAO,IAAI,CAAC;IACjC,IAAI,IAAI,CAAC,aAAa,EAAE,MAAM;QAC5B,OAAO,mFAAmF,CAAC;IAC7F,MAAM,QAAQ,GAAG,aAAa,CAAC,EAAE,CAAC,CAAC;IACnC,MAAM,CAAC,KAAK,CAAC,GAAG,IAAI,CAAC,SAAS,CAAC;IAC/B,MAAM,MAAM,GAAG,KAAK,CAAC,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC;IAC5C,IAAI,CAAC,QAAQ,IAAI,CAAC,MAAM,IAAI,CAAC,EAAE,CAAC,YAAY,CAAC,MAAM,CAAC,IAAI,MAAM,CAAC,IAAI,KAAK,QAAQ;QAC9E,OAAO,mFAAmF,CAAC;IAC7F,OAAO,IAAI,CAAC;AACd,CAAC;AAED,SAAS,QAAQ,CAAC,KAAqB;IACrC,MAAM,MAAM,GAAG,IAAI,GAAG,EAGnB,CAAC;IACJ,KAAK,MAAM,IAAI,IAAI,KAAK,CAAC,KAAK;QAAE,MAAM,CAAC,GAAG,CAAC,IAAI,CAAC,KAAK,EAAE,EAAE,IAAI,EAAE,MAAM,EAAE,CAAC,CAAC;IACzE,KAAK,MAAM,MAAM,IAAI,KAAK,CAAC,YAAY,EAAE,CAAC;QACxC,MAAM,OAAO,GAAG,MAAM,CAAC,GAAG,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC;QACzC,IAAI,OAAO,EAAE,IAAI,KAAK,MAAM;YAAE,SAAS;QACvC,MAAM,CAAC,GAAG,CAAC,MAAM,CAAC,KAAK,EAAE;YACvB,IAAI,EAAE,QAAQ;YACd,KAAK,EAAE,IAAI,GAAG,CAAC,CAAC,GAAG,CAAC,OAAO,EAAE,KAAK,IAAI,EAAE,CAAC,EAAE,GAAG,MAAM,CAAC,KAAK,CAAC,CAAC;SAC7D,CAAC,CAAC;IACL,CAAC;IACD,OAAO,MAAM,CAAC;AAChB,CAAC;AAED,SAAS,UAAU,CAAC,KAAqB;IACvC,OAAO,IAAI,GAAG,CAAC;QACb,GAAG,KAAK,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,IAAI,CAAC,KAAK,CAAC;QACxC,GAAG,KAAK,CAAC,YAAY,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,KAAK,CAAC;KAC1C,CAAC,CAAC;AACL,CAAC;AAED,gGAAgG;AAChG,SAAS,YAAY,CAAC,MAAkB,EAAE,KAAa;IACrD,IAAI,aAAa,CAAC,KAAK,CAAC,IAAI,KAAK,CAAC,UAAU,CAAC,GAAG,CAAC;QAC/C,OAAO,MAAM,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,iDAAiD,CAAC;IACzF,MAAM,QAAQ,GAAG,KAAK,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC;IAClC,MAAM,IAAI,GAAG,QAAQ,CAAC,KAAK,CAAC,CAAC,EAAE,KAAK,CAAC,UAAU,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;IACxE,MAAM,OAAO,GAAG,QAAQ,CAAC,KAAK,CAAC,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,MAAM,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;IACjE,MAAM,QAAQ,GAAG,MAAM,CAAC,WAAW,CACjC,IAAI,CAAC,MAAM,CAAC,MAAM,CAAC,QAAQ,EAAE,cAAc,EAAE,GAAG,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,EAAE,cAAc,CAAC,CACjF,CAAC;IACF,IAAI,QAAQ,KAAK,IAAI;QAAE,OAAO,wBAAwB,IAAI,gCAAgC,CAAC;IAC3F,MAAM,OAAO,GAAa,IAAI,CAAC,KAAK,CAAC,QAAQ,CAA2B,CAAC,OAAO,CAAC;IACjF,MAAM,GAAG,GAAG,OAAO,CAAC,CAAC,CAAC,KAAK,OAAO,EAAE,CAAC,CAAC,CAAC,GAAG,CAAC;IAC3C,OAAO,OAAO,IAAI,OAAO,OAAO,KAAK,QAAQ,IAAI,GAAG,IAAI,OAAO;QAC7D,CAAC,CAAC,IAAI;QACN,CAAC,CAAC,GAAG,IAAI,eAAe,GAAG,8BAA8B,CAAC;AAC9D,CAAC;AAED,SAAS,UAAU,CAAC,MAAkB,EAAE,KAAqB,EAAE,KAAiB;IAC9E,MAAM,QAAQ,GAAc,EAAE,CAAC;IAC/B,MAAM,OAAO,GAAG,SAAS,CAAC,KAAK,CAAC,aAAa,CAAC,CAAC;IAC/C,MAAM,MAAM,GAAG,QAAQ,CAAC,KAAK,CAAC,CAAC;IAC/B,MAAM,IAAI,GAAG,UAAU,CAAC,KAAK,CAAC,CAAC;IAC/B,KAAK,MAAM,KAAK,IAAI,IAAI,EAAE,CAAC;QACzB,MAAM,OAAO,GAAG,YAAY,CAAC,MAAM,EAAE,KAAK,CAAC,CAAC;QAC5C,IAAI,OAAO;YAAE,QAAQ,CAAC,IAAI,CAAC,EAAE,IAAI,EAAE,KAAK,EAAE,MAAM,EAAE,OAAO,EAAE,CAAC,CAAC;IAC/D,CAAC;IACD,KAAK,MAAM,IAAI,IAAI,KAAK,EAAE,CAAC;QACzB,IAAI,IAAI,CAAC,GAAG,CAAC,IAAI,CAAC,IAAI,CAAC;YAAE,SAAS;QAClC,MAAM,OAAO,GAAG,CAAC,IAAY,EAAE,EAAE,CAAC,gBAAgB,CAAC,MAAM,EAAE,IAAI,CAAC,IAAI,EAAE,IAAI,EAAE,OAAO,CAAC,CAAC;QACrF,KAAK,MAAM,IAAI,IAAI,OAAO,CAAC,MAAM,EAAE,IAAI,EAAE,OAAO,CAAC,EAAE,CAAC;YAClD,MAAM,MAAM,GAAG,CAAC,IAAa,EAAE,GAAW,EAAE,EAAE,CAC5C,QAAQ,CAAC,IAAI,CAAC;gBACZ,IAAI,EAAE,IAAI,CAAC,IAAI;gBACf,IAAI,EAAE,UAAU,CAAC,IAAI,EAAE,IAAI,CAAC;gBAC5B,MAAM,EAAE,SAAS,IAAI,CAAC,MAAM,wBAAwB,GAAG,EAAE;aAC1D,CAAC,CAAC;YACL,IAAI,CAAC,IAAI,CAAC,QAAQ;gBAChB,QAAQ,CAAC,IAAI,CAAC;oBACZ,IAAI,EAAE,IAAI,CAAC,IAAI;oBACf,IAAI,EAAE,UAAU,CAAC,IAAI,EAAE,IAAI,CAAC,IAAI,CAAC;oBACjC,MAAM,EACJ,SAAS,IAAI,CAAC,MAAM,oEAAoE;wBACxF,8CAA8C;iBACjD,CAAC,CAAC;YACL,IAAI,CAAC,IAAI,CAAC,OAAO,IAAI,eAAe,CAAC,IAAI,CAAC,OAAO,CAAC;gBAAE,SAAS;YAC7D,MAAM,EAAE,GAAG,eAAe,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC;YACzC,IAAI,CAAC,EAAE,EAAE,CAAC;gBACR,MAAM,CAAC,IAAI,CAAC,IAAI,EAAE,4DAA4D,CAAC,CAAC;gBAChF,SAAS;YACX,CAAC;YACD,MAAM,OAAO,GAAG,mBAAmB,CAAC,EAAE,CAAC,CAAC;YACxC,IAAI,CAAC,OAAO,CAAC,MAAM;gBAAE,MAAM,CAAC,IAAI,CAAC,IAAI,EAAE,iBAAiB,CAAC,CAAC;YAC1D,MAAM,QAAQ,GAAG,UAAU,CAAC,EAAE,EAAE,OAAO,CAAC,CAAC;YACzC,KAAK,MAAM,QAAQ,IAAI,OAAO,EAAE,CAAC;gBAC/B,MAAM,GAAG,GAAG,aAAa,CAAC,QAAQ,EAAE,EAAE,EAAE,MAAM,EAAE,QAAQ,EAAE,OAAO,CAAC,CAAC;gBACnE,IAAI,GAAG;oBAAE,MAAM,CAAC,QAAQ,IAAI,IAAI,CAAC,IAAI,EAAE,GAAG,CAAC,CAAC;YAC9C,CAAC;QACH,CAAC;IACH,CAAC;IACD,OAAO,QAAQ,CAAC;AAClB,CAAC;AAED,MAAM,gBAAgB,GAAG,CAAC,UAAyB,EAAE,QAAuB,EAAW,EAAE;IACvF,MAAM,KAAK,GAAG,OAAO,CAAC,UAAU,CAAC,CAAC;IAClC,IAAI,CAAC,EAAE,CAAC,gBAAgB,CAAC,KAAK,CAAC;QAAE,OAAO,KAAK,CAAC;IAC9C,MAAM,MAAM,GAAG,MAAM,CAAC,KAAK,CAAC,UAAU,CAAC,CAAC;IACxC,OAAO,CACL,CAAC,EAAE,CAAC,YAAY,CAAC,MAAM,CAAC,IAAI,MAAM,CAAC,IAAI,KAAK,QAAQ,CAAC;QACrD,CAAC,EAAE,CAAC,0BAA0B,CAAC,MAAM,CAAC;YACpC,EAAE,CAAC,YAAY,CAAC,MAAM,CAAC,UAAU,CAAC;YAClC,MAAM,CAAC,UAAU,CAAC,IAAI,KAAK,IAAI;YAC/B,MAAM,CAAC,IAAI,CAAC,IAAI,KAAK,cAAc,CAAC,CACvC,CAAC;AACJ,CAAC,CAAC;AAIF,SAAS,WAAW,CAAC,QAAuB,EAAE,QAAuB;IACnE,MAAM,KAAK,GAAG,OAAO,CAAC,QAAQ,CAAC,CAAC;IAChC,MAAM,OAAO,GAAiC,EAAE,CAAC;IACjD,MAAM,GAAG,GAAmB,EAAE,CAAC;IAC/B,IAAI,EAAE,CAAC,yBAAyB,CAAC,KAAK,CAAC;QAAE,OAAO,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;SACxD,IAAI,EAAE,CAAC,gBAAgB,CAAC,KAAK,CAAC;QACjC,KAAK,MAAM,QAAQ,IAAI,KAAK,CAAC,SAAS,EAAE,CAAC;YACvC,MAAM,GAAG,GAAG,MAAM,CAAC,QAAQ,CAAC,CAAC;YAC7B,IAAI,EAAE,CAAC,yBAAyB,CAAC,GAAG,CAAC;gBAAE,OAAO,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;iBACpD,IAAI,CAAC,CAAC,EAAE,CAAC,YAAY,CAAC,GAAG,CAAC,IAAI,GAAG,CAAC,IAAI,KAAK,QAAQ,CAAC;gBAAE,GAAG,CAAC,IAAI,CAAC,EAAE,MAAM,EAAE,GAAG,EAAE,CAAC,CAAC;QACvF,CAAC;IACH,KAAK,MAAM,MAAM,IAAI,OAAO;QAC1B,KAAK,MAAM,QAAQ,IAAI,MAAM,CAAC,UAAU,EAAE,CAAC;YACzC,IAAI,EAAE,CAAC,kBAAkB,CAAC,QAAQ,CAAC,EAAE,CAAC;gBACpC,IAAI,CAAC,gBAAgB,CAAC,QAAQ,CAAC,UAAU,EAAE,QAAQ,CAAC;oBAAE,GAAG,CAAC,IAAI,CAAC,EAAE,MAAM,EAAE,QAAQ,EAAE,CAAC,CAAC;gBACrF,SAAS;YACX,CAAC;YACD,MAAM,IAAI,GAAG,QAAQ,CAAC,IAAI,CAAC;YAC3B,IAAI,IAAI,IAAI,CAAC,EAAE,CAAC,YAAY,CAAC,IAAI,CAAC,IAAI,EAAE,CAAC,mBAAmB,CAAC,IAAI,CAAC,CAAC;gBACjE,GAAG,CAAC,IAAI,CAAC,EAAE,IAAI,EAAE,IAAI,CAAC,IAAI,EAAE,IAAI,EAAE,QAAQ,EAAE,CAAC,CAAC;;gBAC3C,GAAG,CAAC,IAAI,CAAC,EAAE,MAAM,EAAE,QAAQ,EAAE,CAAC,CAAC;QACtC,CAAC;IACH,OAAO,GAAG,CAAC;AACb,CAAC;AAED,SAAS,cAAc,CAAC,MAAkB,EAAE,KAAqB,EAAE,KAAiB;IAClF,MAAM,QAAQ,GAAc,EAAE,CAAC;IAC/B,MAAM,OAAO,GAAG,SAAS,CAAC,KAAK,CAAC,aAAa,CAAC,CAAC;IAC/C,MAAM,QAAQ,GAAG,IAAI,GAAG,CAAC,KAAK,CAAC,kBAAkB,CAAC,GAAG,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,CAAC,KAAK,CAAC,MAAM,EAAE,KAAK,CAAC,CAAC,CAAC,CAAC;IACzF,KAAK,MAAM,IAAI,IAAI,KAAK,EAAE,CAAC;QACzB,KAAK,MAAM,IAAI,IAAI,OAAO,CAAC,MAAM,EAAE,IAAI,EAAE,OAAO,CAAC,EAAE,CAAC;YAClD,MAAM,KAAK,GAAG,QAAQ,CAAC,GAAG,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC;YACxC,IAAI,CAAC,KAAK,IAAI,CAAC,IAAI,CAAC,OAAO,IAAI,eAAe,CAAC,IAAI,CAAC,OAAO,CAAC;gBAAE,SAAS;YACvE,MAAM,EAAE,GAAG,eAAe,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC;YACzC,IAAI,CAAC,EAAE;gBAAE,SAAS;YAClB,MAAM,SAAS,GAAG,IAAI,GAAG,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC;YACvC,KAAK,MAAM,QAAQ,IAAI,mBAAmB,CAAC,EAAE,CAAC,EAAE,CAAC;gBAC/C,IAAI,CAAC,QAAQ;oBAAE,SAAS;gBACxB,KAAK,MAAM,IAAI,IAAI,WAAW,CAAC,QAAQ,EAAE,aAAa,CAAC,EAAE,CAAC,CAAC,EAAE,CAAC;oBAC5D,IAAI,QAAQ,IAAI,IAAI,EAAE,CAAC;wBACrB,QAAQ,CAAC,IAAI,CAAC;4BACZ,IAAI,EAAE,IAAI,CAAC,IAAI;4BACf,IAAI,EAAE,UAAU,CAAC,IAAI,EAAE,IAAI,CAAC,MAAM,CAAC;4BACnC,MAAM,EAAE,aAAa,IAAI,CAAC,MAAM,6EAA6E,KAAK,CAAC,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,oBAAoB,KAAK,CAAC,GAAG,EAAE;yBACnK,CAAC,CAAC;wBACH,SAAS;oBACX,CAAC;oBACD,IAAI,CAAC,SAAS,CAAC,GAAG,CAAC,IAAI,CAAC,IAAI,CAAC;wBAAE,SAAS;oBACxC,QAAQ,CAAC,IAAI,CAAC;wBACZ,IAAI,EAAE,IAAI,CAAC,IAAI;wBACf,IAAI,EAAE,UAAU,CAAC,IAAI,EAAE,IAAI,CAAC,IAAI,CAAC;wBACjC,MAAM,EAAE,aAAa,IAAI,CAAC,MAAM,MAAM,IAAI,CAAC,IAAI,uCAAuC,KAAK,CAAC,GAAG,EAAE;qBAClG,CAAC,CAAC;gBACL,CAAC;YACH,CAAC;QACH,CAAC;IACH,CAAC;IACD,OAAO,QAAQ,CAAC;AAClB,CAAC;AAED,SAAS,YAAY,CAAC,KAAqB;IACzC,MAAM,QAAQ,GAAa,EAAE,CAAC;IAC9B,IAAI,CAAC,aAAa,CAAC,KAAK,EAAE,WAAW,EAAE,QAAQ,CAAC;QAAE,OAAO,QAAQ,CAAC;IAClE,oBAAoB,CAAC,KAAK,CAAC,aAAa,EAAE,eAAe,EAAE,QAAQ,CAAC,CAAC;IACrE,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,KAAK,CAAC,KAAK,CAAC;QAC7B,QAAQ,CAAC,IAAI,CAAC,kDAAkD,CAAC,CAAC;;QAElE,KAAK,MAAM,IAAI,IAAI,KAAK,CAAC,KAAkB,EAAE,CAAC;YAC5C,MAAM,KAAK,GAAG,CAAC,IAAI,IAAI,OAAO,IAAI,KAAK,QAAQ,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,CAA4B,CAAC;YACxF,WAAW,CAAC,KAAK,CAAC,MAAM,EAAE,2BAA2B,EAAE,QAAQ,CAAC,CAAC;YACjE,WAAW,CAAC,KAAK,CAAC,KAAK,EAAE,eAAe,MAAM,CAAC,KAAK,CAAC,MAAM,CAAC,UAAU,EAAE,QAAQ,CAAC,CAAC;QACpF,CAAC;IACH,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,KAAK,CAAC,YAAY,CAAC;QACpC,QAAQ,CAAC,IAAI,CAAC,yDAAyD,CAAC,CAAC;;QAEzE,KAAK,MAAM,MAAM,IAAI,KAAK,CAAC,YAAyB,EAAE,CAAC;YACrD,MAAM,KAAK,GAAG,CAAC,MAAM,IAAI,OAAO,MAAM,KAAK,QAAQ,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,CAA4B,CAAC;YAC9F,WAAW,CAAC,KAAK,CAAC,KAAK,EAAE,8BAA8B,EAAE,QAAQ,CAAC,CAAC;YACnE,YAAY,CAAC,KAAK,CAAC,KAAK,EAAE,sBAAsB,MAAM,CAAC,KAAK,CAAC,KAAK,CAAC,UAAU,EAAE,QAAQ,CAAC,CAAC;QAC3F,CAAC;IACH,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,KAAK,CAAC,kBAAkB,CAAC;QAC1C,QAAQ,CAAC,IAAI,CAAC,+DAA+D,CAAC,CAAC;SAC5E,CAAC;QACJ,MAAM,IAAI,GAAG,IAAI,GAAG,EAAU,CAAC;QAC/B,KAAK,MAAM,GAAG,IAAI,KAAK,CAAC,kBAA+B,EAAE,CAAC;YACxD,MAAM,KAAK,GAAG,CAAC,GAAG,IAAI,OAAO,GAAG,KAAK,QAAQ,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,CAA4B,CAAC;YACrF,IAAI,CAAC,MAAM,CAAC,KAAK,CAAC,MAAM,CAAC,EAAE,CAAC;gBAC1B,QAAQ,CAAC,IAAI,CAAC,gDAAgD,CAAC,CAAC;gBAChE,SAAS;YACX,CAAC;YACD,IAAI,IAAI,CAAC,GAAG,CAAC,KAAK,CAAC,MAAM,CAAC;gBAAE,QAAQ,CAAC,IAAI,CAAC,4BAA4B,KAAK,CAAC,MAAM,QAAQ,CAAC,CAAC;YAC5F,IAAI,CAAC,GAAG,CAAC,KAAK,CAAC,MAAM,CAAC,CAAC;YACvB,YAAY,CAAC,KAAK,CAAC,KAAK,EAAE,4BAA4B,KAAK,CAAC,MAAM,UAAU,EAAE,QAAQ,CAAC,CAAC;YACxF,WAAW,CAAC,KAAK,CAAC,GAAG,EAAE,4BAA4B,KAAK,CAAC,MAAM,QAAQ,EAAE,QAAQ,CAAC,CAAC;QACrF,CAAC;IACH,CAAC;IACD,OAAO,QAAQ,CAAC;AAClB,CAAC;AAED,MAAM,CAAC,MAAM,cAAc,GAA2C;IACpE,EAAE,EAAE,YAAY;IAChB,UAAU,EAAE,KAAK;IACjB,YAAY;IACZ,KAAK,CAAC,KAAK;QACT,OAAO;YACL;gBACE,KAAK,EAAE,2DAA2D;gBAClE,WAAW,EACT,sGAAsG;oBACtG,qGAAqG;oBACrG,wGAAwG;oBACxG,2GAA2G;oBAC3G,sGAAsG;oBACtG,yFAAyF;gBAC3F,SAAS,EAAE,2BAA2B;gBACtC,GAAG,CAAC,OAAO;oBACT,OAAO,UAAU,CAAC,OAAO,CAAC,MAAM,EAAE,KAAK,EAAE,OAAO,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC,CAAC;gBACjE,CAAC;aACF;YACD;gBACE,KAAK,EAAE,kEAAkE;gBACzE,WAAW,EACT,0GAA0G;oBAC1G,uGAAuG;oBACvG,wGAAwG;oBACxG,yGAAyG;oBACzG,kFAAkF;gBACpF,SAAS,EAAE,2BAA2B;gBACtC,GAAG,CAAC,OAAO;oBACT,OAAO,cAAc,CAAC,OAAO,CAAC,MAAM,EAAE,KAAK,EAAE,OAAO,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC,CAAC;gBACrE,CAAC;aACF;SACF,CAAC;IACJ,CAAC;CACF,CAAC","sourcesContent":["import ts from 'typescript';\nimport { join } from 'node:path';\nimport type { Finding } from '../../guard.js';\nimport type { RepoReader, Scope } from '../../node/reader.js';\nimport type { GenericGuardDefinition } from '../../node/suite/config.js';\nimport type { FileView } from '../../reader/view.js';\nimport { lineOfNode, unwrap } from '../../support/ast.js';\nimport {\n awaited,\n bindingsOf,\n calledExport,\n factoryFunction,\n isRunnerOptions,\n parameterName,\n returnedExpressions,\n type Binding,\n type Factory,\n} from '../../support/mock-factories.js';\nimport {\n importAliasesProblem,\n isText,\n namesProblem,\n objectProblem,\n pathProblem,\n textProblem,\n} from '../../support/facts.js';\nimport { aliasList, isLocalModule, resolveSpecifier } from '../../support/resolve-specifier.js';\nimport { REPOSITORY_GUARD_TIMEOUT_MS } from '../../support/timeouts.js';\nimport type { SharedFake } from './shared-fakes.js';\n\nconst MOCK_CALLS: ReadonlySet<string> = new Set(['vi.mock', 'vi.doMock']);\n\nconst SCOPE: Scope = {\n roots: ['app', 'functions', 'scripts', 'workers'],\n kinds: ['code'],\n tests: 'only',\n optionalRoots: ['functions', 'scripts', 'workers'],\n};\n\nexport interface TypedMockHelper {\n owner: string;\n names: readonly string[];\n}\n\nexport interface ForbiddenOverride {\n module: string;\n names: readonly string[];\n why: string;\n}\n\nexport interface TypedMockFacts {\n importAliases: Readonly<Record<string, string>>;\n fakes: readonly SharedFake[];\n typedHelpers: readonly TypedMockHelper[];\n forbiddenOverrides: readonly ForbiddenOverride[];\n}\n\ninterface Site {\n node: ts.CallExpression;\n module: string;\n byImport: boolean;\n factory: ts.Expression | undefined;\n}\n\nfunction sitesOf(reader: RepoReader, file: FileView, aliases: Array<[string, string]>): Site[] {\n const out: Site[] = [];\n for (const site of file.imports) {\n if (site.kind !== 'mock' || !MOCK_CALLS.has(site.callee ?? '')) continue;\n if (!ts.isCallExpression(site.node)) continue;\n out.push({\n node: site.node,\n module: resolveSpecifier(reader, file.path, site.spec, aliases),\n byImport: site.byImport,\n factory: site.node.arguments[1],\n });\n }\n return out;\n}\n\ntype OwnerKind = 'fake' | 'helper';\ntype Owners = ReadonlyMap<\n string,\n { kind: 'fake' } | { kind: 'helper'; names: ReadonlySet<string> }\n>;\n\nfunction ownerKind(owners: Owners, path: string, name: string): OwnerKind | null {\n const owner = owners.get(path);\n if (!owner) return null;\n if (owner.kind === 'fake') return 'fake';\n return owner.names.has(name) ? 'helper' : null;\n}\n\nfunction calleeKind(\n call: ts.CallExpression,\n owners: Owners,\n bindings: Map<string, Binding>,\n resolve: (spec: string) => string,\n): OwnerKind | null {\n const target = calledExport(call, bindings, resolve);\n return target ? ownerKind(owners, target.path, target.name) : null;\n}\n\nfunction untypedReason(\n expression: ts.Expression | null,\n fn: Factory,\n owners: Owners,\n bindings: Map<string, Binding>,\n resolve: (spec: string) => string,\n): string | null {\n const call = expression ? awaited(expression) : null;\n if (!call || !ts.isCallExpression(call))\n return 'returns something other than a shared fake or a typed mock helper';\n const kind = calleeKind(call, owners, bindings, resolve);\n if (!kind) return 'returns through a function that is no listed shared fake or typed mock helper';\n if (kind === 'fake') return null;\n if (call.typeArguments?.length)\n return 'writes the type of its typed mock helper instead of taking it from importOriginal';\n const original = parameterName(fn);\n const [first] = call.arguments;\n const passed = first ? unwrap(first) : null;\n if (!original || !passed || !ts.isIdentifier(passed) || passed.text !== original)\n return \"hands its typed mock helper something other than the factory's own importOriginal\";\n return null;\n}\n\nfunction ownersOf(facts: TypedMockFacts): Owners {\n const owners = new Map<\n string,\n { kind: 'fake' } | { kind: 'helper'; names: ReadonlySet<string> }\n >();\n for (const fake of facts.fakes) owners.set(fake.owner, { kind: 'fake' });\n for (const helper of facts.typedHelpers) {\n const already = owners.get(helper.owner);\n if (already?.kind === 'fake') continue;\n owners.set(helper.owner, {\n kind: 'helper',\n names: new Set([...(already?.names ?? []), ...helper.names]),\n });\n }\n return owners;\n}\n\nfunction ownerFiles(facts: TypedMockFacts): Set<string> {\n return new Set([\n ...facts.fakes.map((fake) => fake.owner),\n ...facts.typedHelpers.map((h) => h.owner),\n ]);\n}\n\n// A package-owned helper is named by its package entry (`@scope/pkg/testing`), not a repo file.\nfunction missingOwner(reader: RepoReader, owner: string): string | null {\n if (isLocalModule(owner) || owner.startsWith('.'))\n return reader.exists(owner) ? null : 'this typed fake or helper file no longer exists';\n const segments = owner.split('/');\n const name = segments.slice(0, owner.startsWith('@') ? 2 : 1).join('/');\n const subpath = segments.slice(name.split('/').length).join('/');\n const manifest = reader.readOutside(\n join(reader.layout.repoRoot, 'node_modules', ...name.split('/'), 'package.json'),\n );\n if (manifest === null) return `no installed package ${name} holds this typed helper entry`;\n const exports: unknown = (JSON.parse(manifest) as { exports?: unknown }).exports;\n const key = subpath ? `./${subpath}` : '.';\n return exports && typeof exports === 'object' && key in exports\n ? null\n : `${name} exports no ${key} entry for this typed helper`;\n}\n\nfunction checkTyped(reader: RepoReader, facts: TypedMockFacts, files: FileView[]): Finding[] {\n const findings: Finding[] = [];\n const aliases = aliasList(facts.importAliases);\n const owners = ownersOf(facts);\n const skip = ownerFiles(facts);\n for (const owner of skip) {\n const missing = missingOwner(reader, owner);\n if (missing) findings.push({ file: owner, detail: missing });\n }\n for (const file of files) {\n if (skip.has(file.path)) continue;\n const resolve = (spec: string) => resolveSpecifier(reader, file.path, spec, aliases);\n for (const site of sitesOf(reader, file, aliases)) {\n const report = (node: ts.Node, why: string) =>\n findings.push({\n file: file.path,\n line: lineOfNode(file, node),\n detail: `mocks ${site.module} with a factory that ${why}`,\n });\n if (!site.byImport)\n findings.push({\n file: file.path,\n line: lineOfNode(file, site.node),\n detail:\n `mocks ${site.module} by a string path — name it through import('…'), so the factory's ` +\n \"importOriginal carries the module's own type\",\n });\n if (!site.factory || isRunnerOptions(site.factory)) continue;\n const fn = factoryFunction(site.factory);\n if (!fn) {\n report(site.node, 'the guard cannot read, since it is not written at the mock');\n continue;\n }\n const returns = returnedExpressions(fn);\n if (!returns.length) report(site.node, 'returns nothing');\n const bindings = bindingsOf(fn, resolve);\n for (const returned of returns) {\n const why = untypedReason(returned, fn, owners, bindings, resolve);\n if (why) report(returned ?? site.node, why);\n }\n }\n }\n return findings;\n}\n\nconst isOriginalSpread = (expression: ts.Expression, original: string | null): boolean => {\n const value = awaited(expression);\n if (!ts.isCallExpression(value)) return false;\n const callee = unwrap(value.expression);\n return (\n (ts.isIdentifier(callee) && callee.text === original) ||\n (ts.isPropertyAccessExpression(callee) &&\n ts.isIdentifier(callee.expression) &&\n callee.expression.text === 'vi' &&\n callee.name.text === 'importActual')\n );\n};\n\ntype OverrideRead = { name: string; node: ts.Node } | { unread: ts.Node };\n\nfunction overridesOf(returned: ts.Expression, original: string | null): OverrideRead[] {\n const value = awaited(returned);\n const objects: ts.ObjectLiteralExpression[] = [];\n const out: OverrideRead[] = [];\n if (ts.isObjectLiteralExpression(value)) objects.push(value);\n else if (ts.isCallExpression(value))\n for (const argument of value.arguments) {\n const arg = unwrap(argument);\n if (ts.isObjectLiteralExpression(arg)) objects.push(arg);\n else if (!(ts.isIdentifier(arg) && arg.text === original)) out.push({ unread: arg });\n }\n for (const object of objects)\n for (const property of object.properties) {\n if (ts.isSpreadAssignment(property)) {\n if (!isOriginalSpread(property.expression, original)) out.push({ unread: property });\n continue;\n }\n const name = property.name;\n if (name && (ts.isIdentifier(name) || ts.isStringLiteralLike(name)))\n out.push({ name: name.text, node: property });\n else out.push({ unread: property });\n }\n return out;\n}\n\nfunction checkForbidden(reader: RepoReader, facts: TypedMockFacts, files: FileView[]): Finding[] {\n const findings: Finding[] = [];\n const aliases = aliasList(facts.importAliases);\n const byModule = new Map(facts.forbiddenOverrides.map((entry) => [entry.module, entry]));\n for (const file of files) {\n for (const site of sitesOf(reader, file, aliases)) {\n const entry = byModule.get(site.module);\n if (!entry || !site.factory || isRunnerOptions(site.factory)) continue;\n const fn = factoryFunction(site.factory);\n if (!fn) continue;\n const forbidden = new Set(entry.names);\n for (const returned of returnedExpressions(fn)) {\n if (!returned) continue;\n for (const read of overridesOf(returned, parameterName(fn))) {\n if ('unread' in read) {\n findings.push({\n file: file.path,\n line: lineOfNode(file, read.unread),\n detail: `overrides ${site.module} through names the guard cannot read — write each override by name, since ${entry.names.join(', ')} must stay real: ${entry.why}`,\n });\n continue;\n }\n if (!forbidden.has(read.name)) continue;\n findings.push({\n file: file.path,\n line: lineOfNode(file, read.node),\n detail: `overrides ${site.module}'s ${read.name} — the real one runs in every test: ${entry.why}`,\n });\n }\n }\n }\n }\n return findings;\n}\n\nfunction factProblems(facts: TypedMockFacts): string[] {\n const problems: string[] = [];\n if (!objectProblem(facts, 'the facts', problems)) return problems;\n importAliasesProblem(facts.importAliases, 'importAliases', problems);\n if (!Array.isArray(facts.fakes))\n problems.push('fakes is missing (declare [] when there is none)');\n else\n for (const fake of facts.fakes as unknown[]) {\n const entry = (fake && typeof fake === 'object' ? fake : {}) as Record<string, unknown>;\n textProblem(entry.module, 'an entry of fakes’ module', problems);\n pathProblem(entry.owner, `fakes entry ${String(entry.module)}'s owner`, problems);\n }\n if (!Array.isArray(facts.typedHelpers))\n problems.push('typedHelpers is missing (declare [] when there is none)');\n else\n for (const helper of facts.typedHelpers as unknown[]) {\n const entry = (helper && typeof helper === 'object' ? helper : {}) as Record<string, unknown>;\n pathProblem(entry.owner, \"a typedHelpers entry's owner\", problems);\n namesProblem(entry.names, `typedHelpers entry ${String(entry.owner)}'s names`, problems);\n }\n if (!Array.isArray(facts.forbiddenOverrides))\n problems.push('forbiddenOverrides is missing (declare [] when there is none)');\n else {\n const seen = new Set<string>();\n for (const raw of facts.forbiddenOverrides as unknown[]) {\n const entry = (raw && typeof raw === 'object' ? raw : {}) as Record<string, unknown>;\n if (!isText(entry.module)) {\n problems.push('an entry of forbiddenOverrides names no module');\n continue;\n }\n if (seen.has(entry.module)) problems.push(`forbiddenOverrides lists ${entry.module} twice`);\n seen.add(entry.module);\n namesProblem(entry.names, `forbiddenOverrides entry ${entry.module}'s names`, problems);\n textProblem(entry.why, `forbiddenOverrides entry ${entry.module}'s why`, problems);\n }\n }\n return problems;\n}\n\nexport const typedMockGuard: GenericGuardDefinition<TypedMockFacts> = {\n id: 'typedMocks',\n absentWhen: false,\n factProblems,\n build(facts) {\n return [\n {\n title: 'every module mock is typed against the module it replaces',\n description:\n \"A test's module mock names its module through `import('…')` and returns, on every path, through the \" +\n \"module's shared fake or a typed mock helper. A helper takes its module type from the factory's own \" +\n '`importOriginal`, never from a type argument the test writes, so a wrong module, a misspelled export, ' +\n 'and a wrong signature fail to compile. A factory that returns a hand-written object is never compared to ' +\n 'the module, so a double that no longer matches it still passes. A mock with no factory, or with the ' +\n 'runner’s options object (`{ spy: true }`) in its place, is the runner’s own and passes.',\n timeoutMs: REPOSITORY_GUARD_TIMEOUT_MS,\n run(context) {\n return checkTyped(context.reader, facts, context.files(SCOPE));\n },\n },\n {\n title: 'no test overrides an export whose real behavior every test needs',\n description:\n 'A listed export is never replaced in a test, by its shared fake’s overrides or by a factory of its own: ' +\n 'a stand-in that behaves differently on the path a case proves lets the case pass on behavior the app ' +\n 'never has. The guard reads the names each override object sets at its top level; a spread of anything ' +\n 'but the real module, a computed name, or an override object it cannot see fails, since it could hide a ' +\n 'listed one. Each entry names its module, the exports, and why the real one runs.',\n timeoutMs: REPOSITORY_GUARD_TIMEOUT_MS,\n run(context) {\n return checkForbidden(context.reader, facts, context.files(SCOPE));\n },\n },\n ];\n },\n};\n"]}
|
|
1
|
+
{"version":3,"file":"typed-mocks.js","sourceRoot":"","sources":["../../../src/modules/repo/typed-mocks.ts"],"names":[],"mappings":"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","sourcesContent":["import ts from 'typescript';\nimport { join } from 'node:path';\nimport type { Finding } from '../../guard.js';\nimport type { RepoReader, Scope } from '../../node/reader.js';\nimport type { GenericGuardDefinition } from '../../node/suite/config.js';\nimport type { FileView } from '../../reader/view.js';\nimport { lineOfNode, unwrap } from '../../support/ast.js';\nimport {\n awaited,\n bindingsOf,\n calledExport,\n factoryFunction,\n isRunnerOptions,\n parameterName,\n returnedExpressions,\n type Binding,\n type Factory,\n} from '../../support/mock-factories.js';\nimport {\n importAliasesProblem,\n isText,\n namesProblem,\n objectProblem,\n pathProblem,\n textProblem,\n} from '../../support/facts.js';\nimport { aliasList, isLocalModule, resolveSpecifier } from '../../support/resolve-specifier.js';\nimport { REPOSITORY_GUARD_TIMEOUT_MS } from '../../support/timeouts.js';\nimport type { SharedFake } from './shared-fakes.js';\n\nconst MOCK_CALLS: ReadonlySet<string> = new Set(['vi.mock', 'vi.doMock']);\n\nconst SCOPE: Scope = {\n roots: ['app', 'functions', 'scripts', 'workers'],\n kinds: ['code'],\n tests: 'only',\n optionalRoots: ['functions', 'scripts', 'workers'],\n};\n\nexport interface TypedMockHelper {\n owner: string;\n names: readonly string[];\n}\n\nexport interface FakeCall {\n name: string;\n overridesArgument: number;\n}\n\nexport interface ForbiddenOverride {\n module: string;\n names: readonly string[];\n why: string;\n fakeCalls?: readonly FakeCall[];\n}\n\nexport interface TypedMockFacts {\n importAliases: Readonly<Record<string, string>>;\n fakes: readonly SharedFake[];\n typedHelpers: readonly TypedMockHelper[];\n forbiddenOverrides: readonly ForbiddenOverride[];\n}\n\ninterface Site {\n node: ts.CallExpression;\n module: string;\n byImport: boolean;\n factory: ts.Expression | undefined;\n}\n\nfunction sitesOf(reader: RepoReader, file: FileView, aliases: Array<[string, string]>): Site[] {\n const out: Site[] = [];\n for (const site of file.imports) {\n if (site.kind !== 'mock' || !MOCK_CALLS.has(site.callee ?? '')) continue;\n if (!ts.isCallExpression(site.node)) continue;\n out.push({\n node: site.node,\n module: resolveSpecifier(reader, file.path, site.spec, aliases),\n byImport: site.byImport,\n factory: site.node.arguments[1],\n });\n }\n return out;\n}\n\ntype OwnerKind = 'fake' | 'helper';\ntype Owners = ReadonlyMap<\n string,\n { kind: 'fake' } | { kind: 'helper'; names: ReadonlySet<string> }\n>;\n\nfunction ownerKind(owners: Owners, path: string, name: string): OwnerKind | null {\n const owner = owners.get(path);\n if (!owner) return null;\n if (owner.kind === 'fake') return 'fake';\n return owner.names.has(name) ? 'helper' : null;\n}\n\nfunction calleeKind(\n call: ts.CallExpression,\n owners: Owners,\n bindings: Map<string, Binding>,\n resolve: (spec: string) => string,\n): OwnerKind | null {\n const target = calledExport(call, bindings, resolve);\n return target ? ownerKind(owners, target.path, target.name) : null;\n}\n\nfunction untypedReason(\n expression: ts.Expression | null,\n fn: Factory,\n owners: Owners,\n bindings: Map<string, Binding>,\n resolve: (spec: string) => string,\n): string | null {\n const call = expression ? awaited(expression) : null;\n if (!call || !ts.isCallExpression(call))\n return 'returns something other than a shared fake or a typed mock helper';\n const kind = calleeKind(call, owners, bindings, resolve);\n if (!kind) return 'returns through a function that is no listed shared fake or typed mock helper';\n if (kind === 'fake') return null;\n if (call.typeArguments?.length)\n return 'writes the type of its typed mock helper instead of taking it from importOriginal';\n const original = parameterName(fn);\n const [first] = call.arguments;\n const passed = first ? unwrap(first) : null;\n if (!original || !passed || !ts.isIdentifier(passed) || passed.text !== original)\n return \"hands its typed mock helper something other than the factory's own importOriginal\";\n return null;\n}\n\nfunction ownersOf(facts: TypedMockFacts): Owners {\n const owners = new Map<\n string,\n { kind: 'fake' } | { kind: 'helper'; names: ReadonlySet<string> }\n >();\n for (const fake of facts.fakes) owners.set(fake.owner, { kind: 'fake' });\n for (const helper of facts.typedHelpers) {\n const already = owners.get(helper.owner);\n if (already?.kind === 'fake') continue;\n owners.set(helper.owner, {\n kind: 'helper',\n names: new Set([...(already?.names ?? []), ...helper.names]),\n });\n }\n return owners;\n}\n\nfunction ownerFiles(facts: TypedMockFacts): Set<string> {\n return new Set([\n ...facts.fakes.map((fake) => fake.owner),\n ...facts.typedHelpers.map((h) => h.owner),\n ]);\n}\n\n// A package-owned helper is named by its package entry (`@scope/pkg/testing`), not a repo file.\nfunction missingOwner(reader: RepoReader, owner: string): string | null {\n if (isLocalModule(owner) || owner.startsWith('.'))\n return reader.exists(owner) ? null : 'this typed fake or helper file no longer exists';\n const segments = owner.split('/');\n const name = segments.slice(0, owner.startsWith('@') ? 2 : 1).join('/');\n const subpath = segments.slice(name.split('/').length).join('/');\n const manifest = reader.readOutside(\n join(reader.layout.repoRoot, 'node_modules', ...name.split('/'), 'package.json'),\n );\n if (manifest === null) return `no installed package ${name} holds this typed helper entry`;\n const exports: unknown = (JSON.parse(manifest) as { exports?: unknown }).exports;\n const key = subpath ? `./${subpath}` : '.';\n return exports && typeof exports === 'object' && key in exports\n ? null\n : `${name} exports no ${key} entry for this typed helper`;\n}\n\nfunction checkTyped(reader: RepoReader, facts: TypedMockFacts, files: FileView[]): Finding[] {\n const findings: Finding[] = [];\n const aliases = aliasList(facts.importAliases);\n const owners = ownersOf(facts);\n const skip = ownerFiles(facts);\n for (const owner of skip) {\n const missing = missingOwner(reader, owner);\n if (missing) findings.push({ file: owner, detail: missing });\n }\n for (const file of files) {\n if (skip.has(file.path)) continue;\n const resolve = (spec: string) => resolveSpecifier(reader, file.path, spec, aliases);\n for (const site of sitesOf(reader, file, aliases)) {\n const report = (node: ts.Node, why: string) =>\n findings.push({\n file: file.path,\n line: lineOfNode(file, node),\n detail: `mocks ${site.module} with a factory that ${why}`,\n });\n if (!site.byImport)\n findings.push({\n file: file.path,\n line: lineOfNode(file, site.node),\n detail:\n `mocks ${site.module} by a string path — name it through import('…'), so the factory's ` +\n \"importOriginal carries the module's own type\",\n });\n if (!site.factory || isRunnerOptions(site.factory)) continue;\n const fn = factoryFunction(site.factory);\n if (!fn) {\n report(site.node, 'the guard cannot read, since it is not written at the mock');\n continue;\n }\n const returns = returnedExpressions(fn);\n if (!returns.length) report(site.node, 'returns nothing');\n const bindings = bindingsOf(fn, resolve);\n for (const returned of returns) {\n const why = untypedReason(returned, fn, owners, bindings, resolve);\n if (why) report(returned ?? site.node, why);\n }\n }\n }\n return findings;\n}\n\nconst isOriginalSpread = (expression: ts.Expression, original: string | null): boolean => {\n const value = awaited(expression);\n if (!ts.isCallExpression(value)) return false;\n const callee = unwrap(value.expression);\n return (\n (ts.isIdentifier(callee) && callee.text === original) ||\n (ts.isPropertyAccessExpression(callee) &&\n ts.isIdentifier(callee.expression) &&\n callee.expression.text === 'vi' &&\n callee.name.text === 'importActual')\n );\n};\n\ntype OverrideRead = { name: string; node: ts.Node } | { unread: ts.Node };\n\nfunction calleeName(call: ts.CallExpression): string | null {\n const callee = unwrap(call.expression);\n if (ts.isIdentifier(callee)) return callee.text;\n if (ts.isPropertyAccessExpression(callee)) return callee.name.text;\n return null;\n}\n\nfunction overridesOf(\n returned: ts.Expression,\n original: string | null,\n fakeCalls: ReadonlyMap<string, number>,\n): OverrideRead[] {\n const value = awaited(returned);\n const objects: ts.ObjectLiteralExpression[] = [];\n const out: OverrideRead[] = [];\n const name = ts.isCallExpression(value) ? calleeName(value) : null;\n const overridesAt = name === null ? undefined : fakeCalls.get(name);\n if (ts.isObjectLiteralExpression(value)) objects.push(value);\n else if (ts.isCallExpression(value) && overridesAt !== undefined) {\n const argument = value.arguments[overridesAt];\n if (argument) {\n const arg = unwrap(argument);\n if (ts.isObjectLiteralExpression(arg)) objects.push(arg);\n else out.push({ unread: arg });\n }\n } else if (ts.isCallExpression(value))\n for (const argument of value.arguments) {\n const arg = unwrap(argument);\n if (ts.isObjectLiteralExpression(arg)) objects.push(arg);\n else if (!(ts.isIdentifier(arg) && arg.text === original)) out.push({ unread: arg });\n }\n for (const object of objects)\n for (const property of object.properties) {\n if (ts.isSpreadAssignment(property)) {\n if (!isOriginalSpread(property.expression, original)) out.push({ unread: property });\n continue;\n }\n const name = property.name;\n if (name && (ts.isIdentifier(name) || ts.isStringLiteralLike(name)))\n out.push({ name: name.text, node: property });\n else out.push({ unread: property });\n }\n return out;\n}\n\nfunction checkForbidden(reader: RepoReader, facts: TypedMockFacts, files: FileView[]): Finding[] {\n const findings: Finding[] = [];\n const aliases = aliasList(facts.importAliases);\n const byModule = new Map(facts.forbiddenOverrides.map((entry) => [entry.module, entry]));\n for (const file of files) {\n for (const site of sitesOf(reader, file, aliases)) {\n const entry = byModule.get(site.module);\n if (!entry || !site.factory || isRunnerOptions(site.factory)) continue;\n const fn = factoryFunction(site.factory);\n if (!fn) continue;\n const forbidden = new Set(entry.names);\n const fakeCalls = new Map(\n (entry.fakeCalls ?? []).map((call) => [call.name, call.overridesArgument]),\n );\n for (const returned of returnedExpressions(fn)) {\n if (!returned) continue;\n for (const read of overridesOf(returned, parameterName(fn), fakeCalls)) {\n if ('unread' in read) {\n findings.push({\n file: file.path,\n line: lineOfNode(file, read.unread),\n detail: `overrides ${site.module} through names the guard cannot read — write each override by name, since ${entry.names.join(', ')} must stay real: ${entry.why}`,\n });\n continue;\n }\n if (!forbidden.has(read.name)) continue;\n findings.push({\n file: file.path,\n line: lineOfNode(file, read.node),\n detail: `overrides ${site.module}'s ${read.name} — the real one runs in every test: ${entry.why}`,\n });\n }\n }\n }\n }\n return findings;\n}\n\nfunction factProblems(facts: TypedMockFacts): string[] {\n const problems: string[] = [];\n if (!objectProblem(facts, 'the facts', problems)) return problems;\n importAliasesProblem(facts.importAliases, 'importAliases', problems);\n if (!Array.isArray(facts.fakes))\n problems.push('fakes is missing (declare [] when there is none)');\n else\n for (const fake of facts.fakes as unknown[]) {\n const entry = (fake && typeof fake === 'object' ? fake : {}) as Record<string, unknown>;\n textProblem(entry.module, 'an entry of fakes’ module', problems);\n pathProblem(entry.owner, `fakes entry ${String(entry.module)}'s owner`, problems);\n }\n if (!Array.isArray(facts.typedHelpers))\n problems.push('typedHelpers is missing (declare [] when there is none)');\n else\n for (const helper of facts.typedHelpers as unknown[]) {\n const entry = (helper && typeof helper === 'object' ? helper : {}) as Record<string, unknown>;\n pathProblem(entry.owner, \"a typedHelpers entry's owner\", problems);\n namesProblem(entry.names, `typedHelpers entry ${String(entry.owner)}'s names`, problems);\n }\n if (!Array.isArray(facts.forbiddenOverrides))\n problems.push('forbiddenOverrides is missing (declare [] when there is none)');\n else {\n const seen = new Set<string>();\n for (const raw of facts.forbiddenOverrides as unknown[]) {\n const entry = (raw && typeof raw === 'object' ? raw : {}) as Record<string, unknown>;\n if (!isText(entry.module)) {\n problems.push('an entry of forbiddenOverrides names no module');\n continue;\n }\n if (seen.has(entry.module)) problems.push(`forbiddenOverrides lists ${entry.module} twice`);\n seen.add(entry.module);\n namesProblem(entry.names, `forbiddenOverrides entry ${entry.module}'s names`, problems);\n textProblem(entry.why, `forbiddenOverrides entry ${entry.module}'s why`, problems);\n if (entry.fakeCalls === undefined) continue;\n if (!Array.isArray(entry.fakeCalls)) {\n problems.push(`forbiddenOverrides entry ${entry.module}'s fakeCalls is not a list`);\n continue;\n }\n for (const call of entry.fakeCalls as unknown[]) {\n const fake = (call && typeof call === 'object' ? call : {}) as Record<string, unknown>;\n if (!isText(fake.name)) problems.push(`a fakeCalls entry of ${entry.module} names no fake`);\n if (\n typeof fake.overridesArgument !== 'number' ||\n !Number.isInteger(fake.overridesArgument) ||\n fake.overridesArgument < 1\n )\n problems.push(\n `fakeCalls entry ${String(fake.name)} of ${entry.module} needs overridesArgument, the position of its override object after importOriginal`,\n );\n }\n }\n }\n return problems;\n}\n\nexport const typedMockGuard: GenericGuardDefinition<TypedMockFacts> = {\n id: 'typedMocks',\n absentWhen: false,\n factProblems,\n build(facts) {\n return [\n {\n title: 'every module mock is typed against the module it replaces',\n description:\n \"A test's module mock names its module through `import('…')` and returns, on every path, through the \" +\n \"module's shared fake or a typed mock helper. A helper takes its module type from the factory's own \" +\n '`importOriginal`, never from a type argument the test writes, so a wrong module, a misspelled export, ' +\n 'and a wrong signature fail to compile. A factory that returns a hand-written object is never compared to ' +\n 'the module, so a double that no longer matches it still passes. A mock with no factory, or with the ' +\n 'runner’s options object (`{ spy: true }`) in its place, is the runner’s own and passes.',\n timeoutMs: REPOSITORY_GUARD_TIMEOUT_MS,\n run(context) {\n return checkTyped(context.reader, facts, context.files(SCOPE));\n },\n },\n {\n title: 'no test overrides an export whose real behavior every test needs',\n description:\n 'A listed export is never replaced in a test, by its shared fake’s overrides or by a factory of its own: ' +\n 'a stand-in that behaves differently on the path a case proves lets the case pass on behavior the app ' +\n 'never has. The guard reads the names each override object sets at its top level; a spread of anything ' +\n 'but the real module, a computed name, or an override object it cannot see fails, since it could hide a ' +\n 'listed one. Each entry names its module, the exports, and why the real one runs. An entry may name ' +\n 'its shared fake’s calls with the position of their override object: for a call to that fake only that ' +\n 'argument is read, and the arguments before it are the fake’s state.',\n timeoutMs: REPOSITORY_GUARD_TIMEOUT_MS,\n run(context) {\n return checkForbidden(context.reader, facts, context.files(SCOPE));\n },\n },\n ];\n },\n};\n"]}
|