@skyramp/mcp 0.4.2 → 0.4.3-rc.1
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/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 +24 -2
- package/build/recommendation/verifierContracts.js +32 -4
- package/build/recommendation/verifiers/changedLinesClaimed.d.ts +17 -0
- package/build/recommendation/verifiers/changedLinesClaimed.js +88 -0
- 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/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 +6 -0
- package/build/tools/code-refactor/reuse-record-store.d.ts +52 -0
- package/build/tools/code-refactor/reuse-record-store.js +126 -0
- package/build/tools/code-refactor/reuse-state.d.ts +18 -6
- package/build/tools/code-refactor/reuse-state.js +78 -131
- package/build/tools/code-refactor/utils-verify-gates.js +3 -3
- 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 +91 -29
- 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 +70 -22
- package/build/types/RepositoryAnalysis.d.ts +20 -20
- package/build/types/ReuseOutcome.d.ts +96 -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/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/head.d.ts +48 -0
- package/build/utils/utils-verify/head.js +115 -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/verify.d.ts +9 -3
- package/build/utils/utils-verify/verify.js +26 -12
- 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/traceRecordingBackend.js +303 -150
- 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.BAkLd5DX.js → index.ZYuEFHsi.js} +1 -1
- 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/{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 +2 -2
- 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
|
@@ -131,6 +131,79 @@ export interface RetrofitOutcome {
|
|
|
131
131
|
file: string;
|
|
132
132
|
execution?: TestExecutionStatus;
|
|
133
133
|
}
|
|
134
|
+
/** What a run did to a helper that already existed in the repository. A helper the
|
|
135
|
+
* module gained is not here: this run created it, and it has no incumbent callers
|
|
136
|
+
* whose behaviour it could change. */
|
|
137
|
+
export type HelperChangeKind =
|
|
138
|
+
/** The parameter list differs — the change a caller's own diff can be read
|
|
139
|
+
* against, and the one an added argument or a re-defaulted argument lands in
|
|
140
|
+
* (a helper re-defaulted to a rejection status is the recorded case). */
|
|
141
|
+
"signature"
|
|
142
|
+
/** The parameter list is byte-identical and the body is not. The dangerous case
|
|
143
|
+
* for a browser helper: a selector change alters what every caller does and
|
|
144
|
+
* appears in no calling test's diff. */
|
|
145
|
+
| "body"
|
|
146
|
+
/** The incumbent defined it and the module no longer does. Every caller's import
|
|
147
|
+
* now names something that is not there. */
|
|
148
|
+
| "removed";
|
|
149
|
+
/** A helper this run changed in a module that already existed in the repository,
|
|
150
|
+
* with the tests that call it.
|
|
151
|
+
*
|
|
152
|
+
* A shared helper's change reaches every caller at once — that is the feature's
|
|
153
|
+
* value and its danger. A caller the run did not touch is not delivered, so nothing
|
|
154
|
+
* executed it, and a break in one reached the customer's own continuous integration
|
|
155
|
+
* instead of this report. A browser helper is the worse case: its selector can
|
|
156
|
+
* change with no signature change at all, and the change appears in no calling
|
|
157
|
+
* test's diff. */
|
|
158
|
+
export interface ChangedHelperEntry {
|
|
159
|
+
/** REPOSITORY-RELATIVE path (posix) of the module that defines it — per helper,
|
|
160
|
+
* as {@link OrphanedHelper} names its: a delivery can carry several modules, and
|
|
161
|
+
* the same helper name in two of them is two entries a reader must tell apart. */
|
|
162
|
+
utilsFile: string;
|
|
163
|
+
helper: string;
|
|
164
|
+
change: HelperChangeKind;
|
|
165
|
+
/** The Skyramp-generated tests that call it, by BASENAME, each with the execution
|
|
166
|
+
* recorded against the module as it stands.
|
|
167
|
+
*
|
|
168
|
+
* SCOPED TO THE MODULE'S OWN DIRECTORY, which is as far as the caller walk reads.
|
|
169
|
+
* A generated test that imports the module from another directory is not here, so
|
|
170
|
+
* this is a lower bound: read it as "the callers found beside the module", never
|
|
171
|
+
* as "every test that calls it". The gate acts only on what is listed, so a caller
|
|
172
|
+
* it cannot see costs a report line and an unrun test — never a false block, which
|
|
173
|
+
* is the direction this feature chooses everywhere.
|
|
174
|
+
*
|
|
175
|
+
* ABSENT means even that much could not be established — never "no test calls
|
|
176
|
+
* it". An empty array is the positive statement that the walk ran and found
|
|
177
|
+
* none beside the module. A caller with no `execution` and no `deliveredByThisRun`
|
|
178
|
+
* was not run: the run could not execute it (a native suite, or a test the prescan
|
|
179
|
+
* never inventoried), which is disclosed rather than blocked on. */
|
|
180
|
+
callers?: ChangedHelperCaller[];
|
|
181
|
+
}
|
|
182
|
+
export interface ChangedHelperCaller {
|
|
183
|
+
/** BASENAME of the calling test. */
|
|
184
|
+
file: string;
|
|
185
|
+
execution?: TestExecutionStatus;
|
|
186
|
+
/** This run delivered or edited the caller, so its own row in this report carries
|
|
187
|
+
* its execution and its own checks judge it. Read an absent `execution` as "not
|
|
188
|
+
* run" only when this is absent too. */
|
|
189
|
+
deliveredByThisRun?: true;
|
|
190
|
+
}
|
|
191
|
+
/** A helper in a shared module that NO Skyramp-generated test imports.
|
|
192
|
+
*
|
|
193
|
+
* A run can delete a test, and a helper that test was the last importer of stays in
|
|
194
|
+
* the module. The customer then maintains code nothing calls, and nothing in the
|
|
195
|
+
* pull request says so — the agent was told, and the reviewer was not.
|
|
196
|
+
*
|
|
197
|
+
* REPORTED, never removed, and never gated. Removing a helper that is still called
|
|
198
|
+
* is far worse than leaving a dead one, and the caller list this is read from is a
|
|
199
|
+
* lower bound scoped to the module's directory. A reader decides; the run does not. */
|
|
200
|
+
export interface OrphanedHelper {
|
|
201
|
+
/** REPOSITORY-RELATIVE path (posix) of the module that defines it. Per helper, not
|
|
202
|
+
* per row: a delivery can carry several modules, and a reader needs the file to
|
|
203
|
+
* open. */
|
|
204
|
+
utilsFile: string;
|
|
205
|
+
helper: string;
|
|
206
|
+
}
|
|
134
207
|
/** Shared-helper reuse for an API test, re-derived from the delivered files at report
|
|
135
208
|
* time — nothing here is read back from what the agent or the verify pass recorded.
|
|
136
209
|
* Absent altogether when no utils file exists for the test (the same omission rule
|
|
@@ -159,6 +232,16 @@ export interface HelperReuseOutcome {
|
|
|
159
232
|
* wraps — reuse that was available and not taken. Omitted at zero. Informational,
|
|
160
233
|
* like {@link ReuseOutcome.missedReuse}. */
|
|
161
234
|
siblingInlineCallSites?: number;
|
|
235
|
+
/** Helpers in `utilsFile` that no Skyramp-generated test imports — see
|
|
236
|
+
* {@link OrphanedHelper}. Omitted when there are none, and omitted (with
|
|
237
|
+
* `orphanScanIncomplete` set) when the walk could not establish the importers at
|
|
238
|
+
* all. Informational: nothing gates on it. */
|
|
239
|
+
orphanedHelpers?: OrphanedHelper[];
|
|
240
|
+
/** Present — and only ever `true` — when the importer walk could not read every
|
|
241
|
+
* file beside the module, so no helper is reported as orphaned. Absent means the
|
|
242
|
+
* walk ran, so an absent `orphanedHelpers` then means none were found rather than
|
|
243
|
+
* that nothing was looked for. */
|
|
244
|
+
orphanScanIncomplete?: true;
|
|
162
245
|
/** Whether the delivered utils file holds the shared-helper invariants: one helper
|
|
163
246
|
* per method+path, status-code-only assertions, method+resource names — with
|
|
164
247
|
* documented declines (`reuse-verify: allow …`) counted as holding. */
|
|
@@ -296,6 +379,19 @@ export interface ReuseOutcome {
|
|
|
296
379
|
* not the module, and is most telling when no module exists at all — the case
|
|
297
380
|
* where `helpers` is rightly absent. Omitted at zero. Informational. */
|
|
298
381
|
unextractedDuplicateSites?: number;
|
|
382
|
+
/** Helpers that already existed in the repository and that this run CHANGED, each
|
|
383
|
+
* with the tests that call it. Omitted when the run changed no incumbent helper —
|
|
384
|
+
* which is every first run, where the module is created rather than edited. A
|
|
385
|
+
* helper an earlier run created and this one changed appears here as changed,
|
|
386
|
+
* never as created.
|
|
387
|
+
*
|
|
388
|
+
* Top level, not on the helpers row, for the same reason as
|
|
389
|
+
* {@link unextractedDuplicateSites}: it describes what happened to a module, and
|
|
390
|
+
* it is most telling when NO module is there to describe — a run that deleted or
|
|
391
|
+
* renamed the shared module removes every helper at once and leaves `helpers`
|
|
392
|
+
* rightly absent, which is exactly when a reader most needs to be told. See
|
|
393
|
+
* {@link ChangedHelperEntry}. */
|
|
394
|
+
changedHelpers?: ChangedHelperEntry[];
|
|
299
395
|
verification?: ReuseVerificationOutcome;
|
|
300
396
|
/** Why `verification` is `failed` on the page-object path, on the same terms as
|
|
301
397
|
* {@link HelperReuseOutcome.verificationFailures}: present only when the
|
|
@@ -14,6 +14,17 @@ export declare enum TestExecutionStatus {
|
|
|
14
14
|
/** Execution was expected (UPDATE/REGENERATE) but no result was recorded. */
|
|
15
15
|
Unknown = "Unknown"
|
|
16
16
|
}
|
|
17
|
+
/** Whether a status is a RESULT: the test ran and pass, fail or error is what it
|
|
18
|
+
* did. `Skipped` is a decision not to execute and `Unknown` is an execution that was
|
|
19
|
+
* expected and recorded nothing — neither may satisfy a demand for a run, or a run
|
|
20
|
+
* that never touched the file would clear the debt for it. */
|
|
21
|
+
export declare function isResult(status: TestExecutionStatus): boolean;
|
|
22
|
+
/** The status of `record` when it is a result that ran the bytes written at
|
|
23
|
+
* `sinceMs` or later, and `undefined` otherwise. THE freshness rule, for every gate
|
|
24
|
+
* that asks whether a file has been run as it now stands: an execution older than
|
|
25
|
+
* the last write to what it exercised ran bytes that no longer exist. Which write —
|
|
26
|
+
* the file's own, or a module's under it — is the caller's to say. */
|
|
27
|
+
export declare function freshExecution(record: Pick<TestExecutionData, "status" | "executedAt"> | undefined, sinceMs: number): TestExecutionStatus | undefined;
|
|
17
28
|
export interface TestExecutionData {
|
|
18
29
|
status: TestExecutionStatus;
|
|
19
30
|
executedAt: string;
|
|
@@ -14,3 +14,22 @@ export var TestExecutionStatus;
|
|
|
14
14
|
/** Execution was expected (UPDATE/REGENERATE) but no result was recorded. */
|
|
15
15
|
TestExecutionStatus["Unknown"] = "Unknown";
|
|
16
16
|
})(TestExecutionStatus || (TestExecutionStatus = {}));
|
|
17
|
+
/** Whether a status is a RESULT: the test ran and pass, fail or error is what it
|
|
18
|
+
* did. `Skipped` is a decision not to execute and `Unknown` is an execution that was
|
|
19
|
+
* expected and recorded nothing — neither may satisfy a demand for a run, or a run
|
|
20
|
+
* that never touched the file would clear the debt for it. */
|
|
21
|
+
export function isResult(status) {
|
|
22
|
+
return (status === TestExecutionStatus.Pass ||
|
|
23
|
+
status === TestExecutionStatus.Fail ||
|
|
24
|
+
status === TestExecutionStatus.Error);
|
|
25
|
+
}
|
|
26
|
+
/** The status of `record` when it is a result that ran the bytes written at
|
|
27
|
+
* `sinceMs` or later, and `undefined` otherwise. THE freshness rule, for every gate
|
|
28
|
+
* that asks whether a file has been run as it now stands: an execution older than
|
|
29
|
+
* the last write to what it exercised ran bytes that no longer exist. Which write —
|
|
30
|
+
* the file's own, or a module's under it — is the caller's to say. */
|
|
31
|
+
export function freshExecution(record, sinceMs) {
|
|
32
|
+
if (!record || !isResult(record.status))
|
|
33
|
+
return undefined;
|
|
34
|
+
return Date.parse(record.executedAt) >= sinceMs ? record.status : undefined;
|
|
35
|
+
}
|
package/build/types/index.d.ts
CHANGED
|
@@ -4,5 +4,5 @@ export { TestType, HttpMethod } from "./TestTypes.js";
|
|
|
4
4
|
export type { TestbotReport } from "./TestbotReport.js";
|
|
5
5
|
export { IssueFoundCategory } from "./TestbotReport.js";
|
|
6
6
|
export { ReuseDeclinedBy, ReuseVerificationOutcome, HelperVerificationOutcome, } from "./ReuseOutcome.js";
|
|
7
|
-
export type { ReuseOutcome, ReuseSkippedEntry, ReuseMissedEntry, HelperReuseOutcome, RetrofitOutcome, } from "./ReuseOutcome.js";
|
|
7
|
+
export type { ReuseOutcome, ReuseSkippedEntry, ReuseMissedEntry, HelperReuseOutcome, RetrofitOutcome, ChangedHelperEntry, ChangedHelperCaller, OrphanedHelper, } from "./ReuseOutcome.js";
|
|
8
8
|
export type { RelatedRepository, TestbotPromptOptions, } from "./TestbotPromptOptions.js";
|
|
@@ -72,5 +72,5 @@ export declare function changedFileNames(diff: BranchDiffData): ChangedFileName[
|
|
|
72
72
|
* compares two commits through the three-dot form, which is the CI shape. */
|
|
73
73
|
export declare function computeBranchDiff(repositoryPath: string, providedBaseBranch?: string, includeUncommitted?: boolean,
|
|
74
74
|
/** Names only: no diff text is computed, and `diffContent`/`diffStat` come back
|
|
75
|
-
* empty.
|
|
75
|
+
* empty. */
|
|
76
76
|
namesOnly?: boolean): Promise<BranchDiffData>;
|
|
@@ -349,7 +349,7 @@ export function changedFileNames(diff) {
|
|
|
349
349
|
* compares two commits through the three-dot form, which is the CI shape. */
|
|
350
350
|
export async function computeBranchDiff(repositoryPath, providedBaseBranch, includeUncommitted = false,
|
|
351
351
|
/** Names only: no diff text is computed, and `diffContent`/`diffStat` come back
|
|
352
|
-
* empty.
|
|
352
|
+
* empty. */
|
|
353
353
|
namesOnly = false) {
|
|
354
354
|
const git = simpleGit(repositoryPath);
|
|
355
355
|
const isRepo = await git.checkIsRepo();
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
/** One run of consecutive changed lines in a file: lines removed, added, or both. */
|
|
2
|
+
export interface ChangedRun {
|
|
3
|
+
file: string;
|
|
4
|
+
/** First and last line of the run in the new file. A run that only removes
|
|
5
|
+
* lines has none of its own, so both name the line that now follows it. */
|
|
6
|
+
start: number;
|
|
7
|
+
end: number;
|
|
8
|
+
/** The run's first changed line as the diff shows it, for the evidence. */
|
|
9
|
+
firstLine: string;
|
|
10
|
+
}
|
|
11
|
+
/** Every run of changed lines, file by file, in the new file's numbering. A run
|
|
12
|
+
* whose changed lines are all blank changes nothing a test can observe. */
|
|
13
|
+
export declare function collectChangedRuns(rawDiff: string): ChangedRun[];
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import { diffBlocksByPath } from "./branchDiff.js";
|
|
2
|
+
/** Every run of changed lines, file by file, in the new file's numbering. A run
|
|
3
|
+
* whose changed lines are all blank changes nothing a test can observe. */
|
|
4
|
+
export function collectChangedRuns(rawDiff) {
|
|
5
|
+
const runs = [];
|
|
6
|
+
for (const [file, block] of diffBlocksByPath(rawDiff)) {
|
|
7
|
+
let inHunk = false;
|
|
8
|
+
let newLine = 0;
|
|
9
|
+
let open;
|
|
10
|
+
const close = () => {
|
|
11
|
+
if (open && open.lines.some((text) => text.slice(1).trim() !== "")) {
|
|
12
|
+
runs.push({
|
|
13
|
+
file,
|
|
14
|
+
start: open.start,
|
|
15
|
+
end: open.added > 0 ? open.start + open.added - 1 : open.start,
|
|
16
|
+
firstLine: open.lines.find((text) => text.slice(1).trim() !== "") ?? "",
|
|
17
|
+
});
|
|
18
|
+
}
|
|
19
|
+
open = undefined;
|
|
20
|
+
};
|
|
21
|
+
for (const text of block.split("\n")) {
|
|
22
|
+
// A path printed twice holds its header twice, and the second one is no hunk.
|
|
23
|
+
if (text.startsWith("diff ")) {
|
|
24
|
+
close();
|
|
25
|
+
inHunk = false;
|
|
26
|
+
continue;
|
|
27
|
+
}
|
|
28
|
+
const hunk = /^@@ -\d+(?:,\d+)? \+(\d+)/.exec(text);
|
|
29
|
+
if (hunk) {
|
|
30
|
+
close();
|
|
31
|
+
inHunk = true;
|
|
32
|
+
newLine = Number(hunk[1]);
|
|
33
|
+
continue;
|
|
34
|
+
}
|
|
35
|
+
// Only the file header spells `---`/`+++`; inside a hunk they are content.
|
|
36
|
+
if (!inHunk)
|
|
37
|
+
continue;
|
|
38
|
+
if (text.startsWith("-") || text.startsWith("+")) {
|
|
39
|
+
open ??= { start: newLine, added: 0, lines: [] };
|
|
40
|
+
open.lines.push(text);
|
|
41
|
+
if (text.startsWith("+")) {
|
|
42
|
+
open.added++;
|
|
43
|
+
newLine++;
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
else if (!text.startsWith("\\")) {
|
|
47
|
+
// `` belongs to the line before it.
|
|
48
|
+
close();
|
|
49
|
+
if (text.startsWith(" "))
|
|
50
|
+
newLine++;
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
close();
|
|
54
|
+
}
|
|
55
|
+
return runs;
|
|
56
|
+
}
|
|
@@ -4,8 +4,9 @@
|
|
|
4
4
|
* Each helper in this module reads a `SKYRAMP_FEATURE_*` env var and returns
|
|
5
5
|
* a boolean. These helpers treat a flag as ON only when the env var is
|
|
6
6
|
* exactly `"1"`; anything else (unset, empty, "0", "true", "false", etc.) is
|
|
7
|
-
* treated as OFF. The utils-reuse
|
|
8
|
-
*
|
|
7
|
+
* treated as OFF. The POM-reuse and utils-reuse flags are additionally ON for
|
|
8
|
+
* the orgs in `POM_REUSE_DEFAULT_ORGS` and `UTILS_REUSE_DEFAULT_ORGS`
|
|
9
|
+
* regardless of the env vars.
|
|
9
10
|
*/
|
|
10
11
|
/**
|
|
11
12
|
* Gates BOTH consumer-side contract tests AND the SDK's "default mode"
|
|
@@ -54,15 +55,12 @@ export declare function isLocalDevEnabled(): boolean;
|
|
|
54
55
|
export declare function isOneClickEnabled(): boolean;
|
|
55
56
|
/**
|
|
56
57
|
* Gates the POM-aware path of `skyramp_reuse_code`
|
|
57
|
-
* (SKYRAMP_FEATURE_POM_REUSE=1).
|
|
58
|
+
* (SKYRAMP_FEATURE_POM_REUSE=1, or a `POM_REUSE_DEFAULT_ORGS` org).
|
|
58
59
|
*
|
|
59
|
-
*
|
|
60
|
-
*
|
|
61
|
-
*
|
|
62
|
-
* default
|
|
63
|
-
* file — the very thing the utils org default exists to provide (SKYR-4290).
|
|
64
|
-
* And an explicit-"0"-override escape hatch cannot work either, because
|
|
65
|
-
* testbot normalizes an unset flag to a literal "0" before forwarding.
|
|
60
|
+
* With both flags on, a UI test takes the POM pass first and the
|
|
61
|
+
* modularize-first flow over what it left inline (see `isPomFirstTarget` in
|
|
62
|
+
* utils/reuseRouting.ts), so a `POM_REUSE_DEFAULT_ORGS` org that also has the
|
|
63
|
+
* utils default still seeds a SkyrampUtils file for its UI tests.
|
|
66
64
|
*
|
|
67
65
|
* When OFF (default), TypeScript/JavaScript + Playwright targets are treated
|
|
68
66
|
* like every other target: `skyramp_reuse_code` returns the SkyrampUtils
|
|
@@ -4,8 +4,9 @@
|
|
|
4
4
|
* Each helper in this module reads a `SKYRAMP_FEATURE_*` env var and returns
|
|
5
5
|
* a boolean. These helpers treat a flag as ON only when the env var is
|
|
6
6
|
* exactly `"1"`; anything else (unset, empty, "0", "true", "false", etc.) is
|
|
7
|
-
* treated as OFF. The utils-reuse
|
|
8
|
-
*
|
|
7
|
+
* treated as OFF. The POM-reuse and utils-reuse flags are additionally ON for
|
|
8
|
+
* the orgs in `POM_REUSE_DEFAULT_ORGS` and `UTILS_REUSE_DEFAULT_ORGS`
|
|
9
|
+
* regardless of the env vars.
|
|
9
10
|
*/
|
|
10
11
|
/**
|
|
11
12
|
* Gates BOTH consumer-side contract tests AND the SDK's "default mode"
|
|
@@ -62,15 +63,12 @@ export function isOneClickEnabled() {
|
|
|
62
63
|
}
|
|
63
64
|
/**
|
|
64
65
|
* Gates the POM-aware path of `skyramp_reuse_code`
|
|
65
|
-
* (SKYRAMP_FEATURE_POM_REUSE=1).
|
|
66
|
+
* (SKYRAMP_FEATURE_POM_REUSE=1, or a `POM_REUSE_DEFAULT_ORGS` org).
|
|
66
67
|
*
|
|
67
|
-
*
|
|
68
|
-
*
|
|
69
|
-
*
|
|
70
|
-
* default
|
|
71
|
-
* file — the very thing the utils org default exists to provide (SKYR-4290).
|
|
72
|
-
* And an explicit-"0"-override escape hatch cannot work either, because
|
|
73
|
-
* testbot normalizes an unset flag to a literal "0" before forwarding.
|
|
68
|
+
* With both flags on, a UI test takes the POM pass first and the
|
|
69
|
+
* modularize-first flow over what it left inline (see `isPomFirstTarget` in
|
|
70
|
+
* utils/reuseRouting.ts), so a `POM_REUSE_DEFAULT_ORGS` org that also has the
|
|
71
|
+
* utils default still seeds a SkyrampUtils file for its UI tests.
|
|
74
72
|
*
|
|
75
73
|
* When OFF (default), TypeScript/JavaScript + Playwright targets are treated
|
|
76
74
|
* like every other target: `skyramp_reuse_code` returns the SkyrampUtils
|
|
@@ -83,7 +81,8 @@ export function isOneClickEnabled() {
|
|
|
83
81
|
* the verification gates apply.
|
|
84
82
|
*/
|
|
85
83
|
export function isPomReuseEnabled() {
|
|
86
|
-
return process.env.SKYRAMP_FEATURE_POM_REUSE === "1"
|
|
84
|
+
return (process.env.SKYRAMP_FEATURE_POM_REUSE === "1" ||
|
|
85
|
+
isDefaultOrg(POM_REUSE_DEFAULT_ORGS));
|
|
87
86
|
}
|
|
88
87
|
/**
|
|
89
88
|
* Gates SkyrampUtils code reuse for integration tests on the testbot path, and
|
|
@@ -107,27 +106,30 @@ export function isPomReuseEnabled() {
|
|
|
107
106
|
* yields the single reuse pass it did before the feature existed.
|
|
108
107
|
*/
|
|
109
108
|
export function isUtilsReuseEnabled() {
|
|
110
|
-
return (process.env.SKYRAMP_FEATURE_UTILS_REUSE === "1" ||
|
|
109
|
+
return (process.env.SKYRAMP_FEATURE_UTILS_REUSE === "1" ||
|
|
110
|
+
isDefaultOrg(UTILS_REUSE_DEFAULT_ORGS));
|
|
111
111
|
}
|
|
112
112
|
/**
|
|
113
|
-
* GitHub orgs where
|
|
114
|
-
*
|
|
115
|
-
*
|
|
116
|
-
*
|
|
117
|
-
* the env
|
|
118
|
-
*
|
|
113
|
+
* GitHub orgs where a reuse feature is on by default, without requiring its
|
|
114
|
+
* SKYRAMP_FEATURE_* env var. Compared case-insensitively (GitHub owner handles
|
|
115
|
+
* are case-insensitive, but GITHUB_REPOSITORY_OWNER preserves display casing).
|
|
116
|
+
* Because these OR with the env check, these orgs cannot turn the feature off
|
|
117
|
+
* by setting the env var to "0" — and testbot normalizes an unset flag to a
|
|
118
|
+
* literal "0" anyway.
|
|
119
119
|
*
|
|
120
|
-
* "Skyramp-HandsOn" is the Wipro / IntelliAssure pilot org (SKYR-4286
|
|
120
|
+
* "Skyramp-HandsOn" is the Wipro / IntelliAssure pilot org (SKYR-4286 for
|
|
121
|
+
* utils reuse, SKYR-4514 for POM reuse).
|
|
121
122
|
*/
|
|
122
123
|
const UTILS_REUSE_DEFAULT_ORGS = ["skyramp-handson"];
|
|
123
|
-
|
|
124
|
+
const POM_REUSE_DEFAULT_ORGS = ["skyramp-handson"];
|
|
125
|
+
function isDefaultOrg(orgs) {
|
|
124
126
|
// GITHUB_REPOSITORY_OWNER is set on GitHub Actions runners; fall back to
|
|
125
127
|
// the owner half of GITHUB_REPOSITORY ("owner/repo") for environments that
|
|
126
128
|
// only set the latter.
|
|
127
129
|
const owner = process.env.GITHUB_REPOSITORY_OWNER ??
|
|
128
130
|
process.env.GITHUB_REPOSITORY?.split("/")[0] ??
|
|
129
131
|
"";
|
|
130
|
-
return
|
|
132
|
+
return orgs.includes(owner.toLowerCase());
|
|
131
133
|
}
|
|
132
134
|
/**
|
|
133
135
|
* Returns true when the calling agent has the Skyramp Agent Skills plugin
|
|
@@ -1,7 +1,10 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Ensure `expect` is imported from `@skyramp/skyramp` rather than `@playwright/test`.
|
|
3
3
|
* - Strips `expect` from any `@playwright/test` import (keeps other identifiers).
|
|
4
|
-
* - If the file uses `expect(...)`, ensures
|
|
4
|
+
* - If the file uses `expect(...)`, ensures its `@skyramp/skyramp` import carries `expect`.
|
|
5
|
+
* A file with no `@skyramp/skyramp` import is not a Skyramp test (a repository's
|
|
6
|
+
* own spec), so it is left alone, as is a file that takes `expect` from another
|
|
7
|
+
* module.
|
|
5
8
|
* Returns the original string if no rewrite was needed.
|
|
6
9
|
*/
|
|
7
10
|
export declare function normalizeSkyrampExpectImport(source: string): string;
|
|
@@ -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 = [];
|