@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
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import type { UtilsLanguageSpec } from "./language-spec.js";
|
|
2
|
+
import type { HelperChangeKind } from "../../types/ReuseOutcome.js";
|
|
3
|
+
export interface ChangedHelper {
|
|
4
|
+
helper: string;
|
|
5
|
+
/** 1-based line of the definition as the module stands now; `0` for a removed
|
|
6
|
+
* helper, which has no line in the current file. */
|
|
7
|
+
line: number;
|
|
8
|
+
change: HelperChangeKind;
|
|
9
|
+
}
|
|
10
|
+
/**
|
|
11
|
+
* The helpers a run CHANGED, by comparing the module as it stands against the same
|
|
12
|
+
* module at the incumbent revision.
|
|
13
|
+
*
|
|
14
|
+
* Both sides are parsed with the same reader the verifier uses, so each body is taken
|
|
15
|
+
* to its structural terminator on a comments-and-strings-blanked copy — a brace typed
|
|
16
|
+
* into a selector or a `#` inside a Python string cannot move a helper's end and
|
|
17
|
+
* swallow the helpers after it.
|
|
18
|
+
*
|
|
19
|
+
* Comparison is deliberately INSENSITIVE to formatting and SENSITIVE to literals.
|
|
20
|
+
* Comments are blanked and whitespace OUTSIDE string literals is removed, so a
|
|
21
|
+
* Prettier or Black rewrap — which also inserts a trailing comma where it wraps —
|
|
22
|
+
* reads as no change; the comma before a closing bracket is dropped for the same
|
|
23
|
+
* reason.
|
|
24
|
+
*
|
|
25
|
+
* A string literal is copied through byte for byte — its own whitespace AND its own
|
|
26
|
+
* punctuation. Every normalising rule runs on the non-literal runs only.
|
|
27
|
+
* Whitespace inside a literal is not formatting, it is the value: `.menu .item`
|
|
28
|
+
* matches a descendant and `.menu.item` matches one element carrying both classes,
|
|
29
|
+
* and a filled `"Acme Corp"` is not `"AcmeCorp"`. A normaliser that strips whitespace
|
|
30
|
+
* everywhere reads all three pairs as unchanged, which silently disables this check
|
|
31
|
+
* on the browser selector case it exists for.
|
|
32
|
+
*
|
|
33
|
+
* The residual error is one-directional and chosen: two genuinely different bodies
|
|
34
|
+
* can normalise equal (Python's indentation carries meaning outside literals, and
|
|
35
|
+
* this drops it), which costs a report line. Nothing here can report a helper as
|
|
36
|
+
* changed that is not, which is the error that would cost a false block.
|
|
37
|
+
*/
|
|
38
|
+
export declare function changedHelpersBetween(headContent: string, currentContent: string, spec: UtilsLanguageSpec): ChangedHelper[];
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
import { blankComments, parseUtilsFile, stringSpansOf, } from "./parse.js";
|
|
2
|
+
/**
|
|
3
|
+
* The helpers a run CHANGED, by comparing the module as it stands against the same
|
|
4
|
+
* module at the incumbent revision.
|
|
5
|
+
*
|
|
6
|
+
* Both sides are parsed with the same reader the verifier uses, so each body is taken
|
|
7
|
+
* to its structural terminator on a comments-and-strings-blanked copy — a brace typed
|
|
8
|
+
* into a selector or a `#` inside a Python string cannot move a helper's end and
|
|
9
|
+
* swallow the helpers after it.
|
|
10
|
+
*
|
|
11
|
+
* Comparison is deliberately INSENSITIVE to formatting and SENSITIVE to literals.
|
|
12
|
+
* Comments are blanked and whitespace OUTSIDE string literals is removed, so a
|
|
13
|
+
* Prettier or Black rewrap — which also inserts a trailing comma where it wraps —
|
|
14
|
+
* reads as no change; the comma before a closing bracket is dropped for the same
|
|
15
|
+
* reason.
|
|
16
|
+
*
|
|
17
|
+
* A string literal is copied through byte for byte — its own whitespace AND its own
|
|
18
|
+
* punctuation. Every normalising rule runs on the non-literal runs only.
|
|
19
|
+
* Whitespace inside a literal is not formatting, it is the value: `.menu .item`
|
|
20
|
+
* matches a descendant and `.menu.item` matches one element carrying both classes,
|
|
21
|
+
* and a filled `"Acme Corp"` is not `"AcmeCorp"`. A normaliser that strips whitespace
|
|
22
|
+
* everywhere reads all three pairs as unchanged, which silently disables this check
|
|
23
|
+
* on the browser selector case it exists for.
|
|
24
|
+
*
|
|
25
|
+
* The residual error is one-directional and chosen: two genuinely different bodies
|
|
26
|
+
* can normalise equal (Python's indentation carries meaning outside literals, and
|
|
27
|
+
* this drops it), which costs a report line. Nothing here can report a helper as
|
|
28
|
+
* changed that is not, which is the error that would cost a false block.
|
|
29
|
+
*/
|
|
30
|
+
export function changedHelpersBetween(headContent, currentContent, spec) {
|
|
31
|
+
const head = byName(parseUtilsFile(headContent, spec), headContent, spec);
|
|
32
|
+
const now = byName(parseUtilsFile(currentContent, spec), currentContent, spec);
|
|
33
|
+
const out = [];
|
|
34
|
+
for (const [name, was] of head) {
|
|
35
|
+
const is = now.get(name);
|
|
36
|
+
if (!is) {
|
|
37
|
+
out.push({ helper: name, line: 0, change: "removed" });
|
|
38
|
+
continue;
|
|
39
|
+
}
|
|
40
|
+
if (was.params !== is.params)
|
|
41
|
+
out.push({ helper: name, line: is.line, change: "signature" });
|
|
42
|
+
else if (was.body !== is.body)
|
|
43
|
+
out.push({ helper: name, line: is.line, change: "body" });
|
|
44
|
+
}
|
|
45
|
+
return out;
|
|
46
|
+
}
|
|
47
|
+
function byName(helpers, content, spec) {
|
|
48
|
+
const norm = (text) => normalize(text, spec);
|
|
49
|
+
// Comments blanked, strings kept, offsets preserved: a reworded comment is not a
|
|
50
|
+
// change to what a caller runs, and a selector literal is.
|
|
51
|
+
const lines = blankComments(content, spec).split("\n");
|
|
52
|
+
const out = new Map();
|
|
53
|
+
for (const h of helpers) {
|
|
54
|
+
// A module that defines one name twice is already a blocking `helper-redefined`
|
|
55
|
+
// finding; keep the first definition rather than letting the second decide.
|
|
56
|
+
if (out.has(h.name))
|
|
57
|
+
continue;
|
|
58
|
+
out.set(h.name, {
|
|
59
|
+
line: h.line,
|
|
60
|
+
params: (h.params ?? []).map(norm).join(","),
|
|
61
|
+
// From startLine, not line: a decorator is part of what the helper does.
|
|
62
|
+
body: norm(lines.slice(h.startLine - 1, h.endLine).join("\n")),
|
|
63
|
+
});
|
|
64
|
+
}
|
|
65
|
+
return out;
|
|
66
|
+
}
|
|
67
|
+
/**
|
|
68
|
+
* Whitespace removed OUTSIDE string literals — the one textual difference a
|
|
69
|
+
* formatter introduces that this treats as no change. Literals are copied through
|
|
70
|
+
* unchanged: their whitespace is the value, not formatting.
|
|
71
|
+
*
|
|
72
|
+
* A formatter's trailing comma is NOT normalised away, deliberately. Four versions
|
|
73
|
+
* of a comma rule were tried and each produced the next defect (`("x",)` is a tuple
|
|
74
|
+
* and `("x")` a string; `[1,,]` has length 2), and what the rule bought was one
|
|
75
|
+
* fewer execution round on a rewrap. Under this feature's own trade — an unneeded
|
|
76
|
+
* run of a caller that is runnable by construction, over a missed change — that is
|
|
77
|
+
* the cheaper side. A rewrapped helper reads as changed and its callers run once.
|
|
78
|
+
*/
|
|
79
|
+
function normalize(text, spec) {
|
|
80
|
+
const spans = stringSpansOf(text, spec);
|
|
81
|
+
let out = "";
|
|
82
|
+
let at = 0;
|
|
83
|
+
for (const { start, end } of spans) {
|
|
84
|
+
if (start < at)
|
|
85
|
+
continue; // nested or overlapping: the outer span already carried it
|
|
86
|
+
out += text.slice(at, start).replace(/\s+/g, "");
|
|
87
|
+
out += text.slice(start, end);
|
|
88
|
+
at = end;
|
|
89
|
+
}
|
|
90
|
+
return out + text.slice(at).replace(/\s+/g, "");
|
|
91
|
+
}
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The lines of `after` that `before` carries unchanged, as a map from the line's
|
|
3
|
+
* number in `after` to its number in `before` (both 1-based), by a
|
|
4
|
+
* longest-common-subsequence alignment of the two files' lines. A line that only
|
|
5
|
+
* SHIFTED because lines were inserted or removed around it is unchanged; a line
|
|
6
|
+
* that was rewritten, or written new, is absent. Two lines that swapped places
|
|
7
|
+
* read as one unchanged and one changed — the alignment is a subsequence, not a
|
|
8
|
+
* reordering.
|
|
9
|
+
*
|
|
10
|
+
* Lines are broken where the compiler breaks them (see `split`), so a CRLF
|
|
11
|
+
* checkout does not read as a whole-file rewrite and a line number here is the
|
|
12
|
+
* one a diagnostic carries.
|
|
13
|
+
*
|
|
14
|
+
* The alignment table is quadratic in the two line counts, which is nothing for a
|
|
15
|
+
* spec of a few hundred lines. Past a fixed cell budget no alignment is made and
|
|
16
|
+
* the map is EMPTY: every line then reads as this pass's, which is the closed
|
|
17
|
+
* direction — the gate that reads this map excuses an error only on a line it can
|
|
18
|
+
* map, so a file too large to align is judged as before this map existed.
|
|
19
|
+
*/
|
|
20
|
+
export declare function alignLines(before: string, after: string): Map<number, number>;
|
|
21
|
+
/** One compiler error the baseline had: the file as delivered, the line in the
|
|
22
|
+
* BASELINE's numbering, the TS code and the flattened message. Code alone let
|
|
23
|
+
* a NEW error hide behind an old one of the same code — `label` changed from
|
|
24
|
+
* returning a string to a boolean is a second TS2322 on the same line. */
|
|
25
|
+
export declare const baselineErrorKey: (file: string, line: number, code: number, message: string) => string;
|
|
26
|
+
/** The baseline's errors as a MULTISET: each excuses at most one current error.
|
|
27
|
+
* A line that had one error at HEAD and has two now carries one the run caused. */
|
|
28
|
+
export declare class BaselineErrors {
|
|
29
|
+
private readonly left;
|
|
30
|
+
add(key: string): void;
|
|
31
|
+
/** Consume one occurrence; false when none is left. */
|
|
32
|
+
take(key: string): boolean;
|
|
33
|
+
}
|
|
34
|
+
/**
|
|
35
|
+
* Why a compiler error at `file:line` is not this run's, or `undefined` when it
|
|
36
|
+
* is. A block is a demand to fix the line named, and a demand the run cannot
|
|
37
|
+
* meet ends in the module being deleted — so the compiler blocks ONLY on an
|
|
38
|
+
* error this run caused, and an error is inherited only by CAUSE: the line is
|
|
39
|
+
* unchanged since HEAD AND the file at HEAD already failed the compiler with the
|
|
40
|
+
* same code and message on that line, once per occurrence. Position alone is
|
|
41
|
+
* not enough — extraction breaks lines it leaves alone (a helper that no longer
|
|
42
|
+
* returns what the next line reads), and those errors are the run's.
|
|
43
|
+
*
|
|
44
|
+
* `unchangedOf` gives each file's unchanged lines mapped to HEAD's numbering (see
|
|
45
|
+
* alignLines), or `undefined` for a file HEAD does not hold: every error in such
|
|
46
|
+
* a file is this run's.
|
|
47
|
+
*/
|
|
48
|
+
export declare function inheritedTypeError(unchangedOf: (file: string) => ReadonlyMap<number, number> | undefined, baselineErrors: BaselineErrors): (file: string, line: number, code: number, message: string) => string | undefined;
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The lines of `after` that `before` carries unchanged, as a map from the line's
|
|
3
|
+
* number in `after` to its number in `before` (both 1-based), by a
|
|
4
|
+
* longest-common-subsequence alignment of the two files' lines. A line that only
|
|
5
|
+
* SHIFTED because lines were inserted or removed around it is unchanged; a line
|
|
6
|
+
* that was rewritten, or written new, is absent. Two lines that swapped places
|
|
7
|
+
* read as one unchanged and one changed — the alignment is a subsequence, not a
|
|
8
|
+
* reordering.
|
|
9
|
+
*
|
|
10
|
+
* Lines are broken where the compiler breaks them (see `split`), so a CRLF
|
|
11
|
+
* checkout does not read as a whole-file rewrite and a line number here is the
|
|
12
|
+
* one a diagnostic carries.
|
|
13
|
+
*
|
|
14
|
+
* The alignment table is quadratic in the two line counts, which is nothing for a
|
|
15
|
+
* spec of a few hundred lines. Past a fixed cell budget no alignment is made and
|
|
16
|
+
* the map is EMPTY: every line then reads as this pass's, which is the closed
|
|
17
|
+
* direction — the gate that reads this map excuses an error only on a line it can
|
|
18
|
+
* map, so a file too large to align is judged as before this map existed.
|
|
19
|
+
*/
|
|
20
|
+
export function alignLines(before, after) {
|
|
21
|
+
const a = split(before);
|
|
22
|
+
const b = split(after);
|
|
23
|
+
const unchanged = new Map();
|
|
24
|
+
if (a.length * b.length > MAX_CELLS)
|
|
25
|
+
return unchanged;
|
|
26
|
+
// lcs[i][j]: length of the LCS of a[i..] and b[j..].
|
|
27
|
+
const w = b.length + 1;
|
|
28
|
+
const lcs = new Uint32Array((a.length + 1) * w);
|
|
29
|
+
for (let i = a.length - 1; i >= 0; i--)
|
|
30
|
+
for (let j = b.length - 1; j >= 0; j--)
|
|
31
|
+
lcs[i * w + j] =
|
|
32
|
+
a[i] === b[j]
|
|
33
|
+
? lcs[(i + 1) * w + j + 1] + 1
|
|
34
|
+
: Math.max(lcs[(i + 1) * w + j], lcs[i * w + j + 1]);
|
|
35
|
+
// Walk the table: a matched pair is an unchanged line of `after`; a step that
|
|
36
|
+
// advances `after` alone is a line the edit wrote or changed.
|
|
37
|
+
let i = 0;
|
|
38
|
+
let j = 0;
|
|
39
|
+
while (j < b.length) {
|
|
40
|
+
if (i < a.length && a[i] === b[j]) {
|
|
41
|
+
unchanged.set(j + 1, i + 1);
|
|
42
|
+
i++;
|
|
43
|
+
j++;
|
|
44
|
+
}
|
|
45
|
+
else if (i < a.length && lcs[(i + 1) * w + j] >= lcs[i * w + j + 1]) {
|
|
46
|
+
i++;
|
|
47
|
+
}
|
|
48
|
+
else {
|
|
49
|
+
j++;
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
return unchanged;
|
|
53
|
+
}
|
|
54
|
+
const MAX_CELLS = 4_000_000;
|
|
55
|
+
/** Lines as the COMPILER counts them — CRLF, LF, a bare CR, U+2028 and U+2029
|
|
56
|
+
* each end a line — so a line number here is the line number a diagnostic
|
|
57
|
+
* carries. Split on LF alone, a U+2028 inside a string literal put every
|
|
58
|
+
* number after it one out of step, and the unchanged check read the wrong line. */
|
|
59
|
+
function split(content) {
|
|
60
|
+
return content.split(/\r\n|\r|\n|\u2028|\u2029/);
|
|
61
|
+
}
|
|
62
|
+
/** One compiler error the baseline had: the file as delivered, the line in the
|
|
63
|
+
* BASELINE's numbering, the TS code and the flattened message. Code alone let
|
|
64
|
+
* a NEW error hide behind an old one of the same code — `label` changed from
|
|
65
|
+
* returning a string to a boolean is a second TS2322 on the same line. */
|
|
66
|
+
export const baselineErrorKey = (file, line, code, message) => `${file}\0${line}\0${code}\0${message}`;
|
|
67
|
+
/** The baseline's errors as a MULTISET: each excuses at most one current error.
|
|
68
|
+
* A line that had one error at HEAD and has two now carries one the run caused. */
|
|
69
|
+
export class BaselineErrors {
|
|
70
|
+
left = new Map();
|
|
71
|
+
add(key) {
|
|
72
|
+
this.left.set(key, (this.left.get(key) ?? 0) + 1);
|
|
73
|
+
}
|
|
74
|
+
/** Consume one occurrence; false when none is left. */
|
|
75
|
+
take(key) {
|
|
76
|
+
const n = this.left.get(key) ?? 0;
|
|
77
|
+
if (n === 0)
|
|
78
|
+
return false;
|
|
79
|
+
this.left.set(key, n - 1);
|
|
80
|
+
return true;
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
/**
|
|
84
|
+
* Why a compiler error at `file:line` is not this run's, or `undefined` when it
|
|
85
|
+
* is. A block is a demand to fix the line named, and a demand the run cannot
|
|
86
|
+
* meet ends in the module being deleted — so the compiler blocks ONLY on an
|
|
87
|
+
* error this run caused, and an error is inherited only by CAUSE: the line is
|
|
88
|
+
* unchanged since HEAD AND the file at HEAD already failed the compiler with the
|
|
89
|
+
* same code and message on that line, once per occurrence. Position alone is
|
|
90
|
+
* not enough — extraction breaks lines it leaves alone (a helper that no longer
|
|
91
|
+
* returns what the next line reads), and those errors are the run's.
|
|
92
|
+
*
|
|
93
|
+
* `unchangedOf` gives each file's unchanged lines mapped to HEAD's numbering (see
|
|
94
|
+
* alignLines), or `undefined` for a file HEAD does not hold: every error in such
|
|
95
|
+
* a file is this run's.
|
|
96
|
+
*/
|
|
97
|
+
export function inheritedTypeError(unchangedOf, baselineErrors) {
|
|
98
|
+
return (file, line, code, message) => {
|
|
99
|
+
const before = unchangedOf(file)?.get(line);
|
|
100
|
+
if (before === undefined)
|
|
101
|
+
return undefined;
|
|
102
|
+
return baselineErrors.take(baselineErrorKey(file, before, code, message))
|
|
103
|
+
? `the line is unchanged since HEAD, where it already failed the compiler the same way (line ${before})`
|
|
104
|
+
: undefined;
|
|
105
|
+
};
|
|
106
|
+
}
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The repository's committed state, asked of git. ONE reader of a file at HEAD and
|
|
3
|
+
* ONE lister of what changed since, so that every gate comparing a delivered file
|
|
4
|
+
* against its baseline agrees on what "at HEAD" means — and so a fix to the
|
|
5
|
+
* canonicalisation reaches all of them at once. Four readers had grown up
|
|
6
|
+
* independently, and the one that took `path.relative(root, file)` uncanonicalised
|
|
7
|
+
* still carried the symlink fault the others had fixed.
|
|
8
|
+
*/
|
|
9
|
+
/** git's own wording for "this path is not in HEAD". Everything else — git missing,
|
|
10
|
+
* a corrupt object store, a permission error — is git NOT ANSWERING, and the two are
|
|
11
|
+
* never treated alike: absence is the ordinary, silent case (this run wrote the
|
|
12
|
+
* file), and a malfunction must neither become a finding nor vanish. Stated once,
|
|
13
|
+
* so two readers cannot drift on which errors mean which. */
|
|
14
|
+
export declare function isAbsentFromHead(err: unknown): boolean;
|
|
15
|
+
/** What git says about a file at HEAD: its content, that HEAD does not hold it,
|
|
16
|
+
* or that git gave no answer — outside a repository, or a git failure. The
|
|
17
|
+
* third is never folded into the second: "absent" is a claim (this run created
|
|
18
|
+
* the file), "unknown" is the absence of one. */
|
|
19
|
+
export type AtHead = {
|
|
20
|
+
content: string;
|
|
21
|
+
} | {
|
|
22
|
+
absent: true;
|
|
23
|
+
} | {
|
|
24
|
+
unknown: true;
|
|
25
|
+
};
|
|
26
|
+
/**
|
|
27
|
+
* The file at HEAD, as one of the three answers above.
|
|
28
|
+
*
|
|
29
|
+
* `root` is any directory of the checkout, and an optimisation for a caller that
|
|
30
|
+
* already holds one. The repository's top level is resolved from it — or from the
|
|
31
|
+
* file's directory, which is still there for a file this run deleted — because
|
|
32
|
+
* `HEAD:<path>` speaks in paths from the top. A file outside the top level is
|
|
33
|
+
* `unknown`: HEAD has no path for it. A git malfunction is logged, as a warning —
|
|
34
|
+
* it must not vanish either.
|
|
35
|
+
*/
|
|
36
|
+
export declare function readAtHead(file: string, root?: string): Promise<AtHead>;
|
|
37
|
+
/**
|
|
38
|
+
* The file's content at HEAD, or `undefined` when there is none to read.
|
|
39
|
+
*
|
|
40
|
+
* `undefined` covers the two non-content answers of `readAtHead`, which must both
|
|
41
|
+
* stay silent for a caller that only compares: reporting anything about a file on
|
|
42
|
+
* either would be a claim the evidence does not carry. A caller that STATES
|
|
43
|
+
* whether this run created the file reads `readAtHead` and keeps them apart.
|
|
44
|
+
*/
|
|
45
|
+
export declare function contentAtHead(file: string, root?: string): Promise<string | undefined>;
|
|
46
|
+
/**
|
|
47
|
+
* Absolute, CANONICAL paths of the files that differ from HEAD in the repository
|
|
48
|
+
* `root` is in, by git's `--diff-filter` letters (`M` modified, `D` deleted, `DM`
|
|
49
|
+
* both …). Canonical for a deleted file too — its parent resolves — so a caller may
|
|
50
|
+
* key on them directly; that is part of the contract, not an accident.
|
|
51
|
+
*
|
|
52
|
+
* `--no-renames` is load-bearing, not tidiness. With rename detection on — the
|
|
53
|
+
* default since git 2.9 — moving a file reports one `R` and NO `D`, so a caller
|
|
54
|
+
* asking for deletions never hears of a module that was renamed away while every
|
|
55
|
+
* test still imports it by its old name. A rename is a deletion here, because a
|
|
56
|
+
* caller's import does not move with the file.
|
|
57
|
+
*
|
|
58
|
+
* `-z`: NUL-separated and unquoted, so a non-ASCII path stays resolvable.
|
|
59
|
+
*
|
|
60
|
+
* Throws when git cannot answer: each caller says in its own words what it will not
|
|
61
|
+
* do as a result, and none of them may read a failed listing as an empty one.
|
|
62
|
+
*/
|
|
63
|
+
export declare function changedSinceHead(root: string, opts: {
|
|
64
|
+
filter: string;
|
|
65
|
+
}): Promise<string[]>;
|
|
66
|
+
/**
|
|
67
|
+
* Absolute, CANONICAL paths of the files in the repository `root` is in that HEAD
|
|
68
|
+
* does not hold: added to the index, or untracked and not ignored. What
|
|
69
|
+
* `changedSinceHead` cannot list — `git diff HEAD` never sees an untracked file —
|
|
70
|
+
* for a caller that must know what did not exist before this run. Throws when git
|
|
71
|
+
* cannot answer, on the same terms.
|
|
72
|
+
*/
|
|
73
|
+
export declare function addedSinceHead(root: string): Promise<string[]>;
|
|
@@ -0,0 +1,156 @@
|
|
|
1
|
+
import { execFile } from "child_process";
|
|
2
|
+
import { promisify } from "util";
|
|
3
|
+
import * as path from "path";
|
|
4
|
+
import { detectGitRoot } from "../gitStaging.js";
|
|
5
|
+
import { logger } from "../logger.js";
|
|
6
|
+
import { realpath } from "./locate.js";
|
|
7
|
+
const execFileAsync = promisify(execFile);
|
|
8
|
+
/**
|
|
9
|
+
* The repository's committed state, asked of git. ONE reader of a file at HEAD and
|
|
10
|
+
* ONE lister of what changed since, so that every gate comparing a delivered file
|
|
11
|
+
* against its baseline agrees on what "at HEAD" means — and so a fix to the
|
|
12
|
+
* canonicalisation reaches all of them at once. Four readers had grown up
|
|
13
|
+
* independently, and the one that took `path.relative(root, file)` uncanonicalised
|
|
14
|
+
* still carried the symlink fault the others had fixed.
|
|
15
|
+
*/
|
|
16
|
+
/** git's own wording for "this path is not in HEAD". Everything else — git missing,
|
|
17
|
+
* a corrupt object store, a permission error — is git NOT ANSWERING, and the two are
|
|
18
|
+
* never treated alike: absence is the ordinary, silent case (this run wrote the
|
|
19
|
+
* file), and a malfunction must neither become a finding nor vanish. Stated once,
|
|
20
|
+
* so two readers cannot drift on which errors mean which. */
|
|
21
|
+
export function isAbsentFromHead(err) {
|
|
22
|
+
const e = err;
|
|
23
|
+
return /does not exist|exists on disk, but not in|Not a valid object name|bad revision|unknown revision/i.test(`${e?.stderr ?? ""} ${String(err)}`);
|
|
24
|
+
}
|
|
25
|
+
/** Canonical absolute path, for a file that may be GONE: `realpath` falls back to
|
|
26
|
+
* `path.resolve` on a missing file, which leaves a symlinked parent (macOS `/tmp` →
|
|
27
|
+
* `/private/tmp`) unresolved and relativises the path to `../../…` against a
|
|
28
|
+
* canonical root. The parent directory is still there to resolve. */
|
|
29
|
+
async function canonicalEvenIfGone(p) {
|
|
30
|
+
const resolved = await realpath(p);
|
|
31
|
+
if (resolved !== path.resolve(p))
|
|
32
|
+
return resolved; // it exists, and moved
|
|
33
|
+
// Already canonical, or gone: resolving the parent answers both.
|
|
34
|
+
return path.join(await realpath(path.dirname(p)), path.basename(p));
|
|
35
|
+
}
|
|
36
|
+
/** `file` relative to the repository's top level as git spells it (posix, both
|
|
37
|
+
* sides canonical), or undefined when the file is outside it. */
|
|
38
|
+
async function gitPath(file, top) {
|
|
39
|
+
const rel = path.relative(await realpath(top), await canonicalEvenIfGone(file));
|
|
40
|
+
if (rel === "" ||
|
|
41
|
+
rel === ".." ||
|
|
42
|
+
rel.startsWith(`..${path.sep}`) ||
|
|
43
|
+
path.isAbsolute(rel))
|
|
44
|
+
return undefined;
|
|
45
|
+
return rel.split(path.sep).join("/");
|
|
46
|
+
}
|
|
47
|
+
/**
|
|
48
|
+
* The file at HEAD, as one of the three answers above.
|
|
49
|
+
*
|
|
50
|
+
* `root` is any directory of the checkout, and an optimisation for a caller that
|
|
51
|
+
* already holds one. The repository's top level is resolved from it — or from the
|
|
52
|
+
* file's directory, which is still there for a file this run deleted — because
|
|
53
|
+
* `HEAD:<path>` speaks in paths from the top. A file outside the top level is
|
|
54
|
+
* `unknown`: HEAD has no path for it. A git malfunction is logged, as a warning —
|
|
55
|
+
* it must not vanish either.
|
|
56
|
+
*/
|
|
57
|
+
export async function readAtHead(file, root) {
|
|
58
|
+
const top = detectGitRoot(root ?? path.dirname(file));
|
|
59
|
+
if (!top)
|
|
60
|
+
return { unknown: true };
|
|
61
|
+
const rel = await gitPath(file, top);
|
|
62
|
+
if (rel === undefined)
|
|
63
|
+
return { unknown: true };
|
|
64
|
+
try {
|
|
65
|
+
const { stdout } = await execFileAsync("git", ["show", `HEAD:${rel}`], {
|
|
66
|
+
cwd: top,
|
|
67
|
+
encoding: "utf8",
|
|
68
|
+
maxBuffer: 16 * 1024 * 1024,
|
|
69
|
+
});
|
|
70
|
+
return { content: stdout };
|
|
71
|
+
}
|
|
72
|
+
catch (err) {
|
|
73
|
+
if (isAbsentFromHead(err))
|
|
74
|
+
return { absent: true };
|
|
75
|
+
logger.warning("Could not read a file at HEAD — whatever compares against it will not run", { file, error: String(err) });
|
|
76
|
+
return { unknown: true };
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
/**
|
|
80
|
+
* The file's content at HEAD, or `undefined` when there is none to read.
|
|
81
|
+
*
|
|
82
|
+
* `undefined` covers the two non-content answers of `readAtHead`, which must both
|
|
83
|
+
* stay silent for a caller that only compares: reporting anything about a file on
|
|
84
|
+
* either would be a claim the evidence does not carry. A caller that STATES
|
|
85
|
+
* whether this run created the file reads `readAtHead` and keeps them apart.
|
|
86
|
+
*/
|
|
87
|
+
export async function contentAtHead(file, root) {
|
|
88
|
+
const at = await readAtHead(file, root);
|
|
89
|
+
return "content" in at ? at.content : undefined;
|
|
90
|
+
}
|
|
91
|
+
/**
|
|
92
|
+
* Absolute, CANONICAL paths of the files that differ from HEAD in the repository
|
|
93
|
+
* `root` is in, by git's `--diff-filter` letters (`M` modified, `D` deleted, `DM`
|
|
94
|
+
* both …). Canonical for a deleted file too — its parent resolves — so a caller may
|
|
95
|
+
* key on them directly; that is part of the contract, not an accident.
|
|
96
|
+
*
|
|
97
|
+
* `--no-renames` is load-bearing, not tidiness. With rename detection on — the
|
|
98
|
+
* default since git 2.9 — moving a file reports one `R` and NO `D`, so a caller
|
|
99
|
+
* asking for deletions never hears of a module that was renamed away while every
|
|
100
|
+
* test still imports it by its old name. A rename is a deletion here, because a
|
|
101
|
+
* caller's import does not move with the file.
|
|
102
|
+
*
|
|
103
|
+
* `-z`: NUL-separated and unquoted, so a non-ASCII path stays resolvable.
|
|
104
|
+
*
|
|
105
|
+
* Throws when git cannot answer: each caller says in its own words what it will not
|
|
106
|
+
* do as a result, and none of them may read a failed listing as an empty one.
|
|
107
|
+
*/
|
|
108
|
+
export async function changedSinceHead(root, opts) {
|
|
109
|
+
const top = detectGitRoot(root);
|
|
110
|
+
if (!top)
|
|
111
|
+
throw new Error(`${root} is not inside a git repository`);
|
|
112
|
+
const { stdout } = await execFileAsync("git", [
|
|
113
|
+
"diff",
|
|
114
|
+
"-z",
|
|
115
|
+
"--name-only",
|
|
116
|
+
`--diff-filter=${opts.filter}`,
|
|
117
|
+
"--no-renames",
|
|
118
|
+
"HEAD",
|
|
119
|
+
], { cwd: top, encoding: "utf8" });
|
|
120
|
+
const realTop = await realpath(top);
|
|
121
|
+
const out = [];
|
|
122
|
+
for (const rel of stdout.split("\0").filter(Boolean))
|
|
123
|
+
out.push(await canonicalEvenIfGone(path.resolve(realTop, rel)));
|
|
124
|
+
return out;
|
|
125
|
+
}
|
|
126
|
+
/**
|
|
127
|
+
* Absolute, CANONICAL paths of the files in the repository `root` is in that HEAD
|
|
128
|
+
* does not hold: added to the index, or untracked and not ignored. What
|
|
129
|
+
* `changedSinceHead` cannot list — `git diff HEAD` never sees an untracked file —
|
|
130
|
+
* for a caller that must know what did not exist before this run. Throws when git
|
|
131
|
+
* cannot answer, on the same terms.
|
|
132
|
+
*/
|
|
133
|
+
export async function addedSinceHead(root) {
|
|
134
|
+
const top = detectGitRoot(root);
|
|
135
|
+
if (!top)
|
|
136
|
+
throw new Error(`${root} is not inside a git repository`);
|
|
137
|
+
const run = async (args) => (await execFileAsync("git", args, { cwd: top, encoding: "utf8" })).stdout
|
|
138
|
+
.split("\0")
|
|
139
|
+
.filter(Boolean);
|
|
140
|
+
const rels = [
|
|
141
|
+
...(await run([
|
|
142
|
+
"diff",
|
|
143
|
+
"-z",
|
|
144
|
+
"--name-only",
|
|
145
|
+
"--diff-filter=A",
|
|
146
|
+
"--no-renames",
|
|
147
|
+
"HEAD",
|
|
148
|
+
])),
|
|
149
|
+
...(await run(["ls-files", "-z", "--others", "--exclude-standard"])),
|
|
150
|
+
];
|
|
151
|
+
const realTop = await realpath(top);
|
|
152
|
+
const out = [];
|
|
153
|
+
for (const rel of rels)
|
|
154
|
+
out.push(await canonicalEvenIfGone(path.resolve(realTop, rel)));
|
|
155
|
+
return out;
|
|
156
|
+
}
|
|
@@ -1,14 +1,30 @@
|
|
|
1
|
-
import { type
|
|
2
|
-
import type
|
|
1
|
+
import { type SiblingScan } from "./call-sites.js";
|
|
2
|
+
import { type UtilsHelper } from "./parse.js";
|
|
3
3
|
import type { UtilsLanguageSpec } from "./language-spec.js";
|
|
4
|
-
/**
|
|
5
|
-
export interface
|
|
4
|
+
/** One helper with the Skyramp-generated tests that import it. */
|
|
5
|
+
export interface HelperImporters {
|
|
6
6
|
/** Absolute path of the utils file. */
|
|
7
7
|
file: string;
|
|
8
8
|
helper: string;
|
|
9
9
|
line: number;
|
|
10
|
-
/**
|
|
11
|
-
|
|
10
|
+
/** ABSOLUTE PATHS of the delivered tests importing it, in directory order — the
|
|
11
|
+
* spec under verification included. The count alone answers "is this helper
|
|
12
|
+
* shared"; the paths answer "which tests does a change to it reach", which is
|
|
13
|
+
* what a caller has to be named and run. */
|
|
14
|
+
importers: string[];
|
|
15
|
+
}
|
|
16
|
+
/** The importer walk's result. `read` is the walk's OWN verdict on whether it ran:
|
|
17
|
+
* an empty `helpers` means "no helper imports anything" only when `read` is true,
|
|
18
|
+
* and means nothing at all when it is false. A caller that blocks or asserts on
|
|
19
|
+
* the absence of an importer must consult it. */
|
|
20
|
+
export interface HelperImporterScan {
|
|
21
|
+
helpers: HelperImporters[];
|
|
22
|
+
/** Absolute paths of the generated tests the walk read. */
|
|
23
|
+
siblings: string[];
|
|
24
|
+
/** The sibling walk saw the whole directory: the listing succeeded and every
|
|
25
|
+
* candidate file was read. False means the importer lists are short by an unknown
|
|
26
|
+
* amount, not that they are empty. */
|
|
27
|
+
read: boolean;
|
|
12
28
|
}
|
|
13
29
|
/** The strings a sibling's source would contain if it imports the module at
|
|
14
30
|
* `modulePath` (relative to the sibling's directory): the path form for TS/JS
|
|
@@ -16,8 +32,21 @@ export interface SingleImporterHelper {
|
|
|
16
32
|
* `shared/X` → `shared.X` / `.shared.X`. */
|
|
17
33
|
export declare function moduleMentionCandidates(modulePath: string): string[];
|
|
18
34
|
/**
|
|
19
|
-
*
|
|
20
|
-
*
|
|
35
|
+
* The Skyramp-generated tests in the spec's directory that import each utils helper —
|
|
36
|
+
* the spec under verification included. THE importer walk: the single-importer
|
|
37
|
+
* advisory and the changed-helper caller set both read it, so one definition decides
|
|
38
|
+
* what counts as an importing test.
|
|
39
|
+
*
|
|
40
|
+
* Fails open to an unread scan on any error. That is a silence, not an empty answer;
|
|
41
|
+
* `read` is what tells the two apart.
|
|
42
|
+
*
|
|
43
|
+
* SCOPE, which every caller inherits: siblings are the spec's OWN DIRECTORY. A
|
|
44
|
+
* generated test importing the module from another directory is not found here, so
|
|
45
|
+
* the importer list is a LOWER BOUND on the callers, never the complete set.
|
|
46
|
+
*/
|
|
47
|
+
export declare function helperImporters(testFile: string, utilsFiles: string[], helpersByFile: ReadonlyMap<string, UtilsHelper[]>, spec: UtilsLanguageSpec, preReadSiblings?: SiblingScan): Promise<HelperImporterScan>;
|
|
48
|
+
/**
|
|
49
|
+
* Helpers with 0 or 1 importing test — the advisory slice of {@link helperImporters}.
|
|
21
50
|
*
|
|
22
51
|
* Findings:
|
|
23
52
|
* - 0 importers: nothing imports the helper, not even the spec that wrote it — a
|
|
@@ -26,6 +55,26 @@ export declare function moduleMentionCandidates(modulePath: string): string[];
|
|
|
26
55
|
* TWO delivered tests: on a first run every helper has exactly one importer by
|
|
27
56
|
* construction (it seeds the utils file), so listing them would be guaranteed
|
|
28
57
|
* noise on the most common path.
|
|
29
|
-
*
|
|
58
|
+
*
|
|
59
|
+
* Reports NOTHING from an incomplete walk. This is a claim, not a gate — it tells the
|
|
60
|
+
* agent a helper is dead and to call it or delete it — and a walk that could not read
|
|
61
|
+
* every candidate cannot support it: the file it failed to read may be the importer.
|
|
62
|
+
* Silence on doubt, so the finding stays trustworthy when it is made.
|
|
63
|
+
*/
|
|
64
|
+
export declare function singleImporterHelpers(testFile: string, utilsFiles: string[], helpersByFile: ReadonlyMap<string, UtilsHelper[]>, spec: UtilsLanguageSpec, preReadSiblings?: SiblingScan): Promise<HelperImporters[]>;
|
|
65
|
+
/**
|
|
66
|
+
* `content` prepared for an import scan: the copy `importedNames` should read.
|
|
67
|
+
*
|
|
68
|
+
* Comments go in both languages — an import quoted in prose is not an import, and
|
|
69
|
+
* counting one makes a test a "caller" of a helper it cannot reach, which a gate then
|
|
70
|
+
* blocks on. Python string CONTENTS go too, since a Python import names no string, so
|
|
71
|
+
* the usage example in a docstring stops counting.
|
|
72
|
+
*
|
|
73
|
+
* TS/JS cannot blank every string, because the module specifier IS a string
|
|
74
|
+
* (`from "./skyrampUtils"`). Only the specifiers are kept: a string that directly
|
|
75
|
+
* follows `from` or `require(` stays, and every other string is blanked. Requiring
|
|
76
|
+
* the import to start a line is not enough on its own — a template literal holds
|
|
77
|
+
* whole lines, and the `require` form is bounded by a declaration keyword rather than
|
|
78
|
+
* by the line start — so quoted code kept reading as executable code.
|
|
30
79
|
*/
|
|
31
|
-
export declare function
|
|
80
|
+
export declare function importScanSource(content: string, spec: UtilsLanguageSpec): string;
|