@jterrazz/test 16.1.1 → 18.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +45 -171
- package/dist/browser/index.js +1 -2980
- package/dist/browser/services.js +1 -0
- package/dist/browser/stubs.js +1 -0
- package/dist/browser-shared-index.browser.js +3233 -0
- package/dist/checker.d.ts +6 -1
- package/dist/checker.js +3721 -2624
- package/dist/config.d.ts +40 -0
- package/dist/config.js +309 -0
- package/dist/in-place.global-setup.d.ts +12 -0
- package/dist/in-place.global-setup.js +21 -0
- package/dist/index.d.ts +1641 -3004
- package/dist/index.js +5095 -3835
- package/dist/mobile-session.global-setup.d.ts +15 -0
- package/dist/mobile-session.global-setup.js +31 -0
- package/dist/oxlint.cjs +6069 -5634
- package/dist/oxlint.d.cts +43 -7212
- package/dist/oxlint.d.ts +43 -7212
- package/dist/oxlint.js +3979 -5799
- package/dist/route-coverage.global-setup.d.ts +43 -0
- package/dist/route-coverage.global-setup.js +1 -0
- package/dist/services.d.ts +3 -0
- package/dist/services.global-setup.d.ts +43 -0
- package/dist/services.global-setup.js +1 -0
- package/dist/services.js +597 -0
- package/dist/shared-appium.adapter.js +428 -0
- package/dist/shared-artifacts.js +34 -0
- package/dist/shared-chapter.js +7 -0
- package/dist/shared-container.port.d.ts +31 -0
- package/dist/shared-derived-projects.js +73 -0
- package/dist/shared-device.port.d.ts +189 -0
- package/dist/shared-exchange.js +447 -0
- package/dist/shared-in-place.js +172 -0
- package/dist/shared-index.services.d.ts +346 -0
- package/dist/shared-index.stubs.d.ts +287 -0
- package/dist/shared-json-shape.js +17 -0
- package/dist/shared-landmarks.js +71 -0
- package/dist/shared-ledger.js +68 -0
- package/dist/shared-listened-port.js +19 -0
- package/dist/shared-literate-plugin.js +184 -0
- package/dist/shared-manifest.js +1321 -0
- package/dist/shared-pause.js +22 -0
- package/dist/shared-peer.js +88 -0
- package/dist/shared-playwright.adapter.js +328 -0
- package/dist/shared-process-alive.js +13 -0
- package/dist/shared-process.adapter.js +74 -0
- package/dist/shared-record-key.js +13 -0
- package/dist/shared-refusal.js +35 -0
- package/dist/shared-resolve.js +24 -0
- package/dist/shared-route-coverage.global-setup.js +187 -0
- package/dist/shared-scenario-failure.js +198 -0
- package/dist/shared-serve.js +407 -0
- package/dist/shared-service-images.js +10 -0
- package/dist/shared-services.global-setup.js +174 -0
- package/dist/shared-session-ledger.js +305 -0
- package/dist/shared-setup-entry.js +12 -0
- package/dist/shared-source-scan.js +133 -0
- package/dist/shared-spec-document.js +303 -0
- package/dist/shared-spec-grammar.js +66 -0
- package/dist/shared-specifications.js +26 -0
- package/dist/shared-specs-modules.js +1033 -0
- package/dist/shared-stub-reply.js +167 -0
- package/dist/shared-stub-server.js +142 -0
- package/dist/shared-temp-root.global-setup.js +73 -0
- package/dist/shared-test-baseline.js +26 -0
- package/dist/shared-test-section.js +58 -0
- package/dist/shared-testcontainers.adapter.js +306 -0
- package/dist/shared-tokens.js +55 -0
- package/dist/shared-yaml-document.js +119 -0
- package/dist/stubs.d.ts +2 -0
- package/dist/stubs.js +657 -0
- package/dist/temp-root.global-setup.d.ts +7 -0
- package/dist/temp-root.global-setup.js +1 -0
- package/dist/vitest.d.ts +93 -164
- package/dist/vitest.js +382 -550
- package/docs/README.md +82 -36
- package/docs/concepts/architecture.md +118 -0
- package/docs/concepts/conventions.md +333 -0
- package/docs/guides/api-specs.md +374 -0
- package/docs/guides/cli-specs.md +566 -0
- package/docs/guides/component-specs.md +252 -0
- package/docs/guides/develop.md +97 -0
- package/docs/guides/integration-specs.md +181 -0
- package/docs/guides/job-specs.md +180 -0
- package/docs/guides/lint-specs.md +174 -0
- package/docs/guides/mobile-specs.md +319 -0
- package/docs/guides/module-tests.md +149 -0
- package/docs/guides/release.md +44 -0
- package/docs/guides/spec-documents.md +189 -0
- package/docs/guides/website-specs.md +413 -0
- package/docs/reference/assertions.md +345 -0
- package/docs/reference/glossary.md +20 -0
- package/docs/reference/linting.md +483 -0
- package/docs/reference/services.md +236 -0
- package/docs/reference/stubs.md +500 -0
- package/docs/reference/time-and-doubles.md +137 -0
- package/docs/reference/tokens.md +258 -0
- package/docs/reference/visitor.md +221 -0
- package/docs/start/getting-started.md +187 -0
- package/docs/start/set-up-a-project.md +228 -0
- package/package.json +56 -36
- package/schema/spec.schema.json +43 -17
- package/dist/appium.adapter.js +0 -489
- package/dist/artifacts.js +0 -32
- package/dist/fixtures.js +0 -110
- package/dist/literate.js +0 -1747
- package/dist/match.js +0 -804
- package/dist/playwright.adapter.js +0 -438
- package/dist/server.js +0 -160
- package/dist/stream.js +0 -321
- package/dist/substring-warning.js +0 -282
- package/docs/01-architecture.md +0 -95
- package/docs/02-developing.md +0 -351
- package/docs/03-testing.md +0 -495
- package/docs/04-operating.md +0 -101
- package/docs/05-module-tests.md +0 -152
- package/docs/06-integration.md +0 -180
- package/docs/07-component.md +0 -246
- package/docs/08-website.md +0 -404
- package/docs/09-mobile.md +0 -270
- package/docs/10-api.md +0 -264
- package/docs/11-jobs.md +0 -157
- package/docs/12-cli.md +0 -677
- package/docs/13-elements.md +0 -248
- package/docs/14-assertions.md +0 -314
- package/docs/15-tokens.md +0 -253
- package/docs/16-contracts.md +0 -438
- package/docs/17-services.md +0 -188
- package/docs/18-conventions.md +0 -332
- package/docs/19-linting.md +0 -512
- package/docs/decisions/001-v9-conventions-on-three-channels.md +0 -50
- package/docs/decisions/002-the-statique-channel-holds-only-what-upstream-does-not.md +0 -49
- package/docs/decisions/003-a-rendered-component-is-a-facet-on-vitest-browser-mode.md +0 -84
- package/docs/decisions/004-a-module-against-real-services-or-a-golden-is-the-integration-facet.md +0 -81
- package/docs/decisions/005-the-doubles-ladder-is-closed.md +0 -86
- package/docs/decisions/006-the-catalogue-reaches-a-file-by-its-role.md +0 -245
- package/docs/decisions/007-compose-mode-leaves-the-package.md +0 -53
- package/docs/decisions/008-vitest-5-node-24-and-the-native-seams-as-optional-peers.md +0 -75
- package/docs/decisions/_template.md +0 -22
- package/docs/reference/classes/Matcher.md +0 -120
- package/docs/reference/classes/ProcessHandle.md +0 -368
- package/docs/reference/functions/defineContract.md +0 -39
- package/docs/reference/functions/defineContracts.md +0 -36
- package/docs/reference/functions/disabled.md +0 -28
- package/docs/reference/functions/enabled.md +0 -20
- package/docs/reference/functions/focused.md +0 -26
- package/docs/reference/functions/mockOf.md +0 -79
- package/docs/reference/functions/postgres.md +0 -31
- package/docs/reference/functions/process.md +0 -33
- package/docs/reference/functions/redis.md +0 -28
- package/docs/reference/functions/required.md +0 -41
- package/docs/reference/functions/selected.md +0 -26
- package/docs/reference/functions/sqlite.md +0 -33
- package/docs/reference/functions/testId.md +0 -20
- package/docs/reference/functions/text.md +0 -40
- package/docs/reference/functions/valued.md +0 -28
- package/docs/reference/functions/waitUntil.md +0 -46
- package/docs/reference/functions/within.md +0 -29
- package/docs/reference/index.md +0 -196
- package/docs/reference/interfaces/BaseResult.md +0 -87
- package/docs/reference/interfaces/CallResult.md +0 -160
- package/docs/reference/interfaces/CaptureScope.md +0 -82
- package/docs/reference/interfaces/CliResult.md +0 -253
- package/docs/reference/interfaces/ContainerAccessor.md +0 -166
- package/docs/reference/interfaces/DirectoryAccessor.md +0 -33
- package/docs/reference/interfaces/FetchResult.md +0 -180
- package/docs/reference/interfaces/FilesystemAccessor.md +0 -46
- package/docs/reference/interfaces/HttpResult.md +0 -126
- package/docs/reference/interfaces/JsonAccessor.md +0 -46
- package/docs/reference/interfaces/PageResult.md +0 -356
- package/docs/reference/interfaces/RenderResult.md +0 -72
- package/docs/reference/interfaces/RenderedText.md +0 -110
- package/docs/reference/interfaces/ResponseAccessor.md +0 -47
- package/docs/reference/interfaces/ScreenResult.md +0 -128
- package/docs/reference/interfaces/TableAccessor.md +0 -26
- package/docs/reference/interfaces/TextAccessor.md +0 -96
- package/docs/reference/type-aliases/ApiHandle.md +0 -68
- package/docs/reference/type-aliases/ApiSpecification.md +0 -213
- package/docs/reference/type-aliases/ApiSpecificationOptions.md +0 -68
- package/docs/reference/type-aliases/BrowserConsoleMessage.md +0 -30
- package/docs/reference/type-aliases/BrowserLinkElement.md +0 -50
- package/docs/reference/type-aliases/BrowserMetaElement.md +0 -40
- package/docs/reference/type-aliases/BrowserOpenOptions.md +0 -87
- package/docs/reference/type-aliases/BrowserPage.md +0 -132
- package/docs/reference/type-aliases/BrowserPort.md +0 -51
- package/docs/reference/type-aliases/CliEnv.md +0 -11
- package/docs/reference/type-aliases/CliHandle.md +0 -80
- package/docs/reference/type-aliases/CliInput.md +0 -36
- package/docs/reference/type-aliases/CliOutput.md +0 -46
- package/docs/reference/type-aliases/CliPort.md +0 -58
- package/docs/reference/type-aliases/CliSpecification.md +0 -144
- package/docs/reference/type-aliases/CliSpecificationOptions.md +0 -134
- package/docs/reference/type-aliases/ComponentChain.md +0 -156
- package/docs/reference/type-aliases/ComponentScenario.md +0 -20
- package/docs/reference/type-aliases/ComponentUi.md +0 -47
- package/docs/reference/type-aliases/ComponentVisitor.md +0 -10
- package/docs/reference/type-aliases/ContainerPort.md +0 -113
- package/docs/reference/type-aliases/Contract.md +0 -68
- package/docs/reference/type-aliases/ContractInput.md +0 -13
- package/docs/reference/type-aliases/ContractRequest.md +0 -95
- package/docs/reference/type-aliases/ContractResponder.md +0 -23
- package/docs/reference/type-aliases/ContractResponse.md +0 -78
- package/docs/reference/type-aliases/ContractResponseValue.md +0 -13
- package/docs/reference/type-aliases/Contracts.md +0 -48
- package/docs/reference/type-aliases/DatabaseKeys.md +0 -16
- package/docs/reference/type-aliases/DatabasePort.md +0 -72
- package/docs/reference/type-aliases/DeviceOpenOptions.md +0 -46
- package/docs/reference/type-aliases/DevicePort.md +0 -51
- package/docs/reference/type-aliases/DeviceScreen.md +0 -32
- package/docs/reference/type-aliases/DeviceTimeouts.md +0 -39
- package/docs/reference/type-aliases/DockerSpecConfig.md +0 -43
- package/docs/reference/type-aliases/DomMount.md +0 -21
- package/docs/reference/type-aliases/ElementKind.md +0 -27
- package/docs/reference/type-aliases/ElementMatch.md +0 -74
- package/docs/reference/type-aliases/ElementOptions.md +0 -29
- package/docs/reference/type-aliases/ElementRef.md +0 -128
- package/docs/reference/type-aliases/ExecOptions.md +0 -37
- package/docs/reference/type-aliases/FileAccessor.md +0 -54
- package/docs/reference/type-aliases/HonoApp.md +0 -31
- package/docs/reference/type-aliases/HttpContractFilter.md +0 -49
- package/docs/reference/type-aliases/HttpResponseInit.md +0 -46
- package/docs/reference/type-aliases/HttpStreamInit.md +0 -58
- package/docs/reference/type-aliases/IntegrationHandle.md +0 -46
- package/docs/reference/type-aliases/IntegrationSpecification.md +0 -111
- package/docs/reference/type-aliases/IntegrationSpecificationOptions.md +0 -42
- package/docs/reference/type-aliases/Intercept.md +0 -19
- package/docs/reference/type-aliases/InterceptOptions.md +0 -28
- package/docs/reference/type-aliases/InterceptScope.md +0 -11
- package/docs/reference/type-aliases/IsolationStrategy.md +0 -75
- package/docs/reference/type-aliases/JobHandle.md +0 -34
- package/docs/reference/type-aliases/JobsHandle.md +0 -57
- package/docs/reference/type-aliases/JobsResult.md +0 -16
- package/docs/reference/type-aliases/JobsSpecification.md +0 -94
- package/docs/reference/type-aliases/JobsSpecificationOptions.md +0 -56
- package/docs/reference/type-aliases/LandmarkKind.md +0 -20
- package/docs/reference/type-aliases/LiterateRunFlags.md +0 -27
- package/docs/reference/type-aliases/LiterateServeRegistration.md +0 -70
- package/docs/reference/type-aliases/MatchFixtureOptions.md +0 -27
- package/docs/reference/type-aliases/MatchableRequest.md +0 -61
- package/docs/reference/type-aliases/MatcherKind.md +0 -34
- package/docs/reference/type-aliases/MobileBackendOptions.md +0 -28
- package/docs/reference/type-aliases/MobileElementKind.md +0 -14
- package/docs/reference/type-aliases/MobileElementMatch.md +0 -60
- package/docs/reference/type-aliases/MobileElementRef.md +0 -20
- package/docs/reference/type-aliases/MobileHandle.md +0 -65
- package/docs/reference/type-aliases/MobileScenario.md +0 -20
- package/docs/reference/type-aliases/MobileSpecification.md +0 -51
- package/docs/reference/type-aliases/MobileSpecificationOptions.md +0 -126
- package/docs/reference/type-aliases/MobileVisitor.md +0 -81
- package/docs/reference/type-aliases/MockOfOptions.md +0 -26
- package/docs/reference/type-aliases/MockPort.md +0 -10
- package/docs/reference/type-aliases/PinnedClock.md +0 -10
- package/docs/reference/type-aliases/PostgresOptions.md +0 -32
- package/docs/reference/type-aliases/ProcessOptions.md +0 -112
- package/docs/reference/type-aliases/RedisOptions.md +0 -20
- package/docs/reference/type-aliases/RenderSubject.md +0 -12
- package/docs/reference/type-aliases/ScreenNode.md +0 -67
- package/docs/reference/type-aliases/ServerPort.md +0 -36
- package/docs/reference/type-aliases/ServerResponse.md +0 -46
- package/docs/reference/type-aliases/ServerSpec.md +0 -21
- package/docs/reference/type-aliases/ServiceHandle.md +0 -216
- package/docs/reference/type-aliases/ServiceRecord.md +0 -12
- package/docs/reference/type-aliases/SpecDocument.md +0 -84
- package/docs/reference/type-aliases/SpecEnvToken.md +0 -21
- package/docs/reference/type-aliases/SpecFileAssertion.md +0 -27
- package/docs/reference/type-aliases/SpecFixture.md +0 -30
- package/docs/reference/type-aliases/SpecKind.md +0 -8
- package/docs/reference/type-aliases/SpecRun.md +0 -108
- package/docs/reference/type-aliases/SpecServeEntry.md +0 -40
- package/docs/reference/type-aliases/SpecStream.md +0 -32
- package/docs/reference/type-aliases/SpecificationConfig.md +0 -290
- package/docs/reference/type-aliases/SqliteOptions.md +0 -36
- package/docs/reference/type-aliases/SseEvent.md +0 -58
- package/docs/reference/type-aliases/StreamBody.md +0 -61
- package/docs/reference/type-aliases/TextFilter.md +0 -22
- package/docs/reference/type-aliases/VisitScenario.md +0 -20
- package/docs/reference/type-aliases/Visitor.md +0 -215
- package/docs/reference/type-aliases/WaitUntilOptions.md +0 -46
- package/docs/reference/type-aliases/WebsiteBackendOptions.md +0 -37
- package/docs/reference/type-aliases/WebsiteHandle.md +0 -51
- package/docs/reference/type-aliases/WebsiteSpecification.md +0 -118
- package/docs/reference/type-aliases/WebsiteSpecificationOptions.md +0 -40
- package/docs/reference/variables/anthropic.md +0 -19
- package/docs/reference/variables/banner.md +0 -10
- package/docs/reference/variables/button.md +0 -10
- package/docs/reference/variables/clock.md +0 -16
- package/docs/reference/variables/complementary.md +0 -10
- package/docs/reference/variables/component.md +0 -10
- package/docs/reference/variables/content.md +0 -10
- package/docs/reference/variables/contentinfo.md +0 -10
- package/docs/reference/variables/dialog.md +0 -10
- package/docs/reference/variables/field.md +0 -10
- package/docs/reference/variables/form.md +0 -10
- package/docs/reference/variables/heading.md +0 -10
- package/docs/reference/variables/http.md +0 -40
- package/docs/reference/variables/intercept.md +0 -11
- package/docs/reference/variables/link.md +0 -10
- package/docs/reference/variables/listitem.md +0 -10
- package/docs/reference/variables/main.md +0 -10
- package/docs/reference/variables/match.md +0 -8
- package/docs/reference/variables/navigation.md +0 -10
- package/docs/reference/variables/openai.md +0 -21
- package/docs/reference/variables/option.md +0 -17
- package/docs/reference/variables/region.md +0 -10
- package/docs/reference/variables/row.md +0 -10
- package/docs/reference/variables/search.md +0 -10
- package/docs/reference/variables/specification.md +0 -8
- package/docs/reference/variables/status.md +0 -10
- package/docs/reference/variables/table.md +0 -10
|
@@ -0,0 +1,1033 @@
|
|
|
1
|
+
import { n as parsedJson, t as isRecord } from "./shared-json-shape.js";
|
|
2
|
+
import { c as isRelativePath } from "./shared-spec-grammar.js";
|
|
3
|
+
import { a as literalOf, n as argumentsOf$1, r as closingOf } from "./shared-source-scan.js";
|
|
4
|
+
import { createRequire } from "node:module";
|
|
5
|
+
import { GROUND_FOLDERS, PRUNED, RENAMED_FOLDERS, RETIRED_SHARED_FOLDERS, SETUP_FOLDER, TESTS_FOLDER, blankComments, folderChanged, holdsManifest, isFile, isGroundFolder, isProjectRoot, isSpecialFolder, nearestRoot, readFileCached, treeEntries, treeKind } from "@jterrazz/typescript/rules";
|
|
6
|
+
import { existsSync, readFileSync, readdirSync, rmSync } from "node:fs";
|
|
7
|
+
import { basename, dirname, isAbsolute, join, relative, resolve } from "node:path";
|
|
8
|
+
//#region lint/kit/project/settings-file.ts
|
|
9
|
+
const SETTINGS_FILE = "jterrazz.config.ts";
|
|
10
|
+
//#endregion
|
|
11
|
+
//#region lint/kit/project/specs-anchor.ts
|
|
12
|
+
function segments(path) {
|
|
13
|
+
return path.split(/[/\\]/u).filter(Boolean);
|
|
14
|
+
}
|
|
15
|
+
/** The NEAREST `specs` ancestor, never above the project root: a checkout living under `~/specs/` must not read as one whole specs tree. */
|
|
16
|
+
function specsAnchor(filename) {
|
|
17
|
+
const relative = [basename(filename)];
|
|
18
|
+
let directory = dirname(filename);
|
|
19
|
+
for (;;) {
|
|
20
|
+
if (basename(directory) === "specs") return {
|
|
21
|
+
directory,
|
|
22
|
+
relative
|
|
23
|
+
};
|
|
24
|
+
if (isProjectRoot(directory)) return;
|
|
25
|
+
const parent = dirname(directory);
|
|
26
|
+
if (parent === directory) return;
|
|
27
|
+
relative.unshift(basename(directory));
|
|
28
|
+
directory = parent;
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
function isUnderSpecs(filename) {
|
|
32
|
+
return specsAnchor(filename) !== void 0;
|
|
33
|
+
}
|
|
34
|
+
//#endregion
|
|
35
|
+
//#region lint/kit/project/role.ts
|
|
36
|
+
const GROUND = new Set(GROUND_FOLDERS);
|
|
37
|
+
function strayTestRootOf(filename) {
|
|
38
|
+
const parts = projectSegments(filename);
|
|
39
|
+
const first = parts.length > 1 ? parts[0] : void 0;
|
|
40
|
+
if (projectRootOf(dirname(filename)) !== void 0 && (first === "tests" || first === "test")) return first;
|
|
41
|
+
return null;
|
|
42
|
+
}
|
|
43
|
+
/** The order matters: a `*.specification.ts` is one wherever it sits, and `.tsx` versus `.ts` tells a rendered unit apart as the two project helpers do. Ground swallows only what carries no suffix of its own. */
|
|
44
|
+
function roleOf(filename) {
|
|
45
|
+
const parts = projectSegments(filename);
|
|
46
|
+
const base = parts.at(-1) ?? "";
|
|
47
|
+
const identity = {
|
|
48
|
+
inSpecs: isUnderSpecs(filename),
|
|
49
|
+
inTests: parts.slice(0, -1).includes(TESTS_FOLDER),
|
|
50
|
+
strayTestRoot: strayTestRootOf(filename)
|
|
51
|
+
};
|
|
52
|
+
if (base.endsWith(".specification.ts") || base.endsWith(".specification.tsx")) return {
|
|
53
|
+
...identity,
|
|
54
|
+
role: "specification"
|
|
55
|
+
};
|
|
56
|
+
if (base.endsWith(".spec.yaml")) return {
|
|
57
|
+
...identity,
|
|
58
|
+
role: "document"
|
|
59
|
+
};
|
|
60
|
+
if (base.endsWith(".spec.ts")) return {
|
|
61
|
+
...identity,
|
|
62
|
+
role: "spec"
|
|
63
|
+
};
|
|
64
|
+
if (base.endsWith(".test.tsx")) return {
|
|
65
|
+
...identity,
|
|
66
|
+
role: "component"
|
|
67
|
+
};
|
|
68
|
+
if (base.endsWith(".test.ts")) return {
|
|
69
|
+
...identity,
|
|
70
|
+
role: "module"
|
|
71
|
+
};
|
|
72
|
+
if (base === "jterrazz.config.ts") return {
|
|
73
|
+
...identity,
|
|
74
|
+
role: "config"
|
|
75
|
+
};
|
|
76
|
+
if (parts.slice(0, -1).some((segment) => GROUND.has(segment))) return {
|
|
77
|
+
...identity,
|
|
78
|
+
role: "ground"
|
|
79
|
+
};
|
|
80
|
+
if (parts.slice(0, -1).includes("stubs")) return {
|
|
81
|
+
...identity,
|
|
82
|
+
role: "stub"
|
|
83
|
+
};
|
|
84
|
+
return {
|
|
85
|
+
...identity,
|
|
86
|
+
role: "other"
|
|
87
|
+
};
|
|
88
|
+
}
|
|
89
|
+
const TEST_ROLES = /* @__PURE__ */ new Set([
|
|
90
|
+
"component",
|
|
91
|
+
"module",
|
|
92
|
+
"spec"
|
|
93
|
+
]);
|
|
94
|
+
/** For the passes that walk a tree and hold only a name: one vocabulary with `roleOf`. */
|
|
95
|
+
function isTestFileName(name) {
|
|
96
|
+
return name.endsWith(".test.ts") || name.endsWith(".test.tsx") || name.endsWith(".spec.ts");
|
|
97
|
+
}
|
|
98
|
+
/** A `.test.tsx` renders and is not one. */
|
|
99
|
+
function isNodeTestFileName(name) {
|
|
100
|
+
return name.endsWith(".test.ts") || name.endsWith(".spec.ts");
|
|
101
|
+
}
|
|
102
|
+
function isTestRole(role) {
|
|
103
|
+
return TEST_ROLES.has(role);
|
|
104
|
+
}
|
|
105
|
+
function packageRootOf(directory) {
|
|
106
|
+
return nearestRoot(directory, holdsManifest);
|
|
107
|
+
}
|
|
108
|
+
const projectRoots = /* @__PURE__ */ new Map();
|
|
109
|
+
/** Remembered per folder, and forgotten for one directory once its own tree listing changes: a `package.json` written or removed there is what the memo must see. */
|
|
110
|
+
function projectRootOf(directory) {
|
|
111
|
+
if (!folderChanged(directory) && projectRoots.has(directory)) return projectRoots.get(directory);
|
|
112
|
+
const root = nearestRoot(directory, isProjectRoot);
|
|
113
|
+
projectRoots.set(directory, root);
|
|
114
|
+
return root;
|
|
115
|
+
}
|
|
116
|
+
/** A path no package and no repository holds (a rule's test case, `/repo/...`) is read whole. A workspace member named `specs` is the specs tree itself, so its folder name stays the first segment of what it holds. */
|
|
117
|
+
function projectSegments(filename) {
|
|
118
|
+
const root = projectRootOf(dirname(filename));
|
|
119
|
+
if (root === void 0) return segments(filename);
|
|
120
|
+
return [...basename(root) === "specs" ? ["specs"] : [], ...segments(relative(root, filename))];
|
|
121
|
+
}
|
|
122
|
+
//#endregion
|
|
123
|
+
//#region lint/kit/project/ast.ts
|
|
124
|
+
function fieldOf(value, key) {
|
|
125
|
+
return typeof value === "object" && value !== null ? Reflect.get(value, key) : void 0;
|
|
126
|
+
}
|
|
127
|
+
function stringValue(node) {
|
|
128
|
+
if (node === void 0) return;
|
|
129
|
+
if (node.type === "Literal" && typeof node.value === "string") return node.value;
|
|
130
|
+
if (node.type !== "TemplateLiteral" || childList(node, "expressions").length > 0) return;
|
|
131
|
+
const [text] = childList(node, "quasis");
|
|
132
|
+
return cookedText(text);
|
|
133
|
+
}
|
|
134
|
+
function cookedText(quasi) {
|
|
135
|
+
const cooked = fieldOf(quasi?.value, "cooked");
|
|
136
|
+
return typeof cooked === "string" ? cooked : void 0;
|
|
137
|
+
}
|
|
138
|
+
function memberPropertyName(node) {
|
|
139
|
+
if (node?.type !== "MemberExpression" || node.computed === true) return;
|
|
140
|
+
const property = child(node, "property");
|
|
141
|
+
return property?.type === "Identifier" ? stringField(property, "name") ?? "" : void 0;
|
|
142
|
+
}
|
|
143
|
+
const CHAIN_LINKS = {
|
|
144
|
+
AwaitExpression: "argument",
|
|
145
|
+
CallExpression: "callee",
|
|
146
|
+
ChainExpression: "expression",
|
|
147
|
+
MemberExpression: "object"
|
|
148
|
+
};
|
|
149
|
+
function chainRootName(node) {
|
|
150
|
+
let current = node;
|
|
151
|
+
while (current !== void 0) {
|
|
152
|
+
if (current.type === "Identifier") return stringField(current, "name") ?? "";
|
|
153
|
+
const link = CHAIN_LINKS[current.type];
|
|
154
|
+
if (link === void 0) return;
|
|
155
|
+
current = child(current, link);
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
function specificationMember(callNode) {
|
|
159
|
+
const callee = child(callNode, "callee");
|
|
160
|
+
if (callee?.type !== "MemberExpression") return;
|
|
161
|
+
const object = child(callee, "object");
|
|
162
|
+
if (object?.type !== "Identifier" || object.name !== "specification") return;
|
|
163
|
+
return memberPropertyName(callee);
|
|
164
|
+
}
|
|
165
|
+
/** Only an object, among the nodes read here, lists properties. */
|
|
166
|
+
function findProperty(objectNode, name) {
|
|
167
|
+
return childList(objectNode, "properties").find((property) => propertyKeyName(property) === name);
|
|
168
|
+
}
|
|
169
|
+
function propertyKeyName(property) {
|
|
170
|
+
if (property.type !== "Property" || property.computed === true) return;
|
|
171
|
+
const key = child(property, "key");
|
|
172
|
+
if (key?.type === "Identifier") return stringField(key, "name") ?? "";
|
|
173
|
+
if (key?.type === "Literal") return String(key.value);
|
|
174
|
+
}
|
|
175
|
+
function walk(root, visit) {
|
|
176
|
+
visit(root);
|
|
177
|
+
for (const key of Object.keys(root)) {
|
|
178
|
+
if (key === "parent") continue;
|
|
179
|
+
const value = root[key];
|
|
180
|
+
if (Array.isArray(value)) {
|
|
181
|
+
for (const item of value) if (isNode(item)) walk(item, visit);
|
|
182
|
+
} else if (isNode(value)) walk(value, visit);
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
function nodeStart(node) {
|
|
186
|
+
return typeof node?.start === "number" ? node.start : -1;
|
|
187
|
+
}
|
|
188
|
+
function isNode(value) {
|
|
189
|
+
return typeof value === "object" && value !== null && "type" in value && typeof value.type === "string";
|
|
190
|
+
}
|
|
191
|
+
function child(node, key) {
|
|
192
|
+
const value = node?.[key];
|
|
193
|
+
return isNode(value) ? value : void 0;
|
|
194
|
+
}
|
|
195
|
+
function childList(node, key) {
|
|
196
|
+
const value = node?.[key];
|
|
197
|
+
return Array.isArray(value) ? value.filter((item) => isNode(item)) : [];
|
|
198
|
+
}
|
|
199
|
+
function stringField(node, key) {
|
|
200
|
+
const value = node?.[key];
|
|
201
|
+
return typeof value === "string" ? value : void 0;
|
|
202
|
+
}
|
|
203
|
+
function identifierName(node) {
|
|
204
|
+
if (node?.type !== "Identifier") return;
|
|
205
|
+
const { name } = node;
|
|
206
|
+
return typeof name === "string" ? name : void 0;
|
|
207
|
+
}
|
|
208
|
+
function nodeEnd(node) {
|
|
209
|
+
return typeof node?.end === "number" ? node.end : -1;
|
|
210
|
+
}
|
|
211
|
+
/** `result.stdout`, `result.meta()`; `undefined` for a computed access: two subjects a rule cannot spell must never be equated. */
|
|
212
|
+
function memberPath(node) {
|
|
213
|
+
if (node === void 0) return;
|
|
214
|
+
if (node.type === "Identifier") return identifierName(node);
|
|
215
|
+
if (node.type === "CallExpression") {
|
|
216
|
+
const callee = memberPath(child(node, "callee"));
|
|
217
|
+
return callee === void 0 || childList(node, "arguments").length > 0 ? void 0 : `${callee}()`;
|
|
218
|
+
}
|
|
219
|
+
if (node.type !== "MemberExpression") return;
|
|
220
|
+
const object = memberPath(child(node, "object"));
|
|
221
|
+
const property = memberPropertyName(node);
|
|
222
|
+
return object === void 0 || property === void 0 ? void 0 : `${object}.${property}`;
|
|
223
|
+
}
|
|
224
|
+
/** `new Date()` with an argument is pinned; its zero-argument form, `Date.now()`, `performance.now()`, `Math.random()` and `randomUUID()` answer differently on the next run. */
|
|
225
|
+
function isSampledValue(node) {
|
|
226
|
+
if (node.type === "NewExpression") return identifierName(child(node, "callee")) === "Date" && childList(node, "arguments").length === 0;
|
|
227
|
+
if (node.type !== "CallExpression") return false;
|
|
228
|
+
const path = memberPath(child(node, "callee"));
|
|
229
|
+
if (path === void 0) return false;
|
|
230
|
+
return path === "Date.now" || path === "performance.now" || path === "Math.random" || path === "randomUUID" || path.endsWith(".randomUUID");
|
|
231
|
+
}
|
|
232
|
+
//#endregion
|
|
233
|
+
//#region lint/kit/project/command-words.ts
|
|
234
|
+
const COMMAND_WORD = /^[a-z][a-z0-9-]*$/u;
|
|
235
|
+
/** Without the runner's declared list, the first word alone: a positional argument is never read as a subcommand. */
|
|
236
|
+
function commandWords(line, commands) {
|
|
237
|
+
const words = line.trim().split(/\s+/u);
|
|
238
|
+
if (commands === void 0) {
|
|
239
|
+
const [first = ""] = words;
|
|
240
|
+
return COMMAND_WORD.test(first) ? [first] : [];
|
|
241
|
+
}
|
|
242
|
+
const named = [];
|
|
243
|
+
for (const word of words) {
|
|
244
|
+
if (!commands.includes(word)) break;
|
|
245
|
+
named.push(word);
|
|
246
|
+
}
|
|
247
|
+
return named;
|
|
248
|
+
}
|
|
249
|
+
const CLI_CONSTRUCTOR = /\bspecification\s*\.\s*cli\s*\(/u;
|
|
250
|
+
function wordsOf(text, open) {
|
|
251
|
+
return argumentsOf$1(text.slice(open + 1, closingOf(text, open))).flatMap((entry) => {
|
|
252
|
+
const word = literalOf(entry);
|
|
253
|
+
return word === void 0 ? [] : [word];
|
|
254
|
+
});
|
|
255
|
+
}
|
|
256
|
+
function constantWords(text, name) {
|
|
257
|
+
const escaped = name.replaceAll("$", String.raw`\$`);
|
|
258
|
+
const pattern = String.raw`\bconst\s+${escaped}\s*(?::[^=]+)?=\s*\[`;
|
|
259
|
+
const binding = new RegExp(pattern, "u").exec(text);
|
|
260
|
+
return binding === null ? void 0 : wordsOf(text, binding.index + binding[0].length - 1);
|
|
261
|
+
}
|
|
262
|
+
/** Written as an array or bound to a `const` of the same file (`commands: COMMANDS`, `{ commands }`). */
|
|
263
|
+
function declaredCommands(door) {
|
|
264
|
+
const text = blankComments(door);
|
|
265
|
+
const call = CLI_CONSTRUCTOR.exec(text);
|
|
266
|
+
if (call === null) return;
|
|
267
|
+
const open = call.index + call[0].length - 1;
|
|
268
|
+
const [, options = ""] = argumentsOf$1(text.slice(open + 1, closingOf(text, open)));
|
|
269
|
+
const list = /[{,\s]commands\s*:\s*\[/u.exec(options);
|
|
270
|
+
if (list !== null) return wordsOf(options, list.index + list[0].length - 1);
|
|
271
|
+
const named = /[{,\s]commands\s*:\s*(?<name>[A-Za-z_$][\w$]*)/u.exec(options)?.groups?.name ?? (/[{,\s]commands\s*(?=[,}])/u.test(options) ? "commands" : void 0);
|
|
272
|
+
return named === void 0 ? void 0 : constantWords(text, named);
|
|
273
|
+
}
|
|
274
|
+
//#endregion
|
|
275
|
+
//#region lint/kit/project/spec-chains.ts
|
|
276
|
+
const SUBJECT_SPECIFICATIONS = [
|
|
277
|
+
"api",
|
|
278
|
+
"cli",
|
|
279
|
+
"job",
|
|
280
|
+
"mobile",
|
|
281
|
+
"website"
|
|
282
|
+
];
|
|
283
|
+
function chainAt(text, handle, start) {
|
|
284
|
+
const calls = [];
|
|
285
|
+
let index = start + handle.length;
|
|
286
|
+
for (;;) {
|
|
287
|
+
const member = /^\s*\.\s*(?<name>[A-Za-z_$][\w$]*)\s*\(/u.exec(text.slice(index));
|
|
288
|
+
if (member === null) return {
|
|
289
|
+
calls,
|
|
290
|
+
end: index,
|
|
291
|
+
handle,
|
|
292
|
+
index: start
|
|
293
|
+
};
|
|
294
|
+
const { name = "" } = member.groups ?? {};
|
|
295
|
+
const open = index + member[0].length - 1;
|
|
296
|
+
const close = closingOf(text, open);
|
|
297
|
+
calls.push({
|
|
298
|
+
args: text.slice(open + 1, close),
|
|
299
|
+
name
|
|
300
|
+
});
|
|
301
|
+
index = close + 1;
|
|
302
|
+
}
|
|
303
|
+
}
|
|
304
|
+
const SPECIFICATION_DOOR = /import\s*\{(?<names>[^}]*)\}\s*from\s*['"](?<from>[^'"]*\.specification(?:\.[jt]s)?)['"]/gu;
|
|
305
|
+
const VIEW = /const\s+(?<view>[A-Za-z_$][\w$]*)\s*=\s*(?<from>[A-Za-z_$][\w$]*)\s*\.\s*with\s*\(/gu;
|
|
306
|
+
function importedDoors(text, wanted) {
|
|
307
|
+
const doors = /* @__PURE__ */ new Map();
|
|
308
|
+
for (const found of text.matchAll(SPECIFICATION_DOOR)) {
|
|
309
|
+
const { from = "", names = "" } = found.groups ?? {};
|
|
310
|
+
for (const name of names.split(",")) {
|
|
311
|
+
const [imported = "", local = imported] = name.trim().split(/\s+as\s+/u);
|
|
312
|
+
if (wanted(imported)) doors.set(local, from);
|
|
313
|
+
}
|
|
314
|
+
}
|
|
315
|
+
return doors;
|
|
316
|
+
}
|
|
317
|
+
function doorsWanted(text, wanted) {
|
|
318
|
+
const doors = importedDoors(text, wanted);
|
|
319
|
+
const views = [...text.matchAll(VIEW)];
|
|
320
|
+
for (let size = -1; size !== doors.size;) {
|
|
321
|
+
({size} = doors);
|
|
322
|
+
for (const found of views) {
|
|
323
|
+
const { from = "", view = "" } = found.groups ?? {};
|
|
324
|
+
const door = doors.get(from);
|
|
325
|
+
if (door !== void 0) doors.set(view, door);
|
|
326
|
+
}
|
|
327
|
+
}
|
|
328
|
+
return doors;
|
|
329
|
+
}
|
|
330
|
+
function handlesOf(text) {
|
|
331
|
+
return [...doorsWanted(text, (imported) => SUBJECT_SPECIFICATIONS.includes(imported)).keys()];
|
|
332
|
+
}
|
|
333
|
+
function chainsOf(text, handles) {
|
|
334
|
+
if (handles.length === 0) return [];
|
|
335
|
+
const pattern = new RegExp(String.raw`(?<![.\w$])(?:${handles.join("|")})(?=\s*\.)`, "gu");
|
|
336
|
+
return [...text.matchAll(pattern)].map((found) => chainAt(text, found[0], found.index));
|
|
337
|
+
}
|
|
338
|
+
function doorsOf(text, specification) {
|
|
339
|
+
return Object.fromEntries(doorsWanted(text, (imported) => imported === specification));
|
|
340
|
+
}
|
|
341
|
+
//#endregion
|
|
342
|
+
//#region lint/kit/project/subject.ts
|
|
343
|
+
const NO_READING = Object.freeze({
|
|
344
|
+
collections: [],
|
|
345
|
+
methodless: []
|
|
346
|
+
});
|
|
347
|
+
const LITERAL_SEGMENT = /^[a-z][a-z._-]*$/u;
|
|
348
|
+
function segmentsOf(path) {
|
|
349
|
+
return (path.split(/[?#]/u)[0] ?? "").split("/").filter((segment) => segment !== "");
|
|
350
|
+
}
|
|
351
|
+
function isLiteral(segment) {
|
|
352
|
+
return LITERAL_SEGMENT.test(segment) && !segment.includes("\0");
|
|
353
|
+
}
|
|
354
|
+
/** Every segment that is a value (a hole, a number, an id, a symbol) is written `:id`. */
|
|
355
|
+
function pathTemplate(path) {
|
|
356
|
+
return `/${segmentsOf(path).map((segment) => isLiteral(segment) ? segment : ":id").join("/")}`;
|
|
357
|
+
}
|
|
358
|
+
function isCollection(word, collections) {
|
|
359
|
+
return collections.includes(word) || word.endsWith("s") && !/(?:ss|us|is)$/u.test(word);
|
|
360
|
+
}
|
|
361
|
+
function actionOf(method, resource, item, collections) {
|
|
362
|
+
if (method === "GET") return !item && isCollection(resource, collections) ? "list" : "get";
|
|
363
|
+
if (method === "POST") return "create";
|
|
364
|
+
if (method === "PUT" || method === "PATCH") return "update";
|
|
365
|
+
return method.toLowerCase();
|
|
366
|
+
}
|
|
367
|
+
function followsACollection(segments, index, collections) {
|
|
368
|
+
return isCollection(segments[index - 1] ?? "", collections);
|
|
369
|
+
}
|
|
370
|
+
/** `btc` in `/indicators/btc/chart`. The last segment is read as the route's: after a collection it names a sub-route far more often than an item. */
|
|
371
|
+
function ambiguousSegment(subject, reading = NO_READING) {
|
|
372
|
+
if (subject.specification !== "api" || subject.template === true) return;
|
|
373
|
+
const segments = segmentsOf(subject.path);
|
|
374
|
+
const { collections } = reading;
|
|
375
|
+
return segments.find((segment, index) => index < segments.length - 1 && isLiteral(segment) && followsACollection(segments, index, collections));
|
|
376
|
+
}
|
|
377
|
+
function routeFolder(method, path, collections) {
|
|
378
|
+
const segments = segmentsOf(pathTemplate(path));
|
|
379
|
+
const literals = segments.filter((segment) => segment !== ":id");
|
|
380
|
+
if (literals.length === 0) return;
|
|
381
|
+
if (method === "ALL" || literals.length > 1) return literals.join("/");
|
|
382
|
+
const [resource = ""] = literals;
|
|
383
|
+
return `${resource}/${actionOf(method, resource, segments.at(-1) === ":id", collections)}`;
|
|
384
|
+
}
|
|
385
|
+
function underBase(path, basePath) {
|
|
386
|
+
if (basePath === void 0) return path;
|
|
387
|
+
if (path === basePath) return "/";
|
|
388
|
+
return path.startsWith(`${basePath}/`) ? path.slice(basePath.length) : path;
|
|
389
|
+
}
|
|
390
|
+
/** Nothing for a subject with no standard name: the root route, a cli run with no command word, a page, a journey. */
|
|
391
|
+
function subjectFolder(subject, reading = NO_READING) {
|
|
392
|
+
if (subject.specification === "api") {
|
|
393
|
+
const mounted = {
|
|
394
|
+
...subject,
|
|
395
|
+
path: underBase(subject.path, reading.basePath)
|
|
396
|
+
};
|
|
397
|
+
return ambiguousSegment(mounted, reading) === void 0 ? routeFolder(subject.method, mounted.path, reading.collections) : void 0;
|
|
398
|
+
}
|
|
399
|
+
if (subject.specification === "cli") return subject.command.length === 0 ? void 0 : subject.command.join("/");
|
|
400
|
+
return subject.specification === "job" ? subject.job : void 0;
|
|
401
|
+
}
|
|
402
|
+
/** `GET /events/:id`, `trigger digest`, ... */
|
|
403
|
+
function subjectName(subject) {
|
|
404
|
+
if (subject.specification === "api") return `${subject.method} ${pathTemplate(subject.path)}`;
|
|
405
|
+
if (subject.specification === "cli") return subject.command.length === 0 ? "exec (no command)" : `exec ${subject.command.join(" ")}`;
|
|
406
|
+
if (subject.specification === "job") return `trigger ${subject.job}`;
|
|
407
|
+
return subject.specification === "mobile" ? `open ${subject.journey}` : `visit ${subject.page}`;
|
|
408
|
+
}
|
|
409
|
+
const HTTP_VERBS = {
|
|
410
|
+
delete: "DELETE",
|
|
411
|
+
get: "GET",
|
|
412
|
+
patch: "PATCH",
|
|
413
|
+
post: "POST",
|
|
414
|
+
put: "PUT"
|
|
415
|
+
};
|
|
416
|
+
const REQUEST_LINE = /^\s*(?<method>DELETE|GET|HEAD|OPTIONS|PATCH|POST|PUT)\s+(?<path>\S+)/u;
|
|
417
|
+
const REQUEST_FILE = /^file\(\s*(?<quote>['"])(?<path>[^'"]*)\k<quote>\s*\)$/u;
|
|
418
|
+
function requestLineOf(text) {
|
|
419
|
+
const { method = "", path = "/" } = REQUEST_LINE.exec(text)?.groups ?? {};
|
|
420
|
+
const verb = HTTP_VERBS[method.toLowerCase()];
|
|
421
|
+
return verb === void 0 ? [] : [{
|
|
422
|
+
specification: "api",
|
|
423
|
+
method: verb,
|
|
424
|
+
path,
|
|
425
|
+
template: true
|
|
426
|
+
}];
|
|
427
|
+
}
|
|
428
|
+
const pageOf = (first) => {
|
|
429
|
+
const literal = literalOf(first);
|
|
430
|
+
return literal?.startsWith("/") === true ? [{
|
|
431
|
+
specification: "website",
|
|
432
|
+
page: pathTemplate(literal)
|
|
433
|
+
}] : [];
|
|
434
|
+
};
|
|
435
|
+
const TERMINALS = {
|
|
436
|
+
exec: (first, _readRequest, commands) => {
|
|
437
|
+
const line = first?.startsWith("[") === true ? literalOf(argumentsOf$1(first.slice(1, -1)).at(-1)) : literalOf(first);
|
|
438
|
+
return line === void 0 ? [] : [{
|
|
439
|
+
command: commandWords(line, commands),
|
|
440
|
+
specification: "cli"
|
|
441
|
+
}];
|
|
442
|
+
},
|
|
443
|
+
fetch: pageOf,
|
|
444
|
+
open: (first) => [{
|
|
445
|
+
specification: "mobile",
|
|
446
|
+
journey: literalOf(first) ?? "(launch)"
|
|
447
|
+
}],
|
|
448
|
+
request: (first, readRequest) => {
|
|
449
|
+
const path = REQUEST_FILE.exec(first ?? "")?.groups?.path;
|
|
450
|
+
return path === void 0 ? [] : requestLineOf(readRequest(path) ?? "");
|
|
451
|
+
},
|
|
452
|
+
trigger: (first) => {
|
|
453
|
+
const job = literalOf(first);
|
|
454
|
+
return job === void 0 ? [] : [{
|
|
455
|
+
specification: "job",
|
|
456
|
+
job
|
|
457
|
+
}];
|
|
458
|
+
},
|
|
459
|
+
visit: pageOf
|
|
460
|
+
};
|
|
461
|
+
function routeOf(method, args) {
|
|
462
|
+
const path = literalOf(args[0]);
|
|
463
|
+
if (path?.startsWith("/") !== true) return [];
|
|
464
|
+
const options = args[method === "POST" || method === "PUT" ? 2 : 1] ?? "";
|
|
465
|
+
return [{
|
|
466
|
+
specification: "api",
|
|
467
|
+
method,
|
|
468
|
+
path,
|
|
469
|
+
template: /\/:[A-Za-z_$]/u.test(path) || /(?<![\w$])params(?![\w$])/u.test(options)
|
|
470
|
+
}];
|
|
471
|
+
}
|
|
472
|
+
function declaredMethod(subject, methodless) {
|
|
473
|
+
if (subject.specification !== "api") return subject;
|
|
474
|
+
const template = pathTemplate(subject.path);
|
|
475
|
+
return methodless.some((path) => pathTemplate(path) === template) ? {
|
|
476
|
+
...subject,
|
|
477
|
+
method: "ALL"
|
|
478
|
+
} : subject;
|
|
479
|
+
}
|
|
480
|
+
function subjectsOfChain(chain, readRequest = () => "", reading = NO_READING) {
|
|
481
|
+
const terminal = chain.calls.at(-1);
|
|
482
|
+
if (terminal === void 0) return [];
|
|
483
|
+
const args = argumentsOf$1(terminal.args);
|
|
484
|
+
const [first] = args;
|
|
485
|
+
const method = HTTP_VERBS[terminal.name];
|
|
486
|
+
return (method === void 0 ? TERMINALS[terminal.name]?.(first, readRequest, reading.commands?.[chain.handle]) ?? [] : routeOf(method, args)).map((subject) => declaredMethod(subject, reading.methodless));
|
|
487
|
+
}
|
|
488
|
+
function subjectsOf(source, readRequest, reading = NO_READING) {
|
|
489
|
+
const text = blankComments(source);
|
|
490
|
+
return chainsOf(text, handlesOf(text)).flatMap((chain) => subjectsOfChain(chain, readRequest, reading).map((subject) => ({
|
|
491
|
+
index: chain.index,
|
|
492
|
+
subject
|
|
493
|
+
})));
|
|
494
|
+
}
|
|
495
|
+
//#endregion
|
|
496
|
+
//#region lint/kit/project/import-sources.ts
|
|
497
|
+
function attributeText(node) {
|
|
498
|
+
return stringValue(node) ?? (node?.type === "Identifier" ? String(node.name) : void 0);
|
|
499
|
+
}
|
|
500
|
+
function isJsonImport(declaration) {
|
|
501
|
+
return childList(declaration, "attributes").some((attribute) => attributeText(child(attribute, "key")) === "type" && stringValue(child(attribute, "value")) === "json");
|
|
502
|
+
}
|
|
503
|
+
function importSourceVisitor(onSource) {
|
|
504
|
+
const fromSourceField = (node) => {
|
|
505
|
+
const source = child(node, "source");
|
|
506
|
+
const value = stringValue(source);
|
|
507
|
+
if (value !== void 0) onSource({
|
|
508
|
+
json: isJsonImport(node),
|
|
509
|
+
node: source ?? node,
|
|
510
|
+
source: value
|
|
511
|
+
});
|
|
512
|
+
};
|
|
513
|
+
return {
|
|
514
|
+
ExportAllDeclaration: fromSourceField,
|
|
515
|
+
ExportNamedDeclaration: fromSourceField,
|
|
516
|
+
ImportDeclaration: fromSourceField,
|
|
517
|
+
ImportExpression: fromSourceField
|
|
518
|
+
};
|
|
519
|
+
}
|
|
520
|
+
//#endregion
|
|
521
|
+
//#region lint/kit/project/path-references.ts
|
|
522
|
+
const FS_READ = /^(?:createReadStream|opendir(?:Sync)?|read\w*)$/u;
|
|
523
|
+
const RELATIVE = /^\.\.?\//u;
|
|
524
|
+
const PATH_BUILDERS = /* @__PURE__ */ new Set(["join", "resolve"]);
|
|
525
|
+
const COMMONJS_ANCHORS = ["__dirname", "__filename"];
|
|
526
|
+
function isMetaMember(node, name) {
|
|
527
|
+
return child(node, "object")?.type === "MetaProperty" && memberPropertyName(node) === name;
|
|
528
|
+
}
|
|
529
|
+
function isOwnDirectory(node) {
|
|
530
|
+
return identifierName(node) === "__dirname" || isMetaMember(node, "dirname");
|
|
531
|
+
}
|
|
532
|
+
function calleeName(node) {
|
|
533
|
+
const callee = child(node, "callee");
|
|
534
|
+
return identifierName(callee) ?? memberPropertyName(callee);
|
|
535
|
+
}
|
|
536
|
+
function argumentsOf(node) {
|
|
537
|
+
return childList(node, "arguments");
|
|
538
|
+
}
|
|
539
|
+
function literalArguments(node) {
|
|
540
|
+
return argumentsOf(node).map((target) => {
|
|
541
|
+
const literal = stringValue(target);
|
|
542
|
+
return literal === void 0 ? void 0 : {
|
|
543
|
+
literal,
|
|
544
|
+
target
|
|
545
|
+
};
|
|
546
|
+
});
|
|
547
|
+
}
|
|
548
|
+
function isAnchor(node, anchors) {
|
|
549
|
+
if (node === void 0) return false;
|
|
550
|
+
let found = false;
|
|
551
|
+
walk(node, (inner) => {
|
|
552
|
+
found ||= inner.type === "MetaProperty" || anchors.has(identifierName(inner));
|
|
553
|
+
});
|
|
554
|
+
return found;
|
|
555
|
+
}
|
|
556
|
+
function anchorsOf(program) {
|
|
557
|
+
const anchors = new Set(COMMONJS_ANCHORS);
|
|
558
|
+
for (const statement of childList(program, "body")) {
|
|
559
|
+
const declaration = statement.type === "ExportNamedDeclaration" ? child(statement, "declaration") : statement;
|
|
560
|
+
for (const declarator of childList(declaration, "declarations")) {
|
|
561
|
+
const name = identifierName(child(declarator, "id"));
|
|
562
|
+
if (name !== void 0 && isAnchor(child(declarator, "init"), anchors)) anchors.add(name);
|
|
563
|
+
}
|
|
564
|
+
}
|
|
565
|
+
return anchors;
|
|
566
|
+
}
|
|
567
|
+
function literalTail(pieces) {
|
|
568
|
+
const tail = [];
|
|
569
|
+
for (const piece of pieces) {
|
|
570
|
+
const value = stringValue(piece);
|
|
571
|
+
if (value === void 0) break;
|
|
572
|
+
tail.push(value);
|
|
573
|
+
}
|
|
574
|
+
return tail;
|
|
575
|
+
}
|
|
576
|
+
function builtPath(node, here, anchors) {
|
|
577
|
+
const [first, ...rest] = argumentsOf(node);
|
|
578
|
+
const tail = literalTail(rest);
|
|
579
|
+
if (!PATH_BUILDERS.has(calleeName(node)) || !isAnchor(first, anchors) || tail.length === 0) return;
|
|
580
|
+
return resolve(here, ...tail);
|
|
581
|
+
}
|
|
582
|
+
function templatePath(node, here, anchors) {
|
|
583
|
+
const [hole] = childList(node, "expressions");
|
|
584
|
+
const [head, tail] = childList(node, "quasis").map(cookedText);
|
|
585
|
+
if (!isAnchor(hole, anchors) || head !== "" || tail?.startsWith("/") !== true) return;
|
|
586
|
+
return resolve(here, `.${tail}`);
|
|
587
|
+
}
|
|
588
|
+
function urlLiteral(node) {
|
|
589
|
+
const [first] = literalArguments(node);
|
|
590
|
+
const [, base] = argumentsOf(node);
|
|
591
|
+
return identifierName(child(node, "callee")) === "URL" && isMetaMember(base, "url") ? first : void 0;
|
|
592
|
+
}
|
|
593
|
+
/** Its `Program` handler reads the module's anchors first, so it must see the program before any node inside it. */
|
|
594
|
+
function pathReferenceVisitor(file, onPath) {
|
|
595
|
+
const here = () => dirname(file());
|
|
596
|
+
let anchors = /* @__PURE__ */ new Set();
|
|
597
|
+
const report = (node, path) => {
|
|
598
|
+
if (path !== void 0) onPath({
|
|
599
|
+
node,
|
|
600
|
+
path
|
|
601
|
+
});
|
|
602
|
+
};
|
|
603
|
+
return {
|
|
604
|
+
CallExpression(node) {
|
|
605
|
+
const place = here();
|
|
606
|
+
report(node, builtPath(node, place, anchors));
|
|
607
|
+
const [first] = literalArguments(node);
|
|
608
|
+
if (first !== void 0 && FS_READ.test(calleeName(node) ?? "") && RELATIVE.test(first.literal)) onPath({
|
|
609
|
+
node: first.target,
|
|
610
|
+
path: resolve(projectRootOf(place) ?? place, first.literal)
|
|
611
|
+
});
|
|
612
|
+
},
|
|
613
|
+
NewExpression(node) {
|
|
614
|
+
const url = urlLiteral(node);
|
|
615
|
+
if (url !== void 0) onPath({
|
|
616
|
+
node: url.target,
|
|
617
|
+
path: resolve(here(), url.literal)
|
|
618
|
+
});
|
|
619
|
+
},
|
|
620
|
+
Program(node) {
|
|
621
|
+
anchors = anchorsOf(node);
|
|
622
|
+
},
|
|
623
|
+
TemplateLiteral(node) {
|
|
624
|
+
report(node, templatePath(node, here(), anchors));
|
|
625
|
+
}
|
|
626
|
+
};
|
|
627
|
+
}
|
|
628
|
+
/** A piece that is not a literal leaves the call out. */
|
|
629
|
+
function literalPathVisitor(onPath) {
|
|
630
|
+
return {
|
|
631
|
+
CallExpression(node) {
|
|
632
|
+
if (!PATH_BUILDERS.has(calleeName(node))) return;
|
|
633
|
+
const [anchor, ...rest] = argumentsOf(node);
|
|
634
|
+
const pieces = literalTail(rest);
|
|
635
|
+
const [head] = rest;
|
|
636
|
+
if (isOwnDirectory(anchor) && head !== void 0 && pieces.length === rest.length) onPath({
|
|
637
|
+
node: head,
|
|
638
|
+
pieces
|
|
639
|
+
});
|
|
640
|
+
},
|
|
641
|
+
NewExpression(node) {
|
|
642
|
+
const url = urlLiteral(node);
|
|
643
|
+
if (url !== void 0) onPath({
|
|
644
|
+
node: url.target,
|
|
645
|
+
pieces: [url.literal]
|
|
646
|
+
});
|
|
647
|
+
}
|
|
648
|
+
};
|
|
649
|
+
}
|
|
650
|
+
function mergeVisitors(...fragments) {
|
|
651
|
+
const merged = {};
|
|
652
|
+
for (const fragment of fragments) for (const [type, handler] of Object.entries(fragment)) {
|
|
653
|
+
const before = merged[type];
|
|
654
|
+
merged[type] = before === void 0 ? handler : (node) => {
|
|
655
|
+
before(node);
|
|
656
|
+
handler(node);
|
|
657
|
+
};
|
|
658
|
+
}
|
|
659
|
+
return merged;
|
|
660
|
+
}
|
|
661
|
+
function visitProgram(program, visitor) {
|
|
662
|
+
walk(program, (node) => {
|
|
663
|
+
visitor[node.type]?.(node);
|
|
664
|
+
});
|
|
665
|
+
}
|
|
666
|
+
//#endregion
|
|
667
|
+
//#region lint/kit/project/module-parser.ts
|
|
668
|
+
let parse;
|
|
669
|
+
function parsedModule(filename, source) {
|
|
670
|
+
if (parse === void 0) parse = createRequire(import.meta.url)("oxc-parser").parseSync;
|
|
671
|
+
return parse(filename, source);
|
|
672
|
+
}
|
|
673
|
+
function parseProgram(filename, source) {
|
|
674
|
+
const { errors, program } = parsedModule(filename, source);
|
|
675
|
+
return errors.length === 0 ? program : void 0;
|
|
676
|
+
}
|
|
677
|
+
//#endregion
|
|
678
|
+
//#region lint/kit/project/specifiers.ts
|
|
679
|
+
const EXTENSIONS = [
|
|
680
|
+
".ts",
|
|
681
|
+
".tsx",
|
|
682
|
+
".mts",
|
|
683
|
+
".cts",
|
|
684
|
+
".js",
|
|
685
|
+
".jsx",
|
|
686
|
+
".mjs",
|
|
687
|
+
".cjs",
|
|
688
|
+
".json"
|
|
689
|
+
];
|
|
690
|
+
const COMPILED = {
|
|
691
|
+
".cjs": [".cts"],
|
|
692
|
+
".js": [".ts", ".tsx"],
|
|
693
|
+
".jsx": [".tsx"],
|
|
694
|
+
".mjs": [".mts"]
|
|
695
|
+
};
|
|
696
|
+
function onDisk(path) {
|
|
697
|
+
if (isFile(path)) return path;
|
|
698
|
+
const extension = /\.[cm]?jsx?$/u.exec(path)?.[0] ?? "";
|
|
699
|
+
return [
|
|
700
|
+
...(COMPILED[extension] ?? []).map((swap) => path.slice(0, -extension.length) + swap),
|
|
701
|
+
...EXTENSIONS.map((added) => path + added),
|
|
702
|
+
...EXTENSIONS.map((added) => join(path, `index${added}`))
|
|
703
|
+
].find((candidate) => isFile(candidate)) ?? path;
|
|
704
|
+
}
|
|
705
|
+
function readJsonc(path) {
|
|
706
|
+
const text = readFileCached(path);
|
|
707
|
+
if (text === null) return;
|
|
708
|
+
return parsedJson(blankComments(text).replaceAll(/,(?=\s*[\]}])/gu, ""), null);
|
|
709
|
+
}
|
|
710
|
+
function recordOf(value) {
|
|
711
|
+
return isRecord(value) ? value : void 0;
|
|
712
|
+
}
|
|
713
|
+
function targetsOf(value) {
|
|
714
|
+
if (typeof value === "string") return [value];
|
|
715
|
+
if (Array.isArray(value)) return value.flatMap((entry) => targetsOf(entry));
|
|
716
|
+
return Object.values(recordOf(value) ?? {}).flatMap((entry) => targetsOf(entry));
|
|
717
|
+
}
|
|
718
|
+
function substitution(key, specifier) {
|
|
719
|
+
const star = key.indexOf("*");
|
|
720
|
+
if (star === -1) return key === specifier ? "" : void 0;
|
|
721
|
+
const head = key.slice(0, star);
|
|
722
|
+
const tail = key.slice(star + 1);
|
|
723
|
+
return specifier.startsWith(head) && specifier.endsWith(tail) && specifier.length >= head.length + tail.length ? specifier.slice(head.length, specifier.length - tail.length) : void 0;
|
|
724
|
+
}
|
|
725
|
+
function mapped(map, specifier, base) {
|
|
726
|
+
for (const [key, value] of Object.entries(map)) {
|
|
727
|
+
const star = substitution(key, specifier);
|
|
728
|
+
if (star !== void 0) return targetsOf(value).map((target) => resolve(base, target.replace("*", star)));
|
|
729
|
+
}
|
|
730
|
+
return [];
|
|
731
|
+
}
|
|
732
|
+
function tsconfigOf(directory) {
|
|
733
|
+
const root = projectRootOf(directory);
|
|
734
|
+
let current = directory;
|
|
735
|
+
for (;;) {
|
|
736
|
+
const candidate = join(current, "tsconfig.json");
|
|
737
|
+
if (isFile(candidate)) return candidate;
|
|
738
|
+
const parent = dirname(current);
|
|
739
|
+
if (current === root || parent === current) return;
|
|
740
|
+
current = parent;
|
|
741
|
+
}
|
|
742
|
+
}
|
|
743
|
+
function configCandidates(base) {
|
|
744
|
+
return [
|
|
745
|
+
base,
|
|
746
|
+
`${base}.json`,
|
|
747
|
+
join(base, "tsconfig.json")
|
|
748
|
+
];
|
|
749
|
+
}
|
|
750
|
+
function extendedConfig(from, entry) {
|
|
751
|
+
const directory = dirname(from);
|
|
752
|
+
if (entry.startsWith(".") || entry.startsWith("/")) return configCandidates(resolve(directory, entry)).find((path) => isFile(path));
|
|
753
|
+
for (let current = directory;; current = dirname(current)) {
|
|
754
|
+
const found = configCandidates(join(current, "node_modules", entry)).find((path) => isFile(path));
|
|
755
|
+
if (found !== void 0 || dirname(current) === current) return found;
|
|
756
|
+
}
|
|
757
|
+
}
|
|
758
|
+
function resolutionOf(tsconfig, seen) {
|
|
759
|
+
if (seen.has(tsconfig)) return {};
|
|
760
|
+
seen.add(tsconfig);
|
|
761
|
+
const config = recordOf(readJsonc(tsconfig));
|
|
762
|
+
const bases = targetsOf(config?.extends);
|
|
763
|
+
const resolution = {};
|
|
764
|
+
for (const entry of bases) {
|
|
765
|
+
const base = extendedConfig(tsconfig, entry);
|
|
766
|
+
if (base !== void 0) Object.assign(resolution, resolutionOf(base, seen));
|
|
767
|
+
}
|
|
768
|
+
const options = recordOf(config?.compilerOptions);
|
|
769
|
+
const directory = dirname(tsconfig);
|
|
770
|
+
if (typeof options?.baseUrl === "string") resolution.baseUrl = resolve(directory, options.baseUrl);
|
|
771
|
+
const paths = recordOf(options?.paths);
|
|
772
|
+
if (paths !== void 0) resolution.paths = {
|
|
773
|
+
base: directory,
|
|
774
|
+
paths
|
|
775
|
+
};
|
|
776
|
+
return resolution;
|
|
777
|
+
}
|
|
778
|
+
function throughPaths(directory, specifier) {
|
|
779
|
+
const tsconfig = tsconfigOf(directory);
|
|
780
|
+
if (tsconfig === void 0) return [];
|
|
781
|
+
const { baseUrl, paths } = resolutionOf(tsconfig, /* @__PURE__ */ new Set());
|
|
782
|
+
if (paths === void 0) return [];
|
|
783
|
+
return mapped(paths.paths, specifier, baseUrl ?? paths.base);
|
|
784
|
+
}
|
|
785
|
+
function throughImports(directory, specifier) {
|
|
786
|
+
const root = packageRootOf(directory);
|
|
787
|
+
if (root === void 0) return [];
|
|
788
|
+
const imports = recordOf(recordOf(readJsonc(join(root, "package.json")))?.imports);
|
|
789
|
+
return imports === void 0 ? [] : mapped(imports, specifier, root);
|
|
790
|
+
}
|
|
791
|
+
function resolveSpecifier(from, specifier) {
|
|
792
|
+
const directory = dirname(from);
|
|
793
|
+
if (specifier === "." || specifier === ".." || /^\.\.?\//u.test(specifier)) return [onDisk(resolve(directory, specifier))];
|
|
794
|
+
if (specifier.startsWith("/") || specifier.startsWith("node:")) return [];
|
|
795
|
+
return (specifier.startsWith("#") ? throughImports(directory, specifier) : throughPaths(directory, specifier)).map((target) => onDisk(target));
|
|
796
|
+
}
|
|
797
|
+
//#endregion
|
|
798
|
+
//#region lint/kit/project/tree.ts
|
|
799
|
+
function isPool(dir) {
|
|
800
|
+
return isGroundFolder(basename(dir)) && basename(dirname(dir)) === "specs";
|
|
801
|
+
}
|
|
802
|
+
function specsRootOfPool(pool) {
|
|
803
|
+
return dirname(pool);
|
|
804
|
+
}
|
|
805
|
+
function isPruned(dir) {
|
|
806
|
+
return PRUNED.has(basename(dir)) || isGroundFolder(basename(dir));
|
|
807
|
+
}
|
|
808
|
+
function listFiles(dir, predicate) {
|
|
809
|
+
const out = [];
|
|
810
|
+
const visit = (current) => {
|
|
811
|
+
let entries;
|
|
812
|
+
try {
|
|
813
|
+
entries = treeEntries(current);
|
|
814
|
+
} catch {
|
|
815
|
+
return;
|
|
816
|
+
}
|
|
817
|
+
for (const entry of entries) {
|
|
818
|
+
const path = join(current, entry.name);
|
|
819
|
+
if (entry.isDirectory()) {
|
|
820
|
+
if (!isPruned(path)) visit(path);
|
|
821
|
+
} else if (predicate(path)) out.push(path);
|
|
822
|
+
}
|
|
823
|
+
};
|
|
824
|
+
visit(dir);
|
|
825
|
+
return out;
|
|
826
|
+
}
|
|
827
|
+
function listDirs(dir) {
|
|
828
|
+
const out = [];
|
|
829
|
+
const visit = (current) => {
|
|
830
|
+
out.push(current);
|
|
831
|
+
let entries;
|
|
832
|
+
try {
|
|
833
|
+
entries = treeEntries(current);
|
|
834
|
+
} catch {
|
|
835
|
+
return;
|
|
836
|
+
}
|
|
837
|
+
for (const entry of entries) {
|
|
838
|
+
const path = join(current, entry.name);
|
|
839
|
+
if (entry.isDirectory() && !isPruned(path)) visit(path);
|
|
840
|
+
}
|
|
841
|
+
};
|
|
842
|
+
visit(dir);
|
|
843
|
+
return out;
|
|
844
|
+
}
|
|
845
|
+
function findSpecsRoots(dir) {
|
|
846
|
+
const roots = [];
|
|
847
|
+
const visit = (current) => {
|
|
848
|
+
if (basename(current) === "specs") {
|
|
849
|
+
roots.push(current);
|
|
850
|
+
return;
|
|
851
|
+
}
|
|
852
|
+
let entries;
|
|
853
|
+
try {
|
|
854
|
+
entries = treeEntries(current);
|
|
855
|
+
} catch {
|
|
856
|
+
return;
|
|
857
|
+
}
|
|
858
|
+
for (const entry of entries) if (entry.isDirectory() && !PRUNED.has(entry.name)) visit(join(current, entry.name));
|
|
859
|
+
};
|
|
860
|
+
visit(dir);
|
|
861
|
+
return roots;
|
|
862
|
+
}
|
|
863
|
+
function findPools(dir) {
|
|
864
|
+
const pools = [];
|
|
865
|
+
const visit = (current) => {
|
|
866
|
+
let entries;
|
|
867
|
+
try {
|
|
868
|
+
entries = treeEntries(current);
|
|
869
|
+
} catch {
|
|
870
|
+
return;
|
|
871
|
+
}
|
|
872
|
+
for (const entry of entries) {
|
|
873
|
+
if (!entry.isDirectory() || PRUNED.has(entry.name)) continue;
|
|
874
|
+
const path = join(current, entry.name);
|
|
875
|
+
if (isPool(path)) pools.push(path);
|
|
876
|
+
else if (!isGroundFolder(entry.name)) visit(path);
|
|
877
|
+
}
|
|
878
|
+
};
|
|
879
|
+
visit(dir);
|
|
880
|
+
return pools;
|
|
881
|
+
}
|
|
882
|
+
function readText(path) {
|
|
883
|
+
try {
|
|
884
|
+
return readFileSync(path, "utf8");
|
|
885
|
+
} catch {
|
|
886
|
+
return "";
|
|
887
|
+
}
|
|
888
|
+
}
|
|
889
|
+
function readSource(path) {
|
|
890
|
+
return blankComments(readText(path));
|
|
891
|
+
}
|
|
892
|
+
/** A document counts: a rendered unit's ARIA golden sits in the same `__expected__/` as a spec's. */
|
|
893
|
+
function isReferrer(name) {
|
|
894
|
+
return isTestFileName(name) || name.endsWith(".spec.yaml");
|
|
895
|
+
}
|
|
896
|
+
function isDir(path) {
|
|
897
|
+
return treeKind(path) === "directory";
|
|
898
|
+
}
|
|
899
|
+
function columnAt(text, index) {
|
|
900
|
+
return index - text.lastIndexOf("\n", index - 1);
|
|
901
|
+
}
|
|
902
|
+
function lineAt(text, index) {
|
|
903
|
+
return text.slice(0, index).split("\n").length;
|
|
904
|
+
}
|
|
905
|
+
const DISK_CALL = /(?<![.\w])(?<kind>file|directory)\(\s*(?<quote>['"`])(?<path>(?:[^'"`\\]|\\.)*)\k<quote>/gu;
|
|
906
|
+
/** A path that is no folder holds no tests and lists no children: it is none. */
|
|
907
|
+
function isConventionalSubdir(path) {
|
|
908
|
+
if (listFiles(path, isTestFileName).length > 0) return false;
|
|
909
|
+
let children;
|
|
910
|
+
try {
|
|
911
|
+
children = treeEntries(path);
|
|
912
|
+
} catch {
|
|
913
|
+
return false;
|
|
914
|
+
}
|
|
915
|
+
return !children.some((child) => child.isDirectory() && isGroundFolder(child.name));
|
|
916
|
+
}
|
|
917
|
+
function listReaders(specsRoot) {
|
|
918
|
+
return listFiles(specsRoot, (path) => path.endsWith(".ts") || path.endsWith(".spec.yaml"));
|
|
919
|
+
}
|
|
920
|
+
function removeIfEmpty(folder) {
|
|
921
|
+
if (existsSync(folder) && readdirSync(folder).length === 0) rmSync(folder, { recursive: true });
|
|
922
|
+
}
|
|
923
|
+
function isWithin(directory, path) {
|
|
924
|
+
const rest = relative(directory, path);
|
|
925
|
+
return rest !== "" && !rest.startsWith("..") && !isAbsolute(rest);
|
|
926
|
+
}
|
|
927
|
+
function lowestCommonFolder(folders) {
|
|
928
|
+
const [first, ...others] = folders;
|
|
929
|
+
if (first === void 0) return;
|
|
930
|
+
let common = first;
|
|
931
|
+
while (!others.every((folder) => folder === common || isWithin(common, folder))) {
|
|
932
|
+
const parent = dirname(common);
|
|
933
|
+
if (parent === common) return;
|
|
934
|
+
common = parent;
|
|
935
|
+
}
|
|
936
|
+
return common;
|
|
937
|
+
}
|
|
938
|
+
function reservedFolderOf(owner, reserved) {
|
|
939
|
+
return join(owner, reserved);
|
|
940
|
+
}
|
|
941
|
+
function decodeText(path) {
|
|
942
|
+
let text;
|
|
943
|
+
try {
|
|
944
|
+
text = readFileSync(path, "utf8");
|
|
945
|
+
} catch {
|
|
946
|
+
return null;
|
|
947
|
+
}
|
|
948
|
+
return text.includes("\0") || text.includes("�") ? null : text;
|
|
949
|
+
}
|
|
950
|
+
//#endregion
|
|
951
|
+
//#region lint/kit/project/specs-modules.ts
|
|
952
|
+
const MODULE = /\.(?:[cm]?[jt]sx?)$/u;
|
|
953
|
+
const TEST_OR_SPEC = /\.(?:test|test-d|spec)\.[cm]?[jt]sx?$/u;
|
|
954
|
+
const RUNNER = /\.specification\.[cm]?[jt]sx?$/u;
|
|
955
|
+
function isModuleName(name) {
|
|
956
|
+
return MODULE.test(name) && !name.endsWith(".d.ts");
|
|
957
|
+
}
|
|
958
|
+
function isTestOrSpecName(name) {
|
|
959
|
+
return TEST_OR_SPEC.test(name);
|
|
960
|
+
}
|
|
961
|
+
function isRunnerName(name) {
|
|
962
|
+
return RUNNER.test(name);
|
|
963
|
+
}
|
|
964
|
+
function modulesUnder(specsRoot) {
|
|
965
|
+
return listFiles(specsRoot, (path) => isModuleName(basename(path)));
|
|
966
|
+
}
|
|
967
|
+
function setupFolderOf(specsRoot, file) {
|
|
968
|
+
for (let folder = dirname(file); isWithin(specsRoot, folder); folder = dirname(folder)) if (basename(folder) === SETUP_FOLDER) return folder;
|
|
969
|
+
}
|
|
970
|
+
function placeOf(specsRoot, reader) {
|
|
971
|
+
const setup = setupFolderOf(specsRoot, reader);
|
|
972
|
+
return setup === void 0 ? dirname(reader) : dirname(setup);
|
|
973
|
+
}
|
|
974
|
+
function importsOf(file) {
|
|
975
|
+
const program = parseProgram(file, readText(file));
|
|
976
|
+
if (!isNode(program)) return [];
|
|
977
|
+
const imported = [];
|
|
978
|
+
visitProgram(program, importSourceVisitor(({ source }) => {
|
|
979
|
+
imported.push({
|
|
980
|
+
relative: isRelativePath(source),
|
|
981
|
+
targets: resolveSpecifier(file, source)
|
|
982
|
+
});
|
|
983
|
+
}));
|
|
984
|
+
return imported;
|
|
985
|
+
}
|
|
986
|
+
function importedModulesOf(file) {
|
|
987
|
+
return importsOf(file).flatMap(({ targets }) => targets);
|
|
988
|
+
}
|
|
989
|
+
function importsThroughAlias(file, module) {
|
|
990
|
+
return importsOf(file).some(({ relative, targets }) => !relative && targets.includes(module));
|
|
991
|
+
}
|
|
992
|
+
function readersOf(modules) {
|
|
993
|
+
const known = new Set(modules);
|
|
994
|
+
const readers = /* @__PURE__ */ new Map();
|
|
995
|
+
for (const file of modules) for (const target of new Set(importedModulesOf(file))) if (target !== file && known.has(target)) readers.set(target, [...readers.get(target) ?? [], file]);
|
|
996
|
+
return readers;
|
|
997
|
+
}
|
|
998
|
+
function lowestPlaceOf(specsRoot, readers) {
|
|
999
|
+
return lowestCommonFolder(readers.map((reader) => placeOf(specsRoot, reader)));
|
|
1000
|
+
}
|
|
1001
|
+
function declaresTests(name) {
|
|
1002
|
+
return isTestOrSpecName(name) || name.endsWith(".spec.yaml");
|
|
1003
|
+
}
|
|
1004
|
+
function holdsASpec(folder) {
|
|
1005
|
+
let entries;
|
|
1006
|
+
try {
|
|
1007
|
+
entries = treeEntries(folder);
|
|
1008
|
+
} catch {
|
|
1009
|
+
return false;
|
|
1010
|
+
}
|
|
1011
|
+
return entries.some((entry) => entry.isDirectory() ? !PRUNED.has(entry.name) && !isSpecialFolder(entry.name) && holdsASpec(join(folder, entry.name)) : declaresTests(entry.name));
|
|
1012
|
+
}
|
|
1013
|
+
/** The special folder a bare earlier spelling names (a `setup/` at the specs root or holding no spec), or `undefined`; a `_` name is refused before it is asked. */
|
|
1014
|
+
function renamedSpecialOf(specsRoot, folder) {
|
|
1015
|
+
const renamed = RENAMED_FOLDERS[basename(folder)];
|
|
1016
|
+
if (renamed === void 0) return;
|
|
1017
|
+
return dirname(folder) === specsRoot || !holdsASpec(folder) ? renamed : void 0;
|
|
1018
|
+
}
|
|
1019
|
+
/** A bare `shared/` at the specs root or holding no spec: what specifications share sits at the specs root now; a `_` name is refused before it is asked. */
|
|
1020
|
+
function isRetiredShared(specsRoot, folder) {
|
|
1021
|
+
return RETIRED_SHARED_FOLDERS.includes(basename(folder)) && (dirname(folder) === specsRoot || !holdsASpec(folder));
|
|
1022
|
+
}
|
|
1023
|
+
function isRefusedReserved(specsRoot, folder) {
|
|
1024
|
+
const name = basename(folder);
|
|
1025
|
+
return name.startsWith("_") && !isSpecialFolder(name) || renamedSpecialOf(specsRoot, folder) !== void 0 || isRetiredShared(specsRoot, folder);
|
|
1026
|
+
}
|
|
1027
|
+
function refusedReservedAbove(specsRoot, file) {
|
|
1028
|
+
for (let folder = dirname(file); isWithin(specsRoot, folder); folder = dirname(folder)) if (isRefusedReserved(specsRoot, folder)) return folder;
|
|
1029
|
+
}
|
|
1030
|
+
//#endregion
|
|
1031
|
+
export { identifierName as $, removeIfEmpty as A, ambiguousSegment as B, lineAt as C, lowestCommonFolder as D, listReaders as E, literalPathVisitor as F, SUBJECT_SPECIFICATIONS as G, subjectFolder as H, mergeVisitors as I, chainRootName as J, doorsOf as K, pathReferenceVisitor as L, specsRootOfPool as M, resolveSpecifier as N, readSource as O, parseProgram as P, findProperty as Q, visitProgram as R, isWithin as S, listFiles as T, subjectName as U, pathTemplate as V, subjectsOf as W, childList as X, child as Y, cookedText as Z, findPools as _, roleOf as _t, isRunnerName as a, nodeStart as at, isDir as b, modulesUnder as c, stringField as ct, refusedReservedAbove as d, isNodeTestFileName as dt, isNode as et, renamedSpecialOf as f, isTestFileName as ft, decodeText as g, projectSegments as gt, columnAt as h, projectRootOf as ht, isRetiredShared as i, nodeEnd as it, reservedFolderOf as j, readText as k, placeOf as l, stringValue as lt, DISK_CALL as m, packageRootOf as mt, importsThroughAlias as n, memberPath as nt, isTestOrSpecName as o, propertyKeyName as ot, setupFolderOf as p, isTestRole as pt, declaredCommands as q, isModuleName as r, memberPropertyName as rt, lowestPlaceOf as s, specificationMember as st, declaresTests as t, isSampledValue as tt, readersOf as u, walk as ut, findSpecsRoots as v, specsAnchor as vt, listDirs as w, isReferrer as x, isConventionalSubdir as y, SETTINGS_FILE as yt, importSourceVisitor as z };
|
|
1032
|
+
|
|
1033
|
+
//# sourceMappingURL=shared-specs-modules.js.map
|