@cplieger/deadset-ts 8.0.0-dev.14 → 8.0.0-dev.16
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/dist/src/alias-chain.js +2 -2
- package/dist/src/analysis.js +43 -38
- package/dist/src/commands/read.js +6 -7
- package/dist/src/conformance-record.js +1 -1
- package/dist/src/consumers.js +4 -2
- package/dist/src/contract/config.generated.js +1 -1
- package/dist/src/contract/contract.generated.js +120 -2
- package/dist/src/dependencies.js +81 -49
- package/dist/src/entries/build-scripts.js +222 -0
- package/dist/src/{configuration-files.js → entries/configuration-files.js} +20 -58
- package/dist/src/{convention-rows.js → entries/convention-rows.js} +120 -27
- package/dist/src/entries/conventions.js +739 -0
- package/dist/src/{html-entries.js → entries/html-entries.js} +3 -3
- package/dist/src/{mdx-entries.js → entries/mdx-entries.js} +2 -2
- package/dist/src/entries/run-directories.js +77 -0
- package/dist/src/entries/sources.js +144 -0
- package/dist/src/entries/static-module.js +669 -0
- package/dist/src/entries/test-runners.js +684 -0
- package/dist/src/{workflow-steps.js → entries/workflow-steps.js} +18 -8
- package/dist/src/extensions.js +37 -0
- package/dist/src/facts.js +38 -0
- package/dist/src/file-facts.js +1 -1
- package/dist/src/findings/emitters.js +5 -8
- package/dist/src/findings/self-check.js +36 -20
- package/dist/src/findings-pass.js +5 -6
- package/dist/src/framework-lifecycle.js +1 -1
- package/dist/src/fs/manifest.js +15 -20
- package/dist/src/generated-file.js +172 -29
- package/dist/src/graph.js +2 -5
- package/dist/src/index.js +1 -1
- package/dist/src/injection-container.js +1 -1
- package/dist/src/inventory/commonjs.js +507 -0
- package/dist/src/inventory/enumerate.js +167 -0
- package/dist/src/inventory/syntax.js +236 -0
- package/dist/src/inventory.js +305 -705
- package/dist/src/javascript-file.js +26 -11
- package/dist/src/load/classify.js +28 -9
- package/dist/src/load/diagnostics.js +8 -1
- package/dist/src/load/discover.js +16 -8
- package/dist/src/load/package-scope.js +56 -0
- package/dist/src/load/plan.js +2 -2
- package/dist/src/load/synthesize.js +5 -5
- package/dist/src/load/workspace-manifests.js +1 -1
- package/dist/src/matrix.js +6 -13
- package/dist/src/outside-tests.js +1 -1
- package/dist/src/paths.js +16 -0
- package/dist/src/platform-calls.js +78 -0
- package/dist/src/platform-symbols.js +140 -0
- package/dist/src/position.js +41 -0
- package/dist/src/ref.js +11 -20
- package/dist/src/references.js +281 -50
- package/dist/src/resolve.js +1 -1
- package/dist/src/{configured-declarations.js → roots/configured-declarations.js} +4 -4
- package/dist/src/{entry-point-gaps.js → roots/entry-point-gaps.js} +5 -8
- package/dist/src/{manifest.js → roots/manifest.js} +4 -4
- package/dist/src/roots/published-reach.js +412 -0
- package/dist/src/roots/root-set.js +489 -0
- package/dist/src/roots/unseen-loads.js +208 -0
- package/dist/src/serialization-contract.js +1 -1
- package/dist/src/session.js +3 -0
- package/dist/src/source-files.js +5 -9
- package/dist/src/suppress-inline.js +33 -81
- package/dist/src/suppress.js +30 -32
- package/dist/src/test-support.js +4 -2
- package/dist/src/verbs/analyze.js +13 -1
- package/dist/src/verbs/describe.js +5 -3
- package/jsr.json +1 -1
- package/package.json +3 -3
- package/src/alias-chain.ts +2 -2
- package/src/analysis.ts +77 -53
- package/src/automatic-types.ts +1 -1
- package/src/commands/read.ts +6 -8
- package/src/conformance-record.ts +1 -1
- package/src/consumers.ts +5 -1
- package/src/contract/config.generated.ts +1 -1
- package/src/contract/contract.generated.ts +139 -2
- package/src/dependencies.ts +98 -50
- package/src/entries/build-scripts.ts +292 -0
- package/src/{configuration-files.ts → entries/configuration-files.ts} +23 -75
- package/src/{convention-rows.ts → entries/convention-rows.ts} +167 -28
- package/src/{conventions.ts → entries/conventions.ts} +347 -522
- package/src/{html-entries.ts → entries/html-entries.ts} +5 -5
- package/src/{mdx-entries.ts → entries/mdx-entries.ts} +4 -4
- package/src/entries/run-directories.ts +108 -0
- package/src/entries/sources.ts +266 -0
- package/src/entries/static-module.ts +857 -0
- package/src/entries/test-runners.ts +868 -0
- package/src/{workflow-steps.ts → entries/workflow-steps.ts} +29 -11
- package/src/exempt.ts +3 -2
- package/src/extensions.ts +45 -0
- package/src/facts.ts +44 -0
- package/src/file-facts.ts +2 -2
- package/src/findings/emitters.ts +14 -10
- package/src/findings/self-check.ts +41 -22
- package/src/findings/visibility-narrowing.ts +1 -1
- package/src/findings-pass.ts +5 -12
- package/src/framework-lifecycle.ts +1 -1
- package/src/fs/manifest.ts +25 -21
- package/src/generated-file.ts +240 -36
- package/src/graph.ts +3 -6
- package/src/index.ts +1 -1
- package/src/injection-container.ts +1 -1
- package/src/inventory/commonjs.ts +696 -0
- package/src/inventory/enumerate.ts +203 -0
- package/src/inventory/syntax.ts +311 -0
- package/src/inventory.ts +424 -878
- package/src/javascript-file.ts +29 -12
- package/src/load/classify.ts +49 -13
- package/src/load/diagnostics.ts +11 -1
- package/src/load/discover.ts +28 -12
- package/src/load/package-scope.ts +74 -0
- package/src/load/plan.ts +11 -2
- package/src/load/synthesize.ts +6 -6
- package/src/load/test-globals.ts +1 -1
- package/src/load/workspace-manifests.ts +1 -1
- package/src/load/workspace-run.ts +1 -1
- package/src/matrix.ts +7 -14
- package/src/outside-tests.ts +1 -1
- package/src/paths.ts +18 -0
- package/src/platform-calls.ts +119 -0
- package/src/platform-symbols.ts +205 -0
- package/src/position.ts +42 -0
- package/src/ref.ts +11 -20
- package/src/references.ts +383 -73
- package/src/report.ts +1 -1
- package/src/resolve.ts +1 -1
- package/src/{configured-declarations.ts → roots/configured-declarations.ts} +7 -7
- package/src/{entry-point-gaps.ts → roots/entry-point-gaps.ts} +5 -8
- package/src/{manifest.ts → roots/manifest.ts} +5 -5
- package/src/roots/published-reach.ts +481 -0
- package/src/{roots.ts → roots/root-set.ts} +203 -630
- package/src/roots/unseen-loads.ts +307 -0
- package/src/serialization-contract.ts +1 -1
- package/src/session.ts +17 -1
- package/src/source-files.ts +5 -10
- package/src/suppress-inline.ts +53 -89
- package/src/suppress.ts +48 -33
- package/src/test-support.ts +8 -3
- package/src/verbs/analyze.ts +17 -4
- package/src/verbs/describe.ts +5 -2
- package/src/verbs/explain.ts +1 -1
- package/dist/src/build-scripts.js +0 -418
- package/dist/src/conventions.js +0 -907
- package/dist/src/entry-points.js +0 -513
- package/dist/src/roots.js +0 -865
- package/dist/src/unseen-loads.js +0 -100
- package/src/build-scripts.ts +0 -536
- package/src/entry-points.ts +0 -661
- package/src/unseen-loads.ts +0 -134
|
@@ -0,0 +1,307 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The loads by a computed path: those whose files the analysis resolves, and those it
|
|
3
|
+
* cannot, whose files are roots the analysis does not see, so the configuration's roots
|
|
4
|
+
* are never complete.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import {
|
|
8
|
+
isBinaryExpression,
|
|
9
|
+
isCallExpression,
|
|
10
|
+
isIdentifier,
|
|
11
|
+
isMetaProperty,
|
|
12
|
+
isNoSubstitutionTemplateLiteral,
|
|
13
|
+
isPropertyAccessExpression,
|
|
14
|
+
isStringLiteral,
|
|
15
|
+
isTemplateExpression,
|
|
16
|
+
SyntaxKind,
|
|
17
|
+
type CallExpression,
|
|
18
|
+
type Identifier,
|
|
19
|
+
type Node,
|
|
20
|
+
type SourceFile,
|
|
21
|
+
} from "@typescript/native/ast";
|
|
22
|
+
import { membersNamed, textsOf, writtenValue, type Value } from "../entries/static-module.ts";
|
|
23
|
+
import { writtenName } from "../platform-calls.ts";
|
|
24
|
+
import { dirnamePath, relativePath, resolvePath } from "../paths.ts";
|
|
25
|
+
import type { Position } from "../position.ts";
|
|
26
|
+
import { unwrapped } from "../unwrap.ts";
|
|
27
|
+
import { globExpression } from "../text/glob.ts";
|
|
28
|
+
|
|
29
|
+
/** One load the analysis cannot resolve, in the configuration whose production file writes it. */
|
|
30
|
+
export interface UnseenLoad {
|
|
31
|
+
readonly configuration: string;
|
|
32
|
+
/** The position of the call. */
|
|
33
|
+
readonly position: Position;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/** The literal text a computed path starts with: a template's head, or a concatenation's first literal. */
|
|
37
|
+
function leadingLiteral(node: Node): string | undefined {
|
|
38
|
+
const at = unwrapped(node);
|
|
39
|
+
if (isTemplateExpression(at)) {
|
|
40
|
+
return at.head.text;
|
|
41
|
+
}
|
|
42
|
+
if (isBinaryExpression(at) && at.operatorToken.kind === SyntaxKind.PlusToken) {
|
|
43
|
+
const left = unwrapped(at.left);
|
|
44
|
+
return isStringLiteral(left) || isNoSubstitutionTemplateLiteral(left)
|
|
45
|
+
? left.text
|
|
46
|
+
: leadingLiteral(at.left);
|
|
47
|
+
}
|
|
48
|
+
return undefined;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* The directory below `dir` a computed load's leading literal names, every file of which
|
|
53
|
+
* the load reaches, or none where it names a package, `dir` itself or a directory holding
|
|
54
|
+
* it, each of which would name every sibling.
|
|
55
|
+
*/
|
|
56
|
+
function computedLoadDirectory(dir: string, prefix: string): string | undefined {
|
|
57
|
+
if (!(prefix.startsWith("./") || prefix.startsWith("../")) || !prefix.endsWith("/")) {
|
|
58
|
+
return undefined;
|
|
59
|
+
}
|
|
60
|
+
const target = resolvePath(dir, prefix);
|
|
61
|
+
return target === dir || relativePath(target, dir) !== undefined ? undefined : target;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
/** One `import()`, `require()` or `import.meta.glob()` call, and the declaration whose code writes it. */
|
|
65
|
+
export interface LoadCall {
|
|
66
|
+
readonly call: CallExpression;
|
|
67
|
+
readonly from: string;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
/** One file a computed load may load, by its id. */
|
|
71
|
+
interface Loadable {
|
|
72
|
+
readonly file: SourceFile;
|
|
73
|
+
readonly fileId: string;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
/** One load by a computed path whose files the analysis resolves. */
|
|
77
|
+
interface ComputedLoad {
|
|
78
|
+
readonly call: CallExpression;
|
|
79
|
+
readonly from: string;
|
|
80
|
+
/** The name a `require()` call is written with; {@link callsPlatform} owns which one loads. */
|
|
81
|
+
readonly require: Identifier | undefined;
|
|
82
|
+
readonly files: readonly Loadable[];
|
|
83
|
+
/** Whether the load reads each file as text or as a URL, which references no declaration of it. */
|
|
84
|
+
readonly asset: boolean;
|
|
85
|
+
/**
|
|
86
|
+
* Whether the analysis names every file the load may load. An unresolved load names none,
|
|
87
|
+
* and leaves the configuration's roots incomplete (confidence.26).
|
|
88
|
+
*/
|
|
89
|
+
readonly resolved: boolean;
|
|
90
|
+
/**
|
|
91
|
+
* Whether the load is an eager glob, which Vite turns into static imports at the top of
|
|
92
|
+
* the calling module (https://vite.dev/guide/features.html#glob-import), so the file
|
|
93
|
+
* evaluates what it loads wherever the call is written.
|
|
94
|
+
*/
|
|
95
|
+
readonly eager: boolean;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
/** Whether one node is a call that may load a module by a computed path. */
|
|
99
|
+
export function isLoadCall(node: Node): node is CallExpression {
|
|
100
|
+
if (!isCallExpression(node)) {
|
|
101
|
+
return false;
|
|
102
|
+
}
|
|
103
|
+
return (
|
|
104
|
+
node.expression.kind === SyntaxKind.ImportKeyword ||
|
|
105
|
+
writtenName(node.expression) !== undefined ||
|
|
106
|
+
isMetaGlob(node)
|
|
107
|
+
);
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
/**
|
|
111
|
+
* The files one `import.meta.glob` call loads, as Vite's `toAbsoluteGlob` reads its globs
|
|
112
|
+
* (https://vite.dev/guide/features.html#glob-import): `/` against the project root, `./`
|
|
113
|
+
* and `../` against a literal `base` (read against the root where it starts with `/`) or the
|
|
114
|
+
* calling file's directory, `**` as written, and `!` removing what it matches. Undefined
|
|
115
|
+
* where Vite reads a glob through an alias, the root is unknown, or a glob or base is computed.
|
|
116
|
+
*/
|
|
117
|
+
function globbed(
|
|
118
|
+
file: SourceFile,
|
|
119
|
+
first: Node,
|
|
120
|
+
options: Node | undefined,
|
|
121
|
+
others: readonly Loadable[],
|
|
122
|
+
rootOf: (fileName: string) => string | undefined,
|
|
123
|
+
): readonly Loadable[] | undefined {
|
|
124
|
+
const patterns = textsOf(writtenValue(first), true);
|
|
125
|
+
const bases = globOption(options, "base");
|
|
126
|
+
const [written, ...more] = bases ?? [];
|
|
127
|
+
const base = written === undefined ? undefined : textsOf(written, false);
|
|
128
|
+
if (
|
|
129
|
+
patterns === undefined ||
|
|
130
|
+
bases === undefined ||
|
|
131
|
+
more.length > 0 ||
|
|
132
|
+
(written !== undefined && base === undefined)
|
|
133
|
+
) {
|
|
134
|
+
return undefined;
|
|
135
|
+
}
|
|
136
|
+
const dir = dirnamePath(file.fileName);
|
|
137
|
+
const root = (): string | undefined => rootOf(file.fileName);
|
|
138
|
+
const against = (at: string): string | undefined => {
|
|
139
|
+
if (!at.startsWith("/")) {
|
|
140
|
+
return resolvePath(dir, at);
|
|
141
|
+
}
|
|
142
|
+
const top = root();
|
|
143
|
+
return top === undefined ? undefined : resolvePath(top, `.${at}`);
|
|
144
|
+
};
|
|
145
|
+
const from = base?.[0] === undefined ? dir : against(base[0]);
|
|
146
|
+
const keep: RegExp[] = [];
|
|
147
|
+
const drop: RegExp[] = [];
|
|
148
|
+
for (const written of patterns) {
|
|
149
|
+
const negated = written.startsWith("!");
|
|
150
|
+
const pattern = negated ? written.slice(1) : written;
|
|
151
|
+
let path: string | undefined;
|
|
152
|
+
if (pattern.startsWith("/")) {
|
|
153
|
+
path = against(pattern);
|
|
154
|
+
} else if (pattern.startsWith("./") || pattern.startsWith("../")) {
|
|
155
|
+
path = from === undefined ? undefined : resolvePath(from, pattern);
|
|
156
|
+
} else if (pattern.startsWith("**")) {
|
|
157
|
+
path = pattern;
|
|
158
|
+
}
|
|
159
|
+
if (path === undefined) {
|
|
160
|
+
return undefined;
|
|
161
|
+
}
|
|
162
|
+
(negated ? drop : keep).push(globExpression(path));
|
|
163
|
+
}
|
|
164
|
+
return others.filter(
|
|
165
|
+
(candidate) =>
|
|
166
|
+
keep.some((glob) => glob.test(candidate.file.fileName)) &&
|
|
167
|
+
!drop.some((glob) => glob.test(candidate.file.fileName)),
|
|
168
|
+
);
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
/**
|
|
172
|
+
* The computed loads among one file's load calls, each loading the `candidates` but the
|
|
173
|
+
* file itself: an `import()` or `require()` whose path starts with a relative literal naming
|
|
174
|
+
* a directory below the file's loads every candidate below it, as a bundler's context module
|
|
175
|
+
* reads it, and an `import.meta.glob` every candidate its globs match ({@link globbed}). A
|
|
176
|
+
* call of a computed path that names no such directory and no package is an unresolved load.
|
|
177
|
+
*/
|
|
178
|
+
export function computedLoadsOf(
|
|
179
|
+
file: SourceFile,
|
|
180
|
+
calls: readonly LoadCall[],
|
|
181
|
+
candidates: readonly Loadable[],
|
|
182
|
+
rootOf: (fileName: string) => string | undefined,
|
|
183
|
+
): readonly ComputedLoad[] {
|
|
184
|
+
const dir = dirnamePath(file.fileName);
|
|
185
|
+
const others = candidates.filter((candidate) => candidate.file !== file);
|
|
186
|
+
return calls.flatMap(({ call, from }): ComputedLoad[] => {
|
|
187
|
+
const [first, options] = call.arguments;
|
|
188
|
+
if (first === undefined) {
|
|
189
|
+
return [];
|
|
190
|
+
}
|
|
191
|
+
if (isMetaGlob(call)) {
|
|
192
|
+
const files = globbed(file, first, options, others, rootOf);
|
|
193
|
+
const asset = readsAsAsset(options);
|
|
194
|
+
return [
|
|
195
|
+
{
|
|
196
|
+
call,
|
|
197
|
+
from,
|
|
198
|
+
require: undefined,
|
|
199
|
+
files: files ?? [],
|
|
200
|
+
resolved: files !== undefined,
|
|
201
|
+
asset,
|
|
202
|
+
eager: isEager(options),
|
|
203
|
+
},
|
|
204
|
+
];
|
|
205
|
+
}
|
|
206
|
+
const argument = unwrapped(first);
|
|
207
|
+
const require =
|
|
208
|
+
call.expression.kind === SyntaxKind.ImportKeyword ? undefined : writtenName(call.expression);
|
|
209
|
+
if (!mayNamePath(argument)) {
|
|
210
|
+
return [];
|
|
211
|
+
}
|
|
212
|
+
const prefix = leadingLiteral(argument);
|
|
213
|
+
if (namesPackage(prefix)) {
|
|
214
|
+
return [];
|
|
215
|
+
}
|
|
216
|
+
const target = prefix === undefined ? undefined : computedLoadDirectory(dir, prefix);
|
|
217
|
+
const files =
|
|
218
|
+
target === undefined
|
|
219
|
+
? []
|
|
220
|
+
: others.filter((candidate) => relativePath(target, candidate.file.fileName) !== undefined);
|
|
221
|
+
return [
|
|
222
|
+
{ call, from, require, files, resolved: target !== undefined, asset: false, eager: false },
|
|
223
|
+
];
|
|
224
|
+
});
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
/**
|
|
228
|
+
* Whether a computed argument may be a module path: anything but a string, which names its
|
|
229
|
+
* module, and a value no path is, a number, a boolean, `null`, an object, an array, a
|
|
230
|
+
* function or a class.
|
|
231
|
+
*/
|
|
232
|
+
function mayNamePath(argument: Node): boolean {
|
|
233
|
+
return !NO_PATH.has(argument.kind);
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
const NO_PATH: ReadonlySet<SyntaxKind> = new Set([
|
|
237
|
+
SyntaxKind.StringLiteral,
|
|
238
|
+
SyntaxKind.NoSubstitutionTemplateLiteral,
|
|
239
|
+
SyntaxKind.NumericLiteral,
|
|
240
|
+
SyntaxKind.BigIntLiteral,
|
|
241
|
+
SyntaxKind.TrueKeyword,
|
|
242
|
+
SyntaxKind.FalseKeyword,
|
|
243
|
+
SyntaxKind.NullKeyword,
|
|
244
|
+
SyntaxKind.ObjectLiteralExpression,
|
|
245
|
+
SyntaxKind.ArrayLiteralExpression,
|
|
246
|
+
SyntaxKind.ArrowFunction,
|
|
247
|
+
SyntaxKind.FunctionExpression,
|
|
248
|
+
SyntaxKind.ClassExpression,
|
|
249
|
+
SyntaxKind.RegularExpressionLiteral,
|
|
250
|
+
]);
|
|
251
|
+
|
|
252
|
+
/** Whether one call is `import.meta.glob(...)`. */
|
|
253
|
+
function isMetaGlob(node: CallExpression): boolean {
|
|
254
|
+
if (!isPropertyAccessExpression(node.expression)) {
|
|
255
|
+
return false;
|
|
256
|
+
}
|
|
257
|
+
const { expression, name } = node.expression;
|
|
258
|
+
return (
|
|
259
|
+
isIdentifier(name) &&
|
|
260
|
+
name.text === "glob" &&
|
|
261
|
+
isMetaProperty(expression) &&
|
|
262
|
+
expression.keywordToken === SyntaxKind.ImportKeyword
|
|
263
|
+
);
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
/** The values the options of an `import.meta.glob` write for one key, undefined where a spread may hide one. */
|
|
267
|
+
function globOption(options: Node | undefined, key: string): readonly Value[] | undefined {
|
|
268
|
+
return options === undefined ? [] : membersNamed(writtenValue(options), key);
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
/** Whether the options of an `import.meta.glob` set `eager` to `true`. */
|
|
272
|
+
function isEager(options: Node | undefined): boolean {
|
|
273
|
+
return (
|
|
274
|
+
globOption(options, "eager")?.some((value) => value.kind === "boolean" && value.value) === true
|
|
275
|
+
);
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
/** The query parameters that make Vite load a file as its text or its URL, each written bare. */
|
|
279
|
+
const ASSET_QUERY = /(?:^\?|&)(?:raw|url|inline)(?:&|$)/u;
|
|
280
|
+
|
|
281
|
+
/**
|
|
282
|
+
* Whether the options of an `import.meta.glob` read each file as text or as a URL: a
|
|
283
|
+
* `query` string holding a bare `raw`, `url` or `inline`, or an `as` of `raw` or `url`,
|
|
284
|
+
* which Vite reads as that query (https://vite.dev/guide/features.html#glob-import). Any
|
|
285
|
+
* other query is read as loading the module, which keeps more than it may.
|
|
286
|
+
*/
|
|
287
|
+
function readsAsAsset(options: Node | undefined): boolean {
|
|
288
|
+
const texts = (key: string): readonly string[] =>
|
|
289
|
+
(globOption(options, key) ?? []).flatMap((value) =>
|
|
290
|
+
value.kind === "text" ? [value.text] : [],
|
|
291
|
+
);
|
|
292
|
+
return (
|
|
293
|
+
texts("as").some((text) => text === "raw" || text === "url") ||
|
|
294
|
+
texts("query").some((text) => ASSET_QUERY.test(text.startsWith("?") ? text : `?${text}`))
|
|
295
|
+
);
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
/** Whether a computed path's leading literal names a package, whose files are none of the target's. */
|
|
299
|
+
function namesPackage(prefix: string | undefined): boolean {
|
|
300
|
+
return (
|
|
301
|
+
prefix !== undefined &&
|
|
302
|
+
prefix !== "" &&
|
|
303
|
+
!prefix.startsWith(".") &&
|
|
304
|
+
!prefix.startsWith("/") &&
|
|
305
|
+
!/^[A-Za-z][A-Za-z\d+.-]*:/u.test(prefix)
|
|
306
|
+
);
|
|
307
|
+
}
|
|
@@ -72,7 +72,7 @@ import {
|
|
|
72
72
|
namesADeclaration,
|
|
73
73
|
resolveEntries,
|
|
74
74
|
spelledEntry,
|
|
75
|
-
} from "./configured-declarations.ts";
|
|
75
|
+
} from "./roots/configured-declarations.ts";
|
|
76
76
|
import type { DetectorInput, Evidence } from "./exempt.ts";
|
|
77
77
|
import { nodeKey, type Inventory } from "./inventory.ts";
|
|
78
78
|
import { renderPosition, type Position } from "./position.ts";
|
package/src/session.ts
CHANGED
|
@@ -13,7 +13,7 @@ import type {
|
|
|
13
13
|
Symbol as TSSymbol,
|
|
14
14
|
TimingInfo,
|
|
15
15
|
} from "@typescript/native/sync";
|
|
16
|
-
import type { Node, SourceFile } from "@typescript/native/ast";
|
|
16
|
+
import type { Node, SourceFile, StringLiteralLikeNode } from "@typescript/native/ast";
|
|
17
17
|
import { componentLayer, mapperManifest } from "./component-layer.ts";
|
|
18
18
|
import type { Host, TemporaryDirectory } from "./host.ts";
|
|
19
19
|
import { joinPath } from "./paths.ts";
|
|
@@ -242,6 +242,11 @@ export interface ProjectView<Brand> {
|
|
|
242
242
|
* file it is in.
|
|
243
243
|
*/
|
|
244
244
|
ownPaths(): ReadonlySet<string>;
|
|
245
|
+
/**
|
|
246
|
+
* The file the compiler's module resolution names for each module specifier of this
|
|
247
|
+
* project's files, in order, or `undefined` where it names none.
|
|
248
|
+
*/
|
|
249
|
+
resolvedFileNames(specifiers: readonly StringLiteralLikeNode[]): (string | undefined)[];
|
|
245
250
|
/** A handle to one node of this project's program. */
|
|
246
251
|
handle(node: Node): Handle<Brand>;
|
|
247
252
|
/**
|
|
@@ -347,6 +352,17 @@ function viewOf<Brand>(
|
|
|
347
352
|
ownKeys ??= new Set(ownSourceFiles().map((file) => file.path));
|
|
348
353
|
return ownKeys;
|
|
349
354
|
},
|
|
355
|
+
resolvedFileNames: (specifiers) =>
|
|
356
|
+
batched(engine, specifiers, (node) =>
|
|
357
|
+
project.program.getResolvedModuleFromModuleSpecifier.gen(
|
|
358
|
+
node,
|
|
359
|
+
node.getSourceFile().fileName,
|
|
360
|
+
),
|
|
361
|
+
).map((resolved) =>
|
|
362
|
+
resolved === undefined || resolved.resolvedFileName === ""
|
|
363
|
+
? undefined
|
|
364
|
+
: resolved.resolvedFileName,
|
|
365
|
+
),
|
|
350
366
|
handle: (node) => ({ node }),
|
|
351
367
|
symbolsAt: (handles, cap) =>
|
|
352
368
|
queries.symbolsAt(
|
package/src/source-files.ts
CHANGED
|
@@ -8,6 +8,7 @@ import type { SourceFile } from "@typescript/native/ast";
|
|
|
8
8
|
import { commonDirectory, emittedFrom } from "./load/emit-map.ts";
|
|
9
9
|
import { dirnamePath, joinPath, relativePath, resolvePath } from "./paths.ts";
|
|
10
10
|
import type { ProjectView } from "./session.ts";
|
|
11
|
+
import { OUTPUT_EXTENSIONS } from "./extensions.ts";
|
|
11
12
|
|
|
12
13
|
/** The one character a subpath pattern of a manifest's `exports` holds. */
|
|
13
14
|
const SUBPATH_WILDCARD = "*";
|
|
@@ -15,9 +16,6 @@ const SUBPATH_WILDCARD = "*";
|
|
|
15
16
|
/** The extension a source file's name drops when the project writes it out. */
|
|
16
17
|
const SOURCE_EXTENSION = /\.[cm]?[jt]sx?$/u;
|
|
17
18
|
|
|
18
|
-
/** The extensions the project may write a source file's output under. */
|
|
19
|
-
const OUTPUT_EXTENSIONS = [".js", ".mjs", ".cjs", ".jsx", ".d.ts", ".d.mts", ".d.cts"] as const;
|
|
20
|
-
|
|
21
19
|
/**
|
|
22
20
|
* Whether one path is what a subpath pattern stands for: the text before the
|
|
23
21
|
* wildcard, any run of characters including the solidus, then the text after it.
|
|
@@ -90,13 +88,10 @@ function layoutOf<Brand>(
|
|
|
90
88
|
}
|
|
91
89
|
|
|
92
90
|
/**
|
|
93
|
-
* The lookups over one project's own files.
|
|
94
|
-
*
|
|
95
|
-
*
|
|
96
|
-
*
|
|
97
|
-
* extension is one the compiler emits from a source extension, and a path below the
|
|
98
|
-
* output directory is read below the directory the compiler emits from, which the
|
|
99
|
-
* configuration declares or which is the directory every input file shares.
|
|
91
|
+
* The lookups over one project's own files. A path naming an emitted file is read back to its
|
|
92
|
+
* source through the project's own emit mapping alone: the extension is one the compiler emits
|
|
93
|
+
* from a source extension, and a path below the output directory is read below the directory
|
|
94
|
+
* the compiler emits from, which the configuration declares or every input file shares.
|
|
100
95
|
*/
|
|
101
96
|
export function sourceFilesOf<Brand>(project: ProjectView<Brand>, targetRoot: string): SourceFiles {
|
|
102
97
|
const byName = new Map<string, SourceFile>();
|
package/src/suppress-inline.ts
CHANGED
|
@@ -5,18 +5,21 @@
|
|
|
5
5
|
*/
|
|
6
6
|
|
|
7
7
|
import { CODE } from "./contract/contract.generated.ts";
|
|
8
|
-
import {
|
|
9
|
-
isConstructorDeclaration,
|
|
10
|
-
SyntaxKind,
|
|
11
|
-
type Node,
|
|
12
|
-
type SourceFile,
|
|
13
|
-
} from "@typescript/native/ast";
|
|
8
|
+
import { SyntaxKind, type Node, type SourceFile } from "@typescript/native/ast";
|
|
14
9
|
import { importBindings } from "./import-bindings.ts";
|
|
15
|
-
import {
|
|
16
|
-
import {
|
|
10
|
+
import { nodeKey, type Inventory } from "./inventory.ts";
|
|
11
|
+
import { relativePath } from "./paths.ts";
|
|
12
|
+
import {
|
|
13
|
+
byPosition,
|
|
14
|
+
lineEnd,
|
|
15
|
+
positionKey,
|
|
16
|
+
renderPosition,
|
|
17
|
+
startLine,
|
|
18
|
+
tokenStart,
|
|
19
|
+
type Position,
|
|
20
|
+
} from "./position.ts";
|
|
17
21
|
import { importRef } from "./ref.ts";
|
|
18
22
|
import type { ProjectView } from "./session.ts";
|
|
19
|
-
import { signatureOf } from "./signatures.ts";
|
|
20
23
|
import {
|
|
21
24
|
isPartKind,
|
|
22
25
|
SuppressionError,
|
|
@@ -88,43 +91,6 @@ const LITERALS: ReadonlySet<SyntaxKind> = new Set([
|
|
|
88
91
|
SyntaxKind.JsxText,
|
|
89
92
|
]);
|
|
90
93
|
|
|
91
|
-
/** Whether one character ends a line, under the language's line terminators. */
|
|
92
|
-
function endsLine(char: string | undefined): boolean {
|
|
93
|
-
return char === "\n" || char === "\r" || char === "\u2028" || char === "\u2029";
|
|
94
|
-
}
|
|
95
|
-
|
|
96
|
-
/** The offset where the line holding `from` ends. */
|
|
97
|
-
function lineEnd(text: string, from: number): number {
|
|
98
|
-
let at = from;
|
|
99
|
-
while (at < text.length && !endsLine(text[at])) {
|
|
100
|
-
at += 1;
|
|
101
|
-
}
|
|
102
|
-
return at;
|
|
103
|
-
}
|
|
104
|
-
|
|
105
|
-
/**
|
|
106
|
-
* The offset of the first token at or after `from`: whitespace, comments and a leading
|
|
107
|
-
* hashbang are trivia.
|
|
108
|
-
*/
|
|
109
|
-
export function tokenStart(text: string, from: number): number {
|
|
110
|
-
let at = from;
|
|
111
|
-
if (at === 0 && text.startsWith("#!")) {
|
|
112
|
-
at = lineEnd(text, 0);
|
|
113
|
-
}
|
|
114
|
-
for (;;) {
|
|
115
|
-
if (/\s/u.test(text[at] ?? "")) {
|
|
116
|
-
at += 1;
|
|
117
|
-
} else if (text.startsWith("//", at)) {
|
|
118
|
-
at = lineEnd(text, at);
|
|
119
|
-
} else if (text.startsWith("/*", at)) {
|
|
120
|
-
const close = text.indexOf("*/", at + 2);
|
|
121
|
-
at = close < 0 ? text.length : close + 2;
|
|
122
|
-
} else {
|
|
123
|
-
return at;
|
|
124
|
-
}
|
|
125
|
-
}
|
|
126
|
-
}
|
|
127
|
-
|
|
128
94
|
/**
|
|
129
95
|
* Every `//` line comment of one file, in file order. The text between tokens is read as
|
|
130
96
|
* the language reads it: a block comment holds no line comment, and the literal tokens the
|
|
@@ -191,63 +157,59 @@ function bindingKey(one: Binding): string {
|
|
|
191
157
|
return `${one.declared?.id ?? ""}\u0000${one.part.id}`;
|
|
192
158
|
}
|
|
193
159
|
|
|
160
|
+
/** One declaration the inventory places, the declaration that holds its parts, and its lines. */
|
|
161
|
+
interface Placed {
|
|
162
|
+
readonly one: Declared;
|
|
163
|
+
readonly holder: Declared;
|
|
164
|
+
readonly startLines: readonly number[];
|
|
165
|
+
readonly partLines: readonly number[];
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
/** The placed declarations of a project by the path of the file each is in. */
|
|
169
|
+
function placedByPath(
|
|
170
|
+
held: Inventory,
|
|
171
|
+
symbols: ReadonlyMap<string, Declared>,
|
|
172
|
+
): ReadonlyMap<string, readonly Placed[]> {
|
|
173
|
+
const byPath = new Map<string, Placed[]>();
|
|
174
|
+
for (const [id, placed] of held.placement) {
|
|
175
|
+
const one = symbols.get(id);
|
|
176
|
+
const holder = symbols.get(placed.holder);
|
|
177
|
+
if (one === undefined || holder === undefined) {
|
|
178
|
+
continue;
|
|
179
|
+
}
|
|
180
|
+
const at = byPath.get(one.position.path) ?? [];
|
|
181
|
+
at.push({ one, holder, startLines: placed.startLines, partLines: placed.partLines });
|
|
182
|
+
byPath.set(one.position.path, at);
|
|
183
|
+
}
|
|
184
|
+
return byPath;
|
|
185
|
+
}
|
|
186
|
+
|
|
194
187
|
/**
|
|
195
|
-
* The declarations of one file by the line each
|
|
196
|
-
*
|
|
197
|
-
* keyword, and a variable written on a later line of the statement also begins there. A
|
|
198
|
-
* function also begins, for a directive, on the line of each of its parameters, and a
|
|
199
|
-
* class, for a part kind, on the lines of its constructor and its constructor's parameters.
|
|
188
|
+
* The declarations of one file by the line a directive binds each on, as the inventory
|
|
189
|
+
* places them, and each import binding of the file on its statement's lines.
|
|
200
190
|
*/
|
|
201
191
|
function declarationLines(
|
|
202
192
|
file: SourceFile,
|
|
193
|
+
placed: readonly Placed[],
|
|
203
194
|
held: Inventory,
|
|
204
|
-
symbols: ReadonlyMap<string, Declared>,
|
|
205
195
|
targetRoot: string,
|
|
206
196
|
): ReadonlyMap<number, readonly Binding[]> {
|
|
207
197
|
const below = new Map<number, Binding[]>();
|
|
208
|
-
const add = (
|
|
209
|
-
const line = file.getLineAndCharacterOfPosition(tokenStart(file.text, offset)).line + 1;
|
|
198
|
+
const add = (line: number, one: Binding): void => {
|
|
210
199
|
const at = below.get(line) ?? [];
|
|
211
200
|
if (!at.some((known) => bindingKey(known) === bindingKey(one))) {
|
|
212
201
|
at.push(one);
|
|
213
202
|
}
|
|
214
203
|
below.set(line, at);
|
|
215
204
|
};
|
|
216
|
-
const
|
|
217
|
-
const
|
|
218
|
-
|
|
219
|
-
};
|
|
220
|
-
const visit = (node: Node): void => {
|
|
221
|
-
const one = declaredAt(node);
|
|
222
|
-
const owner = isConstructorDeclaration(node) ? declaredAt(node.parent) : undefined;
|
|
223
|
-
if (owner !== undefined && isConstructorDeclaration(node)) {
|
|
224
|
-
for (const at of [node, ...node.parameters.filter((one) => !isParameterProperty(one))]) {
|
|
225
|
-
add(at.pos, { declared: undefined, part: owner });
|
|
226
|
-
}
|
|
205
|
+
for (const { one, holder, startLines, partLines } of placed) {
|
|
206
|
+
for (const line of startLines) {
|
|
207
|
+
add(line, { declared: one, part: holder });
|
|
227
208
|
}
|
|
228
|
-
const
|
|
229
|
-
|
|
230
|
-
if (one !== undefined && holder !== undefined) {
|
|
231
|
-
add(node.pos, { declared: one, part: holder });
|
|
232
|
-
}
|
|
233
|
-
if (one !== undefined) {
|
|
234
|
-
// A parameter on a later line of a wrapped signature takes its directive on the
|
|
235
|
-
// line above it, inside the parameter list, and the directive binds the function.
|
|
236
|
-
for (const parameter of signatureOf(node)?.parameters ?? []) {
|
|
237
|
-
add(parameter.pos, { declared: one, part: one });
|
|
238
|
-
}
|
|
239
|
-
const list = node.parent;
|
|
240
|
-
if (
|
|
241
|
-
node.kind === SyntaxKind.VariableDeclaration &&
|
|
242
|
-
list.kind === SyntaxKind.VariableDeclarationList &&
|
|
243
|
-
list.parent.kind === SyntaxKind.VariableStatement
|
|
244
|
-
) {
|
|
245
|
-
add(list.parent.pos, { declared: one, part: one });
|
|
246
|
-
}
|
|
209
|
+
for (const line of partLines) {
|
|
210
|
+
add(line, { declared: undefined, part: one });
|
|
247
211
|
}
|
|
248
|
-
|
|
249
|
-
};
|
|
250
|
-
file.forEachChild(visit);
|
|
212
|
+
}
|
|
251
213
|
// An import binding is a subject of its own, bound on its line as a declaration is.
|
|
252
214
|
const fileId = held.declarations.get(nodeKey(file, file));
|
|
253
215
|
const moduleRef = held.symbols.find((symbol) => symbol.id === fileId)?.ref ?? "";
|
|
@@ -259,7 +221,7 @@ function declarationLines(
|
|
|
259
221
|
statement = statement.parent;
|
|
260
222
|
}
|
|
261
223
|
for (const at of [statement.pos, name.getStart()]) {
|
|
262
|
-
add(at, { declared: one, part: one });
|
|
224
|
+
add(startLine(file, at), { declared: one, part: one });
|
|
263
225
|
}
|
|
264
226
|
}
|
|
265
227
|
return below;
|
|
@@ -296,13 +258,15 @@ export function inlineDirectives<Brand>(
|
|
|
296
258
|
.filter((symbol) => symbol.kind !== "file")
|
|
297
259
|
.map((symbol) => [symbol.id, { id: symbol.id, ref: symbol.ref, position: symbol.position }]),
|
|
298
260
|
);
|
|
261
|
+
const placed = placedByPath(held, symbols);
|
|
299
262
|
const found: InlineDirective[] = [];
|
|
300
263
|
for (const file of project.ownSourceFiles()) {
|
|
301
264
|
const comments = lineComments(file).filter((comment) => CANDIDATE.test(comment.text));
|
|
302
265
|
if (comments.length === 0) {
|
|
303
266
|
continue;
|
|
304
267
|
}
|
|
305
|
-
const
|
|
268
|
+
const path = relativePath(targetRoot, file.fileName) ?? "";
|
|
269
|
+
const below = declarationLines(file, placed.get(path) ?? [], held, targetRoot);
|
|
306
270
|
for (const comment of comments) {
|
|
307
271
|
const site = renderPosition(file, targetRoot, comment.offset);
|
|
308
272
|
const target = trails(file, comment.offset) ? site.line : site.line + 1;
|