@cplieger/deadset-ts 4.0.1 → 5.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 +4 -4
- package/dist/bin/component-mapper.js +5 -0
- package/dist/bin/mapper-connection.js +101 -0
- package/dist/bin/node-host.js +24 -2
- package/dist/src/analysis.js +229 -36
- package/dist/src/component-files.js +612 -0
- package/dist/src/component-layer.js +132 -0
- package/dist/src/config.js +9 -2
- package/dist/src/configuration-files.js +186 -0
- package/dist/src/conformance-record.js +2 -2
- package/dist/src/convention-rows.js +383 -0
- package/dist/src/conventions.js +456 -0
- package/dist/src/dependencies.js +30 -3
- package/dist/src/discover.js +10 -3
- package/dist/src/emit-map.js +72 -0
- package/dist/src/entry-point-gaps.js +81 -35
- package/dist/src/entry-points.js +47 -32
- package/dist/src/exempt.js +11 -3
- package/dist/src/findings/completion.js +19 -29
- package/dist/src/findings/emitters.js +22 -1
- package/dist/src/findings/unused-declarations.js +7 -2
- package/dist/src/graph.js +13 -5
- package/dist/src/inventory.js +4 -2
- package/dist/src/kinds.js +2 -2
- package/dist/src/manifest.js +37 -11
- package/dist/src/matrix.js +21 -13
- package/dist/src/partial.js +3 -1
- package/dist/src/pnpm-workspace.js +120 -0
- package/dist/src/position.js +17 -2
- package/dist/src/print.js +2 -0
- package/dist/src/references.js +209 -30
- package/dist/src/report.js +26 -6
- package/dist/src/reporters/annotations.js +10 -3
- package/dist/src/reporters/template.js +3 -0
- package/dist/src/resolve.js +31 -6
- package/dist/src/roots.js +37 -8
- package/dist/src/run.js +13 -3
- package/dist/src/schema.js +2 -0
- package/dist/src/session.js +141 -34
- package/dist/src/setup-failure.js +23 -0
- package/dist/src/source-files.js +4 -61
- package/dist/src/sweep.js +4 -2
- package/dist/src/template-field.js +38 -6
- package/dist/src/test-support.js +59 -0
- package/dist/src/type-errors.js +252 -0
- package/dist/src/type-query-alias.js +92 -0
- package/dist/src/verbs/analyze.js +24 -4
- package/dist/src/verbs/explain.js +4 -1
- package/dist/src/verbs/print-projects.js +17 -56
- package/dist/src/verbs/print-retained.js +7 -2
- package/dist/src/verbs/print-roots.js +7 -2
- package/dist/src/version-range.js +255 -0
- package/dist/src/version.js +1 -1
- package/dist/src/workspace-manifests.js +68 -0
- package/dist/src/workspace-programs.js +342 -0
- package/dist/src/workspace-resolution.js +307 -0
- package/dist/src/workspace-run.js +75 -0
- package/dist/src/workspace.js +358 -0
- package/jsr.json +1 -1
- package/package.json +2 -1
- package/src/analysis.ts +394 -70
- package/src/component-files.ts +718 -0
- package/src/component-layer.ts +160 -0
- package/src/config.ts +11 -2
- package/src/configuration-files.ts +235 -0
- package/src/conformance-record.ts +2 -2
- package/src/convention-rows.ts +440 -0
- package/src/conventions.ts +640 -0
- package/src/dependencies.ts +36 -2
- package/src/discover.ts +10 -6
- package/src/emit-map.ts +93 -0
- package/src/entry-point-gaps.ts +92 -38
- package/src/entry-points.ts +66 -36
- package/src/exempt.ts +21 -3
- package/src/findings/completion.ts +24 -33
- package/src/findings/emitters.ts +32 -1
- package/src/findings/unused-declarations.ts +9 -2
- package/src/graph.ts +15 -4
- package/src/host.ts +23 -0
- package/src/index.ts +1 -1
- package/src/inventory.ts +13 -2
- package/src/kinds.ts +2 -2
- package/src/manifest.ts +47 -14
- package/src/matrix.ts +43 -15
- package/src/partial.ts +6 -1
- package/src/pnpm-workspace.ts +135 -0
- package/src/position.ts +23 -2
- package/src/print.ts +2 -0
- package/src/references.ts +227 -28
- package/src/report.ts +56 -6
- package/src/reporters/annotations.ts +12 -3
- package/src/reporters/template.ts +3 -0
- package/src/resolve.ts +59 -6
- package/src/roots.ts +60 -9
- package/src/run.ts +15 -5
- package/src/schema.ts +2 -0
- package/src/session.ts +218 -37
- package/src/setup-failure.ts +39 -0
- package/src/source-files.ts +4 -66
- package/src/sweep.ts +9 -2
- package/src/template-field.ts +42 -7
- package/src/test-support.ts +77 -0
- package/src/type-errors.ts +340 -0
- package/src/type-query-alias.ts +133 -0
- package/src/verbs/analyze.ts +38 -4
- package/src/verbs/explain.ts +4 -1
- package/src/verbs/print-projects.ts +17 -69
- package/src/verbs/print-retained.ts +7 -2
- package/src/verbs/print-roots.ts +7 -2
- package/src/verbs/verb.ts +6 -3
- package/src/version-range.ts +309 -0
- package/src/version.ts +1 -1
- package/src/workspace-manifests.ts +100 -0
- package/src/workspace-programs.ts +486 -0
- package/src/workspace-resolution.ts +393 -0
- package/src/workspace-run.ts +148 -0
- package/src/workspace.ts +463 -0
|
@@ -0,0 +1,160 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* How a snapshot reads component files: a file-system layer that never reaches the disk gives
|
|
3
|
+
* every configuration the snapshot opens a `contentMappers` member naming this analyzer's own
|
|
4
|
+
* mapper, or none, in place of any the configuration declares, because a configuration's own
|
|
5
|
+
* member wins over one it extends. The mapper resolves as a package linked into the
|
|
6
|
+
* `node_modules` beside each configuration, to a directory holding only its manifest.
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
import type { CreateSnapshotParams } from "@typescript/native/unstable/sync";
|
|
10
|
+
import type { Host } from "./host.ts";
|
|
11
|
+
import { dirnamePath, joinPath } from "./paths.ts";
|
|
12
|
+
|
|
13
|
+
/** The name the mapper's manifest declares and every configuration's entry names. */
|
|
14
|
+
export const MAPPER_PACKAGE = "deadset-ts-component-files";
|
|
15
|
+
|
|
16
|
+
/** The file system one snapshot is created with. */
|
|
17
|
+
export type SnapshotFileSystem = NonNullable<CreateSnapshotParams["fileSystem"]>;
|
|
18
|
+
|
|
19
|
+
/** The manifest of the mapper's package, which runs `command`. */
|
|
20
|
+
export function mapperManifest(command: readonly string[]): string {
|
|
21
|
+
return `${JSON.stringify(
|
|
22
|
+
{
|
|
23
|
+
name: MAPPER_PACKAGE,
|
|
24
|
+
version: "0.0.0",
|
|
25
|
+
private: true,
|
|
26
|
+
typescript: { contentMapper: { exec: command } },
|
|
27
|
+
},
|
|
28
|
+
null,
|
|
29
|
+
2,
|
|
30
|
+
)}\n`;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/** The offset past the white space and comments from `at`. */
|
|
34
|
+
function pastTrivia(text: string, from: number): number {
|
|
35
|
+
let at = from;
|
|
36
|
+
for (;;) {
|
|
37
|
+
if (/\s/u.test(text[at] ?? "x")) {
|
|
38
|
+
at += 1;
|
|
39
|
+
} else if (text.startsWith("//", at)) {
|
|
40
|
+
const end = text.slice(at).search(/[\n\r]/u);
|
|
41
|
+
at = end < 0 ? text.length : at + end;
|
|
42
|
+
} else if (text.startsWith("/*", at)) {
|
|
43
|
+
const end = text.indexOf("*/", at + 2);
|
|
44
|
+
at = end < 0 ? text.length : end + 2;
|
|
45
|
+
} else {
|
|
46
|
+
return at;
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/** The offset past the string opening at `from`. */
|
|
52
|
+
function pastString(text: string, from: number): number {
|
|
53
|
+
let at = from + 1;
|
|
54
|
+
while (at < text.length && text[at] !== '"') {
|
|
55
|
+
at += text[at] === "\\" ? 2 : 1;
|
|
56
|
+
}
|
|
57
|
+
return at + 1;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
/** The offset past the value starting at `from`: a string, a bracketed value, or a bare word. */
|
|
61
|
+
function pastValue(text: string, from: number): number {
|
|
62
|
+
const first = text[from];
|
|
63
|
+
if (first === '"') {
|
|
64
|
+
return pastString(text, from);
|
|
65
|
+
}
|
|
66
|
+
if (first !== "{" && first !== "[") {
|
|
67
|
+
let at = from;
|
|
68
|
+
while (at < text.length && /[^\s,}\]/]/u.test(text[at] ?? "")) {
|
|
69
|
+
at += 1;
|
|
70
|
+
}
|
|
71
|
+
return at;
|
|
72
|
+
}
|
|
73
|
+
let depth = 0;
|
|
74
|
+
let at = from;
|
|
75
|
+
while (at < text.length) {
|
|
76
|
+
at = pastTrivia(text, at);
|
|
77
|
+
const char = text[at];
|
|
78
|
+
if (char === '"') {
|
|
79
|
+
at = pastString(text, at);
|
|
80
|
+
continue;
|
|
81
|
+
}
|
|
82
|
+
if (char === "{" || char === "[") {
|
|
83
|
+
depth += 1;
|
|
84
|
+
} else if (char === "}" || char === "]") {
|
|
85
|
+
depth -= 1;
|
|
86
|
+
if (depth === 0) {
|
|
87
|
+
return at + 1;
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
at += 1;
|
|
91
|
+
}
|
|
92
|
+
return at;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
/**
|
|
96
|
+
* One configuration's text with its top-level `contentMappers` member set to `mappers`,
|
|
97
|
+
* the member's own value replaced where it has one and the member written first
|
|
98
|
+
* otherwise. Every line keeps its number. Text that holds no object is returned as is,
|
|
99
|
+
* for the compiler to refuse.
|
|
100
|
+
*/
|
|
101
|
+
export function withContentMappers(text: string, mappers: unknown): string {
|
|
102
|
+
const value = JSON.stringify(mappers);
|
|
103
|
+
const open = pastTrivia(text, 0);
|
|
104
|
+
if (text[open] !== "{") {
|
|
105
|
+
return text;
|
|
106
|
+
}
|
|
107
|
+
let at = pastTrivia(text, open + 1);
|
|
108
|
+
const empty = text[at] === "}";
|
|
109
|
+
while (at < text.length && text[at] === '"') {
|
|
110
|
+
const keyEnd = pastString(text, at);
|
|
111
|
+
const key = text.slice(at, keyEnd);
|
|
112
|
+
const valueStart = pastTrivia(text, pastTrivia(text, keyEnd) + 1);
|
|
113
|
+
const valueEnd = pastValue(text, valueStart);
|
|
114
|
+
if (key === '"contentMappers"') {
|
|
115
|
+
return text.slice(0, valueStart) + value + text.slice(valueEnd);
|
|
116
|
+
}
|
|
117
|
+
at = pastTrivia(text, valueEnd);
|
|
118
|
+
if (text[at] !== ",") {
|
|
119
|
+
break;
|
|
120
|
+
}
|
|
121
|
+
at = pastTrivia(text, at + 1);
|
|
122
|
+
}
|
|
123
|
+
return `${text.slice(0, open + 1)}"contentMappers":${value}${empty ? "" : ","}${text.slice(open + 1)}`;
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
/** This analyzer's mapper: the extensions it reads and the directory of its package. */
|
|
127
|
+
interface ComponentMapper {
|
|
128
|
+
readonly extensions: readonly string[];
|
|
129
|
+
readonly packageDirectory: string;
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
/**
|
|
133
|
+
* The layer one snapshot is created with: every configuration named carrying `mapper`, its
|
|
134
|
+
* package linked beside it, or no mapper at all where `mapper` is absent. A configuration
|
|
135
|
+
* `written` holds is that text, which exists nowhere else; any other is read through the host.
|
|
136
|
+
*/
|
|
137
|
+
export function componentLayer(
|
|
138
|
+
host: Host,
|
|
139
|
+
configFiles: readonly string[],
|
|
140
|
+
mapper: ComponentMapper | undefined,
|
|
141
|
+
written: ReadonlyMap<string, string> = new Map(),
|
|
142
|
+
): SnapshotFileSystem {
|
|
143
|
+
const mappers =
|
|
144
|
+
mapper === undefined ? [] : [{ package: MAPPER_PACKAGE, extensions: [...mapper.extensions] }];
|
|
145
|
+
const files: Record<string, string> = {};
|
|
146
|
+
const symlinks: Record<string, { target: string; host: boolean }> = {};
|
|
147
|
+
for (const configFile of configFiles) {
|
|
148
|
+
files[configFile] = withContentMappers(
|
|
149
|
+
written.get(configFile) ?? host.readFile(configFile),
|
|
150
|
+
mappers,
|
|
151
|
+
);
|
|
152
|
+
if (mapper !== undefined) {
|
|
153
|
+
symlinks[joinPath(dirnamePath(configFile), "node_modules", MAPPER_PACKAGE)] = {
|
|
154
|
+
target: mapper.packageDirectory,
|
|
155
|
+
host: true,
|
|
156
|
+
};
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
return { kind: "layer", files, symlinks };
|
|
160
|
+
}
|
package/src/config.ts
CHANGED
|
@@ -162,6 +162,8 @@ export interface Providers {
|
|
|
162
162
|
export interface TSSection {
|
|
163
163
|
readonly testFiles: readonly string[];
|
|
164
164
|
readonly entryFiles: readonly string[];
|
|
165
|
+
readonly componentExtensions: readonly string[];
|
|
166
|
+
readonly disabledConventions: readonly string[];
|
|
165
167
|
readonly injectionRegistrations: readonly DeclarationEntry[];
|
|
166
168
|
readonly lifecycleContracts: readonly LifecycleContract[];
|
|
167
169
|
readonly serializers: readonly DeclarationEntry[];
|
|
@@ -202,7 +204,7 @@ export function defaultConfig(): Config {
|
|
|
202
204
|
targetKind: "",
|
|
203
205
|
analysis: {
|
|
204
206
|
languages: [],
|
|
205
|
-
minConfidence: "
|
|
207
|
+
minConfidence: "probable",
|
|
206
208
|
generatedFiles: "exclude",
|
|
207
209
|
consumerTests: "test",
|
|
208
210
|
configurations: [],
|
|
@@ -228,8 +230,15 @@ export function defaultConfig(): Config {
|
|
|
228
230
|
],
|
|
229
231
|
},
|
|
230
232
|
ts: {
|
|
231
|
-
testFiles: [
|
|
233
|
+
testFiles: [
|
|
234
|
+
"**/*.test.{ts,tsx,mts,cts}",
|
|
235
|
+
"**/*.spec.*",
|
|
236
|
+
"**/__tests__/**",
|
|
237
|
+
"**/__mocks__/**",
|
|
238
|
+
],
|
|
232
239
|
entryFiles: [],
|
|
240
|
+
componentExtensions: [".vue", ".svelte", ".astro"],
|
|
241
|
+
disabledConventions: [],
|
|
233
242
|
injectionRegistrations: [],
|
|
234
243
|
lifecycleContracts: [],
|
|
235
244
|
serializers: [],
|
|
@@ -0,0 +1,235 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The configuration files a tool loads by name, and the strings they hold. A file in a
|
|
3
|
+
* directory that holds a `package.json` is a configuration module when its name is
|
|
4
|
+
* `<stem>.config.<ext>`, `<stem>.<qualifier>.config.<ext>` or `.<stem>rc.<ext>`, and a
|
|
5
|
+
* JSON configuration file when its name is the JSON form of one of them. A tool reads
|
|
6
|
+
* every string either holds as it likes, so each one that spells a declared dependency
|
|
7
|
+
* uses it and each relative one that names a source file of the program roots the file.
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
import {
|
|
11
|
+
isNoSubstitutionTemplateLiteral,
|
|
12
|
+
isStringLiteral,
|
|
13
|
+
type Node,
|
|
14
|
+
type SourceFile,
|
|
15
|
+
} from "@typescript/native/unstable/ast";
|
|
16
|
+
import type { Host } from "./host.ts";
|
|
17
|
+
import { dirnamePath, joinPath, relativePath, resolvePath } from "./paths.ts";
|
|
18
|
+
import type { SourceFiles } from "./source-files.ts";
|
|
19
|
+
|
|
20
|
+
const MANIFEST = "package.json";
|
|
21
|
+
|
|
22
|
+
/** A configuration module's name, each form with the spelling a root reports it by. */
|
|
23
|
+
const MODULE_FORMS: readonly { readonly name: RegExp; readonly form: string }[] = [
|
|
24
|
+
{ name: /^[\w-]+\.config\.(?:js|mjs|cjs|ts|mts|cts)$/u, form: "<stem>.config.<ext>" },
|
|
25
|
+
{
|
|
26
|
+
name: /^[\w-]+\.[\w-]+\.config\.(?:js|mjs|cjs|ts|mts|cts)$/u,
|
|
27
|
+
form: "<stem>.<qualifier>.config.<ext>",
|
|
28
|
+
},
|
|
29
|
+
{ name: /^\.[\w-]+rc\.(?:js|mjs|cjs)$/u, form: ".<stem>rc.<ext>" },
|
|
30
|
+
];
|
|
31
|
+
|
|
32
|
+
/** A JSON configuration file's name. */
|
|
33
|
+
const DOCUMENT_NAME = /^(?:[\w-]+(?:\.[\w-]+)?\.config\.json|\.[\w-]+rc(?:\.json)?)$/u;
|
|
34
|
+
|
|
35
|
+
/** The manifest members a dependency is declared in. */
|
|
36
|
+
const DEPENDENCY_MEMBERS = [
|
|
37
|
+
"dependencies",
|
|
38
|
+
"devDependencies",
|
|
39
|
+
"peerDependencies",
|
|
40
|
+
"optionalDependencies",
|
|
41
|
+
] as const;
|
|
42
|
+
|
|
43
|
+
/** The prefixes a string names a file with, relative to the file that writes it. */
|
|
44
|
+
const RELATIVE_PREFIXES = ["./", "../"] as const;
|
|
45
|
+
|
|
46
|
+
/** The extensions the program's module resolution appends to a specifier written without one. */
|
|
47
|
+
const RESOLVED_EXTENSIONS = [
|
|
48
|
+
".ts",
|
|
49
|
+
".tsx",
|
|
50
|
+
".d.ts",
|
|
51
|
+
".mts",
|
|
52
|
+
".cts",
|
|
53
|
+
".js",
|
|
54
|
+
".jsx",
|
|
55
|
+
".mjs",
|
|
56
|
+
".cjs",
|
|
57
|
+
] as const;
|
|
58
|
+
|
|
59
|
+
/** The file's name, the last segment of its path. */
|
|
60
|
+
function basename(path: string): string {
|
|
61
|
+
return path.slice(path.lastIndexOf("/") + 1);
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
function isRecord(value: unknown): value is Record<string, unknown> {
|
|
65
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
/** The form one file name is as a configuration module, or undefined where it is none. */
|
|
69
|
+
export function configurationModuleForm(name: string): string | undefined {
|
|
70
|
+
return MODULE_FORMS.find((one) => one.name.test(name))?.form;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
/** Whether one file name is a JSON configuration file's. */
|
|
74
|
+
export function isConfigurationDocumentName(name: string): boolean {
|
|
75
|
+
return DOCUMENT_NAME.test(name);
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
/** One JSON configuration file, with every string value it holds. */
|
|
79
|
+
interface ConfigurationDocument {
|
|
80
|
+
/** The file, absolute. */
|
|
81
|
+
readonly path: string;
|
|
82
|
+
/** Every string value at any depth, in document order; a member's name is no value. */
|
|
83
|
+
readonly strings: readonly string[];
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
/** The configuration files one project holds and sits beside. */
|
|
87
|
+
interface ConfigurationFiles {
|
|
88
|
+
/** Every own file that is a configuration module, with the form its name has. */
|
|
89
|
+
readonly modules: readonly { readonly file: SourceFile; readonly form: string }[];
|
|
90
|
+
/** Every JSON configuration file in a directory of the project's files or above one. */
|
|
91
|
+
readonly documents: readonly ConfigurationDocument[];
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
/** Every string value one decoded JSON value holds at any depth. */
|
|
95
|
+
export function documentStrings(value: unknown): string[] {
|
|
96
|
+
if (typeof value === "string") {
|
|
97
|
+
return [value];
|
|
98
|
+
}
|
|
99
|
+
if (Array.isArray(value)) {
|
|
100
|
+
return value.flatMap(documentStrings);
|
|
101
|
+
}
|
|
102
|
+
return isRecord(value) ? Object.values(value).flatMap(documentStrings) : [];
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
/** Every string literal and every template literal with no substitution one file writes. */
|
|
106
|
+
export function moduleStrings(
|
|
107
|
+
file: SourceFile,
|
|
108
|
+
): readonly { readonly node: Node; readonly text: string }[] {
|
|
109
|
+
const found: { node: Node; text: string }[] = [];
|
|
110
|
+
const visit = (node: Node): void => {
|
|
111
|
+
if (isStringLiteral(node) || isNoSubstitutionTemplateLiteral(node)) {
|
|
112
|
+
found.push({ node, text: node.text });
|
|
113
|
+
}
|
|
114
|
+
node.forEachChild(visit);
|
|
115
|
+
};
|
|
116
|
+
file.forEachChild(visit);
|
|
117
|
+
return found;
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
/**
|
|
121
|
+
* The configuration files of one project's own files below the target root. A module
|
|
122
|
+
* is an own file; a JSON configuration file is read from each directory below the
|
|
123
|
+
* target root, the root included, that holds an own file or is above one. Either sits
|
|
124
|
+
* in a directory that holds a manifest. A document that cannot be read or parsed as
|
|
125
|
+
* JSON holds no string.
|
|
126
|
+
*/
|
|
127
|
+
export function configurationFiles(
|
|
128
|
+
host: Host,
|
|
129
|
+
targetRoot: string,
|
|
130
|
+
files: SourceFiles,
|
|
131
|
+
): ConfigurationFiles {
|
|
132
|
+
const root = resolvePath(host.workingDirectory(), targetRoot);
|
|
133
|
+
const manifests = new Map<string, boolean>();
|
|
134
|
+
const holdsManifest = (dir: string): boolean => {
|
|
135
|
+
let held = manifests.get(dir);
|
|
136
|
+
if (held === undefined) {
|
|
137
|
+
held = host.kindOf(joinPath(dir, MANIFEST)) === "file";
|
|
138
|
+
manifests.set(dir, held);
|
|
139
|
+
}
|
|
140
|
+
return held;
|
|
141
|
+
};
|
|
142
|
+
|
|
143
|
+
const modules: { file: SourceFile; form: string }[] = [];
|
|
144
|
+
const dirs = new Set<string>([root]);
|
|
145
|
+
for (const file of files.byPath.values()) {
|
|
146
|
+
const form = configurationModuleForm(basename(file.fileName));
|
|
147
|
+
if (form !== undefined && holdsManifest(dirnamePath(file.fileName))) {
|
|
148
|
+
modules.push({ file, form });
|
|
149
|
+
}
|
|
150
|
+
for (
|
|
151
|
+
let dir = dirnamePath(file.fileName);
|
|
152
|
+
relativePath(root, dir) !== undefined && !dirs.has(dir);
|
|
153
|
+
dir = dirnamePath(dir)
|
|
154
|
+
) {
|
|
155
|
+
dirs.add(dir);
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
const documents: ConfigurationDocument[] = [];
|
|
160
|
+
for (const dir of [...dirs].sort()) {
|
|
161
|
+
if (!holdsManifest(dir)) {
|
|
162
|
+
continue;
|
|
163
|
+
}
|
|
164
|
+
let names: readonly string[];
|
|
165
|
+
try {
|
|
166
|
+
names = host
|
|
167
|
+
.readDirectory(dir)
|
|
168
|
+
.filter((entry) => !entry.directory && isConfigurationDocumentName(entry.name))
|
|
169
|
+
.map((entry) => entry.name)
|
|
170
|
+
.sort();
|
|
171
|
+
} catch {
|
|
172
|
+
continue;
|
|
173
|
+
}
|
|
174
|
+
for (const name of names) {
|
|
175
|
+
const path = joinPath(dir, name);
|
|
176
|
+
let value: unknown;
|
|
177
|
+
try {
|
|
178
|
+
value = JSON.parse(host.readFile(path));
|
|
179
|
+
} catch {
|
|
180
|
+
continue;
|
|
181
|
+
}
|
|
182
|
+
documents.push({ path, strings: documentStrings(value) });
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
return { modules, documents };
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
/** Every dependency the manifest in one directory declares, or none where it holds none to read. */
|
|
189
|
+
export function declaredIn(host: Host, dir: string): readonly string[] {
|
|
190
|
+
let value: unknown;
|
|
191
|
+
try {
|
|
192
|
+
value = JSON.parse(host.readFile(joinPath(dir, MANIFEST)));
|
|
193
|
+
} catch {
|
|
194
|
+
return [];
|
|
195
|
+
}
|
|
196
|
+
if (!isRecord(value)) {
|
|
197
|
+
return [];
|
|
198
|
+
}
|
|
199
|
+
const names = new Set<string>();
|
|
200
|
+
for (const member of DEPENDENCY_MEMBERS) {
|
|
201
|
+
const section = value[member];
|
|
202
|
+
if (isRecord(section)) {
|
|
203
|
+
Object.keys(section).forEach((name) => names.add(name));
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
return [...names];
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
/** The dependencies one string uses: each it equals, or is followed by `/` and a subpath in. */
|
|
210
|
+
export function dependenciesNamed(text: string, declared: readonly string[]): readonly string[] {
|
|
211
|
+
return declared.filter((name) => text === name || text.startsWith(`${name}/`));
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
/**
|
|
215
|
+
* The own files one string names read against a directory: none unless it starts with
|
|
216
|
+
* `./` or `../`, else the file it names directly or through the emit mapping, or the
|
|
217
|
+
* file it names with an extension the module resolution appends.
|
|
218
|
+
*/
|
|
219
|
+
export function filesNamedBy(files: SourceFiles, dir: string, text: string): readonly SourceFile[] {
|
|
220
|
+
if (!RELATIVE_PREFIXES.some((prefix) => text.startsWith(prefix)) || text.includes("*")) {
|
|
221
|
+
return [];
|
|
222
|
+
}
|
|
223
|
+
const path = resolvePath(dir, text);
|
|
224
|
+
const direct = files.named(path);
|
|
225
|
+
if (direct.length > 0) {
|
|
226
|
+
return direct;
|
|
227
|
+
}
|
|
228
|
+
for (const extension of RESOLVED_EXTENSIONS) {
|
|
229
|
+
const held = files.byName.get(path + extension);
|
|
230
|
+
if (held !== undefined) {
|
|
231
|
+
return [held];
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
return [];
|
|
235
|
+
}
|
|
@@ -2,9 +2,9 @@
|
|
|
2
2
|
// Regenerate it with `UPDATE_GOLDEN=1 npx vitest --run src/conformance.corpus.test.ts`.
|
|
3
3
|
export const RECORD = {
|
|
4
4
|
conformance: {
|
|
5
|
-
corpusVersion: "2.
|
|
5
|
+
corpusVersion: "2.1.1",
|
|
6
6
|
result: "pass",
|
|
7
|
-
digest: "sha256:
|
|
7
|
+
digest: "sha256:e5e456bf3a706f1fa9f906ae71e10ea98a78da52d7d99a5ed59dca8a72bbe927",
|
|
8
8
|
},
|
|
9
9
|
gaps: [
|
|
10
10
|
{
|