@ttt-productions/repo-guards 0.1.22 → 0.1.24
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/core-definitions/text-field-declarations.d.ts +1 -1
- package/dist/modules/core-definitions/text-field-declarations.d.ts.map +1 -1
- package/dist/modules/core-definitions/text-field-declarations.js +2 -2
- package/dist/modules/core-definitions/text-field-declarations.js.map +1 -1
- package/dist/modules/import-paths/guard.d.ts.map +1 -1
- package/dist/modules/import-paths/guard.js +6 -5
- package/dist/modules/import-paths/guard.js.map +1 -1
- package/dist/modules/repo/e2e-actions.d.ts +7 -2
- package/dist/modules/repo/e2e-actions.d.ts.map +1 -1
- package/dist/modules/repo/e2e-actions.js +163 -54
- package/dist/modules/repo/e2e-actions.js.map +1 -1
- package/dist/node/tooling/vitest-report.d.ts +1 -0
- package/dist/node/tooling/vitest-report.d.ts.map +1 -1
- package/dist/node/tooling/vitest-report.js +14 -1
- package/dist/node/tooling/vitest-report.js.map +1 -1
- package/package.json +1 -1
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"text-field-declarations.d.ts","sourceRoot":"","sources":["../../../src/modules/core-definitions/text-field-declarations.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,sBAAsB,EAAE,MAAM,4BAA4B,CAAC;AACzE,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,sBAAsB,CAAC;AAErD,OAAO,EAIL,KAAK,MAAM,EAEZ,MAAM,wBAAwB,CAAC;AAIhC,KAAK,cAAc,GAAG,aAAa,CAAC;IAClC,IAAI,EAAE,MAAM,CAAC;IACb,WAAW,EAAE,MAAM,CAAC;IACpB,KAAK,EAAE,MAAM,CAAC;IACd,GAAG,EAAE,MAAM,CAAC;CACb,CAAC,CAAC;AACH,KAAK,WAAW,GAAG;IAAE,SAAS,EAAE,CAAC,KAAK,EAAE,OAAO,KAAK;QAAE,OAAO,EAAE,OAAO,CAAC;QAAC,IAAI,CAAC,EAAE,OAAO,CAAA;KAAE,CAAA;CAAE,CAAC;AAI3F,UAAU,aAAa;IACrB,QAAQ,CAAC,WAAW,EAAE,OAAO,GAAG,WAAW,CAAC;CAC7C;AAED,MAAM,WAAW,yBAAyB;IACxC,WAAW,EAAE,SAAS,MAAM,EAAE,CAAC;IAC/B,gBAAgB,EAAE,MAAM,CAAC;IACzB,qBAAqB,EAAE,SAAS,MAAM,EAAE,CAAC;IACzC,WAAW,EAAE,cAAc,CAAC;IAC5B,kBAAkB,EAAE,cAAc,CAAC;IACnC,WAAW,EAAE,aAAa,CAAC;QAAE,WAAW,EAAE,MAAM,CAAC;QAAC,GAAG,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;IACjE,YAAY,EAAE,MAAM,CAAC,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,CAAC,CAAC;IACxD,WAAW,EAAE,MAAM,CAAC,aAAa,CAAC,
|
|
1
|
+
{"version":3,"file":"text-field-declarations.d.ts","sourceRoot":"","sources":["../../../src/modules/core-definitions/text-field-declarations.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,sBAAsB,EAAE,MAAM,4BAA4B,CAAC;AACzE,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,sBAAsB,CAAC;AAErD,OAAO,EAIL,KAAK,MAAM,EAEZ,MAAM,wBAAwB,CAAC;AAIhC,KAAK,cAAc,GAAG,aAAa,CAAC;IAClC,IAAI,EAAE,MAAM,CAAC;IACb,WAAW,EAAE,MAAM,CAAC;IACpB,KAAK,EAAE,MAAM,CAAC;IACd,GAAG,EAAE,MAAM,CAAC;CACb,CAAC,CAAC;AACH,KAAK,WAAW,GAAG;IAAE,SAAS,EAAE,CAAC,KAAK,EAAE,OAAO,KAAK;QAAE,OAAO,EAAE,OAAO,CAAC;QAAC,IAAI,CAAC,EAAE,OAAO,CAAA;KAAE,CAAA;CAAE,CAAC;AAI3F,UAAU,aAAa;IACrB,QAAQ,CAAC,WAAW,EAAE,OAAO,GAAG,WAAW,CAAC;CAC7C;AAED,MAAM,WAAW,yBAAyB;IACxC,WAAW,EAAE,SAAS,MAAM,EAAE,CAAC;IAC/B,gBAAgB,EAAE,MAAM,CAAC;IACzB,qBAAqB,EAAE,SAAS,MAAM,EAAE,CAAC;IACzC,WAAW,EAAE,cAAc,CAAC;IAC5B,kBAAkB,EAAE,cAAc,CAAC;IACnC,WAAW,EAAE,aAAa,CAAC;QAAE,WAAW,EAAE,MAAM,CAAC;QAAC,GAAG,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;IACjE,YAAY,EAAE,MAAM,CAAC,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,CAAC,CAAC;IACxD,WAAW,EAAE,MAAM,CAAC,aAAa,CAAC,CAAC;CACpC;AA+BD,UAAU,IAAI;IACZ,GAAG,EAAE,MAAM,CAAC;IACZ,IAAI,EAAE,MAAM,CAAC;CACd;AAED,wBAAgB,iBAAiB,CAAC,IAAI,EAAE,QAAQ,GAAG,IAAI,EAAE,CAyBxD;AAED,wBAAgB,kBAAkB,CAChC,IAAI,EAAE,QAAQ,EACd,WAAW,EAAE,CAAC,WAAW,EAAE,MAAM,KAAK,OAAO,GAC5C,IAAI,EAAE,CAiCR;AAsDD,eAAO,MAAM,0BAA0B,EAAE,sBAAsB,CAAC,yBAAyB,CAmKxF,CAAC"}
|
|
@@ -232,14 +232,14 @@ export const textFieldDeclarationsGuard = {
|
|
|
232
232
|
const findings = [];
|
|
233
233
|
coreBuild(context, findings);
|
|
234
234
|
const declarations = textDeclarations(await facts.declarations());
|
|
235
|
-
const
|
|
235
|
+
const reader = await facts.fieldSchema();
|
|
236
236
|
if (!declarations.length)
|
|
237
237
|
findings.push({
|
|
238
238
|
file: facts.declarationsFile,
|
|
239
239
|
detail: 'no text-field declaration was loaded',
|
|
240
240
|
});
|
|
241
241
|
for (const [name, declaration] of declarations) {
|
|
242
|
-
const schema = schemaOf(declaration);
|
|
242
|
+
const schema = reader.schemaOf(declaration);
|
|
243
243
|
const min = typeof declaration.min === 'number' ? declaration.min : 0;
|
|
244
244
|
if (min >= 1 && schema.safeParse(' \n\t ').success)
|
|
245
245
|
findings.push({
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"text-field-declarations.js","sourceRoot":"","sources":["../../../src/modules/core-definitions/text-field-declarations.ts"],"names":[],"mappings":"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","sourcesContent":["import ts from 'typescript';\nimport type { Finding, GuardContext } from '../../guard.js';\nimport type { GenericGuardDefinition } from '../../node/suite/config.js';\nimport type { FileView } from '../../reader/view.js';\nimport { lineOfNode } from '../../support/ast.js';\nimport {\n namesProblem,\n reviewedProblems,\n loaderProblem,\n type Loader,\n textProblem,\n} from '../../support/facts.js';\nimport { CORE_CODE, coreBuild, filesIn, isCount, topLevelDeclaration } from './shared.js';\nimport { REPOSITORY_GUARD_TIMEOUT_MS } from '../../support/timeouts.js';\n\ntype ReviewedCounts = ReadonlyArray<{\n file: string;\n declaration: string;\n count: number;\n why: string;\n}>;\ntype FieldSchema = { safeParse: (value: unknown) => { success: boolean; data?: unknown } };\n\n// A method, so the app's own reader — typed on its declaration type — fits it as declared, and the\n// guard hands it the declarations it loaded from that same module.\ninterface FieldSchemaOf {\n schemaOf(declaration: unknown): FieldSchema;\n}\n\nexport interface TextFieldDeclarationFacts {\n wireFolders: readonly string[];\n declarationsFile: string;\n wireInputNameSuffixes: readonly string[];\n selfBounded: ReviewedCounts;\n undeclaredFreeText: ReviewedCounts;\n keepAsTyped: ReadonlyArray<{ declaration: string; why: string }>;\n declarations: Loader<Readonly<Record<string, unknown>>>;\n fieldSchema: Loader<FieldSchemaOf['schemaOf']>;\n}\n\nconst isZString = (node: ts.Node): node is ts.CallExpression =>\n ts.isCallExpression(node) &&\n ts.isPropertyAccessExpression(node.expression) &&\n node.expression.name.text === 'string' &&\n ts.isIdentifier(node.expression.expression) &&\n node.expression.expression.text === 'z';\n\nconst TEXT_LENGTH_CONSTANT = /^(MAX|MIN)_(?![A-Z0-9_]*_ID_LENGTH$)[A-Z0-9_]*LENGTH$/;\nconst FREE_TEXT_PROPERTY =\n /reason|message|text|note|comment|title|description|name|subject|caption|summary|statement|facts|letter|label|heading|body|query|content|suggestion|region|country|value|intro|points|strings|term/i;\nconst BUILT_IN_FORMAT_METHODS = new Set(['regex', 'url', 'email', 'uuid']);\n\nfunction chainedCalls(root: ts.CallExpression): ts.CallExpression[] {\n const calls: ts.CallExpression[] = [];\n let node: ts.Node = root;\n while (\n ts.isPropertyAccessExpression(node.parent) &&\n node.parent.expression === node &&\n ts.isCallExpression(node.parent.parent)\n ) {\n calls.push(node.parent.parent);\n node = node.parent.parent;\n }\n return calls;\n}\n\nconst methodOf = (call: ts.CallExpression) =>\n (call.expression as ts.PropertyAccessExpression).name.text;\n\ninterface Site {\n key: string;\n line: number;\n}\n\nexport function selfBoundedChains(file: FileView): Site[] {\n const sites: Site[] = [];\n const visit = (node: ts.Node): void => {\n if (isZString(node)) {\n const bounded = chainedCalls(node).some((call) => {\n const method = methodOf(call);\n if (method === 'trim') return true;\n if (method !== 'min' && method !== 'max') return false;\n const [argument] = call.arguments;\n return (\n argument !== undefined &&\n ts.isIdentifier(argument) &&\n TEXT_LENGTH_CONSTANT.test(argument.text)\n );\n });\n if (bounded)\n sites.push({\n key: `${file.path}#${topLevelDeclaration(node)}`,\n line: lineOfNode(file, node),\n });\n }\n ts.forEachChild(node, visit);\n };\n visit(file.sourceFile);\n return sites;\n}\n\nexport function undeclaredFreeText(\n file: FileView,\n isWireInput: (declaration: string) => boolean,\n): Site[] {\n const sites: Site[] = [];\n const visit = (node: ts.Node): void => {\n if (isZString(node)) {\n const declaration = topLevelDeclaration(node);\n const calls = chainedCalls(node);\n const outer: ts.Node = calls.length ? calls[calls.length - 1] : node;\n const parent = outer.parent;\n let property: string | null = null;\n if (parent && ts.isPropertyAssignment(parent))\n property = parent.name.getText(file.sourceFile);\n else if (\n parent &&\n ts.isCallExpression(parent) &&\n parent.parent &&\n ts.isPropertyAssignment(parent.parent)\n ) {\n property = parent.parent.name.getText(file.sourceFile);\n }\n const builtIn = calls.some((call) => BUILT_IN_FORMAT_METHODS.has(methodOf(call)));\n if (\n property !== null &&\n isWireInput(declaration) &&\n FREE_TEXT_PROPERTY.test(property) &&\n !builtIn\n ) {\n sites.push({ key: `${file.path}#${declaration}`, line: lineOfNode(file, node) });\n }\n }\n ts.forEachChild(node, visit);\n };\n visit(file.sourceFile);\n return sites;\n}\n\nfunction compareReviewed(\n sites: readonly Site[],\n reviewed: ReviewedCounts,\n what: string,\n): Finding[] {\n const findings: Finding[] = [];\n const byKey = new Map<string, Site[]>();\n for (const site of sites) byKey.set(site.key, [...(byKey.get(site.key) ?? []), site]);\n const allowed = new Map(reviewed.map((e) => [`${e.file}#${e.declaration}`, e.count]));\n for (const [key, found] of byKey) {\n const count = allowed.get(key) ?? 0;\n const file = key.slice(0, key.indexOf('#'));\n for (const site of found.slice(count))\n findings.push({\n file,\n line: site.line,\n detail: `${what} in ${key.slice(key.indexOf('#') + 1)}${count ? ` beyond the ${count} reviewed` : ''}`,\n });\n }\n for (const [key, count] of allowed) {\n const found = byKey.get(key)?.length ?? 0;\n if (found < count)\n findings.push({\n file: key.slice(0, key.indexOf('#')),\n detail: `stale reviewed count for ${key}: reviewed ${count}, found ${found}`,\n });\n }\n return findings;\n}\n\ntype Declaration = { format?: unknown; min?: number; keepAsTyped?: unknown };\n\nconst FREE_TEXT_FORMAT = 'none';\nconst textDeclarations = (exports: Readonly<Record<string, unknown>>) =>\n Object.entries(exports).filter(\n (e): e is [string, Declaration] =>\n typeof e[1] === 'object' && e[1] !== null && 'format' in e[1],\n );\n\nconst reviewedCountProblems = (value: unknown, what: string, problems: string[]) =>\n reviewedProblems(value, what, problems, {\n key: 'file',\n id: (e) =>\n typeof e.declaration === 'string' && e.declaration.trim()\n ? `${e.file}#${e.declaration}`\n : undefined,\n idNames: 'declaration',\n extra(entry, label) {\n if (!isCount(entry.count)) problems.push(`${label}'s count must be a positive whole number`);\n },\n });\n\nexport const textFieldDeclarationsGuard: GenericGuardDefinition<TextFieldDeclarationFacts> = {\n id: 'textFieldDeclarations',\n absentWhen: \"the app's core declares no text field through input-format-core\",\n factProblems(facts) {\n const problems: string[] = [];\n namesProblem(facts?.wireFolders, 'wireFolders', problems);\n textProblem(facts?.declarationsFile, 'declarationsFile', problems);\n namesProblem(facts?.wireInputNameSuffixes, 'wireInputNameSuffixes', problems);\n reviewedCountProblems(facts?.selfBounded, 'selfBounded', problems);\n reviewedCountProblems(facts?.undeclaredFreeText, 'undeclaredFreeText', problems);\n reviewedProblems(facts?.keepAsTyped, 'keepAsTyped', problems, { key: 'declaration' });\n loaderProblem(facts?.declarations, 'declarations', problems);\n loaderProblem(facts?.fieldSchema, 'fieldSchema', problems);\n return problems;\n },\n build(facts) {\n const isWireInput = (name: string) => facts.wireInputNameSuffixes.some((s) => name.endsWith(s));\n const wireFiles = (context: GuardContext, findings: Finding[]) =>\n filesIn(context.files(CORE_CODE), facts.wireFolders, findings);\n return [\n {\n title: 'every free-text property of a core wire input is a declared text field',\n description:\n 'A wire input property that names free text (reason, message, title, …) reads its declared text field through ' +\n \"the core's field-schema builder, or carries a built-in format (.regex / .url / .email / .uuid). Wire text no \" +\n 'person types is a reviewed module#declaration count with why; a count that no longer matches fails.',\n timeoutMs: REPOSITORY_GUARD_TIMEOUT_MS,\n run(context) {\n const findings: Finding[] = [];\n const sites = wireFiles(context, findings).flatMap((f) =>\n undeclaredFreeText(f, isWireInput),\n );\n return [\n ...findings,\n ...compareReviewed(\n sites,\n facts.undeclaredFreeText,\n 'an undeclared free-text z.string() property',\n ),\n ];\n },\n },\n {\n title: 'no core wire schema trims or bounds a text field itself',\n description:\n 'A wire z.string() chain never trims itself or bounds itself by a text-length constant: that is a second ' +\n \"declaration of the field. Read the field's declaration through the core's field-schema builder. Wire text no \" +\n 'person types (stored values, fixed strings, ids, addresses) is a reviewed module#declaration count with why.',\n timeoutMs: REPOSITORY_GUARD_TIMEOUT_MS,\n run(context) {\n const findings: Finding[] = [];\n const sites = wireFiles(context, findings).flatMap(selfBoundedChains);\n return [\n ...findings,\n ...compareReviewed(sites, facts.selfBounded, 'a self-bounded z.string() chain'),\n ];\n },\n },\n {\n title: 'text fields are declared only in the core declarations module',\n description:\n 'defineInputFormat is called, and keepAsTyped is set, only in the one module that declares every text field, ' +\n 'so each field has one declaration. Move the declaration there and import it.',\n timeoutMs: REPOSITORY_GUARD_TIMEOUT_MS,\n run(context) {\n const findings: Finding[] = [];\n const files = context.files(CORE_CODE);\n if (!files.some((f) => f.path === facts.declarationsFile)) {\n findings.push({\n file: facts.declarationsFile,\n detail: 'the declarations module is not in the core build',\n });\n }\n for (const file of files) {\n if (file.path === facts.declarationsFile) continue;\n for (const m of file.code.matchAll(/\\bdefineInputFormat\\s*\\(|\\bkeepAsTyped\\b/g)) {\n findings.push({\n file: file.path,\n line: file.lineOf(m.index ?? 0),\n detail: `${m[0].replace(/\\s*\\($/, '')} outside the declarations module`,\n });\n }\n }\n return findings;\n },\n },\n {\n title: 'only the reviewed text fields keep their text as typed',\n description:\n 'Every declared text field is trimmed unless it is a reviewed keep-as-typed declaration with why (a legal or ' +\n 'statutory text whose exact characters matter). A declaration that newly keeps its text fails until reviewed, ' +\n 'and a reviewed one that no longer does is stale.',\n async run(context) {\n const findings: Finding[] = [];\n coreBuild(context, findings);\n const kept = new Set(\n textDeclarations(await facts.declarations())\n .filter(([, d]) => 'keepAsTyped' in d)\n .map(([n]) => n),\n );\n const reviewed = new Set(facts.keepAsTyped.map((e) => e.declaration));\n for (const name of [...kept].sort())\n if (!reviewed.has(name))\n findings.push({\n file: `(core) ${name}`,\n detail: 'keeps its text as typed without review',\n });\n for (const name of reviewed)\n if (!kept.has(name))\n findings.push({\n file: `(core) ${name}`,\n detail:\n 'stale keep-as-typed entry: the declaration no longer keeps its text, or is gone',\n });\n return findings;\n },\n },\n {\n title:\n 'every declared text field trims, one that holds words keeps one space for each run of spaces, and a required one refuses blank text',\n description:\n \"Each text field's wire schema, built from its declaration, refuses whitespace-only text when the field requires \" +\n 'text (min of 1 or more), and trims the text of every field that does not keep it as typed; a field whose format ' +\n \"holds words (every format but free text, 'none') keeps one space for a run of them — so the form, the wire, and the writer agree, and a \" +\n 'name spaced twice is never stored, searched, or reserved apart from the same name spaced once.',\n async run(context) {\n const findings: Finding[] = [];\n coreBuild(context, findings);\n const declarations = textDeclarations(await facts.declarations());\n const schemaOf = await facts.fieldSchema();\n if (!declarations.length)\n findings.push({\n file: facts.declarationsFile,\n detail: 'no text-field declaration was loaded',\n });\n for (const [name, declaration] of declarations) {\n const schema = schemaOf(declaration);\n const min = typeof declaration.min === 'number' ? declaration.min : 0;\n if (min >= 1 && schema.safeParse(' \\n\\t ').success)\n findings.push({\n file: `(core) ${name}`,\n detail: 'accepts whitespace-only text although the field requires text',\n });\n if ('keepAsTyped' in declaration) continue;\n const sample = 'a'.repeat(Math.max(min, 1));\n const parsed = schema.safeParse(` ${sample} `);\n if (!parsed.success || parsed.data !== sample)\n findings.push({ file: `(core) ${name}`, detail: 'does not trim its text' });\n if (declaration.format === FREE_TEXT_FORMAT) continue;\n const word = 'a'.repeat(Math.max(min - 2, 1));\n if (!schema.safeParse(`${word} a`).success) continue;\n const run = schema.safeParse(`${word} a`);\n if (!run.success || run.data !== `${word} a`)\n findings.push({\n file: `(core) ${name}`,\n detail: 'keeps a run of spaces instead of one space',\n });\n }\n return findings;\n },\n },\n ];\n },\n};\n"]}
|
|
1
|
+
{"version":3,"file":"text-field-declarations.js","sourceRoot":"","sources":["../../../src/modules/core-definitions/text-field-declarations.ts"],"names":[],"mappings":"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","sourcesContent":["import ts from 'typescript';\nimport type { Finding, GuardContext } from '../../guard.js';\nimport type { GenericGuardDefinition } from '../../node/suite/config.js';\nimport type { FileView } from '../../reader/view.js';\nimport { lineOfNode } from '../../support/ast.js';\nimport {\n namesProblem,\n reviewedProblems,\n loaderProblem,\n type Loader,\n textProblem,\n} from '../../support/facts.js';\nimport { CORE_CODE, coreBuild, filesIn, isCount, topLevelDeclaration } from './shared.js';\nimport { REPOSITORY_GUARD_TIMEOUT_MS } from '../../support/timeouts.js';\n\ntype ReviewedCounts = ReadonlyArray<{\n file: string;\n declaration: string;\n count: number;\n why: string;\n}>;\ntype FieldSchema = { safeParse: (value: unknown) => { success: boolean; data?: unknown } };\n\n// A method, so the app's own reader — typed on its declaration type — fits it as declared, and the\n// guard hands it the declarations it loaded from that same module.\ninterface FieldSchemaOf {\n schemaOf(declaration: unknown): FieldSchema;\n}\n\nexport interface TextFieldDeclarationFacts {\n wireFolders: readonly string[];\n declarationsFile: string;\n wireInputNameSuffixes: readonly string[];\n selfBounded: ReviewedCounts;\n undeclaredFreeText: ReviewedCounts;\n keepAsTyped: ReadonlyArray<{ declaration: string; why: string }>;\n declarations: Loader<Readonly<Record<string, unknown>>>;\n fieldSchema: Loader<FieldSchemaOf>;\n}\n\nconst isZString = (node: ts.Node): node is ts.CallExpression =>\n ts.isCallExpression(node) &&\n ts.isPropertyAccessExpression(node.expression) &&\n node.expression.name.text === 'string' &&\n ts.isIdentifier(node.expression.expression) &&\n node.expression.expression.text === 'z';\n\nconst TEXT_LENGTH_CONSTANT = /^(MAX|MIN)_(?![A-Z0-9_]*_ID_LENGTH$)[A-Z0-9_]*LENGTH$/;\nconst FREE_TEXT_PROPERTY =\n /reason|message|text|note|comment|title|description|name|subject|caption|summary|statement|facts|letter|label|heading|body|query|content|suggestion|region|country|value|intro|points|strings|term/i;\nconst BUILT_IN_FORMAT_METHODS = new Set(['regex', 'url', 'email', 'uuid']);\n\nfunction chainedCalls(root: ts.CallExpression): ts.CallExpression[] {\n const calls: ts.CallExpression[] = [];\n let node: ts.Node = root;\n while (\n ts.isPropertyAccessExpression(node.parent) &&\n node.parent.expression === node &&\n ts.isCallExpression(node.parent.parent)\n ) {\n calls.push(node.parent.parent);\n node = node.parent.parent;\n }\n return calls;\n}\n\nconst methodOf = (call: ts.CallExpression) =>\n (call.expression as ts.PropertyAccessExpression).name.text;\n\ninterface Site {\n key: string;\n line: number;\n}\n\nexport function selfBoundedChains(file: FileView): Site[] {\n const sites: Site[] = [];\n const visit = (node: ts.Node): void => {\n if (isZString(node)) {\n const bounded = chainedCalls(node).some((call) => {\n const method = methodOf(call);\n if (method === 'trim') return true;\n if (method !== 'min' && method !== 'max') return false;\n const [argument] = call.arguments;\n return (\n argument !== undefined &&\n ts.isIdentifier(argument) &&\n TEXT_LENGTH_CONSTANT.test(argument.text)\n );\n });\n if (bounded)\n sites.push({\n key: `${file.path}#${topLevelDeclaration(node)}`,\n line: lineOfNode(file, node),\n });\n }\n ts.forEachChild(node, visit);\n };\n visit(file.sourceFile);\n return sites;\n}\n\nexport function undeclaredFreeText(\n file: FileView,\n isWireInput: (declaration: string) => boolean,\n): Site[] {\n const sites: Site[] = [];\n const visit = (node: ts.Node): void => {\n if (isZString(node)) {\n const declaration = topLevelDeclaration(node);\n const calls = chainedCalls(node);\n const outer: ts.Node = calls.length ? calls[calls.length - 1] : node;\n const parent = outer.parent;\n let property: string | null = null;\n if (parent && ts.isPropertyAssignment(parent))\n property = parent.name.getText(file.sourceFile);\n else if (\n parent &&\n ts.isCallExpression(parent) &&\n parent.parent &&\n ts.isPropertyAssignment(parent.parent)\n ) {\n property = parent.parent.name.getText(file.sourceFile);\n }\n const builtIn = calls.some((call) => BUILT_IN_FORMAT_METHODS.has(methodOf(call)));\n if (\n property !== null &&\n isWireInput(declaration) &&\n FREE_TEXT_PROPERTY.test(property) &&\n !builtIn\n ) {\n sites.push({ key: `${file.path}#${declaration}`, line: lineOfNode(file, node) });\n }\n }\n ts.forEachChild(node, visit);\n };\n visit(file.sourceFile);\n return sites;\n}\n\nfunction compareReviewed(\n sites: readonly Site[],\n reviewed: ReviewedCounts,\n what: string,\n): Finding[] {\n const findings: Finding[] = [];\n const byKey = new Map<string, Site[]>();\n for (const site of sites) byKey.set(site.key, [...(byKey.get(site.key) ?? []), site]);\n const allowed = new Map(reviewed.map((e) => [`${e.file}#${e.declaration}`, e.count]));\n for (const [key, found] of byKey) {\n const count = allowed.get(key) ?? 0;\n const file = key.slice(0, key.indexOf('#'));\n for (const site of found.slice(count))\n findings.push({\n file,\n line: site.line,\n detail: `${what} in ${key.slice(key.indexOf('#') + 1)}${count ? ` beyond the ${count} reviewed` : ''}`,\n });\n }\n for (const [key, count] of allowed) {\n const found = byKey.get(key)?.length ?? 0;\n if (found < count)\n findings.push({\n file: key.slice(0, key.indexOf('#')),\n detail: `stale reviewed count for ${key}: reviewed ${count}, found ${found}`,\n });\n }\n return findings;\n}\n\ntype Declaration = { format?: unknown; min?: number; keepAsTyped?: unknown };\n\nconst FREE_TEXT_FORMAT = 'none';\nconst textDeclarations = (exports: Readonly<Record<string, unknown>>) =>\n Object.entries(exports).filter(\n (e): e is [string, Declaration] =>\n typeof e[1] === 'object' && e[1] !== null && 'format' in e[1],\n );\n\nconst reviewedCountProblems = (value: unknown, what: string, problems: string[]) =>\n reviewedProblems(value, what, problems, {\n key: 'file',\n id: (e) =>\n typeof e.declaration === 'string' && e.declaration.trim()\n ? `${e.file}#${e.declaration}`\n : undefined,\n idNames: 'declaration',\n extra(entry, label) {\n if (!isCount(entry.count)) problems.push(`${label}'s count must be a positive whole number`);\n },\n });\n\nexport const textFieldDeclarationsGuard: GenericGuardDefinition<TextFieldDeclarationFacts> = {\n id: 'textFieldDeclarations',\n absentWhen: \"the app's core declares no text field through input-format-core\",\n factProblems(facts) {\n const problems: string[] = [];\n namesProblem(facts?.wireFolders, 'wireFolders', problems);\n textProblem(facts?.declarationsFile, 'declarationsFile', problems);\n namesProblem(facts?.wireInputNameSuffixes, 'wireInputNameSuffixes', problems);\n reviewedCountProblems(facts?.selfBounded, 'selfBounded', problems);\n reviewedCountProblems(facts?.undeclaredFreeText, 'undeclaredFreeText', problems);\n reviewedProblems(facts?.keepAsTyped, 'keepAsTyped', problems, { key: 'declaration' });\n loaderProblem(facts?.declarations, 'declarations', problems);\n loaderProblem(facts?.fieldSchema, 'fieldSchema', problems);\n return problems;\n },\n build(facts) {\n const isWireInput = (name: string) => facts.wireInputNameSuffixes.some((s) => name.endsWith(s));\n const wireFiles = (context: GuardContext, findings: Finding[]) =>\n filesIn(context.files(CORE_CODE), facts.wireFolders, findings);\n return [\n {\n title: 'every free-text property of a core wire input is a declared text field',\n description:\n 'A wire input property that names free text (reason, message, title, …) reads its declared text field through ' +\n \"the core's field-schema builder, or carries a built-in format (.regex / .url / .email / .uuid). Wire text no \" +\n 'person types is a reviewed module#declaration count with why; a count that no longer matches fails.',\n timeoutMs: REPOSITORY_GUARD_TIMEOUT_MS,\n run(context) {\n const findings: Finding[] = [];\n const sites = wireFiles(context, findings).flatMap((f) =>\n undeclaredFreeText(f, isWireInput),\n );\n return [\n ...findings,\n ...compareReviewed(\n sites,\n facts.undeclaredFreeText,\n 'an undeclared free-text z.string() property',\n ),\n ];\n },\n },\n {\n title: 'no core wire schema trims or bounds a text field itself',\n description:\n 'A wire z.string() chain never trims itself or bounds itself by a text-length constant: that is a second ' +\n \"declaration of the field. Read the field's declaration through the core's field-schema builder. Wire text no \" +\n 'person types (stored values, fixed strings, ids, addresses) is a reviewed module#declaration count with why.',\n timeoutMs: REPOSITORY_GUARD_TIMEOUT_MS,\n run(context) {\n const findings: Finding[] = [];\n const sites = wireFiles(context, findings).flatMap(selfBoundedChains);\n return [\n ...findings,\n ...compareReviewed(sites, facts.selfBounded, 'a self-bounded z.string() chain'),\n ];\n },\n },\n {\n title: 'text fields are declared only in the core declarations module',\n description:\n 'defineInputFormat is called, and keepAsTyped is set, only in the one module that declares every text field, ' +\n 'so each field has one declaration. Move the declaration there and import it.',\n timeoutMs: REPOSITORY_GUARD_TIMEOUT_MS,\n run(context) {\n const findings: Finding[] = [];\n const files = context.files(CORE_CODE);\n if (!files.some((f) => f.path === facts.declarationsFile)) {\n findings.push({\n file: facts.declarationsFile,\n detail: 'the declarations module is not in the core build',\n });\n }\n for (const file of files) {\n if (file.path === facts.declarationsFile) continue;\n for (const m of file.code.matchAll(/\\bdefineInputFormat\\s*\\(|\\bkeepAsTyped\\b/g)) {\n findings.push({\n file: file.path,\n line: file.lineOf(m.index ?? 0),\n detail: `${m[0].replace(/\\s*\\($/, '')} outside the declarations module`,\n });\n }\n }\n return findings;\n },\n },\n {\n title: 'only the reviewed text fields keep their text as typed',\n description:\n 'Every declared text field is trimmed unless it is a reviewed keep-as-typed declaration with why (a legal or ' +\n 'statutory text whose exact characters matter). A declaration that newly keeps its text fails until reviewed, ' +\n 'and a reviewed one that no longer does is stale.',\n async run(context) {\n const findings: Finding[] = [];\n coreBuild(context, findings);\n const kept = new Set(\n textDeclarations(await facts.declarations())\n .filter(([, d]) => 'keepAsTyped' in d)\n .map(([n]) => n),\n );\n const reviewed = new Set(facts.keepAsTyped.map((e) => e.declaration));\n for (const name of [...kept].sort())\n if (!reviewed.has(name))\n findings.push({\n file: `(core) ${name}`,\n detail: 'keeps its text as typed without review',\n });\n for (const name of reviewed)\n if (!kept.has(name))\n findings.push({\n file: `(core) ${name}`,\n detail:\n 'stale keep-as-typed entry: the declaration no longer keeps its text, or is gone',\n });\n return findings;\n },\n },\n {\n title:\n 'every declared text field trims, one that holds words keeps one space for each run of spaces, and a required one refuses blank text',\n description:\n \"Each text field's wire schema, built from its declaration, refuses whitespace-only text when the field requires \" +\n 'text (min of 1 or more), and trims the text of every field that does not keep it as typed; a field whose format ' +\n \"holds words (every format but free text, 'none') keeps one space for a run of them — so the form, the wire, and the writer agree, and a \" +\n 'name spaced twice is never stored, searched, or reserved apart from the same name spaced once.',\n async run(context) {\n const findings: Finding[] = [];\n coreBuild(context, findings);\n const declarations = textDeclarations(await facts.declarations());\n const reader = await facts.fieldSchema();\n if (!declarations.length)\n findings.push({\n file: facts.declarationsFile,\n detail: 'no text-field declaration was loaded',\n });\n for (const [name, declaration] of declarations) {\n const schema = reader.schemaOf(declaration);\n const min = typeof declaration.min === 'number' ? declaration.min : 0;\n if (min >= 1 && schema.safeParse(' \\n\\t ').success)\n findings.push({\n file: `(core) ${name}`,\n detail: 'accepts whitespace-only text although the field requires text',\n });\n if ('keepAsTyped' in declaration) continue;\n const sample = 'a'.repeat(Math.max(min, 1));\n const parsed = schema.safeParse(` ${sample} `);\n if (!parsed.success || parsed.data !== sample)\n findings.push({ file: `(core) ${name}`, detail: 'does not trim its text' });\n if (declaration.format === FREE_TEXT_FORMAT) continue;\n const word = 'a'.repeat(Math.max(min - 2, 1));\n if (!schema.safeParse(`${word} a`).success) continue;\n const run = schema.safeParse(`${word} a`);\n if (!run.success || run.data !== `${word} a`)\n findings.push({\n file: `(core) ${name}`,\n detail: 'keeps a run of spaces instead of one space',\n });\n }\n return findings;\n },\n },\n ];\n },\n};\n"]}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"guard.d.ts","sourceRoot":"","sources":["../../../src/modules/import-paths/guard.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,UAAU,EAAS,MAAM,sBAAsB,CAAC;AAE9D,OAAO,KAAK,EAAE,sBAAsB,EAAE,MAAM,4BAA4B,CAAC;AAKzE,MAAM,WAAW,eAAe;IAC9B,aAAa,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC,CAAC;IAChD,UAAU,EAAE,aAAa,CAAC;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,GAAG,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;CAC1D;
|
|
1
|
+
{"version":3,"file":"guard.d.ts","sourceRoot":"","sources":["../../../src/modules/import-paths/guard.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,UAAU,EAAS,MAAM,sBAAsB,CAAC;AAE9D,OAAO,KAAK,EAAE,sBAAsB,EAAE,MAAM,4BAA4B,CAAC;AAKzE,MAAM,WAAW,eAAe;IAC9B,aAAa,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC,CAAC;IAChD,UAAU,EAAE,aAAa,CAAC;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,GAAG,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;CAC1D;AAuCD,eAAO,MAAM,eAAe,EAAE,sBAAsB,CAAC,eAAe,CAiCnE,CAAC;AAEF,wBAAgB,kBAAkB,CAChC,MAAM,EAAE,UAAU,EAClB,KAAK,EAAE,eAAe,GACrB,KAAK,CAAC;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,MAAM,EAAE,CAAA;CAAE,CAAC,CAM3C"}
|
|
@@ -1,12 +1,12 @@
|
|
|
1
|
-
import { isTestPath, isUnderRoot, kindOfFile } from '../../node/layout.js';
|
|
1
|
+
import { declaredRoots, isTestPath, isUnderRoot, kindOfFile } from '../../node/layout.js';
|
|
2
2
|
import { importAliasesProblem, reviewedProblems } from '../../support/facts.js';
|
|
3
3
|
import { analyzeImportPaths, createImportPathProfile, findUnusedIndexMembers } from './analyzer.js';
|
|
4
4
|
import { REPOSITORY_GUARD_TIMEOUT_MS } from '../../support/timeouts.js';
|
|
5
5
|
const SCOPE = {
|
|
6
|
-
roots: ['app', 'functions'],
|
|
6
|
+
roots: ['app', 'functions', 'e2e', 'hostedE2e', 'scripts', 'workers'],
|
|
7
7
|
kinds: ['code'],
|
|
8
8
|
tests: true,
|
|
9
|
-
optionalRoots: ['functions'],
|
|
9
|
+
optionalRoots: ['functions', 'e2e', 'hostedE2e', 'scripts', 'workers'],
|
|
10
10
|
};
|
|
11
11
|
function readModules(files) {
|
|
12
12
|
const modules = new Map();
|
|
@@ -15,8 +15,9 @@ function readModules(files) {
|
|
|
15
15
|
return modules;
|
|
16
16
|
}
|
|
17
17
|
function analyzedRoots(reader) {
|
|
18
|
-
|
|
19
|
-
|
|
18
|
+
return declaredRoots(reader.layout)
|
|
19
|
+
.filter(({ role }) => role !== 'coreDist')
|
|
20
|
+
.map(({ root }) => root.path);
|
|
20
21
|
}
|
|
21
22
|
function excludedModuleExists(reader, p) {
|
|
22
23
|
return ((reader.layout.exclusions ?? []).some((e) => isUnderRoot(p, e.path)) &&
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"guard.js","sourceRoot":"","sources":["../../../src/modules/import-paths/guard.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,UAAU,EAAE,WAAW,EAAE,UAAU,EAAE,MAAM,sBAAsB,CAAC;
|
|
1
|
+
{"version":3,"file":"guard.js","sourceRoot":"","sources":["../../../src/modules/import-paths/guard.ts"],"names":[],"mappings":"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","sourcesContent":["import type { Finding, GuardContext } from '../../guard.js';\nimport type { RepoReader, Scope } from '../../node/reader.js';\nimport { declaredRoots, isTestPath, isUnderRoot, kindOfFile } from '../../node/layout.js';\nimport type { GenericGuardDefinition } from '../../node/suite/config.js';\nimport { importAliasesProblem, reviewedProblems } from '../../support/facts.js';\nimport { analyzeImportPaths, createImportPathProfile, findUnusedIndexMembers } from './analyzer.js';\nimport { REPOSITORY_GUARD_TIMEOUT_MS } from '../../support/timeouts.js';\n\nexport interface ImportPathFacts {\n importAliases: Readonly<Record<string, string>>;\n exceptions: ReadonlyArray<{ file: string; why: string }>;\n}\n\nconst SCOPE: Scope = {\n roots: ['app', 'functions', 'e2e', 'hostedE2e', 'scripts', 'workers'],\n kinds: ['code'],\n tests: true,\n optionalRoots: ['functions', 'e2e', 'hostedE2e', 'scripts', 'workers'],\n};\n\nfunction readModules(files: ReturnType<RepoReader['files']>) {\n const modules = new Map<string, { text: string; sourceFile: import('typescript').SourceFile }>();\n for (const file of files)\n modules.set(file.path, { text: file.text, sourceFile: file.sourceFile });\n return modules;\n}\n\nfunction analyzedRoots(reader: RepoReader) {\n return declaredRoots(reader.layout)\n .filter(({ role }) => role !== 'coreDist')\n .map(({ root }) => root.path);\n}\n\nfunction excludedModuleExists(reader: RepoReader, p: string): boolean {\n return (\n (reader.layout.exclusions ?? []).some((e) => isUnderRoot(p, e.path)) &&\n kindOfFile(p.slice(p.lastIndexOf('/') + 1)) === 'code' &&\n reader.exists(p)\n );\n}\n\nfunction profileFor(reader: RepoReader, facts: ImportPathFacts) {\n return createImportPathProfile({\n importAliases: { ...facts.importAliases },\n exceptions: facts.exceptions.map((e) => ({ ...e })),\n isTestFile: (p: string) => isTestPath(reader.layout, p),\n isExcludedModule: (p: string) => excludedModuleExists(reader, p),\n });\n}\n\nexport const importPathGuard: GenericGuardDefinition<ImportPathFacts> = {\n id: 'importPaths',\n absentWhen: false,\n factProblems(facts) {\n const problems: string[] = [];\n importAliasesProblem(facts?.importAliases, 'importAliases', problems);\n reviewedProblems(facts?.exceptions, 'exceptions', problems, { key: 'file' });\n return problems;\n },\n build(facts) {\n return [\n {\n title: 'every definition has exactly one import path',\n description:\n 'A module that is not a folder index never re-exports; a folder index forwards only files inside its folder; ' +\n 'code outside a folder that has an index imports through it, never past it; code inside a folder imports its ' +\n 'siblings directly, never through an index of a folder that contains it. Import the owner — the sibling file or the package.',\n timeoutMs: REPOSITORY_GUARD_TIMEOUT_MS,\n run(context: GuardContext): Finding[] {\n const files = context.files(SCOPE);\n const result = analyzeImportPaths({\n files: readModules(files),\n roots: analyzedRoots(context.reader),\n profile: profileFor(context.reader, facts),\n });\n return result.violations.map((v) => ({\n file: v.file,\n detail: `${v.kind.replace(/-/g, ' ')}: ${v.detail}`,\n }));\n },\n },\n ];\n },\n};\n\nexport function unusedIndexMembers(\n reader: RepoReader,\n facts: ImportPathFacts,\n): Array<{ file: string; unused: string[] }> {\n return findUnusedIndexMembers({\n files: readModules(reader.files(SCOPE)),\n roots: analyzedRoots(reader),\n profile: profileFor(reader, facts),\n });\n}\n"]}
|
|
@@ -1,8 +1,13 @@
|
|
|
1
|
+
import ts from 'typescript';
|
|
1
2
|
import type { GenericGuardDefinition } from '../../node/suite/config.js';
|
|
2
3
|
import { type FileView } from '../../reader/view.js';
|
|
3
4
|
import { type AnchoredEntry, type Occurrence } from '../../support/anchored-review.js';
|
|
4
|
-
export
|
|
5
|
-
|
|
5
|
+
export interface ActionReach {
|
|
6
|
+
actionIn(file: FileView, root: ts.Node): string[] | undefined;
|
|
7
|
+
}
|
|
8
|
+
export declare function createActionReach(files: readonly FileView[], helpers?: readonly string[]): ActionReach;
|
|
9
|
+
export declare function retriedActions(source: FileView | string, helpers?: readonly string[], reach?: ActionReach): Occurrence[];
|
|
10
|
+
export declare function swallowedActions(source: FileView | string, helpers?: readonly string[], reach?: ActionReach): Occurrence[];
|
|
6
11
|
export interface E2eActionFacts {
|
|
7
12
|
actionHelpers: readonly string[];
|
|
8
13
|
reviewed: readonly AnchoredEntry[];
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"e2e-actions.d.ts","sourceRoot":"","sources":["../../../src/modules/repo/e2e-actions.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"e2e-actions.d.ts","sourceRoot":"","sources":["../../../src/modules/repo/e2e-actions.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,YAAY,CAAC;AAC5B,OAAO,KAAK,EAAE,sBAAsB,EAAE,MAAM,4BAA4B,CAAC;AAEzE,OAAO,EAAc,KAAK,QAAQ,EAAE,MAAM,sBAAsB,CAAC;AAGjE,OAAO,EAGL,KAAK,aAAa,EAClB,KAAK,UAAU,EAChB,MAAM,kCAAkC,CAAC;AAmI1C,MAAM,WAAW,WAAW;IAC1B,QAAQ,CAAC,IAAI,EAAE,QAAQ,EAAE,IAAI,EAAE,EAAE,CAAC,IAAI,GAAG,MAAM,EAAE,GAAG,SAAS,CAAC;CAC/D;AAED,wBAAgB,iBAAiB,CAC/B,KAAK,EAAE,SAAS,QAAQ,EAAE,EAC1B,OAAO,GAAE,SAAS,MAAM,EAAO,GAC9B,WAAW,CAgDb;AAcD,wBAAgB,cAAc,CAC5B,MAAM,EAAE,QAAQ,GAAG,MAAM,EACzB,OAAO,GAAE,SAAS,MAAM,EAAO,EAC/B,KAAK,CAAC,EAAE,WAAW,GAClB,UAAU,EAAE,CA4Bd;AAmBD,wBAAgB,gBAAgB,CAC9B,MAAM,EAAE,QAAQ,GAAG,MAAM,EACzB,OAAO,GAAE,SAAS,MAAM,EAAO,EAC/B,KAAK,CAAC,EAAE,WAAW,GAClB,UAAU,EAAE,CA8Cd;AAWD,MAAM,WAAW,cAAc;IAC7B,aAAa,EAAE,SAAS,MAAM,EAAE,CAAC;IACjC,QAAQ,EAAE,SAAS,aAAa,EAAE,CAAC;CACpC;AAUD,eAAO,MAAM,oBAAoB,EAAE,sBAAsB,CAAC,cAAc,CA8BvE,CAAC;AAEF,eAAO,MAAM,oBAAoB,EAAE,sBAAsB,CAAC,cAAc,CA6BvE,CAAC"}
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import ts from 'typescript';
|
|
2
2
|
import { viewOfText } from '../../reader/view.js';
|
|
3
3
|
import { calleeName, forEachNode } from '../../support/ast.js';
|
|
4
|
+
import { resolveRelative } from '../../support/paths.js';
|
|
4
5
|
import { anchoredFindings, anchoredProblems, } from '../../support/anchored-review.js';
|
|
5
6
|
import { namesProblem, objectProblem } from '../../support/facts.js';
|
|
6
7
|
import { REPOSITORY_GUARD_TIMEOUT_MS } from '../../support/timeouts.js';
|
|
@@ -66,29 +67,141 @@ function retriedCallbacks(sf) {
|
|
|
66
67
|
});
|
|
67
68
|
return callbacks;
|
|
68
69
|
}
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
const
|
|
70
|
+
const isRepeatingLoop = (node) => ts.isForStatement(node) || ts.isWhileStatement(node) || ts.isDoStatement(node);
|
|
71
|
+
function functionBodiesOf(sf) {
|
|
72
|
+
const bodies = new Map();
|
|
73
|
+
const add = (name, body) => bodies.set(name, [...(bodies.get(name) ?? []), body]);
|
|
74
|
+
forEachNode(sf, (node) => {
|
|
75
|
+
if (ts.isFunctionDeclaration(node) && node.name && node.body)
|
|
76
|
+
add(node.name.text, node.body);
|
|
77
|
+
if (ts.isVariableDeclaration(node) &&
|
|
78
|
+
ts.isIdentifier(node.name) &&
|
|
79
|
+
node.initializer &&
|
|
80
|
+
isFunctionNode(node.initializer))
|
|
81
|
+
add(node.name.text, node.initializer.body);
|
|
82
|
+
});
|
|
83
|
+
return bodies;
|
|
84
|
+
}
|
|
85
|
+
const SOURCE_EXTENSIONS = ['.ts', '.tsx', '.mts', '.cts', '.js', '.jsx', '.mjs', '.cjs'];
|
|
86
|
+
function moduleCandidates(base) {
|
|
87
|
+
const stem = base.replace(/\.(?:[cm]?js|jsx)$/, '');
|
|
88
|
+
return [
|
|
89
|
+
base,
|
|
90
|
+
...SOURCE_EXTENSIONS.map((ext) => stem + ext),
|
|
91
|
+
...SOURCE_EXTENSIONS.map((ext) => `${base}/index${ext}`),
|
|
92
|
+
];
|
|
93
|
+
}
|
|
94
|
+
// Where an imported function lives: the file and the name it is declared under there.
|
|
95
|
+
function namedImportsOf(file, paths) {
|
|
96
|
+
const imports = new Map();
|
|
97
|
+
for (const statement of file.sourceFile.statements) {
|
|
98
|
+
if (!ts.isImportDeclaration(statement) || !ts.isStringLiteral(statement.moduleSpecifier))
|
|
99
|
+
continue;
|
|
100
|
+
const bindings = statement.importClause?.namedBindings;
|
|
101
|
+
if (!bindings || !ts.isNamedImports(bindings))
|
|
102
|
+
continue;
|
|
103
|
+
const base = resolveRelative(file.path, statement.moduleSpecifier.text)?.path;
|
|
104
|
+
const target = base && moduleCandidates(base).find((candidate) => paths.has(candidate));
|
|
105
|
+
if (!target)
|
|
106
|
+
continue;
|
|
107
|
+
for (const element of bindings.elements)
|
|
108
|
+
imports.set(element.name.text, {
|
|
109
|
+
path: target,
|
|
110
|
+
name: (element.propertyName ?? element.name).text,
|
|
111
|
+
});
|
|
112
|
+
}
|
|
113
|
+
return imports;
|
|
114
|
+
}
|
|
115
|
+
export function createActionReach(files, helpers = []) {
|
|
72
116
|
const actions = actionNamesOf(helpers);
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
117
|
+
const byPath = new Map(files.map((file) => [file.path, file]));
|
|
118
|
+
const paths = new Set(byPath.keys());
|
|
119
|
+
const bodiesCache = new Map();
|
|
120
|
+
const importsCache = new Map();
|
|
121
|
+
const bodiesOf = (file) => {
|
|
122
|
+
let bodies = bodiesCache.get(file.path);
|
|
123
|
+
if (!bodies)
|
|
124
|
+
bodiesCache.set(file.path, (bodies = functionBodiesOf(file.sourceFile)));
|
|
125
|
+
return bodies;
|
|
126
|
+
};
|
|
127
|
+
const importsOf = (file) => {
|
|
128
|
+
let imports = importsCache.get(file.path);
|
|
129
|
+
if (!imports)
|
|
130
|
+
importsCache.set(file.path, (imports = namedImportsOf(file, paths)));
|
|
131
|
+
return imports;
|
|
132
|
+
};
|
|
133
|
+
function search(file, root, seen) {
|
|
134
|
+
let found;
|
|
135
|
+
forEachNode(root, (node) => {
|
|
136
|
+
if (found || !ts.isCallExpression(node))
|
|
137
|
+
return;
|
|
138
|
+
const name = calleeName(node);
|
|
139
|
+
if (!name)
|
|
140
|
+
return;
|
|
141
|
+
if (actions.has(name)) {
|
|
142
|
+
found = [name];
|
|
143
|
+
return;
|
|
144
|
+
}
|
|
145
|
+
if (!ts.isIdentifier(node.expression))
|
|
146
|
+
return;
|
|
147
|
+
const imported = importsOf(file).get(name);
|
|
148
|
+
const local = bodiesOf(file).get(name);
|
|
149
|
+
const owner = local ? file : imported ? byPath.get(imported.path) : undefined;
|
|
150
|
+
const declared = local ? name : imported?.name;
|
|
151
|
+
if (!owner || !declared)
|
|
152
|
+
return;
|
|
153
|
+
const key = `${owner.path}#${declared}`;
|
|
154
|
+
if (seen.has(key))
|
|
155
|
+
return;
|
|
156
|
+
seen.add(key);
|
|
157
|
+
for (const body of bodiesOf(owner).get(declared) ?? []) {
|
|
158
|
+
const inner = search(owner, body, seen);
|
|
159
|
+
if (inner) {
|
|
160
|
+
found = [name, ...inner];
|
|
161
|
+
return;
|
|
162
|
+
}
|
|
80
163
|
}
|
|
81
164
|
});
|
|
82
|
-
return
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
165
|
+
return found;
|
|
166
|
+
}
|
|
167
|
+
return { actionIn: (file, root) => search(file, root, new Set()) };
|
|
168
|
+
}
|
|
169
|
+
// `['openWork', 'goto']` reads "goto() through openWork".
|
|
170
|
+
function describeAction(chain) {
|
|
171
|
+
const action = `${chain[chain.length - 1]}()`;
|
|
172
|
+
return chain.length > 1 ? `${action} through ${chain.slice(0, -1).join(' → ')}` : action;
|
|
173
|
+
}
|
|
174
|
+
const within = (inner, outer) => inner !== outer && inner.pos >= outer.pos && inner.end <= outer.end;
|
|
175
|
+
const viewOf = (source) => typeof source === 'string' ? viewOfText('sample.spec.ts', source) : source;
|
|
176
|
+
export function retriedActions(source, helpers = [], reach) {
|
|
177
|
+
const file = viewOf(source);
|
|
178
|
+
const sf = file.sourceFile;
|
|
179
|
+
const reachFrom = reach ?? createActionReach([file], helpers);
|
|
180
|
+
const loops = [];
|
|
181
|
+
forEachNode(sf, (node) => {
|
|
182
|
+
if (isRepeatingLoop(node))
|
|
183
|
+
loops.push(node);
|
|
184
|
+
});
|
|
185
|
+
const acting = [
|
|
186
|
+
...retriedCallbacks(sf).map((node) => ({
|
|
187
|
+
node,
|
|
188
|
+
body: node.body,
|
|
189
|
+
kind: 'a retry loop',
|
|
190
|
+
tail: ' until it passes',
|
|
191
|
+
})),
|
|
192
|
+
...loops.map((node) => ({ node, body: node, kind: 'a for / while / do loop', tail: '' })),
|
|
193
|
+
].flatMap((loop) => {
|
|
194
|
+
const chain = reachFrom.actionIn(file, loop.body);
|
|
195
|
+
return chain ? [{ ...loop, chain }] : [];
|
|
91
196
|
});
|
|
197
|
+
return acting
|
|
198
|
+
.filter((loop) => !acting.some((outer) => within(loop.node, outer.node)))
|
|
199
|
+
.sort((a, b) => a.node.getStart(sf) - b.node.getStart(sf))
|
|
200
|
+
.map((loop) => ({
|
|
201
|
+
start: loop.node.getStart(sf),
|
|
202
|
+
end: loop.node.getEnd(),
|
|
203
|
+
detail: `${loop.kind} repeats ${describeAction(loop.chain)}${loop.tail}`,
|
|
204
|
+
}));
|
|
92
205
|
}
|
|
93
206
|
function catchesRejection(handler) {
|
|
94
207
|
if (!isFunctionNode(handler))
|
|
@@ -100,21 +213,18 @@ function catchesRejection(handler) {
|
|
|
100
213
|
});
|
|
101
214
|
return !rethrows;
|
|
102
215
|
}
|
|
103
|
-
// The call a `.catch()` is chained onto: `x.click().catch(…)` → `click
|
|
104
|
-
function
|
|
216
|
+
// The call a `.catch()` is chained onto: `x.click().catch(…)` → the `click()` call.
|
|
217
|
+
function caughtCall(catchCall) {
|
|
105
218
|
if (!ts.isPropertyAccessExpression(catchCall.expression))
|
|
106
219
|
return undefined;
|
|
107
220
|
const receiver = catchCall.expression.expression;
|
|
108
221
|
const call = ts.isParenthesizedExpression(receiver) ? receiver.expression : receiver;
|
|
109
|
-
|
|
110
|
-
return undefined;
|
|
111
|
-
const name = calleeName(call);
|
|
112
|
-
return name && names.has(name) ? name : undefined;
|
|
222
|
+
return ts.isCallExpression(call) ? call : undefined;
|
|
113
223
|
}
|
|
114
|
-
export function swallowedActions(source, helpers = []) {
|
|
115
|
-
const file =
|
|
224
|
+
export function swallowedActions(source, helpers = [], reach) {
|
|
225
|
+
const file = viewOf(source);
|
|
116
226
|
const sf = file.sourceFile;
|
|
117
|
-
const
|
|
227
|
+
const reachFrom = reach ?? createActionReach([file], helpers);
|
|
118
228
|
const hits = [];
|
|
119
229
|
forEachNode(sf, (node) => {
|
|
120
230
|
if (ts.isCallExpression(node) &&
|
|
@@ -122,19 +232,22 @@ export function swallowedActions(source, helpers = []) {
|
|
|
122
232
|
node.expression.name.text === 'catch' &&
|
|
123
233
|
node.arguments.length === 1 &&
|
|
124
234
|
catchesRejection(node.arguments[0])) {
|
|
125
|
-
const
|
|
126
|
-
|
|
127
|
-
|
|
235
|
+
const call = caughtCall(node);
|
|
236
|
+
if (!call)
|
|
237
|
+
return;
|
|
238
|
+
const name = calleeName(call);
|
|
239
|
+
const chain = reachFrom.actionIn(file, call);
|
|
240
|
+
if (chain)
|
|
128
241
|
hits.push({
|
|
129
242
|
start: node.getStart(sf),
|
|
130
243
|
end: node.getEnd(),
|
|
131
|
-
detail: `a .catch() swallows a failed ${
|
|
244
|
+
detail: `a .catch() swallows a failed ${describeAction(chain)}`,
|
|
132
245
|
});
|
|
133
|
-
else if (
|
|
246
|
+
else if (name && STATE_PROBES.has(name))
|
|
134
247
|
hits.push({
|
|
135
248
|
start: node.getStart(sf),
|
|
136
249
|
end: node.getEnd(),
|
|
137
|
-
detail: `a .catch() turns a failed ${
|
|
250
|
+
detail: `a .catch() turns a failed ${name}() — an ambiguous locator included — into a value`,
|
|
138
251
|
});
|
|
139
252
|
}
|
|
140
253
|
if (ts.isTryStatement(node) && node.catchClause) {
|
|
@@ -145,24 +258,21 @@ export function swallowedActions(source, helpers = []) {
|
|
|
145
258
|
});
|
|
146
259
|
if (rethrows)
|
|
147
260
|
return;
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
if (!action && ts.isCallExpression(inner)) {
|
|
151
|
-
const name = calleeName(inner);
|
|
152
|
-
if (name && actions.has(name))
|
|
153
|
-
action = name;
|
|
154
|
-
}
|
|
155
|
-
});
|
|
156
|
-
if (action)
|
|
261
|
+
const chain = reachFrom.actionIn(file, node.tryBlock);
|
|
262
|
+
if (chain)
|
|
157
263
|
hits.push({
|
|
158
264
|
start: node.getStart(sf),
|
|
159
265
|
end: node.getEnd(),
|
|
160
|
-
detail: `a try/catch swallows a failed ${
|
|
266
|
+
detail: `a try/catch swallows a failed ${describeAction(chain)}`,
|
|
161
267
|
});
|
|
162
268
|
}
|
|
163
269
|
});
|
|
164
270
|
return hits;
|
|
165
271
|
}
|
|
272
|
+
function withReach(files, helpers, find) {
|
|
273
|
+
const reach = createActionReach(files, helpers);
|
|
274
|
+
return files.map((file) => ({ file, hits: find(file, reach) }));
|
|
275
|
+
}
|
|
166
276
|
function e2eActionProblems(facts) {
|
|
167
277
|
const problems = [];
|
|
168
278
|
if (!objectProblem(facts, 'the facts', problems))
|
|
@@ -180,16 +290,16 @@ export const retryLoopActionGuard = {
|
|
|
180
290
|
return [
|
|
181
291
|
{
|
|
182
292
|
title: 'no end-to-end retry loop repeats a user action',
|
|
183
|
-
description: 'A toPass() or expect.poll() callback that clicks, types, or navigates
|
|
184
|
-
'happens to work, so the failure a real user would hit passes as green
|
|
293
|
+
description: 'A toPass() or expect.poll() callback, or a for (;;) / while / do loop, that clicks, types, or navigates ' +
|
|
294
|
+
'repeats the action until it happens to work, so the failure a real user would hit passes as green ' +
|
|
295
|
+
'(RULE-STD-QUALITY-003). Walking a list is written for…of, which is not a repeat. Act once, ' +
|
|
185
296
|
'then assert with a web-first assertion; a loop that only re-reads state is fine. The actions are ' +
|
|
186
|
-
"Playwright's input and navigation calls plus the app's declared actionHelpers
|
|
297
|
+
"Playwright's input and navigation calls plus the app's declared actionHelpers, reached directly or through " +
|
|
298
|
+
'a plain call to a function the suite declares in the same file or imports from its own files. A loop that must act — ' +
|
|
187
299
|
'polling a background result by reloading — is a reviewed entry whose anchor names that one loop, with ' +
|
|
188
300
|
'its why; an entry that no longer matches a loop is stale.',
|
|
189
301
|
timeoutMs: REPOSITORY_GUARD_TIMEOUT_MS,
|
|
190
|
-
run: (context) => anchoredFindings(context
|
|
191
|
-
.files(E2E_SCOPE)
|
|
192
|
-
.map((file) => ({ file, hits: retriedActions(file, facts.actionHelpers) })), facts.reviewed, 'its anchor names no retry loop that acts'),
|
|
302
|
+
run: (context) => anchoredFindings(withReach(context.files(E2E_SCOPE), facts.actionHelpers, (file, reach) => retriedActions(file, facts.actionHelpers, reach)), facts.reviewed, 'its anchor names no retry loop that acts'),
|
|
193
303
|
},
|
|
194
304
|
];
|
|
195
305
|
},
|
|
@@ -203,16 +313,15 @@ export const swallowedActionGuard = {
|
|
|
203
313
|
return [
|
|
204
314
|
{
|
|
205
315
|
title: 'no end-to-end action or state read swallows its failure',
|
|
206
|
-
description: 'A .catch() or a try/catch that discards a failed click, fill, or navigation
|
|
316
|
+
description: 'A .catch() or a try/catch that discards a failed click, fill, or navigation — made directly or through a plain ' +
|
|
317
|
+
"call to one of the suite's own functions — lets the next step run on a " +
|
|
207
318
|
'page the action never reached, and one on isVisible(), isEnabled(), or isChecked() turns an ambiguous ' +
|
|
208
319
|
'(strict-mode) locator into a silent "no". Let the failure throw: an action whose outcome is uncertain ' +
|
|
209
320
|
"goes through the suite's outcome-verified helper, and a branch reads state with no catch. A catch that " +
|
|
210
321
|
'must stay is a reviewed entry whose anchor names that one occurrence, with its why; an entry that no ' +
|
|
211
322
|
'longer matches one is stale.',
|
|
212
323
|
timeoutMs: REPOSITORY_GUARD_TIMEOUT_MS,
|
|
213
|
-
run: (context) => anchoredFindings(context
|
|
214
|
-
.files(E2E_SCOPE)
|
|
215
|
-
.map((file) => ({ file, hits: swallowedActions(file, facts.actionHelpers) })), facts.reviewed, 'its anchor names no swallowed action or state read'),
|
|
324
|
+
run: (context) => anchoredFindings(withReach(context.files(E2E_SCOPE), facts.actionHelpers, (file, reach) => swallowedActions(file, facts.actionHelpers, reach)), facts.reviewed, 'its anchor names no swallowed action or state read'),
|
|
216
325
|
},
|
|
217
326
|
];
|
|
218
327
|
},
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"e2e-actions.js","sourceRoot":"","sources":["../../../src/modules/repo/e2e-actions.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 { viewOfText, type FileView } from '../../reader/view.js';\nimport { calleeName, forEachNode } from '../../support/ast.js';\nimport {\n anchoredFindings,\n anchoredProblems,\n type AnchoredEntry,\n type Occurrence,\n} from '../../support/anchored-review.js';\nimport { namesProblem, objectProblem } from '../../support/facts.js';\nimport { REPOSITORY_GUARD_TIMEOUT_MS } from '../../support/timeouts.js';\n\n// Playwright's locator and page operations that act on the page: input, and navigation.\nconst PLAYWRIGHT_ACTIONS: ReadonlySet<string> = new Set([\n 'check',\n 'clear',\n 'click',\n 'dblclick',\n 'dispatchEvent',\n 'dragTo',\n 'fill',\n 'goBack',\n 'goForward',\n 'goto',\n 'hover',\n 'press',\n 'pressSequentially',\n 'reload',\n 'selectOption',\n 'selectText',\n 'setChecked',\n 'setInputFiles',\n 'tap',\n 'type',\n 'uncheck',\n]);\n\n// Playwright's state reads: strict locators throw when a locator matches more than one element.\nconst STATE_PROBES: ReadonlySet<string> = new Set([\n 'isChecked',\n 'isDisabled',\n 'isEditable',\n 'isEnabled',\n 'isHidden',\n 'isVisible',\n]);\n\nconst E2E_SCOPE: Scope = {\n roots: ['e2e', 'hostedE2e'],\n kinds: ['code'],\n tests: true,\n optionalRoots: ['hostedE2e'],\n};\n\nconst actionNamesOf = (helpers: readonly string[]) => new Set([...PLAYWRIGHT_ACTIONS, ...helpers]);\n\nconst isFunctionNode = (node: ts.Node): node is ts.ArrowFunction | ts.FunctionExpression =>\n ts.isArrowFunction(node) || ts.isFunctionExpression(node);\n\n// `expect(fn).toPass(…)` and `expect.poll(fn)` retry `fn` until it passes.\nfunction retriedCallbacks(sf: ts.SourceFile): Array<ts.ArrowFunction | ts.FunctionExpression> {\n const callbacks: Array<ts.ArrowFunction | ts.FunctionExpression> = [];\n forEachNode(sf, (node) => {\n if (!ts.isCallExpression(node) || !ts.isPropertyAccessExpression(node.expression)) return;\n const method = node.expression.name.text;\n const target = node.expression.expression;\n if (method === 'toPass' && ts.isCallExpression(target)) {\n const fn = target.arguments[0];\n if (fn && isFunctionNode(fn) && calleeName(target)?.toLowerCase().endsWith('expect'))\n callbacks.push(fn);\n }\n if (method === 'poll' && calleeName(node) === 'poll') {\n const fn = node.arguments[0];\n if (fn && isFunctionNode(fn)) callbacks.push(fn);\n }\n });\n return callbacks;\n}\n\nexport function retriedActions(\n source: FileView | string,\n helpers: readonly string[] = [],\n): Occurrence[] {\n const file = typeof source === 'string' ? viewOfText('sample.spec.ts', source) : source;\n const sf = file.sourceFile;\n const actions = actionNamesOf(helpers);\n return retriedCallbacks(sf).flatMap((fn) => {\n let action: string | undefined;\n forEachNode(fn.body, (node) => {\n if (!action && ts.isCallExpression(node)) {\n const name = calleeName(node);\n if (name && actions.has(name)) action = name;\n }\n });\n return action\n ? [\n {\n start: fn.getStart(sf),\n end: fn.getEnd(),\n detail: `a retry loop repeats ${action}() until it passes`,\n },\n ]\n : [];\n });\n}\n\nfunction catchesRejection(handler: ts.Expression): boolean {\n if (!isFunctionNode(handler)) return true;\n let rethrows = false;\n forEachNode(handler.body, (node) => {\n if (ts.isThrowStatement(node)) rethrows = true;\n });\n return !rethrows;\n}\n\n// The call a `.catch()` is chained onto: `x.click().catch(…)` → `click`.\nfunction caughtCallOf(\n catchCall: ts.CallExpression,\n names: ReadonlySet<string>,\n): string | undefined {\n if (!ts.isPropertyAccessExpression(catchCall.expression)) return undefined;\n const receiver = catchCall.expression.expression;\n const call = ts.isParenthesizedExpression(receiver) ? receiver.expression : receiver;\n if (!ts.isCallExpression(call)) return undefined;\n const name = calleeName(call);\n return name && names.has(name) ? name : undefined;\n}\n\nexport function swallowedActions(\n source: FileView | string,\n helpers: readonly string[] = [],\n): Occurrence[] {\n const file = typeof source === 'string' ? viewOfText('sample.spec.ts', source) : source;\n const sf = file.sourceFile;\n const actions = actionNamesOf(helpers);\n const hits: Occurrence[] = [];\n forEachNode(sf, (node) => {\n if (\n ts.isCallExpression(node) &&\n ts.isPropertyAccessExpression(node.expression) &&\n node.expression.name.text === 'catch' &&\n node.arguments.length === 1 &&\n catchesRejection(node.arguments[0])\n ) {\n const action = caughtCallOf(node, actions);\n const probe = caughtCallOf(node, STATE_PROBES);\n if (action)\n hits.push({\n start: node.getStart(sf),\n end: node.getEnd(),\n detail: `a .catch() swallows a failed ${action}()`,\n });\n else if (probe)\n hits.push({\n start: node.getStart(sf),\n end: node.getEnd(),\n detail: `a .catch() turns a failed ${probe}() — an ambiguous locator included — into a value`,\n });\n }\n if (ts.isTryStatement(node) && node.catchClause) {\n let rethrows = false;\n forEachNode(node.catchClause.block, (inner) => {\n if (ts.isThrowStatement(inner)) rethrows = true;\n });\n if (rethrows) return;\n let action: string | undefined;\n forEachNode(node.tryBlock, (inner) => {\n if (!action && ts.isCallExpression(inner)) {\n const name = calleeName(inner);\n if (name && actions.has(name)) action = name;\n }\n });\n if (action)\n hits.push({\n start: node.getStart(sf),\n end: node.getEnd(),\n detail: `a try/catch swallows a failed ${action}()`,\n });\n }\n });\n return hits;\n}\n\nexport interface E2eActionFacts {\n actionHelpers: readonly string[];\n reviewed: readonly AnchoredEntry[];\n}\n\nfunction e2eActionProblems(facts: E2eActionFacts): string[] {\n const problems: string[] = [];\n if (!objectProblem(facts, 'the facts', problems)) return problems;\n namesProblem(facts.actionHelpers, 'actionHelpers', problems, { nonEmpty: false });\n anchoredProblems(facts.reviewed, 'reviewed', problems);\n return problems;\n}\n\nexport const retryLoopActionGuard: GenericGuardDefinition<E2eActionFacts> = {\n id: 'retryLoopActions',\n absentWhen: 'the repository has no end-to-end suite',\n absentOnlyWithoutRoot: 'e2e',\n factProblems: e2eActionProblems,\n build(facts) {\n return [\n {\n title: 'no end-to-end retry loop repeats a user action',\n description:\n 'A toPass() or expect.poll() callback that clicks, types, or navigates repeats the action until it ' +\n 'happens to work, so the failure a real user would hit passes as green (RULE-STD-QUALITY-003). Act once, ' +\n 'then assert with a web-first assertion; a loop that only re-reads state is fine. The actions are ' +\n \"Playwright's input and navigation calls plus the app's declared actionHelpers. A loop that must act — \" +\n 'polling a background result by reloading — is a reviewed entry whose anchor names that one loop, with ' +\n 'its why; an entry that no longer matches a loop is stale.',\n timeoutMs: REPOSITORY_GUARD_TIMEOUT_MS,\n run: (context) =>\n anchoredFindings(\n context\n .files(E2E_SCOPE)\n .map((file) => ({ file, hits: retriedActions(file, facts.actionHelpers) })),\n facts.reviewed,\n 'its anchor names no retry loop that acts',\n ),\n },\n ];\n },\n};\n\nexport const swallowedActionGuard: GenericGuardDefinition<E2eActionFacts> = {\n id: 'swallowedE2eActions',\n absentWhen: 'the repository has no end-to-end suite',\n absentOnlyWithoutRoot: 'e2e',\n factProblems: e2eActionProblems,\n build(facts) {\n return [\n {\n title: 'no end-to-end action or state read swallows its failure',\n description:\n 'A .catch() or a try/catch that discards a failed click, fill, or navigation lets the next step run on a ' +\n 'page the action never reached, and one on isVisible(), isEnabled(), or isChecked() turns an ambiguous ' +\n '(strict-mode) locator into a silent \"no\". Let the failure throw: an action whose outcome is uncertain ' +\n \"goes through the suite's outcome-verified helper, and a branch reads state with no catch. A catch that \" +\n 'must stay is a reviewed entry whose anchor names that one occurrence, with its why; an entry that no ' +\n 'longer matches one is stale.',\n timeoutMs: REPOSITORY_GUARD_TIMEOUT_MS,\n run: (context) =>\n anchoredFindings(\n context\n .files(E2E_SCOPE)\n .map((file) => ({ file, hits: swallowedActions(file, facts.actionHelpers) })),\n facts.reviewed,\n 'its anchor names no swallowed action or state read',\n ),\n },\n ];\n },\n};\n"]}
|
|
1
|
+
{"version":3,"file":"e2e-actions.js","sourceRoot":"","sources":["../../../src/modules/repo/e2e-actions.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 { viewOfText, type FileView } from '../../reader/view.js';\nimport { calleeName, forEachNode } from '../../support/ast.js';\nimport { resolveRelative } from '../../support/paths.js';\nimport {\n anchoredFindings,\n anchoredProblems,\n type AnchoredEntry,\n type Occurrence,\n} from '../../support/anchored-review.js';\nimport { namesProblem, objectProblem } from '../../support/facts.js';\nimport { REPOSITORY_GUARD_TIMEOUT_MS } from '../../support/timeouts.js';\n\n// Playwright's locator and page operations that act on the page: input, and navigation.\nconst PLAYWRIGHT_ACTIONS: ReadonlySet<string> = new Set([\n 'check',\n 'clear',\n 'click',\n 'dblclick',\n 'dispatchEvent',\n 'dragTo',\n 'fill',\n 'goBack',\n 'goForward',\n 'goto',\n 'hover',\n 'press',\n 'pressSequentially',\n 'reload',\n 'selectOption',\n 'selectText',\n 'setChecked',\n 'setInputFiles',\n 'tap',\n 'type',\n 'uncheck',\n]);\n\n// Playwright's state reads: strict locators throw when a locator matches more than one element.\nconst STATE_PROBES: ReadonlySet<string> = new Set([\n 'isChecked',\n 'isDisabled',\n 'isEditable',\n 'isEnabled',\n 'isHidden',\n 'isVisible',\n]);\n\nconst E2E_SCOPE: Scope = {\n roots: ['e2e', 'hostedE2e'],\n kinds: ['code'],\n tests: true,\n optionalRoots: ['hostedE2e'],\n};\n\nconst actionNamesOf = (helpers: readonly string[]) => new Set([...PLAYWRIGHT_ACTIONS, ...helpers]);\n\nconst isFunctionNode = (node: ts.Node): node is ts.ArrowFunction | ts.FunctionExpression =>\n ts.isArrowFunction(node) || ts.isFunctionExpression(node);\n\n// `expect(fn).toPass(…)` and `expect.poll(fn)` retry `fn` until it passes.\nfunction retriedCallbacks(sf: ts.SourceFile): Array<ts.ArrowFunction | ts.FunctionExpression> {\n const callbacks: Array<ts.ArrowFunction | ts.FunctionExpression> = [];\n forEachNode(sf, (node) => {\n if (!ts.isCallExpression(node) || !ts.isPropertyAccessExpression(node.expression)) return;\n const method = node.expression.name.text;\n const target = node.expression.expression;\n if (method === 'toPass' && ts.isCallExpression(target)) {\n const fn = target.arguments[0];\n if (fn && isFunctionNode(fn) && calleeName(target)?.toLowerCase().endsWith('expect'))\n callbacks.push(fn);\n }\n if (method === 'poll' && calleeName(node) === 'poll') {\n const fn = node.arguments[0];\n if (fn && isFunctionNode(fn)) callbacks.push(fn);\n }\n });\n return callbacks;\n}\n\nconst isRepeatingLoop = (\n node: ts.Node,\n): node is ts.ForStatement | ts.WhileStatement | ts.DoStatement =>\n ts.isForStatement(node) || ts.isWhileStatement(node) || ts.isDoStatement(node);\n\ntype FunctionBodies = ReadonlyMap<string, readonly ts.Node[]>;\n\nfunction functionBodiesOf(sf: ts.SourceFile): FunctionBodies {\n const bodies = new Map<string, ts.Node[]>();\n const add = (name: string, body: ts.Node) =>\n bodies.set(name, [...(bodies.get(name) ?? []), body]);\n forEachNode(sf, (node) => {\n if (ts.isFunctionDeclaration(node) && node.name && node.body) add(node.name.text, node.body);\n if (\n ts.isVariableDeclaration(node) &&\n ts.isIdentifier(node.name) &&\n node.initializer &&\n isFunctionNode(node.initializer)\n )\n add(node.name.text, node.initializer.body);\n });\n return bodies;\n}\n\nconst SOURCE_EXTENSIONS = ['.ts', '.tsx', '.mts', '.cts', '.js', '.jsx', '.mjs', '.cjs'];\n\nfunction moduleCandidates(base: string): string[] {\n const stem = base.replace(/\\.(?:[cm]?js|jsx)$/, '');\n return [\n base,\n ...SOURCE_EXTENSIONS.map((ext) => stem + ext),\n ...SOURCE_EXTENSIONS.map((ext) => `${base}/index${ext}`),\n ];\n}\n\n// Where an imported function lives: the file and the name it is declared under there.\nfunction namedImportsOf(\n file: FileView,\n paths: ReadonlySet<string>,\n): Map<string, { path: string; name: string }> {\n const imports = new Map<string, { path: string; name: string }>();\n for (const statement of file.sourceFile.statements) {\n if (!ts.isImportDeclaration(statement) || !ts.isStringLiteral(statement.moduleSpecifier))\n continue;\n const bindings = statement.importClause?.namedBindings;\n if (!bindings || !ts.isNamedImports(bindings)) continue;\n const base = resolveRelative(file.path, statement.moduleSpecifier.text)?.path;\n const target = base && moduleCandidates(base).find((candidate) => paths.has(candidate));\n if (!target) continue;\n for (const element of bindings.elements)\n imports.set(element.name.text, {\n path: target,\n name: (element.propertyName ?? element.name).text,\n });\n }\n return imports;\n}\n\n// Finds the first action a piece of code performs, directly or through the plain function calls it\n// makes into functions declared in the same file or imported from the suite's own files.\nexport interface ActionReach {\n actionIn(file: FileView, root: ts.Node): string[] | undefined;\n}\n\nexport function createActionReach(\n files: readonly FileView[],\n helpers: readonly string[] = [],\n): ActionReach {\n const actions = actionNamesOf(helpers);\n const byPath = new Map(files.map((file) => [file.path, file]));\n const paths = new Set(byPath.keys());\n const bodiesCache = new Map<string, FunctionBodies>();\n const importsCache = new Map<string, Map<string, { path: string; name: string }>>();\n const bodiesOf = (file: FileView) => {\n let bodies = bodiesCache.get(file.path);\n if (!bodies) bodiesCache.set(file.path, (bodies = functionBodiesOf(file.sourceFile)));\n return bodies;\n };\n const importsOf = (file: FileView) => {\n let imports = importsCache.get(file.path);\n if (!imports) importsCache.set(file.path, (imports = namedImportsOf(file, paths)));\n return imports;\n };\n\n function search(file: FileView, root: ts.Node, seen: Set<string>): string[] | undefined {\n let found: string[] | undefined;\n forEachNode(root, (node) => {\n if (found || !ts.isCallExpression(node)) return;\n const name = calleeName(node);\n if (!name) return;\n if (actions.has(name)) {\n found = [name];\n return;\n }\n if (!ts.isIdentifier(node.expression)) return;\n const imported = importsOf(file).get(name);\n const local = bodiesOf(file).get(name);\n const owner = local ? file : imported ? byPath.get(imported.path) : undefined;\n const declared = local ? name : imported?.name;\n if (!owner || !declared) return;\n const key = `${owner.path}#${declared}`;\n if (seen.has(key)) return;\n seen.add(key);\n for (const body of bodiesOf(owner).get(declared) ?? []) {\n const inner = search(owner, body, seen);\n if (inner) {\n found = [name, ...inner];\n return;\n }\n }\n });\n return found;\n }\n\n return { actionIn: (file, root) => search(file, root, new Set()) };\n}\n\n// `['openWork', 'goto']` reads \"goto() through openWork\".\nfunction describeAction(chain: readonly string[]): string {\n const action = `${chain[chain.length - 1]}()`;\n return chain.length > 1 ? `${action} through ${chain.slice(0, -1).join(' → ')}` : action;\n}\n\nconst within = (inner: ts.Node, outer: ts.Node): boolean =>\n inner !== outer && inner.pos >= outer.pos && inner.end <= outer.end;\n\nconst viewOf = (source: FileView | string) =>\n typeof source === 'string' ? viewOfText('sample.spec.ts', source) : source;\n\nexport function retriedActions(\n source: FileView | string,\n helpers: readonly string[] = [],\n reach?: ActionReach,\n): Occurrence[] {\n const file = viewOf(source);\n const sf = file.sourceFile;\n const reachFrom = reach ?? createActionReach([file], helpers);\n const loops: ts.Node[] = [];\n forEachNode(sf, (node) => {\n if (isRepeatingLoop(node)) loops.push(node);\n });\n const acting = [\n ...retriedCallbacks(sf).map((node) => ({\n node,\n body: node.body,\n kind: 'a retry loop',\n tail: ' until it passes',\n })),\n ...loops.map((node) => ({ node, body: node, kind: 'a for / while / do loop', tail: '' })),\n ].flatMap((loop) => {\n const chain = reachFrom.actionIn(file, loop.body);\n return chain ? [{ ...loop, chain }] : [];\n });\n return acting\n .filter((loop) => !acting.some((outer) => within(loop.node, outer.node)))\n .sort((a, b) => a.node.getStart(sf) - b.node.getStart(sf))\n .map((loop) => ({\n start: loop.node.getStart(sf),\n end: loop.node.getEnd(),\n detail: `${loop.kind} repeats ${describeAction(loop.chain)}${loop.tail}`,\n }));\n}\n\nfunction catchesRejection(handler: ts.Expression): boolean {\n if (!isFunctionNode(handler)) return true;\n let rethrows = false;\n forEachNode(handler.body, (node) => {\n if (ts.isThrowStatement(node)) rethrows = true;\n });\n return !rethrows;\n}\n\n// The call a `.catch()` is chained onto: `x.click().catch(…)` → the `click()` call.\nfunction caughtCall(catchCall: ts.CallExpression): ts.CallExpression | undefined {\n if (!ts.isPropertyAccessExpression(catchCall.expression)) return undefined;\n const receiver = catchCall.expression.expression;\n const call = ts.isParenthesizedExpression(receiver) ? receiver.expression : receiver;\n return ts.isCallExpression(call) ? call : undefined;\n}\n\nexport function swallowedActions(\n source: FileView | string,\n helpers: readonly string[] = [],\n reach?: ActionReach,\n): Occurrence[] {\n const file = viewOf(source);\n const sf = file.sourceFile;\n const reachFrom = reach ?? createActionReach([file], helpers);\n const hits: Occurrence[] = [];\n forEachNode(sf, (node) => {\n if (\n ts.isCallExpression(node) &&\n ts.isPropertyAccessExpression(node.expression) &&\n node.expression.name.text === 'catch' &&\n node.arguments.length === 1 &&\n catchesRejection(node.arguments[0])\n ) {\n const call = caughtCall(node);\n if (!call) return;\n const name = calleeName(call);\n const chain = reachFrom.actionIn(file, call);\n if (chain)\n hits.push({\n start: node.getStart(sf),\n end: node.getEnd(),\n detail: `a .catch() swallows a failed ${describeAction(chain)}`,\n });\n else if (name && STATE_PROBES.has(name))\n hits.push({\n start: node.getStart(sf),\n end: node.getEnd(),\n detail: `a .catch() turns a failed ${name}() — an ambiguous locator included — into a value`,\n });\n }\n if (ts.isTryStatement(node) && node.catchClause) {\n let rethrows = false;\n forEachNode(node.catchClause.block, (inner) => {\n if (ts.isThrowStatement(inner)) rethrows = true;\n });\n if (rethrows) return;\n const chain = reachFrom.actionIn(file, node.tryBlock);\n if (chain)\n hits.push({\n start: node.getStart(sf),\n end: node.getEnd(),\n detail: `a try/catch swallows a failed ${describeAction(chain)}`,\n });\n }\n });\n return hits;\n}\n\nfunction withReach(\n files: readonly FileView[],\n helpers: readonly string[],\n find: (file: FileView, reach: ActionReach) => Occurrence[],\n): Array<{ file: FileView; hits: Occurrence[] }> {\n const reach = createActionReach(files, helpers);\n return files.map((file) => ({ file, hits: find(file, reach) }));\n}\n\nexport interface E2eActionFacts {\n actionHelpers: readonly string[];\n reviewed: readonly AnchoredEntry[];\n}\n\nfunction e2eActionProblems(facts: E2eActionFacts): string[] {\n const problems: string[] = [];\n if (!objectProblem(facts, 'the facts', problems)) return problems;\n namesProblem(facts.actionHelpers, 'actionHelpers', problems, { nonEmpty: false });\n anchoredProblems(facts.reviewed, 'reviewed', problems);\n return problems;\n}\n\nexport const retryLoopActionGuard: GenericGuardDefinition<E2eActionFacts> = {\n id: 'retryLoopActions',\n absentWhen: 'the repository has no end-to-end suite',\n absentOnlyWithoutRoot: 'e2e',\n factProblems: e2eActionProblems,\n build(facts) {\n return [\n {\n title: 'no end-to-end retry loop repeats a user action',\n description:\n 'A toPass() or expect.poll() callback, or a for (;;) / while / do loop, that clicks, types, or navigates ' +\n 'repeats the action until it happens to work, so the failure a real user would hit passes as green ' +\n '(RULE-STD-QUALITY-003). Walking a list is written for…of, which is not a repeat. Act once, ' +\n 'then assert with a web-first assertion; a loop that only re-reads state is fine. The actions are ' +\n \"Playwright's input and navigation calls plus the app's declared actionHelpers, reached directly or through \" +\n 'a plain call to a function the suite declares in the same file or imports from its own files. A loop that must act — ' +\n 'polling a background result by reloading — is a reviewed entry whose anchor names that one loop, with ' +\n 'its why; an entry that no longer matches a loop is stale.',\n timeoutMs: REPOSITORY_GUARD_TIMEOUT_MS,\n run: (context) =>\n anchoredFindings(\n withReach(context.files(E2E_SCOPE), facts.actionHelpers, (file, reach) =>\n retriedActions(file, facts.actionHelpers, reach),\n ),\n facts.reviewed,\n 'its anchor names no retry loop that acts',\n ),\n },\n ];\n },\n};\n\nexport const swallowedActionGuard: GenericGuardDefinition<E2eActionFacts> = {\n id: 'swallowedE2eActions',\n absentWhen: 'the repository has no end-to-end suite',\n absentOnlyWithoutRoot: 'e2e',\n factProblems: e2eActionProblems,\n build(facts) {\n return [\n {\n title: 'no end-to-end action or state read swallows its failure',\n description:\n 'A .catch() or a try/catch that discards a failed click, fill, or navigation — made directly or through a plain ' +\n \"call to one of the suite's own functions — lets the next step run on a \" +\n 'page the action never reached, and one on isVisible(), isEnabled(), or isChecked() turns an ambiguous ' +\n '(strict-mode) locator into a silent \"no\". Let the failure throw: an action whose outcome is uncertain ' +\n \"goes through the suite's outcome-verified helper, and a branch reads state with no catch. A catch that \" +\n 'must stay is a reviewed entry whose anchor names that one occurrence, with its why; an entry that no ' +\n 'longer matches one is stale.',\n timeoutMs: REPOSITORY_GUARD_TIMEOUT_MS,\n run: (context) =>\n anchoredFindings(\n withReach(context.files(E2E_SCOPE), facts.actionHelpers, (file, reach) =>\n swallowedActions(file, facts.actionHelpers, reach),\n ),\n facts.reviewed,\n 'its anchor names no swallowed action or state read',\n ),\n },\n ];\n },\n};\n"]}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"vitest-report.d.ts","sourceRoot":"","sources":["../../../src/node/tooling/vitest-report.ts"],"names":[],"mappings":"AAAA,MAAM,WAAW,eAAe;IAC9B,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,eAAe,CAAC,EAAE,MAAM,EAAE,CAAC;CAC5B;AAED,MAAM,WAAW,gBAAgB;IAC/B,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,gBAAgB,CAAC,EAAE,eAAe,EAAE,CAAC;CACtC;AAED,MAAM,WAAW,gBAAgB;IAC/B,WAAW,CAAC,EAAE,gBAAgB,EAAE,CAAC;IACjC,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,aAAa,CAAC,EAAE,MAAM,CAAC;CACxB;AAED,MAAM,WAAW,WAAW;IAC1B,IAAI,EAAE,MAAM,CAAC;IACb,KAAK,EAAE,MAAM,CAAC;IACd,QAAQ,EAAE,MAAM,EAAE,CAAC;CACpB;AAED,MAAM,WAAW,YAAY;IAC3B,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,MAAM,CAAC;CACjB;AAED,MAAM,WAAW,WAAW;IAC1B,IAAI,EAAE,MAAM,CAAC;IACb,KAAK,EAAE,MAAM,CAAC;IACd,MAAM,EAAE,MAAM,CAAC;CAChB;AAED,MAAM,WAAW,YAAY;IAC3B,MAAM,EAAE,MAAM,CAAC;IACf,MAAM,EAAE,MAAM,CAAC;IACf,OAAO,EAAE,MAAM,CAAC;IAChB,KAAK,EAAE,MAAM,CAAC;IACd,YAAY,EAAE,MAAM,CAAC;CACtB;AAGD,wBAAgB,eAAe,CAAC,MAAM,EAAE,MAAM,GAAG,gBAAgB,GAAG,IAAI,CAcvE;
|
|
1
|
+
{"version":3,"file":"vitest-report.d.ts","sourceRoot":"","sources":["../../../src/node/tooling/vitest-report.ts"],"names":[],"mappings":"AAAA,MAAM,WAAW,eAAe;IAC9B,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,QAAQ,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IACzB,eAAe,CAAC,EAAE,MAAM,EAAE,CAAC;CAC5B;AAED,MAAM,WAAW,gBAAgB;IAC/B,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,gBAAgB,CAAC,EAAE,eAAe,EAAE,CAAC;CACtC;AAED,MAAM,WAAW,gBAAgB;IAC/B,WAAW,CAAC,EAAE,gBAAgB,EAAE,CAAC;IACjC,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,aAAa,CAAC,EAAE,MAAM,CAAC;CACxB;AAED,MAAM,WAAW,WAAW;IAC1B,IAAI,EAAE,MAAM,CAAC;IACb,KAAK,EAAE,MAAM,CAAC;IACd,QAAQ,EAAE,MAAM,EAAE,CAAC;CACpB;AAED,MAAM,WAAW,YAAY;IAC3B,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,MAAM,CAAC;CACjB;AAED,MAAM,WAAW,WAAW;IAC1B,IAAI,EAAE,MAAM,CAAC;IACb,KAAK,EAAE,MAAM,CAAC;IACd,MAAM,EAAE,MAAM,CAAC;CAChB;AAED,MAAM,WAAW,YAAY;IAC3B,MAAM,EAAE,MAAM,CAAC;IACf,MAAM,EAAE,MAAM,CAAC;IACf,OAAO,EAAE,MAAM,CAAC;IAChB,KAAK,EAAE,MAAM,CAAC;IACd,YAAY,EAAE,MAAM,CAAC;CACtB;AAGD,wBAAgB,eAAe,CAAC,MAAM,EAAE,MAAM,GAAG,gBAAgB,GAAG,IAAI,CAcvE;AAcD,wBAAgB,qBAAqB,CAAC,MAAM,EAAE,gBAAgB,GAAG,IAAI,GAAG;IACtE,QAAQ,EAAE,WAAW,EAAE,CAAC;IACxB,aAAa,EAAE,YAAY,EAAE,CAAC;IAC9B,YAAY,EAAE,WAAW,EAAE,CAAC;IAC5B,MAAM,EAAE,YAAY,GAAG,IAAI,CAAC;CAC7B,CAoCA;AAED,wBAAgB,kBAAkB,CAAC,EACjC,IAAI,EACJ,MAAM,EACN,aAAa,GACd,EAAE;IACD,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,EAAE,YAAY,GAAG,IAAI,CAAC;IAC5B,aAAa,EAAE,YAAY,EAAE,CAAC;CAC/B,GAAG;IAAE,EAAE,EAAE,OAAO,CAAC;IAAC,aAAa,EAAE,MAAM,GAAG,IAAI,CAAA;CAAE,CAWhD;AAED,wBAAgB,mBAAmB,CAAC,aAAa,EAAE,YAAY,EAAE,GAAG,MAAM,CAUzE"}
|
|
@@ -17,6 +17,15 @@ export function parseVitestJson(stdout) {
|
|
|
17
17
|
}
|
|
18
18
|
}
|
|
19
19
|
}
|
|
20
|
+
const TIMEOUT_PLACEHOLDER = 'Error: STACK_TRACE_ERROR';
|
|
21
|
+
// Vitest reports a test that passed its time limit with the stack of a placeholder error, so the
|
|
22
|
+
// report carries `STACK_TRACE_ERROR` where the "timed out" message should be.
|
|
23
|
+
function readableFailureMessage(message, durationMs) {
|
|
24
|
+
if (!message.startsWith(TIMEOUT_PLACEHOLDER))
|
|
25
|
+
return message;
|
|
26
|
+
const ran = typeof durationMs === 'number' ? ` after ${Math.round(durationMs)} ms` : '';
|
|
27
|
+
return `Timed out${ran}: the test passed its time limit.${message.slice(TIMEOUT_PLACEHOLDER.length)}`;
|
|
28
|
+
}
|
|
20
29
|
// A test file that failed to load carries its error as the file's own `message` and adds nothing
|
|
21
30
|
// to `numFailedTests`.
|
|
22
31
|
export function collectVitestFailures(report) {
|
|
@@ -31,7 +40,11 @@ export function collectVitestFailures(report) {
|
|
|
31
40
|
const title = a.fullName || a.title || '';
|
|
32
41
|
if (a.status === 'failed') {
|
|
33
42
|
failedTests += 1;
|
|
34
|
-
failures.push({
|
|
43
|
+
failures.push({
|
|
44
|
+
file: file.name,
|
|
45
|
+
title,
|
|
46
|
+
messages: (a.failureMessages ?? []).map((m) => readableFailureMessage(m, a.duration)),
|
|
47
|
+
});
|
|
35
48
|
}
|
|
36
49
|
else if (a.status === 'skipped' || a.status === 'pending' || a.status === 'todo') {
|
|
37
50
|
skippedTests.push({ file: file.name, title, status: a.status });
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"vitest-report.js","sourceRoot":"","sources":["../../../src/node/tooling/vitest-report.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"vitest-report.js","sourceRoot":"","sources":["../../../src/node/tooling/vitest-report.ts"],"names":[],"mappings":"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","sourcesContent":["export interface VitestAssertion {\n status?: string;\n title?: string;\n fullName?: string;\n duration?: number | null;\n failureMessages?: string[];\n}\n\nexport interface VitestFileResult {\n name: string;\n status?: string;\n message?: string;\n assertionResults?: VitestAssertion[];\n}\n\nexport interface VitestJsonReport {\n testResults?: VitestFileResult[];\n numPassedTests?: number;\n numFailedTests?: number;\n numPendingTests?: number;\n numTodoTests?: number;\n numTotalTests?: number;\n}\n\nexport interface TestFailure {\n file: string;\n title: string;\n messages: string[];\n}\n\nexport interface SuiteFailure {\n file: string;\n message: string;\n}\n\nexport interface SkippedTest {\n file: string;\n title: string;\n status: string;\n}\n\nexport interface VitestTotals {\n passed: number;\n failed: number;\n skipped: number;\n total: number;\n failedSuites: number;\n}\n\n// Vitest may print other text around the JSON object.\nexport function parseVitestJson(stdout: string): VitestJsonReport | null {\n const trimmed = stdout.trim();\n try {\n return JSON.parse(trimmed) as VitestJsonReport;\n } catch {\n const first = trimmed.indexOf('{');\n const last = trimmed.lastIndexOf('}');\n if (first === -1 || last === -1 || last < first) return null;\n try {\n return JSON.parse(trimmed.slice(first, last + 1)) as VitestJsonReport;\n } catch {\n return null;\n }\n }\n}\n\nconst TIMEOUT_PLACEHOLDER = 'Error: STACK_TRACE_ERROR';\n\n// Vitest reports a test that passed its time limit with the stack of a placeholder error, so the\n// report carries `STACK_TRACE_ERROR` where the \"timed out\" message should be.\nfunction readableFailureMessage(message: string, durationMs?: number | null): string {\n if (!message.startsWith(TIMEOUT_PLACEHOLDER)) return message;\n const ran = typeof durationMs === 'number' ? ` after ${Math.round(durationMs)} ms` : '';\n return `Timed out${ran}: the test passed its time limit.${message.slice(TIMEOUT_PLACEHOLDER.length)}`;\n}\n\n// A test file that failed to load carries its error as the file's own `message` and adds nothing\n// to `numFailedTests`.\nexport function collectVitestFailures(report: VitestJsonReport | null): {\n failures: TestFailure[];\n suiteFailures: SuiteFailure[];\n skippedTests: SkippedTest[];\n totals: VitestTotals | null;\n} {\n if (!report) return { failures: [], suiteFailures: [], skippedTests: [], totals: null };\n const failures: TestFailure[] = [];\n const suiteFailures: SuiteFailure[] = [];\n const skippedTests: SkippedTest[] = [];\n for (const file of report.testResults ?? []) {\n let failedTests = 0;\n for (const a of file.assertionResults ?? []) {\n const title = a.fullName || a.title || '';\n if (a.status === 'failed') {\n failedTests += 1;\n failures.push({\n file: file.name,\n title,\n messages: (a.failureMessages ?? []).map((m) => readableFailureMessage(m, a.duration)),\n });\n } else if (a.status === 'skipped' || a.status === 'pending' || a.status === 'todo') {\n skippedTests.push({ file: file.name, title, status: a.status });\n }\n }\n if (file.status === 'failed' && failedTests === 0) {\n suiteFailures.push({\n file: file.name,\n message: file.message || '(no error message in the report)',\n });\n }\n }\n const totals: VitestTotals = {\n passed: report.numPassedTests ?? 0,\n failed: report.numFailedTests ?? 0,\n // `.skip` and `xit` land in numPendingTests; `.todo` is counted on its own.\n skipped: (report.numPendingTests ?? 0) + (report.numTodoTests ?? 0),\n total: report.numTotalTests ?? 0,\n failedSuites: suiteFailures.length,\n };\n return { failures, suiteFailures, skippedTests, totals };\n}\n\nexport function vitestStageVerdict({\n code,\n totals,\n suiteFailures,\n}: {\n code: number;\n totals: VitestTotals | null;\n suiteFailures: SuiteFailure[];\n}): { ok: boolean; skippedReason: string | null } {\n const skipped = totals?.skipped ?? 0;\n const ok =\n code === 0 &&\n totals !== null &&\n totals.failed === 0 &&\n skipped === 0 &&\n suiteFailures.length === 0;\n const skippedReason =\n skipped > 0 ? `${skipped} skipped/pending test(s) make this stage non-green.` : null;\n return { ok, skippedReason };\n}\n\nexport function formatSuiteFailures(suiteFailures: SuiteFailure[]): string {\n return suiteFailures\n .map(\n (suite) =>\n `Failed to load: ${suite.file}\\n${suite.message\n .split('\\n')\n .map((line) => ` ${line}`)\n .join('\\n')}`,\n )\n .join('\\n\\n');\n}\n"]}
|