@gate-forge/pack-playwright 0.7.1 → 0.9.0
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/README.md +31 -0
- package/dist/constants.d.ts +13 -140
- package/dist/constants.d.ts.map +1 -1
- package/dist/constants.js +13 -148
- package/dist/constants.js.map +1 -1
- package/dist/cypress/plugin.d.ts +53 -0
- package/dist/cypress/plugin.d.ts.map +1 -0
- package/dist/cypress/plugin.js +369 -0
- package/dist/cypress/plugin.js.map +1 -0
- package/dist/cypress/spec-scan.d.ts +39 -0
- package/dist/cypress/spec-scan.d.ts.map +1 -0
- package/dist/cypress/spec-scan.js +301 -0
- package/dist/cypress/spec-scan.js.map +1 -0
- package/dist/diagnosis.d.ts +39 -0
- package/dist/diagnosis.d.ts.map +1 -0
- package/dist/diagnosis.js +63 -0
- package/dist/diagnosis.js.map +1 -0
- package/dist/discovery/adapter-projection.d.ts +13 -0
- package/dist/discovery/adapter-projection.d.ts.map +1 -0
- package/dist/discovery/adapter-projection.js +84 -0
- package/dist/discovery/adapter-projection.js.map +1 -0
- package/dist/discovery/config-locations.d.ts +5 -0
- package/dist/discovery/config-locations.d.ts.map +1 -0
- package/dist/discovery/config-locations.js +19 -0
- package/dist/discovery/config-locations.js.map +1 -0
- package/dist/discovery/cypress-runner-adapter.d.ts +112 -0
- package/dist/discovery/cypress-runner-adapter.d.ts.map +1 -0
- package/dist/discovery/cypress-runner-adapter.js +501 -0
- package/dist/discovery/cypress-runner-adapter.js.map +1 -0
- package/dist/discovery/discover.d.ts +53 -1
- package/dist/discovery/discover.d.ts.map +1 -1
- package/dist/discovery/discover.js +290 -30
- package/dist/discovery/discover.js.map +1 -1
- package/dist/discovery/git-ignore.d.ts +13 -0
- package/dist/discovery/git-ignore.d.ts.map +1 -0
- package/dist/discovery/git-ignore.js +62 -0
- package/dist/discovery/git-ignore.js.map +1 -0
- package/dist/discovery/index.d.ts +10 -0
- package/dist/discovery/index.d.ts.map +1 -1
- package/dist/discovery/index.js +10 -0
- package/dist/discovery/index.js.map +1 -1
- package/dist/discovery/playwright-runner-adapter.d.ts +108 -0
- package/dist/discovery/playwright-runner-adapter.d.ts.map +1 -0
- package/dist/discovery/playwright-runner-adapter.js +185 -0
- package/dist/discovery/playwright-runner-adapter.js.map +1 -0
- package/dist/discovery/prepare-barrier.d.ts +522 -0
- package/dist/discovery/prepare-barrier.d.ts.map +1 -0
- package/dist/discovery/prepare-barrier.js +739 -0
- package/dist/discovery/prepare-barrier.js.map +1 -0
- package/dist/discovery/pytest-adapter.d.ts.map +1 -1
- package/dist/discovery/pytest-adapter.js +6 -2
- package/dist/discovery/pytest-adapter.js.map +1 -1
- package/dist/discovery/pytest-runner-adapter.d.ts +124 -0
- package/dist/discovery/pytest-runner-adapter.d.ts.map +1 -0
- package/dist/discovery/pytest-runner-adapter.js +454 -0
- package/dist/discovery/pytest-runner-adapter.js.map +1 -0
- package/dist/discovery/reconcile.d.ts +89 -11
- package/dist/discovery/reconcile.d.ts.map +1 -1
- package/dist/discovery/reconcile.js +440 -78
- package/dist/discovery/reconcile.js.map +1 -1
- package/dist/discovery/runner-env.d.ts +88 -2
- package/dist/discovery/runner-env.d.ts.map +1 -1
- package/dist/discovery/runner-env.js +142 -4
- package/dist/discovery/runner-env.js.map +1 -1
- package/dist/discovery/runner-file-scope.d.ts +114 -0
- package/dist/discovery/runner-file-scope.d.ts.map +1 -0
- package/dist/discovery/runner-file-scope.js +638 -0
- package/dist/discovery/runner-file-scope.js.map +1 -0
- package/dist/discovery/static-discovery.d.ts +49 -3
- package/dist/discovery/static-discovery.d.ts.map +1 -1
- package/dist/discovery/static-discovery.js +477 -18
- package/dist/discovery/static-discovery.js.map +1 -1
- package/dist/discovery/supervised-run.d.ts +199 -10
- package/dist/discovery/supervised-run.d.ts.map +1 -1
- package/dist/discovery/supervised-run.js +350 -39
- package/dist/discovery/supervised-run.js.map +1 -1
- package/dist/discovery/trusted-config.d.ts +180 -6
- package/dist/discovery/trusted-config.d.ts.map +1 -1
- package/dist/discovery/trusted-config.js +279 -10
- package/dist/discovery/trusted-config.js.map +1 -1
- package/dist/discovery/vitest-runner-adapter.d.ts +172 -0
- package/dist/discovery/vitest-runner-adapter.d.ts.map +1 -0
- package/dist/discovery/vitest-runner-adapter.js +607 -0
- package/dist/discovery/vitest-runner-adapter.js.map +1 -0
- package/dist/fixture/consumer-runner.d.ts +4 -0
- package/dist/fixture/consumer-runner.d.ts.map +1 -0
- package/dist/fixture/consumer-runner.js +38 -0
- package/dist/fixture/consumer-runner.js.map +1 -0
- package/dist/fixture/evidence.d.ts +27 -0
- package/dist/fixture/evidence.d.ts.map +1 -1
- package/dist/fixture/evidence.js +12 -0
- package/dist/fixture/evidence.js.map +1 -1
- package/dist/fixture/fixture.d.ts +2 -1
- package/dist/fixture/fixture.d.ts.map +1 -1
- package/dist/fixture/fixture.js +1 -17
- package/dist/fixture/fixture.js.map +1 -1
- package/dist/fixture/witness-client.d.ts +6 -174
- package/dist/fixture/witness-client.d.ts.map +1 -1
- package/dist/fixture/witness-client.js +6 -267
- package/dist/fixture/witness-client.js.map +1 -1
- package/dist/index.d.ts +9 -6
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +5 -4
- package/dist/index.js.map +1 -1
- package/dist/json.d.ts +6 -12
- package/dist/json.d.ts.map +1 -1
- package/dist/json.js +6 -17
- package/dist/json.js.map +1 -1
- package/dist/reporter/project-graph-reporter.d.ts +121 -0
- package/dist/reporter/project-graph-reporter.d.ts.map +1 -0
- package/dist/reporter/project-graph-reporter.js +132 -0
- package/dist/reporter/project-graph-reporter.js.map +1 -0
- package/dist/reporter/reporter.cjs.map +1 -1
- package/dist/reporter/reporter.d.ts +93 -4
- package/dist/reporter/reporter.d.ts.map +1 -1
- package/dist/reporter/reporter.js +174 -18
- package/dist/reporter/reporter.js.map +1 -1
- package/dist/runner-claims.d.ts +13 -0
- package/dist/runner-claims.d.ts.map +1 -0
- package/dist/runner-claims.js +46 -0
- package/dist/runner-claims.js.map +1 -0
- package/dist/runner-resolution.d.ts +3 -0
- package/dist/runner-resolution.d.ts.map +1 -0
- package/dist/runner-resolution.js +14 -0
- package/dist/runner-resolution.js.map +1 -0
- package/dist/supervisor/client.d.ts +23 -1
- package/dist/supervisor/client.d.ts.map +1 -1
- package/dist/supervisor/client.js +49 -0
- package/dist/supervisor/client.js.map +1 -1
- package/dist/supervisor/drain.d.ts +65 -0
- package/dist/supervisor/drain.d.ts.map +1 -1
- package/dist/supervisor/drain.js +353 -9
- package/dist/supervisor/drain.js.map +1 -1
- package/dist/supervisor/index.d.ts +7 -3
- package/dist/supervisor/index.d.ts.map +1 -1
- package/dist/supervisor/index.js +7 -3
- package/dist/supervisor/index.js.map +1 -1
- package/dist/supervisor/spool.d.ts +65 -2
- package/dist/supervisor/spool.d.ts.map +1 -1
- package/dist/supervisor/spool.js +47 -0
- package/dist/supervisor/spool.js.map +1 -1
- package/dist/surface.d.ts +6 -207
- package/dist/surface.d.ts.map +1 -1
- package/dist/surface.js +6 -264
- package/dist/surface.js.map +1 -1
- package/dist/vitest/index.d.ts +23 -0
- package/dist/vitest/index.d.ts.map +1 -0
- package/dist/vitest/index.js +356 -0
- package/dist/vitest/index.js.map +1 -0
- package/dist/vitest/reporter.d.ts +83 -0
- package/dist/vitest/reporter.d.ts.map +1 -0
- package/dist/vitest/reporter.js +211 -0
- package/dist/vitest/reporter.js.map +1 -0
- package/dist/vitest/worker-slot.d.ts +36 -0
- package/dist/vitest/worker-slot.d.ts.map +1 -0
- package/dist/vitest/worker-slot.js +51 -0
- package/dist/vitest/worker-slot.js.map +1 -0
- package/dist/witness/adapter-registry.d.ts +6 -50
- package/dist/witness/adapter-registry.d.ts.map +1 -1
- package/dist/witness/adapter-registry.js +6 -257
- package/dist/witness/adapter-registry.js.map +1 -1
- package/dist/witness/behavior-request.d.ts +6 -76
- package/dist/witness/behavior-request.d.ts.map +1 -1
- package/dist/witness/behavior-request.js +6 -273
- package/dist/witness/behavior-request.js.map +1 -1
- package/dist/witness/behavior.d.ts +6 -48
- package/dist/witness/behavior.d.ts.map +1 -1
- package/dist/witness/behavior.js +6 -114
- package/dist/witness/behavior.js.map +1 -1
- package/dist/witness/bin.d.ts +6 -17
- package/dist/witness/bin.d.ts.map +1 -1
- package/dist/witness/bin.js +6 -168
- package/dist/witness/bin.js.map +1 -1
- package/dist/witness/browser.d.ts +14 -206
- package/dist/witness/browser.d.ts.map +1 -1
- package/dist/witness/browser.js +15 -654
- package/dist/witness/browser.js.map +1 -1
- package/dist/witness/classifications.d.ts +6 -38
- package/dist/witness/classifications.d.ts.map +1 -1
- package/dist/witness/classifications.js +6 -83
- package/dist/witness/classifications.js.map +1 -1
- package/dist/witness/env-attestation.d.ts +6 -94
- package/dist/witness/env-attestation.d.ts.map +1 -1
- package/dist/witness/env-attestation.js +6 -198
- package/dist/witness/env-attestation.js.map +1 -1
- package/dist/witness/fixture-provider.d.ts +6 -82
- package/dist/witness/fixture-provider.d.ts.map +1 -1
- package/dist/witness/fixture-provider.js +6 -105
- package/dist/witness/fixture-provider.js.map +1 -1
- package/dist/witness/loopback-pins.d.ts +6 -75
- package/dist/witness/loopback-pins.d.ts.map +1 -1
- package/dist/witness/loopback-pins.js +6 -240
- package/dist/witness/loopback-pins.js.map +1 -1
- package/dist/witness/server.d.ts +8 -26
- package/dist/witness/server.d.ts.map +1 -1
- package/dist/witness/server.js +24 -3761
- package/dist/witness/server.js.map +1 -1
- package/dist/witness/types.d.ts +6 -882
- package/dist/witness/types.d.ts.map +1 -1
- package/dist/witness/types.js +9 -1
- package/dist/witness/types.js.map +1 -1
- package/examples/overlay-proof.spec.js +75 -0
- package/package.json +12 -3
- package/python/gateforge_pytest_plugin.py +462 -0
- package/python/__pycache__/gateforge_persistence_intents.cpython-314.pyc +0 -0
|
@@ -33,6 +33,8 @@ import { readFileSync, existsSync, readdirSync, statSync } from 'node:fs';
|
|
|
33
33
|
import { join, posix } from 'node:path';
|
|
34
34
|
import ts from 'typescript';
|
|
35
35
|
import { pathInScope } from '@gate-forge/core';
|
|
36
|
+
import { CLAIM_ANNOTATION_TYPE } from '../constants.js';
|
|
37
|
+
import { gitIgnoredPaths } from './git-ignore.js';
|
|
36
38
|
/** Default cap on files pulled in through import traversal. */
|
|
37
39
|
export const DEFAULT_MAX_TRAVERSED_FILES = 200;
|
|
38
40
|
/** Default bound on import-traversal depth (chain length). */
|
|
@@ -125,12 +127,127 @@ function locationOf(file, source, node) {
|
|
|
125
127
|
const { line, character } = source.getLineAndCharacterOfPosition(node.getStart(source));
|
|
126
128
|
return { file, line: line + 1, col: character };
|
|
127
129
|
}
|
|
130
|
+
/**
|
|
131
|
+
* Tests whether a syntax node is nested inside another node's subtree.
|
|
132
|
+
*
|
|
133
|
+
* Args:
|
|
134
|
+
* container: candidate ancestor node.
|
|
135
|
+
* target: node whose ancestry is checked.
|
|
136
|
+
*
|
|
137
|
+
* Returns:
|
|
138
|
+
* boolean: true when target is inside container.
|
|
139
|
+
*/
|
|
140
|
+
function isWithinNode(container, target) {
|
|
141
|
+
let current = target;
|
|
142
|
+
while (current !== undefined && current !== container)
|
|
143
|
+
current = current.parent;
|
|
144
|
+
return current === container;
|
|
145
|
+
}
|
|
146
|
+
/**
|
|
147
|
+
* Finds Gateforge environment reads in conditional ancestors of a test
|
|
148
|
+
* registration so environment-dependent registrations can be reviewed.
|
|
149
|
+
*
|
|
150
|
+
* Args:
|
|
151
|
+
* file: repo-relative file containing the registration.
|
|
152
|
+
* source: parsed TypeScript source file.
|
|
153
|
+
* node: registration call node.
|
|
154
|
+
* titlePath: static title path for the registration.
|
|
155
|
+
*
|
|
156
|
+
* Returns:
|
|
157
|
+
* StaticRegistrationWarning[]: one warning per relevant environment variable.
|
|
158
|
+
*/
|
|
159
|
+
function registrationWarningsForCall(file, source, node, titlePath) {
|
|
160
|
+
const guards = [];
|
|
161
|
+
for (let parent = node.parent; parent !== undefined; parent = parent.parent) {
|
|
162
|
+
if (ts.isIfStatement(parent) &&
|
|
163
|
+
(isWithinNode(parent.thenStatement, node) ||
|
|
164
|
+
(parent.elseStatement !== undefined && isWithinNode(parent.elseStatement, node)))) {
|
|
165
|
+
guards.push(parent.expression);
|
|
166
|
+
}
|
|
167
|
+
else if (ts.isConditionalExpression(parent) &&
|
|
168
|
+
(isWithinNode(parent.whenTrue, node) || isWithinNode(parent.whenFalse, node))) {
|
|
169
|
+
guards.push(parent.condition);
|
|
170
|
+
}
|
|
171
|
+
else if (ts.isBinaryExpression(parent) &&
|
|
172
|
+
isWithinNode(parent.right, node) &&
|
|
173
|
+
(parent.operatorToken.kind === ts.SyntaxKind.AmpersandAmpersandToken ||
|
|
174
|
+
parent.operatorToken.kind === ts.SyntaxKind.BarBarToken ||
|
|
175
|
+
parent.operatorToken.kind === ts.SyntaxKind.QuestionQuestionToken)) {
|
|
176
|
+
guards.push(parent.left);
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
if (guards.length === 0)
|
|
180
|
+
return [];
|
|
181
|
+
const variables = new Map();
|
|
182
|
+
/**
|
|
183
|
+
* Recognizes property or literal-index access to process.env.
|
|
184
|
+
*
|
|
185
|
+
* Args:
|
|
186
|
+
* expression: expression to inspect.
|
|
187
|
+
*
|
|
188
|
+
* Returns:
|
|
189
|
+
* boolean: true when expression is process.env.
|
|
190
|
+
*/
|
|
191
|
+
const isProcessEnv = (expression) => (ts.isPropertyAccessExpression(expression) &&
|
|
192
|
+
ts.isIdentifier(expression.expression) &&
|
|
193
|
+
expression.expression.text === 'process' &&
|
|
194
|
+
expression.name.text === 'env') ||
|
|
195
|
+
(ts.isElementAccessExpression(expression) &&
|
|
196
|
+
ts.isIdentifier(expression.expression) &&
|
|
197
|
+
expression.expression.text === 'process' &&
|
|
198
|
+
expression.argumentExpression !== undefined &&
|
|
199
|
+
ts.isStringLiteral(expression.argumentExpression) &&
|
|
200
|
+
expression.argumentExpression.text === 'env');
|
|
201
|
+
/**
|
|
202
|
+
* Records Gateforge environment reads from one registration guard.
|
|
203
|
+
*
|
|
204
|
+
* Args:
|
|
205
|
+
* guard: conditional expression to scan.
|
|
206
|
+
*
|
|
207
|
+
* Returns:
|
|
208
|
+
* void.
|
|
209
|
+
*/
|
|
210
|
+
const visitGuard = (guard) => {
|
|
211
|
+
if (ts.isPropertyAccessExpression(guard) && isProcessEnv(guard.expression)) {
|
|
212
|
+
const name = guard.name.text;
|
|
213
|
+
if (name.startsWith('GATEFORGE_') && !variables.has(name)) {
|
|
214
|
+
variables.set(name, locationOf(file, source, guard));
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
else if (ts.isElementAccessExpression(guard) &&
|
|
218
|
+
isProcessEnv(guard.expression) &&
|
|
219
|
+
guard.argumentExpression !== undefined &&
|
|
220
|
+
ts.isStringLiteral(guard.argumentExpression)) {
|
|
221
|
+
const name = guard.argumentExpression.text;
|
|
222
|
+
if (name.startsWith('GATEFORGE_') && !variables.has(name)) {
|
|
223
|
+
variables.set(name, locationOf(file, source, guard));
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
ts.forEachChild(guard, visitGuard);
|
|
227
|
+
};
|
|
228
|
+
for (const guard of guards)
|
|
229
|
+
visitGuard(guard);
|
|
230
|
+
return [...variables.entries()].map(([environmentVariable, location]) => ({
|
|
231
|
+
file,
|
|
232
|
+
titlePath: [...titlePath],
|
|
233
|
+
environmentVariable,
|
|
234
|
+
location,
|
|
235
|
+
}));
|
|
236
|
+
}
|
|
128
237
|
/**
|
|
129
238
|
* Walks the repo (pruning VCS/deps/build dirs) and returns the sorted
|
|
130
|
-
* repo-relative posix files matching the include globs
|
|
131
|
-
*
|
|
239
|
+
* repo-relative posix files matching the include globs, no exclude glob,
|
|
240
|
+
* and no git ignore rule. Pure filesystem listing — no content is read
|
|
241
|
+
* here.
|
|
242
|
+
*
|
|
243
|
+
* A path the repository ignores (e.g. the built, git-ignored bundle a
|
|
244
|
+
* frontend build writes into the tree) can never be enumerated by the
|
|
245
|
+
* runner, so a row from it would be a permanent `unenumeratedReason`
|
|
246
|
+
* gap. Where git cannot answer — outside a work tree, or git
|
|
247
|
+
* unavailable — every candidate is kept exactly as before
|
|
248
|
+
* (see {@link gitIgnoredPaths}).
|
|
132
249
|
*/
|
|
133
|
-
function collectCandidateFiles(cwd, include, exclude) {
|
|
250
|
+
function collectCandidateFiles(cwd, include, exclude, excludeFile) {
|
|
134
251
|
const found = [];
|
|
135
252
|
const walk = (dir, rel) => {
|
|
136
253
|
let names;
|
|
@@ -157,13 +274,17 @@ function collectCandidateFiles(cwd, include, exclude) {
|
|
|
157
274
|
}
|
|
158
275
|
else if (PARSEABLE_EXTENSIONS.some((ext) => name.endsWith(ext)) &&
|
|
159
276
|
pathInScope(relPath, include) &&
|
|
160
|
-
!pathInScope(relPath, exclude)
|
|
277
|
+
!pathInScope(relPath, exclude) &&
|
|
278
|
+
excludeFile?.(relPath) !== true) {
|
|
161
279
|
found.push(relPath);
|
|
162
280
|
}
|
|
163
281
|
}
|
|
164
282
|
};
|
|
165
283
|
walk(cwd, '');
|
|
166
|
-
|
|
284
|
+
const ignored = gitIgnoredPaths(cwd, found);
|
|
285
|
+
if (ignored === null)
|
|
286
|
+
return found.sort();
|
|
287
|
+
return found.filter((file) => !ignored.has(file)).sort();
|
|
167
288
|
}
|
|
168
289
|
/** The callee shape `{base, names}` of `a.b.c(...)`, else null. */
|
|
169
290
|
function calleeChain(expression) {
|
|
@@ -206,10 +327,19 @@ function bodyReferencesTest(body) {
|
|
|
206
327
|
visit(body);
|
|
207
328
|
return found;
|
|
208
329
|
}
|
|
209
|
-
/**
|
|
330
|
+
/**
|
|
331
|
+
* Reads a file's text; unreadable content is a parse error row. A path
|
|
332
|
+
* that is not a regular file (a directory that reached the read through
|
|
333
|
+
* a seeded or traversed row) is skipped silently — a directory is not
|
|
334
|
+
* a parse failure of the consumer's source, and reporting one used to
|
|
335
|
+
* flip `inventoryComplete` on a repo that parses fine.
|
|
336
|
+
*/
|
|
210
337
|
function readText(state, file) {
|
|
338
|
+
const absolute = join(state.cwd, file);
|
|
339
|
+
if (!isFile(absolute))
|
|
340
|
+
return null;
|
|
211
341
|
try {
|
|
212
|
-
return readFileSync(
|
|
342
|
+
return readFileSync(absolute, 'utf8');
|
|
213
343
|
}
|
|
214
344
|
catch (error) {
|
|
215
345
|
state.result.parseErrors.push({
|
|
@@ -539,7 +669,25 @@ function isPlaywrightSpecifier(specifier) {
|
|
|
539
669
|
function isTestModuleSpecifier(specifier) {
|
|
540
670
|
return isPlaywrightSpecifier(specifier) || isPackSpecifier(specifier);
|
|
541
671
|
}
|
|
542
|
-
/**
|
|
672
|
+
/**
|
|
673
|
+
* Resolves a relative specifier the way node/TS resolve it: a FILE
|
|
674
|
+
* first (the bare path when it names one, then each parseable
|
|
675
|
+
* extension), then a directory's `index.*`. A directory is never a
|
|
676
|
+
* resolution result — `existsSync` is true for one, and returning it
|
|
677
|
+
* made `readText` fail with EISDIR and report a false parse error
|
|
678
|
+
* against the consumer's own source whenever a file and a directory
|
|
679
|
+
* shared a name (install rehearsal F16).
|
|
680
|
+
*
|
|
681
|
+
* Args:
|
|
682
|
+
* state: the running scan (seeded/traversed files win outright).
|
|
683
|
+
* cwd: absolute repo root.
|
|
684
|
+
* fromFile: repo-relative file holding the import.
|
|
685
|
+
* specifier: the import specifier, as written.
|
|
686
|
+
*
|
|
687
|
+
* Returns:
|
|
688
|
+
* string | null: repo-relative FILE path, or null when nothing
|
|
689
|
+
* resolves.
|
|
690
|
+
*/
|
|
543
691
|
function resolveSpecifier(state, cwd, fromFile, specifier) {
|
|
544
692
|
const base = posix.dirname(fromFile);
|
|
545
693
|
const joined = posix.normalize(posix.join(base, specifier));
|
|
@@ -549,12 +697,33 @@ function resolveSpecifier(state, cwd, fromFile, specifier) {
|
|
|
549
697
|
...PARSEABLE_EXTENSIONS.map((ext) => `${joined}/index${ext}`),
|
|
550
698
|
];
|
|
551
699
|
for (const candidate of candidates) {
|
|
552
|
-
if (state.seeded.has(candidate) || state.traversed.has(candidate)
|
|
700
|
+
if (state.seeded.has(candidate) || state.traversed.has(candidate))
|
|
701
|
+
return candidate;
|
|
702
|
+
}
|
|
703
|
+
for (const candidate of candidates) {
|
|
704
|
+
if (isFile(join(cwd, candidate)))
|
|
553
705
|
return candidate;
|
|
554
|
-
}
|
|
555
706
|
}
|
|
556
707
|
return null;
|
|
557
708
|
}
|
|
709
|
+
/**
|
|
710
|
+
* Whether an absolute path is an existing regular file (never a
|
|
711
|
+
* directory, a socket, or a device node).
|
|
712
|
+
*
|
|
713
|
+
* Args:
|
|
714
|
+
* absolute: absolute filesystem path.
|
|
715
|
+
*
|
|
716
|
+
* Returns:
|
|
717
|
+
* boolean: true only for an existing regular file.
|
|
718
|
+
*/
|
|
719
|
+
function isFile(absolute) {
|
|
720
|
+
try {
|
|
721
|
+
return statSync(absolute).isFile();
|
|
722
|
+
}
|
|
723
|
+
catch {
|
|
724
|
+
return false;
|
|
725
|
+
}
|
|
726
|
+
}
|
|
558
727
|
/**
|
|
559
728
|
* Resolves whether `name` in `file` binds to a playwright test function,
|
|
560
729
|
* through aliases and relative imports, bounded by depth + file budget.
|
|
@@ -696,6 +865,34 @@ function findPageRoute(node, source, file) {
|
|
|
696
865
|
visit(node);
|
|
697
866
|
return found;
|
|
698
867
|
}
|
|
868
|
+
/**
|
|
869
|
+
* Collects literal URL patterns intercepted by `page.route` in one test.
|
|
870
|
+
*
|
|
871
|
+
* Args:
|
|
872
|
+
* node: the test callback subtree.
|
|
873
|
+
*
|
|
874
|
+
* Returns:
|
|
875
|
+
* string[]: unique, sorted static route patterns.
|
|
876
|
+
*/
|
|
877
|
+
function pageRouteTargets(node) {
|
|
878
|
+
const targets = new Set();
|
|
879
|
+
const visit = (current) => {
|
|
880
|
+
if (ts.isCallExpression(current) &&
|
|
881
|
+
ts.isPropertyAccessExpression(current.expression) &&
|
|
882
|
+
current.expression.name.text === 'route' &&
|
|
883
|
+
ts.isIdentifier(current.expression.expression)) {
|
|
884
|
+
const target = current.arguments[0];
|
|
885
|
+
if (target !== undefined &&
|
|
886
|
+
ts.isStringLiteralLike(target) &&
|
|
887
|
+
target.text.length > 0) {
|
|
888
|
+
targets.add(target.text);
|
|
889
|
+
}
|
|
890
|
+
}
|
|
891
|
+
ts.forEachChild(current, visit);
|
|
892
|
+
};
|
|
893
|
+
visit(node);
|
|
894
|
+
return [...targets].sort();
|
|
895
|
+
}
|
|
699
896
|
/** Whether the subtree calls fetch/axios (the app's HTTP boundary). */
|
|
700
897
|
function findHttpClientCall(node, source, file) {
|
|
701
898
|
let found = null;
|
|
@@ -769,22 +966,30 @@ function findModuleMock(source, file) {
|
|
|
769
966
|
* options: cwd, include/exclude globs, and optional budgets.
|
|
770
967
|
*
|
|
771
968
|
* Returns:
|
|
772
|
-
* StaticScanResult: entries, unresolved rows, parse errors,
|
|
773
|
-
* files, and the budget flag. Never throws for
|
|
774
|
-
* problems — those are rows (fail closed as data, not silence).
|
|
969
|
+
* StaticScanResult: entries, unresolved rows, parse errors, registration
|
|
970
|
+
* warnings, scanned files, and the budget flag. Never throws for
|
|
971
|
+
* scanner-detectable problems — those are rows (fail closed as data, not silence).
|
|
775
972
|
*/
|
|
776
973
|
export function scanTestFiles(options) {
|
|
777
974
|
const state = {
|
|
778
975
|
cwd: options.cwd,
|
|
779
|
-
result: {
|
|
976
|
+
result: {
|
|
977
|
+
entries: [],
|
|
978
|
+
unresolved: [],
|
|
979
|
+
parseErrors: [],
|
|
980
|
+
registrationWarnings: [],
|
|
981
|
+
scannedFiles: [],
|
|
982
|
+
budgetExceeded: false,
|
|
983
|
+
},
|
|
780
984
|
models: new Map(),
|
|
781
985
|
seeded: new Set(),
|
|
782
986
|
traversed: new Set(),
|
|
783
987
|
resolving: new Set(),
|
|
784
988
|
maxTraversedFiles: options.budget?.maxTraversedFiles ?? DEFAULT_MAX_TRAVERSED_FILES,
|
|
785
989
|
maxImportDepth: options.budget?.maxImportDepth ?? DEFAULT_MAX_IMPORT_DEPTH,
|
|
990
|
+
testGlobals: options.testGlobals ?? (() => false),
|
|
786
991
|
};
|
|
787
|
-
const seededFiles = collectCandidateFiles(options.cwd, options.include, options.exclude);
|
|
992
|
+
const seededFiles = collectCandidateFiles(options.cwd, options.include, options.exclude, options.excludeFile);
|
|
788
993
|
for (const file of seededFiles)
|
|
789
994
|
state.seeded.add(file);
|
|
790
995
|
for (const file of seededFiles) {
|
|
@@ -796,6 +1001,10 @@ export function scanTestFiles(options) {
|
|
|
796
1001
|
const gateforgeImport = findGateforgeFixtureImport(model.source, file);
|
|
797
1002
|
scanFileForTests(state, options.cwd, model, fileHttpClient, fileMock, gateforgeImport);
|
|
798
1003
|
}
|
|
1004
|
+
state.result.registrationWarnings.sort((a, b) => (a.file < b.file ? -1 : a.file > b.file ? 1 : 0) ||
|
|
1005
|
+
a.location.line - b.location.line ||
|
|
1006
|
+
(a.environmentVariable < b.environmentVariable ? -1 : a.environmentVariable > b.environmentVariable ? 1 : 0) ||
|
|
1007
|
+
(a.titlePath.join('>') < b.titlePath.join('>') ? -1 : a.titlePath.join('>') > b.titlePath.join('>') ? 1 : 0));
|
|
799
1008
|
state.result.entries.sort((a, b) => compareEntry(a, b));
|
|
800
1009
|
state.result.unresolved.sort((a, b) => (a.file < b.file ? -1 : a.file > b.file ? 1 : 0) ||
|
|
801
1010
|
a.location.line - b.location.line ||
|
|
@@ -813,6 +1022,9 @@ function compareEntry(a, b) {
|
|
|
813
1022
|
/** Visits one modeled file, harvesting describe stacks and test calls. */
|
|
814
1023
|
function scanFileForTests(state, cwd, model, fileHttpClient, fileMock, gateforgeImport) {
|
|
815
1024
|
const { file, source } = model;
|
|
1025
|
+
// Once per file, not once per call site: the runner-globals predicate
|
|
1026
|
+
// matches globs, and a file has hundreds of call expressions.
|
|
1027
|
+
const runnerGlobals = state.testGlobals(file);
|
|
816
1028
|
const visit = (node, describeStack, enclosing) => {
|
|
817
1029
|
if (!ts.isCallExpression(node)) {
|
|
818
1030
|
ts.forEachChild(node, (child) => visit(child, describeStack, enclosing));
|
|
@@ -826,7 +1038,13 @@ function scanFileForTests(state, cwd, model, fileHttpClient, fileMock, gateforge
|
|
|
826
1038
|
const location = locationOf(file, source, node);
|
|
827
1039
|
const resolution = resolveTestAlias(state, cwd, file, chain.base, 0);
|
|
828
1040
|
const names = chain.names;
|
|
829
|
-
|
|
1041
|
+
// A runner that injects the test GLOBALS for this file (vitest
|
|
1042
|
+
// `globals: true`) owns a bare `describe(...)` exactly as it owns a
|
|
1043
|
+
// bare `it(...)`: the runner itself would execute this suite. Without
|
|
1044
|
+
// it, an import-less describe reads as an unresolvable alias and the
|
|
1045
|
+
// whole file collapses into gaps.
|
|
1046
|
+
const isDescribe = names.includes('describe') ||
|
|
1047
|
+
(resolution === 'unknown' && names.length === 0 && chain.base === 'describe' && runnerGlobals);
|
|
830
1048
|
const isExtend = names.includes('extend');
|
|
831
1049
|
const lifecycle = names.some((name) => NON_TEST_SEGMENTS.has(name));
|
|
832
1050
|
const suppression = names.filter((name) => SUPPRESSION_SEGMENTS.has(name));
|
|
@@ -836,9 +1054,19 @@ function scanFileForTests(state, cwd, model, fileHttpClient, fileMock, gateforge
|
|
|
836
1054
|
// `pw.test(...)` / `base.test(...)` where pw/base is the required or
|
|
837
1055
|
// namespaced test module object: the test function itself (not a
|
|
838
1056
|
// wrapper) — grades exactly like a direct `test(...)` registration.
|
|
839
|
-
|
|
1057
|
+
// A runner that provides GLOBALS for this file (vitest `globals: true`)
|
|
1058
|
+
// makes a bare `it('…')`/`test('…')` a registration the runner itself
|
|
1059
|
+
// owns: the runner proves those files by executing them, so the scan
|
|
1060
|
+
// must not file them as an unresolvable alias.
|
|
1061
|
+
const globalsRegistration = resolution === 'unknown' &&
|
|
1062
|
+
names.length === 0 &&
|
|
1063
|
+
(chain.base === 'test' || chain.base === 'it') &&
|
|
1064
|
+
runnerGlobals;
|
|
1065
|
+
const effectiveResolution = globalsRegistration
|
|
840
1066
|
? 'test'
|
|
841
|
-
: resolution
|
|
1067
|
+
: resolution === 'testmodule' && !isExtend && !lifecycle && !isDescribe && (names[0] === 'test' || names[0] === 'it')
|
|
1068
|
+
? 'test'
|
|
1069
|
+
: resolution;
|
|
842
1070
|
// Zero-arg conditional suppression INSIDE a test body: test.skip() / test.fixme()
|
|
843
1071
|
if (effectiveResolution === 'test' &&
|
|
844
1072
|
suppression.length > 0 &&
|
|
@@ -951,6 +1179,7 @@ function scanFileForTests(state, cwd, model, fileHttpClient, fileMock, gateforge
|
|
|
951
1179
|
gateforgeImport,
|
|
952
1180
|
});
|
|
953
1181
|
state.result.entries.push(entry);
|
|
1182
|
+
state.result.registrationWarnings.push(...registrationWarningsForCall(file, source, node, entry.titlePath));
|
|
954
1183
|
// Walk the body so inner zero-arg `test.skip()` / `test.fixme()`
|
|
955
1184
|
// calls attach to THIS entry's signals.
|
|
956
1185
|
if (callback !== undefined) {
|
|
@@ -991,6 +1220,7 @@ function scanFileForTests(state, cwd, model, fileHttpClient, fileMock, gateforge
|
|
|
991
1220
|
gateforgeImport,
|
|
992
1221
|
});
|
|
993
1222
|
state.result.entries.push(entry);
|
|
1223
|
+
state.result.registrationWarnings.push(...registrationWarningsForCall(file, source, outer, entry.titlePath));
|
|
994
1224
|
if (outerCallback !== undefined) {
|
|
995
1225
|
const enclosing = { facts: entry.facts, signals: entry.signals };
|
|
996
1226
|
ts.forEachChild(outerCallback, (child) => visit(child, describeStack, enclosing));
|
|
@@ -1034,9 +1264,234 @@ function scanFileForTests(state, cwd, model, fileHttpClient, fileMock, gateforge
|
|
|
1034
1264
|
};
|
|
1035
1265
|
visit(source, [], null);
|
|
1036
1266
|
}
|
|
1267
|
+
/**
|
|
1268
|
+
* Finds one top-level local constant initializer by name.
|
|
1269
|
+
*
|
|
1270
|
+
* Args:
|
|
1271
|
+
* source: parsed test source file.
|
|
1272
|
+
* name: local identifier to resolve.
|
|
1273
|
+
*
|
|
1274
|
+
* Returns:
|
|
1275
|
+
* ts.Expression | null: a unique const initializer, or null when it
|
|
1276
|
+
* is absent, ambiguous, mutable, or destructured.
|
|
1277
|
+
*/
|
|
1278
|
+
function localConstantInitializer(source, name) {
|
|
1279
|
+
const matches = [];
|
|
1280
|
+
for (const statement of source.statements) {
|
|
1281
|
+
if (!ts.isVariableStatement(statement) || (statement.declarationList.flags & ts.NodeFlags.Const) === 0)
|
|
1282
|
+
continue;
|
|
1283
|
+
for (const declaration of statement.declarationList.declarations) {
|
|
1284
|
+
if (ts.isIdentifier(declaration.name) && declaration.name.text === name && declaration.initializer !== undefined) {
|
|
1285
|
+
matches.push(declaration.initializer);
|
|
1286
|
+
}
|
|
1287
|
+
}
|
|
1288
|
+
}
|
|
1289
|
+
return matches.length === 1 ? (matches[0] ?? null) : null;
|
|
1290
|
+
}
|
|
1291
|
+
/**
|
|
1292
|
+
* Finds a pure local helper declared as a function or const arrow.
|
|
1293
|
+
*
|
|
1294
|
+
* Args:
|
|
1295
|
+
* source: parsed test source file.
|
|
1296
|
+
* name: helper identifier to resolve.
|
|
1297
|
+
*
|
|
1298
|
+
* Returns:
|
|
1299
|
+
* ts.FunctionDeclaration | ts.ArrowFunction | ts.FunctionExpression | null:
|
|
1300
|
+
* one unambiguous top-level function helper.
|
|
1301
|
+
*/
|
|
1302
|
+
function localStaticFunction(source, name) {
|
|
1303
|
+
const matches = [];
|
|
1304
|
+
for (const statement of source.statements) {
|
|
1305
|
+
if (ts.isFunctionDeclaration(statement) && statement.name?.text === name) {
|
|
1306
|
+
matches.push(statement);
|
|
1307
|
+
continue;
|
|
1308
|
+
}
|
|
1309
|
+
if (!ts.isVariableStatement(statement) || (statement.declarationList.flags & ts.NodeFlags.Const) === 0)
|
|
1310
|
+
continue;
|
|
1311
|
+
for (const declaration of statement.declarationList.declarations) {
|
|
1312
|
+
if (!ts.isIdentifier(declaration.name) || declaration.name.text !== name || declaration.initializer === undefined)
|
|
1313
|
+
continue;
|
|
1314
|
+
if (ts.isArrowFunction(declaration.initializer) || ts.isFunctionExpression(declaration.initializer)) {
|
|
1315
|
+
matches.push(declaration.initializer);
|
|
1316
|
+
}
|
|
1317
|
+
}
|
|
1318
|
+
}
|
|
1319
|
+
return matches.length === 1 ? (matches[0] ?? null) : null;
|
|
1320
|
+
}
|
|
1321
|
+
/**
|
|
1322
|
+
* Resolves a bounded JSON-like expression without executing consumer code.
|
|
1323
|
+
*
|
|
1324
|
+
* Args:
|
|
1325
|
+
* expression: source expression to resolve.
|
|
1326
|
+
* source: parsed file that may contain local consts and pure helpers.
|
|
1327
|
+
* bindings: literal arguments bound to a local helper's parameters.
|
|
1328
|
+
* depth: recursion depth used to reject cycles.
|
|
1329
|
+
*
|
|
1330
|
+
* Returns:
|
|
1331
|
+
* StaticJsonResult: a literal value when fully resolvable, else failure.
|
|
1332
|
+
*/
|
|
1333
|
+
function staticJsonValue(expression, source, bindings = new Map(), depth = 0) {
|
|
1334
|
+
if (depth > 8)
|
|
1335
|
+
return { ok: false };
|
|
1336
|
+
if (ts.isAsExpression(expression) ||
|
|
1337
|
+
ts.isTypeAssertionExpression(expression) ||
|
|
1338
|
+
ts.isSatisfiesExpression(expression) ||
|
|
1339
|
+
ts.isNonNullExpression(expression)) {
|
|
1340
|
+
return staticJsonValue(expression.expression, source, bindings, depth + 1);
|
|
1341
|
+
}
|
|
1342
|
+
if (ts.isStringLiteralLike(expression))
|
|
1343
|
+
return { ok: true, value: expression.text };
|
|
1344
|
+
if (ts.isNumericLiteral(expression)) {
|
|
1345
|
+
const value = Number(expression.text);
|
|
1346
|
+
return Number.isFinite(value) ? { ok: true, value } : { ok: false };
|
|
1347
|
+
}
|
|
1348
|
+
if (expression.kind === ts.SyntaxKind.TrueKeyword)
|
|
1349
|
+
return { ok: true, value: true };
|
|
1350
|
+
if (expression.kind === ts.SyntaxKind.FalseKeyword)
|
|
1351
|
+
return { ok: true, value: false };
|
|
1352
|
+
if (expression.kind === ts.SyntaxKind.NullKeyword)
|
|
1353
|
+
return { ok: true, value: null };
|
|
1354
|
+
if (ts.isIdentifier(expression)) {
|
|
1355
|
+
const bound = bindings.get(expression.text);
|
|
1356
|
+
if (bound !== undefined)
|
|
1357
|
+
return { ok: true, value: bound };
|
|
1358
|
+
const initializer = localConstantInitializer(source, expression.text);
|
|
1359
|
+
return initializer === null ? { ok: false } : staticJsonValue(initializer, source, bindings, depth + 1);
|
|
1360
|
+
}
|
|
1361
|
+
if (ts.isArrayLiteralExpression(expression)) {
|
|
1362
|
+
const values = [];
|
|
1363
|
+
for (const element of expression.elements) {
|
|
1364
|
+
if (!ts.isExpression(element))
|
|
1365
|
+
return { ok: false };
|
|
1366
|
+
const resolved = staticJsonValue(element, source, bindings, depth + 1);
|
|
1367
|
+
if (!resolved.ok)
|
|
1368
|
+
return { ok: false };
|
|
1369
|
+
values.push(resolved.value);
|
|
1370
|
+
}
|
|
1371
|
+
return { ok: true, value: values };
|
|
1372
|
+
}
|
|
1373
|
+
if (ts.isObjectLiteralExpression(expression)) {
|
|
1374
|
+
const value = {};
|
|
1375
|
+
for (const property of expression.properties) {
|
|
1376
|
+
if (!ts.isPropertyAssignment(property))
|
|
1377
|
+
return { ok: false };
|
|
1378
|
+
const key = property.name;
|
|
1379
|
+
if (!(ts.isIdentifier(key) || ts.isStringLiteralLike(key) || ts.isNumericLiteral(key)))
|
|
1380
|
+
return { ok: false };
|
|
1381
|
+
const resolved = staticJsonValue(property.initializer, source, bindings, depth + 1);
|
|
1382
|
+
if (!resolved.ok)
|
|
1383
|
+
return { ok: false };
|
|
1384
|
+
value[key.text] = resolved.value;
|
|
1385
|
+
}
|
|
1386
|
+
return { ok: true, value };
|
|
1387
|
+
}
|
|
1388
|
+
if (ts.isCallExpression(expression) && ts.isIdentifier(expression.expression)) {
|
|
1389
|
+
const helper = localStaticFunction(source, expression.expression.text);
|
|
1390
|
+
if (helper === null || helper.parameters.length !== expression.arguments.length)
|
|
1391
|
+
return { ok: false };
|
|
1392
|
+
const values = [];
|
|
1393
|
+
for (const argument of expression.arguments) {
|
|
1394
|
+
if (ts.isSpreadElement(argument))
|
|
1395
|
+
return { ok: false };
|
|
1396
|
+
const resolved = staticJsonValue(argument, source, bindings, depth + 1);
|
|
1397
|
+
if (!resolved.ok)
|
|
1398
|
+
return { ok: false };
|
|
1399
|
+
values.push(resolved.value);
|
|
1400
|
+
}
|
|
1401
|
+
const helperBindings = new Map();
|
|
1402
|
+
for (const [index, parameter] of helper.parameters.entries()) {
|
|
1403
|
+
if (!ts.isIdentifier(parameter.name))
|
|
1404
|
+
return { ok: false };
|
|
1405
|
+
const value = values[index];
|
|
1406
|
+
if (value === undefined)
|
|
1407
|
+
return { ok: false };
|
|
1408
|
+
helperBindings.set(parameter.name.text, value);
|
|
1409
|
+
}
|
|
1410
|
+
const helperBody = helper.body;
|
|
1411
|
+
let returned;
|
|
1412
|
+
if (helperBody === undefined)
|
|
1413
|
+
return { ok: false };
|
|
1414
|
+
if (!ts.isBlock(helperBody)) {
|
|
1415
|
+
returned = helperBody;
|
|
1416
|
+
}
|
|
1417
|
+
else {
|
|
1418
|
+
const statement = helperBody.statements[0];
|
|
1419
|
+
if (helperBody.statements.length === 1 &&
|
|
1420
|
+
statement !== undefined &&
|
|
1421
|
+
ts.isReturnStatement(statement) &&
|
|
1422
|
+
statement.expression !== undefined) {
|
|
1423
|
+
returned = statement.expression;
|
|
1424
|
+
}
|
|
1425
|
+
}
|
|
1426
|
+
return returned === undefined
|
|
1427
|
+
? { ok: false }
|
|
1428
|
+
: staticJsonValue(returned, source, helperBindings, depth + 1);
|
|
1429
|
+
}
|
|
1430
|
+
return { ok: false };
|
|
1431
|
+
}
|
|
1432
|
+
/**
|
|
1433
|
+
* Extracts Gateforge claim annotations from one test call using literals only.
|
|
1434
|
+
*
|
|
1435
|
+
* Args:
|
|
1436
|
+
* node: parsed test-call node.
|
|
1437
|
+
* source: parsed test source file for local const/helper resolution.
|
|
1438
|
+
*
|
|
1439
|
+
* Returns:
|
|
1440
|
+
* { claims, issue }: deduplicated claim ids, or a visible reason when a
|
|
1441
|
+
* possible annotation cannot be resolved statically.
|
|
1442
|
+
*/
|
|
1443
|
+
function gateforgeAnnotationClaims(node, source) {
|
|
1444
|
+
if (!ts.isCallExpression(node))
|
|
1445
|
+
return { claims: [], issue: null };
|
|
1446
|
+
const options = node.arguments[1];
|
|
1447
|
+
if (options === undefined || ts.isArrowFunction(options) || ts.isFunctionExpression(options)) {
|
|
1448
|
+
return { claims: [], issue: null };
|
|
1449
|
+
}
|
|
1450
|
+
if (ts.isObjectLiteralExpression(options)) {
|
|
1451
|
+
const hasAnnotation = options.properties.some((property) => ts.isPropertyAssignment(property) && ts.isIdentifier(property.name) && property.name.text === 'annotation');
|
|
1452
|
+
if (!hasAnnotation) {
|
|
1453
|
+
return {
|
|
1454
|
+
claims: [],
|
|
1455
|
+
issue: options.properties.some(ts.isSpreadAssignment)
|
|
1456
|
+
? 'test options contain a spread that may hide a Gateforge annotation'
|
|
1457
|
+
: null,
|
|
1458
|
+
};
|
|
1459
|
+
}
|
|
1460
|
+
}
|
|
1461
|
+
const resolved = staticJsonValue(options, source);
|
|
1462
|
+
if (!resolved.ok) {
|
|
1463
|
+
return { claims: [], issue: 'test options may contain a Gateforge annotation but are not a local literal or pure helper result' };
|
|
1464
|
+
}
|
|
1465
|
+
if (resolved.value === null || Array.isArray(resolved.value) || typeof resolved.value !== 'object') {
|
|
1466
|
+
return { claims: [], issue: null };
|
|
1467
|
+
}
|
|
1468
|
+
const annotation = resolved.value['annotation'];
|
|
1469
|
+
if (annotation === undefined)
|
|
1470
|
+
return { claims: [], issue: null };
|
|
1471
|
+
const items = Array.isArray(annotation) ? annotation : [annotation];
|
|
1472
|
+
const claims = [];
|
|
1473
|
+
for (const item of items) {
|
|
1474
|
+
if (item === null || Array.isArray(item) || typeof item !== 'object') {
|
|
1475
|
+
return { claims: [], issue: 'annotation value is not a statically resolved object' };
|
|
1476
|
+
}
|
|
1477
|
+
const type = item['type'];
|
|
1478
|
+
if (typeof type !== 'string') {
|
|
1479
|
+
return { claims: [], issue: 'annotation type is not a statically resolved string' };
|
|
1480
|
+
}
|
|
1481
|
+
if (type !== CLAIM_ANNOTATION_TYPE)
|
|
1482
|
+
continue;
|
|
1483
|
+
const description = item['description'];
|
|
1484
|
+
if (typeof description !== 'string' || description.length === 0) {
|
|
1485
|
+
return { claims: [], issue: 'Gateforge annotation description is not a non-empty static string' };
|
|
1486
|
+
}
|
|
1487
|
+
claims.push(description);
|
|
1488
|
+
}
|
|
1489
|
+
return { claims: [...new Set(claims)].sort(), issue: null };
|
|
1490
|
+
}
|
|
1037
1491
|
/** Builds one static entry: facts from the callback body + signals. */
|
|
1038
1492
|
function buildEntry(input) {
|
|
1039
1493
|
const { file, source, node, callback, titlePath, location } = input;
|
|
1494
|
+
const annotations = gateforgeAnnotationClaims(node, source);
|
|
1040
1495
|
const params = callback === undefined ? [] : signatureParamsOf(callback);
|
|
1041
1496
|
const signals = [...input.inheritedSignals];
|
|
1042
1497
|
for (const kind of input.suppression) {
|
|
@@ -1044,6 +1499,7 @@ function buildEntry(input) {
|
|
|
1044
1499
|
}
|
|
1045
1500
|
let pageRoute = null;
|
|
1046
1501
|
let httpClientCall = null;
|
|
1502
|
+
const interceptedTargets = callback === undefined ? [] : pageRouteTargets(callback);
|
|
1047
1503
|
if (callback !== undefined) {
|
|
1048
1504
|
pageRoute = findPageRoute(callback, source, file);
|
|
1049
1505
|
httpClientCall = findHttpClientCall(callback, source, file);
|
|
@@ -1058,11 +1514,14 @@ function buildEntry(input) {
|
|
|
1058
1514
|
facts: {
|
|
1059
1515
|
signatureParams: params,
|
|
1060
1516
|
pageRoute,
|
|
1517
|
+
...(interceptedTargets.length > 0 ? { pageRouteTargets: interceptedTargets } : {}),
|
|
1061
1518
|
httpClientCall,
|
|
1062
1519
|
fileHttpClientCall: input.fileHttpClient,
|
|
1063
1520
|
fileMockImport: input.fileMock,
|
|
1064
1521
|
gateforgeFixtureImport: input.gateforgeImport,
|
|
1065
1522
|
},
|
|
1523
|
+
...(annotations.claims.length > 0 ? { annotationClaims: annotations.claims } : {}),
|
|
1524
|
+
...(annotations.issue !== null ? { annotationIssue: annotations.issue } : {}),
|
|
1066
1525
|
};
|
|
1067
1526
|
}
|
|
1068
1527
|
//# sourceMappingURL=static-discovery.js.map
|