@skyramp/mcp 0.4.2 → 0.4.3-rc.2
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/build/execution/wrapperConfig.d.ts +4 -0
- package/build/execution/wrapperConfig.js +14 -2
- package/build/prompts/code-reuse.d.ts +5 -1
- package/build/prompts/code-reuse.js +16 -10
- package/build/prompts/modularization/ui-test-modularization.js +9 -2
- package/build/prompts/pom-aware-code-reuse.d.ts +4 -1
- package/build/prompts/pom-aware-code-reuse.js +38 -6
- package/build/prompts/reuse-hand-off.d.ts +4 -0
- package/build/prompts/reuse-hand-off.js +10 -5
- package/build/prompts/shared-helper-policy.d.ts +5 -0
- package/build/prompts/shared-helper-policy.js +5 -0
- package/build/prompts/test-maintenance/driftAnalysisShared.js +2 -0
- package/build/prompts/test-maintenance/uiDriftAnalysisSections.js +2 -1
- package/build/prompts/testbot/testbot-prompts.js +33 -16
- package/build/recommendation/answers.d.ts +5 -3
- package/build/recommendation/answers.js +5 -2
- package/build/recommendation/fakeVerifyContext.d.ts +3 -0
- package/build/recommendation/fakeVerifyContext.js +16 -0
- package/build/recommendation/runVerifiers.js +2 -0
- package/build/recommendation/types.d.ts +12 -0
- package/build/recommendation/verifierContracts.d.ts +32 -2
- package/build/recommendation/verifierContracts.js +45 -4
- package/build/recommendation/verifiers/changedLinesClaimed.d.ts +17 -0
- package/build/recommendation/verifiers/changedLinesClaimed.js +88 -0
- package/build/recommendation/verifiers/coverage.js +143 -4
- package/build/recommendation/verifiers/expectedValueSourced.js +8 -0
- package/build/recommendation/verifiers/requirementSourced.js +141 -11
- package/build/services/TestGenerationService.js +4 -0
- package/build/tools/code-refactor/caller-gate.d.ts +105 -0
- package/build/tools/code-refactor/caller-gate.js +174 -0
- package/build/tools/code-refactor/changed-helper-report.d.ts +62 -0
- package/build/tools/code-refactor/changed-helper-report.js +114 -0
- package/build/tools/code-refactor/codeReuseTool.js +131 -62
- package/build/tools/code-refactor/enhanceAssertionsTool.js +2 -2
- package/build/tools/code-refactor/helper-callers.d.ts +65 -0
- package/build/tools/code-refactor/helper-callers.js +85 -0
- package/build/tools/code-refactor/modularizationTool.js +10 -0
- package/build/tools/code-refactor/pom-pass-state.d.ts +51 -0
- package/build/tools/code-refactor/pom-pass-state.js +115 -0
- package/build/tools/code-refactor/removed-modules.d.ts +33 -0
- package/build/tools/code-refactor/removed-modules.js +61 -0
- package/build/tools/code-refactor/retrofit-state.d.ts +3 -2
- package/build/tools/code-refactor/retrofit-state.js +11 -12
- package/build/tools/code-refactor/reuse-outcome.d.ts +33 -1
- package/build/tools/code-refactor/reuse-record-store.d.ts +57 -0
- package/build/tools/code-refactor/reuse-record-store.js +140 -0
- package/build/tools/code-refactor/reuse-state.d.ts +34 -9
- package/build/tools/code-refactor/reuse-state.js +140 -135
- package/build/tools/code-refactor/utils-verify-gates.js +42 -18
- package/build/tools/code-refactor/verify-gates.d.ts +4 -0
- package/build/tools/code-refactor/verify-gates.js +8 -1
- package/build/tools/generate-tests/generateContractRestTool.js +7 -7
- package/build/tools/generate-tests/generateIntegrationRestTool.js +8 -8
- package/build/tools/generate-tests/generateUIRestTool.js +3 -3
- package/build/tools/submitReportTool.js +121 -30
- package/build/tools/test-management/actionsTool.js +7 -0
- package/build/tools/test-management/registerTestPlanTool.d.ts +1 -0
- package/build/tools/test-management/registerTestPlanTool.js +71 -23
- package/build/types/RepositoryAnalysis.d.ts +20 -20
- package/build/types/ReuseOutcome.d.ts +123 -0
- package/build/types/TestExecution.d.ts +11 -0
- package/build/types/TestExecution.js +19 -0
- package/build/types/index.d.ts +1 -1
- package/build/utils/branchDiff.d.ts +1 -1
- package/build/utils/branchDiff.js +1 -1
- package/build/utils/changedRuns.d.ts +13 -0
- package/build/utils/changedRuns.js +56 -0
- package/build/utils/featureFlags.d.ts +8 -10
- package/build/utils/featureFlags.js +23 -21
- package/build/utils/normalizeSkyrampImports.d.ts +4 -1
- package/build/utils/normalizeSkyrampImports.js +24 -17
- package/build/utils/pom-scope/ownership.d.ts +19 -0
- package/build/utils/pom-scope/ownership.js +31 -0
- package/build/utils/pom-scope/pom-files.d.ts +8 -0
- package/build/utils/pom-scope/pom-files.js +8 -0
- package/build/utils/pom-scope/scoring.js +13 -1
- package/build/utils/pom-scope/strip.d.ts +10 -0
- package/build/utils/pom-scope/strip.js +9 -0
- package/build/utils/pom-verify/verify.js +13 -2
- package/build/utils/reuseRouting.d.ts +30 -2
- package/build/utils/reuseRouting.js +36 -15
- package/build/utils/testDependencyPolicy.js +4 -16
- package/build/utils/utils-verify/action-sites.d.ts +2 -2
- package/build/utils/utils-verify/allow.d.ts +1 -1
- package/build/utils/utils-verify/allow.js +15 -0
- package/build/utils/utils-verify/body-reach.d.ts +2 -2
- package/build/utils/utils-verify/body-reach.js +4 -1
- package/build/utils/utils-verify/call-sites.d.ts +16 -6
- package/build/utils/utils-verify/call-sites.js +12 -6
- package/build/utils/utils-verify/changed-helpers.d.ts +38 -0
- package/build/utils/utils-verify/changed-helpers.js +91 -0
- package/build/utils/utils-verify/changed-lines.d.ts +48 -0
- package/build/utils/utils-verify/changed-lines.js +106 -0
- package/build/utils/utils-verify/head.d.ts +73 -0
- package/build/utils/utils-verify/head.js +156 -0
- package/build/utils/utils-verify/importers.d.ts +59 -10
- package/build/utils/utils-verify/importers.js +121 -31
- package/build/utils/utils-verify/in-house.js +26 -31
- package/build/utils/utils-verify/index.d.ts +3 -0
- package/build/utils/utils-verify/index.js +3 -0
- package/build/utils/utils-verify/language-spec.js +10 -2
- package/build/utils/utils-verify/locate.d.ts +3 -0
- package/build/utils/utils-verify/locate.js +6 -6
- package/build/utils/utils-verify/parse.d.ts +13 -1
- package/build/utils/utils-verify/parse.js +122 -49
- package/build/utils/utils-verify/retrofit-equivalence.d.ts +0 -5
- package/build/utils/utils-verify/retrofit-equivalence.js +2 -34
- package/build/utils/utils-verify/stage.js +4 -16
- package/build/utils/utils-verify/typecheck.d.ts +25 -4
- package/build/utils/utils-verify/typecheck.js +95 -13
- package/build/utils/utils-verify/verify.d.ts +33 -8
- package/build/utils/utils-verify/verify.js +180 -18
- package/build/utils/versions.d.ts +3 -3
- package/build/utils/versions.js +1 -1
- package/node_modules/playwright/lib/dom-analyzer/blueprint.js +1 -0
- package/node_modules/playwright/lib/dom-analyzer/dynamicId.js +1 -0
- package/node_modules/playwright/lib/dom-analyzer/dynamicId.test.js +6 -0
- package/node_modules/playwright/lib/mcp/skyramp/exportTool.js +4 -2
- package/node_modules/playwright/lib/mcp/skyramp/snapshotLocators.js +246 -0
- package/node_modules/playwright/lib/mcp/skyramp/traceRecordingBackend.js +378 -237
- package/node_modules/playwright/lib/mcp/test/skyRampExport.js +21 -0
- package/node_modules/playwright/node_modules/playwright-core/lib/generated/pollingRecorderSource.js +1 -1
- package/node_modules/playwright/node_modules/playwright-core/lib/vite/traceViewer/assets/{codeMirrorModule-LNgEKtdV.js → codeMirrorModule-Bou8kKzE.js} +1 -1
- package/node_modules/playwright/node_modules/playwright-core/lib/vite/traceViewer/assets/{defaultSettingsView-Bwr1eMKC.js → defaultSettingsView-DTp2-8Si.js} +3 -3
- package/node_modules/playwright/node_modules/playwright-core/lib/vite/traceViewer/index.html +2 -2
- package/node_modules/playwright/node_modules/playwright-core/lib/vite/traceViewer/{index.BAkLd5DX.js → index.uiPTFncY.js} +1 -1
- package/node_modules/playwright/node_modules/playwright-core/lib/vite/traceViewer/{uiMode.BPopbasy.js → uiMode.GLqsx5cI.js} +1 -1
- package/node_modules/playwright/node_modules/playwright-core/lib/vite/traceViewer/uiMode.html +2 -2
- package/node_modules/playwright/node_modules/playwright-core/package.json +1 -1
- package/node_modules/playwright/node_modules/playwright-core/src/generated/pollingRecorderSource.ts +1 -1
- package/node_modules/playwright/package.json +1 -1
- package/package.json +3 -3
- package/plugin/prompts/code-reuse/hand-off.md +13 -1
- package/plugin/prompts/plan-tests.md +2 -2
- package/plugin/skills/enhance-assertions/reference/shared-rules.md +1 -0
|
@@ -3,7 +3,19 @@ import { logger } from "./logger.js";
|
|
|
3
3
|
const PLAYWRIGHT_IMPORT_RE = /import\s*\{([^}]*)\}\s*from\s*['"]@playwright\/test['"]\s*;?[ \t]*\r?\n?/g;
|
|
4
4
|
const SKYRAMP_IMPORT_RE = /import\s*\{([^}]*)\}\s*from\s*['"]@skyramp\/skyramp['"]\s*;?/;
|
|
5
5
|
const EXPECT_USAGE_RE = /\bexpect\s*\(/;
|
|
6
|
-
const
|
|
6
|
+
const NAMED_IMPORT_RE = /import\s*\{([^}]*)\}\s*from\s*['"]([^'"]+)['"]/g;
|
|
7
|
+
/** True when a module other than @playwright/test or @skyramp/skyramp imports
|
|
8
|
+
* `expect` (e.g. a repository's own harness); moving it would break that file. */
|
|
9
|
+
function expectFromOtherModule(source) {
|
|
10
|
+
for (const [, raw, from] of source.matchAll(NAMED_IMPORT_RE)) {
|
|
11
|
+
if (from === "@playwright/test" || from === "@skyramp/skyramp")
|
|
12
|
+
continue;
|
|
13
|
+
const names = splitImports(raw).map((item) => item.split(/\s+as\s+/).pop());
|
|
14
|
+
if (names.includes("expect"))
|
|
15
|
+
return true;
|
|
16
|
+
}
|
|
17
|
+
return false;
|
|
18
|
+
}
|
|
7
19
|
function splitImports(raw) {
|
|
8
20
|
return raw
|
|
9
21
|
.split(",")
|
|
@@ -13,10 +25,16 @@ function splitImports(raw) {
|
|
|
13
25
|
/**
|
|
14
26
|
* Ensure `expect` is imported from `@skyramp/skyramp` rather than `@playwright/test`.
|
|
15
27
|
* - Strips `expect` from any `@playwright/test` import (keeps other identifiers).
|
|
16
|
-
* - If the file uses `expect(...)`, ensures
|
|
28
|
+
* - If the file uses `expect(...)`, ensures its `@skyramp/skyramp` import carries `expect`.
|
|
29
|
+
* A file with no `@skyramp/skyramp` import is not a Skyramp test (a repository's
|
|
30
|
+
* own spec), so it is left alone, as is a file that takes `expect` from another
|
|
31
|
+
* module.
|
|
17
32
|
* Returns the original string if no rewrite was needed.
|
|
18
33
|
*/
|
|
19
34
|
export function normalizeSkyrampExpectImport(source) {
|
|
35
|
+
if (!SKYRAMP_IMPORT_RE.test(source) || expectFromOtherModule(source)) {
|
|
36
|
+
return source;
|
|
37
|
+
}
|
|
20
38
|
let out = source;
|
|
21
39
|
out = out.replace(PLAYWRIGHT_IMPORT_RE, (match, raw) => {
|
|
22
40
|
const items = splitImports(raw);
|
|
@@ -31,22 +49,11 @@ export function normalizeSkyrampExpectImport(source) {
|
|
|
31
49
|
if (!EXPECT_USAGE_RE.test(out))
|
|
32
50
|
return out;
|
|
33
51
|
const skyrampMatch = out.match(SKYRAMP_IMPORT_RE);
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
if (items.includes("expect"))
|
|
37
|
-
return out;
|
|
38
|
-
items.push("expect");
|
|
39
|
-
out = out.replace(SKYRAMP_IMPORT_RE, `import { ${items.join(", ")} } from '@skyramp/skyramp';`);
|
|
52
|
+
const items = splitImports(skyrampMatch[1]);
|
|
53
|
+
if (items.includes("expect"))
|
|
40
54
|
return out;
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
const imports = [...out.matchAll(LAST_IMPORT_RE)];
|
|
44
|
-
const last = imports[imports.length - 1];
|
|
45
|
-
if (last && last.index !== undefined) {
|
|
46
|
-
const insertAt = last.index + last[0].length;
|
|
47
|
-
return out.slice(0, insertAt) + "\n" + newImport + out.slice(insertAt);
|
|
48
|
-
}
|
|
49
|
-
return newImport + "\n" + out;
|
|
55
|
+
items.push("expect");
|
|
56
|
+
return out.replace(SKYRAMP_IMPORT_RE, `import { ${items.join(", ")} } from '@skyramp/skyramp';`);
|
|
50
57
|
}
|
|
51
58
|
/**
|
|
52
59
|
* Normalize the `expect` import in a file on disk. No-op if the file is missing,
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Which reuse path owns a repository module when both reuse settings are on: the
|
|
3
|
+
* page-object path owns a module that declares a class, the in-house helper path
|
|
4
|
+
* (utils-verify/in-house.ts) owns one that does not. ONE predicate, read by both
|
|
5
|
+
* sides, so a module is never claimed by both and never by neither.
|
|
6
|
+
*
|
|
7
|
+
* Decided by shape because the in-house parser cannot read a class — it reports
|
|
8
|
+
* one as unverifiable — while page-object detection also globs free-function
|
|
9
|
+
* helper libraries (`support/`, `testUtils`). Without this split both paths would
|
|
10
|
+
* work on the same free-function module in one run.
|
|
11
|
+
*/
|
|
12
|
+
export declare function declaresClass(content: string): boolean;
|
|
13
|
+
/** The file's working-copy content decides; an unreadable file is not a page
|
|
14
|
+
* object (fails toward the in-house path, whose additive gate reads HEAD). */
|
|
15
|
+
export declare function isPageObjectModule(file: string): Promise<boolean>;
|
|
16
|
+
/** Keep only the candidates the page-object path owns. */
|
|
17
|
+
export declare function pageObjectCandidates<T extends {
|
|
18
|
+
file: string;
|
|
19
|
+
}>(candidates: T[]): Promise<T[]>;
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import { readFile } from "fs/promises";
|
|
2
|
+
import { stripCommentsAndStrings } from "./strip.js";
|
|
3
|
+
/**
|
|
4
|
+
* Which reuse path owns a repository module when both reuse settings are on: the
|
|
5
|
+
* page-object path owns a module that declares a class, the in-house helper path
|
|
6
|
+
* (utils-verify/in-house.ts) owns one that does not. ONE predicate, read by both
|
|
7
|
+
* sides, so a module is never claimed by both and never by neither.
|
|
8
|
+
*
|
|
9
|
+
* Decided by shape because the in-house parser cannot read a class — it reports
|
|
10
|
+
* one as unverifiable — while page-object detection also globs free-function
|
|
11
|
+
* helper libraries (`support/`, `testUtils`). Without this split both paths would
|
|
12
|
+
* work on the same free-function module in one run.
|
|
13
|
+
*/
|
|
14
|
+
export function declaresClass(content) {
|
|
15
|
+
return /(?:^|[\s;}])class\s+[A-Za-z_$][\w$]*/.test(stripCommentsAndStrings(content));
|
|
16
|
+
}
|
|
17
|
+
/** The file's working-copy content decides; an unreadable file is not a page
|
|
18
|
+
* object (fails toward the in-house path, whose additive gate reads HEAD). */
|
|
19
|
+
export async function isPageObjectModule(file) {
|
|
20
|
+
try {
|
|
21
|
+
return declaresClass(await readFile(file, "utf8"));
|
|
22
|
+
}
|
|
23
|
+
catch {
|
|
24
|
+
return false;
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
/** Keep only the candidates the page-object path owns. */
|
|
28
|
+
export async function pageObjectCandidates(candidates) {
|
|
29
|
+
const owned = await Promise.all(candidates.map((c) => isPageObjectModule(c.file)));
|
|
30
|
+
return candidates.filter((_, i) => owned[i]);
|
|
31
|
+
}
|
|
@@ -3,6 +3,14 @@
|
|
|
3
3
|
* targets them.
|
|
4
4
|
*/
|
|
5
5
|
export declare const SPEC_BASENAME: RegExp;
|
|
6
|
+
/**
|
|
7
|
+
* Skyramp's own shared helper module is never a page object, wherever it lives — a
|
|
8
|
+
* customer layout such as `tests/lib/skyrampUtils.ts` matches the test-tree helper
|
|
9
|
+
* glob above. These globs only LIST files, and the whole-repository fallback lists
|
|
10
|
+
* every source file, so the module is not recognised here: the two readers that
|
|
11
|
+
* open candidates anyway — `scorePoms` for discovery, `verifyReuse` for the files a
|
|
12
|
+
* spec binds — drop a file whose head carries the shared-module header.
|
|
13
|
+
*/
|
|
6
14
|
export declare function globPomFiles(root: string): Promise<string[]>;
|
|
7
15
|
/**
|
|
8
16
|
* Spec files anywhere under `root`, by basename rather than by directory.
|
|
@@ -34,6 +34,14 @@ const BUILD_IGNORE = [
|
|
|
34
34
|
/** Test trees a POM is not expected to live in. Not skipped when the tests themselves are the subject. */
|
|
35
35
|
const TEST_TREE_IGNORE = ["**/cypress/**", "**/__tests__/**"];
|
|
36
36
|
const ALL_SOURCE_IGNORE = [...BUILD_IGNORE, ...TEST_TREE_IGNORE];
|
|
37
|
+
/**
|
|
38
|
+
* Skyramp's own shared helper module is never a page object, wherever it lives — a
|
|
39
|
+
* customer layout such as `tests/lib/skyrampUtils.ts` matches the test-tree helper
|
|
40
|
+
* glob above. These globs only LIST files, and the whole-repository fallback lists
|
|
41
|
+
* every source file, so the module is not recognised here: the two readers that
|
|
42
|
+
* open candidates anyway — `scorePoms` for discovery, `verifyReuse` for the files a
|
|
43
|
+
* spec binds — drop a file whose head carries the shared-module header.
|
|
44
|
+
*/
|
|
37
45
|
export async function globPomFiles(root) {
|
|
38
46
|
const matches = await fg(POM_GLOBS, {
|
|
39
47
|
cwd: root,
|
|
@@ -1,4 +1,6 @@
|
|
|
1
1
|
import { readFile } from "fs/promises";
|
|
2
|
+
import { hasUtilsHeaderContent } from "../utils-verify/locate.js";
|
|
3
|
+
import { logger } from "../logger.js";
|
|
2
4
|
export const RARE_DF = 5;
|
|
3
5
|
/**
|
|
4
6
|
* IDF-style scoring: each selector contributes 1/df to every file containing it
|
|
@@ -18,7 +20,17 @@ export async function scorePoms(selectors, files) {
|
|
|
18
20
|
const contents = new Map();
|
|
19
21
|
await Promise.all(files.map(async (f) => {
|
|
20
22
|
try {
|
|
21
|
-
|
|
23
|
+
// The whole file: the scoring loop below counts a selector wherever it
|
|
24
|
+
// appears in the content, so a partial read would change the scores.
|
|
25
|
+
const text = await readFile(f, "utf8");
|
|
26
|
+
// Skyramp's own shared module is never a page-object candidate, wherever the
|
|
27
|
+
// globs found it (see pom-files.ts). Judged on the first lines of the content
|
|
28
|
+
// the scorer holds anyway, so the check adds no read.
|
|
29
|
+
if (hasUtilsHeaderContent(text)) {
|
|
30
|
+
logger.info("Page-object scoring skipped a Skyramp shared module the globs listed", { file: f });
|
|
31
|
+
return;
|
|
32
|
+
}
|
|
33
|
+
contents.set(f, text.replace(/\\/g, ""));
|
|
22
34
|
}
|
|
23
35
|
catch {
|
|
24
36
|
/* unreadable file: skip */
|
|
@@ -1,3 +1,8 @@
|
|
|
1
|
+
/** Half-open `[start, end)` offsets of a region in the source. */
|
|
2
|
+
export interface SourceSpan {
|
|
3
|
+
start: number;
|
|
4
|
+
end: number;
|
|
5
|
+
}
|
|
1
6
|
/** Every line- and block-comment span in `src`, in source order. Exported for callers
|
|
2
7
|
* that must decide whether a `//` OPENS a comment rather than sits inside one: a `//`
|
|
3
8
|
* on its own line inside `/* … *\/` opens nothing, and deleting that line can take the
|
|
@@ -11,6 +16,11 @@ export declare function commentSpans(src: string): {
|
|
|
11
16
|
* (newlines preserved) so remaining code characters keep their original offset. */
|
|
12
17
|
export declare function stripComments(src: string): string;
|
|
13
18
|
/** Blanks string/template literal contents only, preserving comments as-is. */
|
|
19
|
+
/** The string literals in TS/JS source, quotes INCLUDED, in order. The same scan the
|
|
20
|
+
* blanking helpers use — exported so a caller that must treat literal contents
|
|
21
|
+
* differently from code (rather than blank them) has one definition of where a
|
|
22
|
+
* literal starts and ends, not a second scanner of its own. */
|
|
23
|
+
export declare function stringSpans(src: string): SourceSpan[];
|
|
14
24
|
export declare function stripStrings(src: string): string;
|
|
15
25
|
/** Blanks both comments and string/template literals -- for scanners (like call-chain matching)
|
|
16
26
|
* that never need to read quoted text and want prose in either kind fully inert. */
|
|
@@ -317,6 +317,15 @@ export function stripComments(src) {
|
|
|
317
317
|
return blankSpans(src, classifySpans(src).filter((s) => s.kind === "comment"));
|
|
318
318
|
}
|
|
319
319
|
/** Blanks string/template literal contents only, preserving comments as-is. */
|
|
320
|
+
/** The string literals in TS/JS source, quotes INCLUDED, in order. The same scan the
|
|
321
|
+
* blanking helpers use — exported so a caller that must treat literal contents
|
|
322
|
+
* differently from code (rather than blank them) has one definition of where a
|
|
323
|
+
* literal starts and ends, not a second scanner of its own. */
|
|
324
|
+
export function stringSpans(src) {
|
|
325
|
+
return classifySpans(src)
|
|
326
|
+
.filter((s) => s.kind === "string")
|
|
327
|
+
.map(({ start, end }) => ({ start, end }));
|
|
328
|
+
}
|
|
320
329
|
export function stripStrings(src) {
|
|
321
330
|
return blankSpans(src, classifySpans(src).filter((s) => s.kind === "string"));
|
|
322
331
|
}
|
|
@@ -4,7 +4,8 @@ import * as path from "path";
|
|
|
4
4
|
import { promisify } from "util";
|
|
5
5
|
import { findRoot } from "../pom-scope/index.js";
|
|
6
6
|
import { globPomFiles, globAllSourceFiles } from "../pom-scope/pom-files.js";
|
|
7
|
-
import { extractPomBindings } from "./bindings.js";
|
|
7
|
+
import { extractPomBindings, primaryFile } from "./bindings.js";
|
|
8
|
+
import { hasUtilsHeader } from "../utils-verify/locate.js";
|
|
8
9
|
import { extractPomCalls } from "./calls.js";
|
|
9
10
|
import { resolveMemberSource } from "./resolve.js";
|
|
10
11
|
import { stripComments, stripCommentsAndStrings } from "../pom-scope/strip.js";
|
|
@@ -128,12 +129,22 @@ export async function verifyReuse(testFile, language) {
|
|
|
128
129
|
pomFiles = await globAllSourceFiles(root);
|
|
129
130
|
const pageManagerFile = pomFiles.find((f) => PAGE_MANAGER_RE.test(path.basename(f)));
|
|
130
131
|
const aliasMap = await loadAliasMap(root);
|
|
131
|
-
const
|
|
132
|
+
const extracted = await extractPomBindings(spec, {
|
|
132
133
|
specDir: path.dirname(testFile),
|
|
133
134
|
pomFiles,
|
|
134
135
|
pageManagerFile,
|
|
135
136
|
aliasMap,
|
|
136
137
|
});
|
|
138
|
+
// Skyramp's own shared module can sit inside a page-object glob (`tests/lib/`), and
|
|
139
|
+
// a call to one of its helpers must not count as page-object reuse. The header is
|
|
140
|
+
// read on the few files the spec binds, never across the glob: the whole-repository
|
|
141
|
+
// fallback above lists every source file. Dropping the binding is the whole change:
|
|
142
|
+
// `unrecognized` fills only from a `new` of an unresolved class, which a dropped
|
|
143
|
+
// binding never was, so nothing needs a second extraction.
|
|
144
|
+
const bound = [...new Set(extracted.bindings.map(primaryFile))];
|
|
145
|
+
const ours = new Set((await Promise.all(bound.map(async (f) => ((await hasUtilsHeader(f)) ? f : undefined)))).filter((f) => f !== undefined));
|
|
146
|
+
const { unrecognized } = extracted;
|
|
147
|
+
const bindings = extracted.bindings.filter((b) => !ours.has(primaryFile(b)));
|
|
137
148
|
const calls = extractPomCalls(spec, bindings);
|
|
138
149
|
const bindingsByName = new Map(bindings.map((b) => [b.name, b]));
|
|
139
150
|
const violations = [];
|
|
@@ -1,12 +1,40 @@
|
|
|
1
1
|
export declare function isBrowserTestType(testType?: string): boolean;
|
|
2
2
|
export declare function isModularizeFirstTarget(testType?: string, language?: string): boolean;
|
|
3
|
+
/**
|
|
4
|
+
* Which pass one reuse call serves, resolved ONCE per call and handed to every
|
|
5
|
+
* branch — discovery, verify, the prompt and the modularization check — so none
|
|
6
|
+
* of them re-derives it. `pass` is the page-object pass or the SkyrampUtils flow;
|
|
7
|
+
* `composed` says the target takes both (isPomFirstTarget), so the utils flow
|
|
8
|
+
* follows the page-object pass and one verify checks both halves.
|
|
9
|
+
*/
|
|
10
|
+
export interface ReuseRoute {
|
|
11
|
+
pass: "pom" | "utils";
|
|
12
|
+
composed: boolean;
|
|
13
|
+
}
|
|
14
|
+
/** The route for a target. `pomPassFinished` is the one run-state fact it turns on:
|
|
15
|
+
* with both settings on, a finished page-object pass hands the spec to the utils flow. */
|
|
16
|
+
export declare function reuseRouteFor(language: string, framework: string | undefined, testType: string | undefined, pomPassFinished?: boolean): ReuseRoute;
|
|
17
|
+
/**
|
|
18
|
+
* Both reuse settings on, for a test both paths serve: a UI test in TypeScript or
|
|
19
|
+
* JavaScript. Such a test takes the page-object pass first — it owns every
|
|
20
|
+
* page-object class its detection returns — and then the seeded SkyrampUtils flow
|
|
21
|
+
* over what that pass left inline. `isPomAwareTarget` alone still routes the
|
|
22
|
+
* page-object pass; this names the composition, so e2e keeps its single path.
|
|
23
|
+
*
|
|
24
|
+
* No framework parameter: a UI test in TypeScript or JavaScript is a Playwright
|
|
25
|
+
* test — the generator derives that framework for it (deriveEffectiveFramework), and
|
|
26
|
+
* the tools that read this predicate after generation (modularization, the
|
|
27
|
+
* shared-helper verifier, the maintenance step) carry no framework to pass.
|
|
28
|
+
*/
|
|
29
|
+
export declare function isPomFirstTarget(language: string | undefined, testType: string | undefined): boolean;
|
|
3
30
|
export declare function isPomAwareTarget(language: string, framework?: string, testType?: string): boolean;
|
|
4
31
|
/**
|
|
5
32
|
* Which family's inline call-site rule and sibling scan a test gets — the ONE gate
|
|
6
33
|
* for both. `api` for integration, whatever the flags (the rule is stated in API
|
|
7
34
|
* terms and predates the flag); `browser` for the seeded UI flow, where a Skyramp
|
|
8
|
-
* browser module exists to call into
|
|
9
|
-
*
|
|
35
|
+
* browser module exists to call into — with page-object reuse on as well, since the
|
|
36
|
+
* seeded flow runs after the page-object pass; `undefined` everywhere else (`contract`,
|
|
37
|
+
* `load`, e2e, an absent type, UI with the flag off), where an
|
|
10
38
|
* advisory would describe reuse the feature could not have taken. The prompt renders
|
|
11
39
|
* the rule and STEP 5c on it; the verify pass runs the scan on it.
|
|
12
40
|
*/
|
|
@@ -19,30 +19,50 @@ export function isBrowserTestType(testType) {
|
|
|
19
19
|
// maintenance step) read isModularizeFirstHandOffTarget in prompts/reuse-hand-off.ts,
|
|
20
20
|
// which is THIS predicate plus a utils spec for the language: a language the
|
|
21
21
|
// verifier does not cover has no payable reuse half, so no hand-off is owed for
|
|
22
|
-
// it. Integration qualifies whenever the flag is on;
|
|
23
|
-
//
|
|
24
|
-
//
|
|
25
|
-
//
|
|
26
|
-
//
|
|
27
|
-
// Deliberately keyed on the POM *flag*, not on isPomAwareTarget: with the flag
|
|
28
|
-
// on, a UI target the POM path does not cover keeps today's plain reuse pass.
|
|
29
|
-
// Composing utils reuse with the POM path is a separate change; this predicate
|
|
30
|
-
// only decides the flag-off UI flow. The modularization prompts read it too, so
|
|
31
|
-
// extraction and seeding agree for every codeReuse flow; a modularizeCode-only
|
|
32
|
-
// call (no codeReuse) still receives the shared-helper rules — harmless without
|
|
33
|
-
// a reuse pass, and the tool cannot see codeReuse.
|
|
22
|
+
// it. Integration qualifies whenever the flag is on; UI in TypeScript or
|
|
23
|
+
// JavaScript too, whatever the page-object flag says. With page-object reuse
|
|
24
|
+
// also on, the page-object pass runs FIRST and the seeded flow takes what it
|
|
25
|
+
// left inline (see isPomFirstTarget). e2e and load are not routed here: load has
|
|
26
|
+
// no modularization routing at all, e2e is deferred.
|
|
34
27
|
export function isModularizeFirstTarget(testType, language) {
|
|
35
28
|
if (!isUtilsReuseEnabled())
|
|
36
29
|
return false;
|
|
37
30
|
if (testType === "integration")
|
|
38
31
|
return true;
|
|
39
|
-
if (testType !== "ui"
|
|
32
|
+
if (testType !== "ui")
|
|
40
33
|
return false;
|
|
41
34
|
// Shared browser helpers are Playwright TS/JS; other UI languages have no
|
|
42
35
|
// utils-file convention here and would fall through to a Python file name.
|
|
43
36
|
const lang = language?.toLowerCase();
|
|
44
37
|
return lang === "typescript" || lang === "javascript";
|
|
45
38
|
}
|
|
39
|
+
/** The route for a target. `pomPassFinished` is the one run-state fact it turns on:
|
|
40
|
+
* with both settings on, a finished page-object pass hands the spec to the utils flow. */
|
|
41
|
+
export function reuseRouteFor(language, framework, testType, pomPassFinished = false) {
|
|
42
|
+
const composed = isPomFirstTarget(language, testType);
|
|
43
|
+
const pomAware = isPomAwareTarget(language, framework, testType);
|
|
44
|
+
return {
|
|
45
|
+
pass: pomAware && !(composed && pomPassFinished) ? "pom" : "utils",
|
|
46
|
+
composed,
|
|
47
|
+
};
|
|
48
|
+
}
|
|
49
|
+
/**
|
|
50
|
+
* Both reuse settings on, for a test both paths serve: a UI test in TypeScript or
|
|
51
|
+
* JavaScript. Such a test takes the page-object pass first — it owns every
|
|
52
|
+
* page-object class its detection returns — and then the seeded SkyrampUtils flow
|
|
53
|
+
* over what that pass left inline. `isPomAwareTarget` alone still routes the
|
|
54
|
+
* page-object pass; this names the composition, so e2e keeps its single path.
|
|
55
|
+
*
|
|
56
|
+
* No framework parameter: a UI test in TypeScript or JavaScript is a Playwright
|
|
57
|
+
* test — the generator derives that framework for it (deriveEffectiveFramework), and
|
|
58
|
+
* the tools that read this predicate after generation (modularization, the
|
|
59
|
+
* shared-helper verifier, the maintenance step) carry no framework to pass.
|
|
60
|
+
*/
|
|
61
|
+
export function isPomFirstTarget(language, testType) {
|
|
62
|
+
return (isPomReuseEnabled() &&
|
|
63
|
+
testType === "ui" &&
|
|
64
|
+
isModularizeFirstTarget(testType, language));
|
|
65
|
+
}
|
|
46
66
|
export function isPomAwareTarget(language, framework, testType) {
|
|
47
67
|
if (!isPomReuseEnabled())
|
|
48
68
|
return false;
|
|
@@ -56,8 +76,9 @@ export function isPomAwareTarget(language, framework, testType) {
|
|
|
56
76
|
* Which family's inline call-site rule and sibling scan a test gets — the ONE gate
|
|
57
77
|
* for both. `api` for integration, whatever the flags (the rule is stated in API
|
|
58
78
|
* terms and predates the flag); `browser` for the seeded UI flow, where a Skyramp
|
|
59
|
-
* browser module exists to call into
|
|
60
|
-
*
|
|
79
|
+
* browser module exists to call into — with page-object reuse on as well, since the
|
|
80
|
+
* seeded flow runs after the page-object pass; `undefined` everywhere else (`contract`,
|
|
81
|
+
* `load`, e2e, an absent type, UI with the flag off), where an
|
|
61
82
|
* advisory would describe reuse the feature could not have taken. The prompt renders
|
|
62
83
|
* the rule and STEP 5c on it; the verify pass runs the scan on it.
|
|
63
84
|
*/
|
|
@@ -6,6 +6,7 @@ import { promisify } from "util";
|
|
|
6
6
|
import fg from "fast-glob";
|
|
7
7
|
import { canonicalJson as canonical, isPlainObject as isObject, } from "./canonicalJson.js";
|
|
8
8
|
import { listChangedFiles } from "./reportVerification.js";
|
|
9
|
+
import { contentAtHead } from "./utils-verify/head.js";
|
|
9
10
|
import { SKYRAMP_SDK_VERSION } from "./skyrampSdkVersion.js";
|
|
10
11
|
const execFileAsync = promisify(execFile);
|
|
11
12
|
export const NPM_TEST_PACKAGES = new Set([
|
|
@@ -96,7 +97,7 @@ export async function verifyTestDependencyChanges(repositoryPath) {
|
|
|
96
97
|
}
|
|
97
98
|
continue;
|
|
98
99
|
}
|
|
99
|
-
const committedBaseline = await
|
|
100
|
+
const committedBaseline = await contentAtHead(path.join(repositoryPath, file), repositoryPath);
|
|
100
101
|
const baseline = committedBaseline ??
|
|
101
102
|
(await baselineForNewManifest(repositoryPath, file, name, violations));
|
|
102
103
|
if (baseline === undefined)
|
|
@@ -203,19 +204,6 @@ function verifyJavaManifest(file, name, baseline, current, violations) {
|
|
|
203
204
|
return verifyMaven(file, baseline, current, violations);
|
|
204
205
|
return verifyGradle(file, baseline, current, violations);
|
|
205
206
|
}
|
|
206
|
-
async function baselineFile(repositoryPath, file) {
|
|
207
|
-
try {
|
|
208
|
-
const { stdout } = await execFileAsync("git", ["show", `HEAD:${file}`], {
|
|
209
|
-
cwd: repositoryPath,
|
|
210
|
-
encoding: "utf8",
|
|
211
|
-
maxBuffer: 10 * 1024 * 1024,
|
|
212
|
-
});
|
|
213
|
-
return stdout;
|
|
214
|
-
}
|
|
215
|
-
catch {
|
|
216
|
-
return undefined;
|
|
217
|
-
}
|
|
218
|
-
}
|
|
219
207
|
async function baselineFileExists(repositoryPath, file) {
|
|
220
208
|
try {
|
|
221
209
|
await execFileAsync("git", ["cat-file", "-e", `HEAD:${file}`], {
|
|
@@ -540,7 +528,7 @@ async function correspondingManifests(repositoryPath, file, lockName) {
|
|
|
540
528
|
const rootManifest = at("package.json");
|
|
541
529
|
// Membership comes from the committed root manifest, not the edited lockfile.
|
|
542
530
|
// A workspace addition is an unrelated manifest edit and remains disallowed.
|
|
543
|
-
const baseline = await
|
|
531
|
+
const baseline = await contentAtHead(path.join(repositoryPath, rootManifest), repositoryPath);
|
|
544
532
|
if (baseline === undefined)
|
|
545
533
|
return [rootManifest];
|
|
546
534
|
const manifest = JSON.parse(baseline);
|
|
@@ -575,7 +563,7 @@ async function correspondingManifests(repositoryPath, file, lockName) {
|
|
|
575
563
|
return [];
|
|
576
564
|
}
|
|
577
565
|
async function verifyLockfile(repositoryPath, file, name, pairedManifests, validManifests, violations) {
|
|
578
|
-
const committedBaseline = await
|
|
566
|
+
const committedBaseline = await contentAtHead(path.join(repositoryPath, file), repositoryPath);
|
|
579
567
|
const baseline = committedBaseline ?? '{"packages":{}}\n';
|
|
580
568
|
let current;
|
|
581
569
|
try {
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { type UtilsHelper } from "./parse.js";
|
|
2
2
|
import { type UtilsLanguageSpec } from "./language-spec.js";
|
|
3
|
-
import { type InlineCallSite, type
|
|
3
|
+
import { type InlineCallSite, type SiblingScan } from "./call-sites.js";
|
|
4
4
|
/**
|
|
5
5
|
* The browser family's sibling scan: a contiguous run of actions in OTHER
|
|
6
6
|
* Skyramp-generated tests beside `testFile` whose action sequence equals a utils
|
|
@@ -29,4 +29,4 @@ import { type InlineCallSite, type SiblingFile } from "./call-sites.js";
|
|
|
29
29
|
* consumed and the scan continues after it. A run inside the sibling's own local
|
|
30
30
|
* helper is reported with that helper's name, as the API scan does.
|
|
31
31
|
*/
|
|
32
|
-
export declare function findSiblingInlineActionSites(testFile: string, helpersByFile: ReadonlyMap<string, UtilsHelper[]>, spec: UtilsLanguageSpec, preReadSiblings:
|
|
32
|
+
export declare function findSiblingInlineActionSites(testFile: string, helpersByFile: ReadonlyMap<string, UtilsHelper[]>, spec: UtilsLanguageSpec, preReadSiblings: SiblingScan | undefined): Promise<InlineCallSite[]>;
|
|
@@ -74,7 +74,7 @@ export type UtilsViolationKind = "duplicate-helper" | "body-assertion" | "scenar
|
|
|
74
74
|
/** Kinds that only ever advise. Kept OUT of UtilsViolationKind so that type keeps
|
|
75
75
|
* meaning "a kind the allow grammar and the gate know about" — an advisory can
|
|
76
76
|
* never fail a verify and never needs (or accepts) an allow marker. */
|
|
77
|
-
export declare const UTILS_ADVISORY_KINDS: readonly ["unusable-parameter-type", "fixed-sleep", "module-name", "duplicate-body-reach", "duplicate-action", "tsconfig-type-error", "ambient-type-error", "in-house-duplicate"];
|
|
77
|
+
export declare const UTILS_ADVISORY_KINDS: readonly ["unusable-parameter-type", "fixed-sleep", "module-name", "duplicate-body-reach", "duplicate-action", "tsconfig-type-error", "ambient-type-error", "inherited-type-error", "in-house-duplicate"];
|
|
78
78
|
export type UtilsAdvisoryKind = (typeof UTILS_ADVISORY_KINDS)[number];
|
|
79
79
|
export declare const MARKERABLE_UTILS_KINDS: readonly ["frozen-body", "duplicate-helper", "body-assertion", "scenario-name", "assertion-loss", "status-default", "status-restated"];
|
|
80
80
|
/** A kind the allow grammar accepts. `guard-removed` is a gate kind but never
|
|
@@ -50,6 +50,21 @@ export const UTILS_ADVISORY_KINDS = [
|
|
|
50
50
|
* never blocked: a block here made an agent rewrite `process.env` into a
|
|
51
51
|
* `globalThis` cast to get past the gate. */
|
|
52
52
|
"ambient-type-error",
|
|
53
|
+
/** A compiler error this run INHERITED: the line is unchanged since HEAD AND
|
|
54
|
+
* the file at HEAD already failed the compiler with the same code on that
|
|
55
|
+
* line. Whatever the error is, the customer or an earlier run wrote it, it is
|
|
56
|
+
* not the shared module's fault, and it must never be the reason a valid
|
|
57
|
+
* module is deleted: a run once destroyed its module over four assertion lines
|
|
58
|
+
* that pre-dated the run. Advised, never blocked.
|
|
59
|
+
*
|
|
60
|
+
* Attribution is by CAUSE, not by position: an error the run caused on a line
|
|
61
|
+
* it left alone — a helper rewritten to return nothing under an unchanged line
|
|
62
|
+
* that reads its result — was not there at HEAD and blocks. A file HEAD does
|
|
63
|
+
* not hold (a module or a spec this run generated) has nothing to inherit
|
|
64
|
+
* from, and every error in it blocks, the generator's included: a line any
|
|
65
|
+
* pass of this run wrote is the run's to fix. See inheritedTypeError in
|
|
66
|
+
* changed-lines.ts. */
|
|
67
|
+
"inherited-type-error",
|
|
53
68
|
/** A helper this run added to one of the repository's own modules performs an
|
|
54
69
|
* action sequence a helper already in that module performs — the duplicate the
|
|
55
70
|
* extension exists to avoid, now inside the customer's file. Advisory for the
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { UtilsLanguageSpec } from "./language-spec.js";
|
|
2
|
-
import { type
|
|
2
|
+
import { type SiblingScan } from "./call-sites.js";
|
|
3
3
|
/**
|
|
4
4
|
* A shared helper whose request body no parameter of its signature reaches, beside a
|
|
5
5
|
* spec that defines its OWN helper for the same route.
|
|
@@ -117,4 +117,4 @@ export declare function findDuplicateBodyReach(utilsFiles: string[], spec: Utils
|
|
|
117
117
|
* Shared helpers whose body no parameter reaches, each paired with a sibling spec
|
|
118
118
|
* that defines its own helper for the same route. Never throws; fails open to [].
|
|
119
119
|
*/
|
|
120
|
-
export declare function findUnreachableBodies(testFile: string, utilsFiles: string[], spec: UtilsLanguageSpec, preReadSiblings?:
|
|
120
|
+
export declare function findUnreachableBodies(testFile: string, utilsFiles: string[], spec: UtilsLanguageSpec, preReadSiblings?: SiblingScan): Promise<UnreachableBody[]>;
|
|
@@ -319,7 +319,10 @@ export async function findUnreachableBodies(testFile, utilsFiles, spec, preReadS
|
|
|
319
319
|
const siblings = preReadSiblings ??
|
|
320
320
|
(await generatedSiblings(testFile, spec, { includeSelf: true }));
|
|
321
321
|
const out = [];
|
|
322
|
-
|
|
322
|
+
// `.files`: the walk reports what it read alongside whether it read the whole
|
|
323
|
+
// directory. This scan acts only on siblings it HAS, so an incomplete walk costs it
|
|
324
|
+
// a finding and never invents one.
|
|
325
|
+
for (const { file, content } of siblings.files) {
|
|
323
326
|
for (const h of localHelpersOf(content, spec)) {
|
|
324
327
|
if (!h.method || !h.normalizedPath)
|
|
325
328
|
continue;
|
|
@@ -10,16 +10,26 @@ export interface SiblingFile {
|
|
|
10
10
|
file: string;
|
|
11
11
|
content: string;
|
|
12
12
|
}
|
|
13
|
+
/** The sibling walk's result. `complete` is the walk's own verdict on whether it saw
|
|
14
|
+
* the whole directory: the listing succeeded AND every candidate file was read. An
|
|
15
|
+
* incomplete walk still returns what it found — the findings stand — but a caller
|
|
16
|
+
* that blocks or asserts on the ABSENCE of a sibling must not treat the list as the
|
|
17
|
+
* full set. A silently short list is indistinguishable from an honest one. */
|
|
18
|
+
export interface SiblingScan {
|
|
19
|
+
files: SiblingFile[];
|
|
20
|
+
complete: boolean;
|
|
21
|
+
}
|
|
13
22
|
/**
|
|
14
23
|
* The Skyramp-generated tests in the spec's directory, each read once — THE sibling
|
|
15
24
|
* walk, shared by the two sibling call-site scans and the importer count (the inline
|
|
16
25
|
* scan excludes the spec itself; the other two include it). Utils files carry no
|
|
17
|
-
* codegen version marker, so the marker check alone excludes them. Fails open to
|
|
18
|
-
* on
|
|
26
|
+
* codegen version marker, so the marker check alone excludes them. Fails open to an
|
|
27
|
+
* empty, INCOMPLETE scan on a listing failure, and drops an unreadable file from a
|
|
28
|
+
* scan it then reports as incomplete.
|
|
19
29
|
*/
|
|
20
30
|
export declare function generatedSiblings(testFile: string, spec: UtilsLanguageSpec, opts: {
|
|
21
31
|
includeSelf: boolean;
|
|
22
|
-
}): Promise<
|
|
32
|
+
}): Promise<SiblingScan>;
|
|
23
33
|
/** An SDK request call. Global: clone with `new RegExp(...)` before `matchAll`. */
|
|
24
34
|
export declare const REQUEST_CALL_RE: RegExp;
|
|
25
35
|
interface InlineSiteBase {
|
|
@@ -70,7 +80,7 @@ export type InlineCallSite = (InlineSiteBase & {
|
|
|
70
80
|
* findSiblingInlineActionSites is the browser family's, and inlineCallSiteFamily
|
|
71
81
|
* decides which runs.
|
|
72
82
|
*/
|
|
73
|
-
export declare function findSiblingInlineCallSites(testFile: string, utilsFiles: string[], _helpers: UtilsHelper[], spec: UtilsLanguageSpec, preReadSiblings?:
|
|
83
|
+
export declare function findSiblingInlineCallSites(testFile: string, utilsFiles: string[], _helpers: UtilsHelper[], spec: UtilsLanguageSpec, preReadSiblings?: SiblingScan): Promise<InlineCallSite[]>;
|
|
74
84
|
/** One operation written in two or more sibling generated specs that no shared
|
|
75
85
|
* helper wraps — the extraction the reuse pass exists to perform and did not. */
|
|
76
86
|
export interface UnextractedDuplicate {
|
|
@@ -108,11 +118,11 @@ export interface UnextractedSite {
|
|
|
108
118
|
* A group must span two or more FILES: a spec repeating one request twice is a
|
|
109
119
|
* different question. Advisory by design — consolidation is a judgement. Never throws.
|
|
110
120
|
*/
|
|
111
|
-
export declare function findUnextractedDuplicates(testFile: string, utilsFiles: string[], spec: UtilsLanguageSpec, preReadSiblings?:
|
|
121
|
+
export declare function findUnextractedDuplicates(testFile: string, utilsFiles: string[], spec: UtilsLanguageSpec, preReadSiblings?: SiblingScan): Promise<UnextractedDuplicate[]>;
|
|
112
122
|
/** The generated siblings less the utils files themselves (a utils file carries no
|
|
113
123
|
* codegen marker, so this is belt and braces). Pre-read siblings are used as given —
|
|
114
124
|
* the caller's walk decides whether the spec itself is among them. */
|
|
115
|
-
export declare function nonUtilsSiblings(testFile: string, utilsFiles: string[], spec: UtilsLanguageSpec, preReadSiblings:
|
|
125
|
+
export declare function nonUtilsSiblings(testFile: string, utilsFiles: string[], spec: UtilsLanguageSpec, preReadSiblings: SiblingScan | undefined, includeSelf: boolean): Promise<SiblingFile[]>;
|
|
116
126
|
/** Lines after a call scanned for its schema assertion — the generated shape puts it
|
|
117
127
|
* immediately after the status-code assertion. */
|
|
118
128
|
export declare const SCHEMA_SCAN_LINES = 12;
|
|
@@ -14,8 +14,9 @@ const HEADER_SCAN_LINES = 5;
|
|
|
14
14
|
* The Skyramp-generated tests in the spec's directory, each read once — THE sibling
|
|
15
15
|
* walk, shared by the two sibling call-site scans and the importer count (the inline
|
|
16
16
|
* scan excludes the spec itself; the other two include it). Utils files carry no
|
|
17
|
-
* codegen version marker, so the marker check alone excludes them. Fails open to
|
|
18
|
-
* on
|
|
17
|
+
* codegen version marker, so the marker check alone excludes them. Fails open to an
|
|
18
|
+
* empty, INCOMPLETE scan on a listing failure, and drops an unreadable file from a
|
|
19
|
+
* scan it then reports as incomplete.
|
|
19
20
|
*/
|
|
20
21
|
export async function generatedSiblings(testFile, spec, opts) {
|
|
21
22
|
let dir;
|
|
@@ -27,8 +28,9 @@ export async function generatedSiblings(testFile, spec, opts) {
|
|
|
27
28
|
entries = await fs.promises.readdir(dir);
|
|
28
29
|
}
|
|
29
30
|
catch {
|
|
30
|
-
return [];
|
|
31
|
+
return { files: [], complete: false };
|
|
31
32
|
}
|
|
33
|
+
let complete = true;
|
|
32
34
|
const out = [];
|
|
33
35
|
for (const name of entries.sort()) {
|
|
34
36
|
const file = path.join(dir, name);
|
|
@@ -41,13 +43,16 @@ export async function generatedSiblings(testFile, spec, opts) {
|
|
|
41
43
|
content = await fs.promises.readFile(file, "utf8");
|
|
42
44
|
}
|
|
43
45
|
catch {
|
|
46
|
+
// A candidate this walk could not read may or may not have been a generated
|
|
47
|
+
// test importing the module — so the scan is short by an unknown amount.
|
|
48
|
+
complete = false;
|
|
44
49
|
continue;
|
|
45
50
|
}
|
|
46
51
|
if (!CODEGEN_MARKER_RE.test(content.split("\n").slice(0, HEADER_SCAN_LINES).join("\n")))
|
|
47
52
|
continue;
|
|
48
53
|
out.push({ file, content });
|
|
49
54
|
}
|
|
50
|
-
return out;
|
|
55
|
+
return { files: out, complete };
|
|
51
56
|
}
|
|
52
57
|
/** An SDK request call. Global: clone with `new RegExp(...)` before `matchAll`. */
|
|
53
58
|
export const REQUEST_CALL_RE = /\b(?:send_request|sendRequest)\s*\(/g;
|
|
@@ -183,8 +188,9 @@ async function helperRoutes(utilsFiles, spec) {
|
|
|
183
188
|
* the caller's walk decides whether the spec itself is among them. */
|
|
184
189
|
export async function nonUtilsSiblings(testFile, utilsFiles, spec, preReadSiblings, includeSelf) {
|
|
185
190
|
const utilsSet = new Set(await Promise.all(utilsFiles.map((f) => realpath(f))));
|
|
186
|
-
|
|
187
|
-
(await generatedSiblings(testFile, spec, { includeSelf }))
|
|
191
|
+
const scan = preReadSiblings ??
|
|
192
|
+
(await generatedSiblings(testFile, spec, { includeSelf }));
|
|
193
|
+
return scan.files.filter((sib) => !utilsSet.has(path.resolve(sib.file)));
|
|
188
194
|
}
|
|
189
195
|
/** Every keyable request call in a generated test. Calls whose method or path cannot
|
|
190
196
|
* be read are skipped: they have no route to compare. Calls are located, and their
|