@cplieger/deadset-ts 7.2.0-dev.3 → 8.0.0-dev.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/bin/mapper-connection.js +7 -4
- package/dist/src/analysis.js +116 -16
- package/dist/src/augmentations.js +21 -8
- package/dist/src/automatic-types.js +189 -0
- package/dist/src/base-url.js +18 -0
- package/dist/src/component-files.js +96 -31
- package/dist/src/components.js +7 -6
- package/dist/src/config.js +1 -0
- package/dist/src/configuration-files.js +54 -15
- package/dist/src/configured-declarations.js +24 -2
- package/dist/src/conformance-record.js +2 -2
- package/dist/src/contextual-uses.js +26 -26
- package/dist/src/convention-rows.js +75 -0
- package/dist/src/conventions.js +101 -5
- package/dist/src/decorator.js +21 -18
- package/dist/src/dependencies.js +185 -30
- package/dist/src/destructuring.js +77 -2
- package/dist/src/discover.js +188 -85
- package/dist/src/edges.js +1 -1
- package/dist/src/entry-point-gaps.js +7 -10
- package/dist/src/entry-points.js +112 -11
- package/dist/src/findings/completion.js +116 -21
- package/dist/src/findings/dependencies-and-module-machinery.js +71 -25
- package/dist/src/findings/emitters.js +1 -1
- package/dist/src/findings/intra-function.js +44 -13
- package/dist/src/findings/non-code-artifacts.js +66 -33
- package/dist/src/findings/unused-declarations.js +21 -1
- package/dist/src/findings/visibility-narrowing.js +22 -3
- package/dist/src/generated-file.js +11 -0
- package/dist/src/git-ignore.js +86 -0
- package/dist/src/graph.js +4 -1
- package/dist/src/import-bindings.js +97 -0
- package/dist/src/indexed-access.js +130 -28
- package/dist/src/interface-satisfaction.js +30 -12
- package/dist/src/intra-function-parts.js +313 -50
- package/dist/src/inventory.js +63 -8
- package/dist/src/json-document.js +5 -5
- package/dist/src/kinds.js +13 -13
- package/dist/src/matrix.js +18 -9
- package/dist/src/mdx-entries.js +197 -0
- package/dist/src/module-declarations.js +9 -13
- package/dist/src/namespace-objects.js +28 -18
- package/dist/src/overrides.js +45 -9
- package/dist/src/paths.js +9 -0
- package/dist/src/print.js +1 -0
- package/dist/src/ref.js +10 -0
- package/dist/src/references.js +29 -13
- package/dist/src/reflective-lookup.js +8 -6
- package/dist/src/report.js +4 -2
- package/dist/src/reporters/template.js +22 -13
- package/dist/src/resolve.js +2 -0
- package/dist/src/roots.js +110 -38
- package/dist/src/schema.js +1 -0
- package/dist/src/serialization-contract.js +97 -20
- package/dist/src/session.js +26 -1
- package/dist/src/source-files.js +39 -14
- package/dist/src/spelled-names.js +58 -0
- package/dist/src/suppress-file.js +12 -4
- package/dist/src/suppress-inline.js +21 -3
- package/dist/src/suppress.js +7 -5
- package/dist/src/sweep.js +27 -10
- package/dist/src/symbol-keys.js +103 -2
- package/dist/src/template-field.js +9 -6
- package/dist/src/test-support.js +121 -17
- package/dist/src/type-errors.js +116 -35
- package/dist/src/type-parameter-reads.js +172 -0
- package/dist/src/value-reach.js +17 -5
- package/dist/src/verbs/analyze.js +19 -1
- package/dist/src/verbs/explain.js +4 -2
- package/dist/src/version-range.js +10 -5
- package/dist/src/version.js +1 -1
- package/dist/src/workflow-steps.js +119 -22
- package/dist/src/workspace-programs.js +7 -3
- package/dist/src/workspace-resolution.js +72 -13
- package/dist/src/workspace.js +1 -1
- package/jsr.json +1 -1
- package/package.json +2 -2
- package/src/analysis.ts +175 -16
- package/src/augmentations.ts +21 -10
- package/src/automatic-types.ts +246 -0
- package/src/base-url.ts +24 -0
- package/src/component-files.ts +116 -31
- package/src/components.ts +7 -6
- package/src/config.ts +3 -0
- package/src/configuration-files.ts +80 -18
- package/src/configured-declarations.ts +33 -4
- package/src/conformance-record.ts +2 -2
- package/src/contextual-uses.ts +27 -27
- package/src/convention-rows.ts +103 -0
- package/src/conventions.ts +121 -7
- package/src/decorator.ts +28 -18
- package/src/dependencies.ts +249 -43
- package/src/destructuring.ts +93 -2
- package/src/discover.ts +234 -86
- package/src/edges.ts +1 -1
- package/src/entry-point-gaps.ts +8 -11
- package/src/entry-points.ts +133 -11
- package/src/finding.ts +10 -0
- package/src/findings/completion.ts +147 -24
- package/src/findings/dependencies-and-module-machinery.ts +90 -28
- package/src/findings/emitters.ts +1 -1
- package/src/findings/intra-function.ts +56 -14
- package/src/findings/non-code-artifacts.ts +81 -41
- package/src/findings/unused-declarations.ts +25 -1
- package/src/findings/visibility-narrowing.ts +32 -3
- package/src/generated-file.ts +16 -0
- package/src/git-ignore.ts +98 -0
- package/src/graph.ts +6 -0
- package/src/host.ts +5 -9
- package/src/import-bindings.ts +128 -0
- package/src/indexed-access.ts +152 -30
- package/src/interface-satisfaction.ts +33 -13
- package/src/intra-function-parts.ts +415 -64
- package/src/inventory.ts +83 -7
- package/src/json-document.ts +7 -7
- package/src/kinds.ts +13 -13
- package/src/matrix.ts +32 -14
- package/src/mdx-entries.ts +241 -0
- package/src/module-declarations.ts +11 -13
- package/src/namespace-objects.ts +34 -21
- package/src/overrides.ts +56 -9
- package/src/paths.ts +10 -0
- package/src/print.ts +1 -0
- package/src/ref.ts +12 -0
- package/src/references.ts +38 -17
- package/src/reflective-lookup.ts +9 -7
- package/src/report.ts +16 -3
- package/src/reporters/template.ts +21 -13
- package/src/resolve.ts +3 -0
- package/src/roots.ts +163 -45
- package/src/schema.ts +1 -0
- package/src/serialization-contract.ts +108 -17
- package/src/session.ts +29 -1
- package/src/source-files.ts +66 -16
- package/src/spelled-names.ts +102 -0
- package/src/suppress-file.ts +14 -3
- package/src/suppress-inline.ts +21 -2
- package/src/suppress.ts +9 -5
- package/src/sweep.ts +32 -11
- package/src/symbol-keys.ts +129 -2
- package/src/template-field.ts +9 -6
- package/src/test-support.ts +149 -16
- package/src/type-errors.ts +132 -41
- package/src/type-parameter-reads.ts +227 -0
- package/src/value-reach.ts +19 -6
- package/src/verbs/analyze.ts +28 -1
- package/src/verbs/explain.ts +5 -2
- package/src/version-range.ts +10 -5
- package/src/version.ts +1 -1
- package/src/workflow-steps.ts +127 -22
- package/src/workspace-programs.ts +7 -3
- package/src/workspace-resolution.ts +86 -14
- package/src/workspace.ts +1 -1
package/src/base-url.ts
ADDED
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import { resolvePath } from "./paths.ts";
|
|
2
|
+
import type { Engine } from "./session.ts";
|
|
3
|
+
|
|
4
|
+
type CompilerOptions = ReturnType<Engine["parseConfigFile"]>["options"];
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* The `paths` one configuration's resolved options map under the compilers before version
|
|
8
|
+
* 7, each target absolute: a target is read against the effective `baseUrl`, and a specifier
|
|
9
|
+
* no other pattern maps is looked up below it. Undefined where no `baseUrl` is set, so the
|
|
10
|
+
* options' own `paths` stand. Version 7 reads no `baseUrl`:
|
|
11
|
+
* https://github.com/microsoft/TypeScript/issues/62207
|
|
12
|
+
*/
|
|
13
|
+
export function baseUrlPaths(options: CompilerOptions): Record<string, string[]> | undefined {
|
|
14
|
+
const baseUrl: unknown = Reflect.get(options, "baseUrl");
|
|
15
|
+
if (typeof baseUrl !== "string") {
|
|
16
|
+
return undefined;
|
|
17
|
+
}
|
|
18
|
+
return Object.fromEntries(
|
|
19
|
+
Object.entries({ "*": ["*"], ...options.paths }).map(([key, targets]) => [
|
|
20
|
+
key,
|
|
21
|
+
targets.map((target) => resolvePath(baseUrl, target)),
|
|
22
|
+
]),
|
|
23
|
+
);
|
|
24
|
+
}
|
package/src/component-files.ts
CHANGED
|
@@ -188,18 +188,25 @@ function scriptTagAt(text: string, offset: number): boolean {
|
|
|
188
188
|
);
|
|
189
189
|
}
|
|
190
190
|
|
|
191
|
+
/** Where the braces opening at one offset end, and whether a `}` matched them. */
|
|
192
|
+
interface Braces {
|
|
193
|
+
/** Past the matching `}`, or where the scan stopped: a line-start `<script` or the end. */
|
|
194
|
+
readonly end: number;
|
|
195
|
+
readonly closed: boolean;
|
|
196
|
+
}
|
|
197
|
+
|
|
191
198
|
/**
|
|
192
|
-
* The
|
|
193
|
-
*
|
|
194
|
-
*
|
|
199
|
+
* The braces opening at `from`, quoted strings skipped. The scan stops unclosed at the
|
|
200
|
+
* end, or before a `<script` start tag that begins a line, so a stray brace in markup
|
|
201
|
+
* never hides a block.
|
|
195
202
|
*/
|
|
196
|
-
function
|
|
203
|
+
function scanBraces(text: string, from: number): Braces {
|
|
197
204
|
let depth = 0;
|
|
198
205
|
let at = from;
|
|
199
206
|
while (at < text.length) {
|
|
200
207
|
const code = text.charCodeAt(at);
|
|
201
208
|
if (scriptTagAt(text, at)) {
|
|
202
|
-
return
|
|
209
|
+
return { end: at, closed: false };
|
|
203
210
|
}
|
|
204
211
|
if (code === 0x22 || code === 0x27 || code === 0x60) {
|
|
205
212
|
at = pastQuoted(text, at);
|
|
@@ -210,12 +217,59 @@ function pastBraces(text: string, from: number): number {
|
|
|
210
217
|
} else if (code === 0x7d) {
|
|
211
218
|
depth -= 1;
|
|
212
219
|
if (depth === 0) {
|
|
213
|
-
return at + 1;
|
|
220
|
+
return { end: at + 1, closed: true };
|
|
214
221
|
}
|
|
215
222
|
}
|
|
216
223
|
at += 1;
|
|
217
224
|
}
|
|
218
|
-
return
|
|
225
|
+
return { end: text.length, closed: false };
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
/** The offset past the `}` matching the `{` at `from`, or -1 where {@link scanBraces} finds none. */
|
|
229
|
+
function pastBraces(text: string, from: number): number {
|
|
230
|
+
const braces = scanBraces(text, from);
|
|
231
|
+
return braces.closed ? braces.end : -1;
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
/** How one framework's parser reads the forms the frameworks disagree on. */
|
|
235
|
+
interface Dialect {
|
|
236
|
+
/** Whether `<!-->`, `<!--->` and `--!>` end a comment, the opening dashes counted. */
|
|
237
|
+
readonly shortComments: boolean;
|
|
238
|
+
/** Whether a start tag's unmatched `{` is a character of an attribute name or value. */
|
|
239
|
+
readonly braceIsText: boolean;
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
/**
|
|
243
|
+
* The dialect each framework's parser reads: Vue's tokenizer, Svelte's parser and Astro's
|
|
244
|
+
* tokenizer, the last for any other extension too. `.vue` also closes `--!>`, which Vue
|
|
245
|
+
* does not, so a block Vue drops there is still read.
|
|
246
|
+
* https://github.com/vuejs/core/blob/main/packages/compiler-core/src/tokenizer.ts
|
|
247
|
+
* https://github.com/sveltejs/svelte/blob/main/packages/svelte/src/compiler/phases/1-parse/state/element.js
|
|
248
|
+
* https://github.com/withastro/compiler/blob/main/internal/token.go
|
|
249
|
+
*/
|
|
250
|
+
function dialectOf(extension: string): Dialect {
|
|
251
|
+
switch (extension.toLowerCase()) {
|
|
252
|
+
case ".svelte":
|
|
253
|
+
return { shortComments: false, braceIsText: false };
|
|
254
|
+
case ".vue":
|
|
255
|
+
return { shortComments: true, braceIsText: true };
|
|
256
|
+
default:
|
|
257
|
+
return { shortComments: true, braceIsText: false };
|
|
258
|
+
}
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
/** The offset past the comment whose `<!--` is at `lt`, or the text's length where none ends it. */
|
|
262
|
+
function pastComment(text: string, lt: number, dialect: Dialect): number {
|
|
263
|
+
if (!dialect.shortComments) {
|
|
264
|
+
const close = text.indexOf("-->", lt + 4);
|
|
265
|
+
return close < 0 ? text.length : close + 3;
|
|
266
|
+
}
|
|
267
|
+
const dashes = text.indexOf("-->", lt + 2);
|
|
268
|
+
const bang = text.indexOf("--!>", lt + 2);
|
|
269
|
+
if (bang >= 0 && (dashes < 0 || bang < dashes)) {
|
|
270
|
+
return bang + 4;
|
|
271
|
+
}
|
|
272
|
+
return dashes < 0 ? text.length : dashes + 3;
|
|
219
273
|
}
|
|
220
274
|
|
|
221
275
|
interface Attribute {
|
|
@@ -234,17 +288,43 @@ interface StartTag {
|
|
|
234
288
|
readonly attributes: readonly Attribute[];
|
|
235
289
|
}
|
|
236
290
|
|
|
237
|
-
/**
|
|
238
|
-
|
|
291
|
+
/**
|
|
292
|
+
* Where an unmatched `{` of a start tag ends its braced value: at the first `>`, or where
|
|
293
|
+
* the brace scan stopped where that comes first.
|
|
294
|
+
*/
|
|
295
|
+
function unmatchedBraceEnd(text: string, from: number, braces: Braces): number {
|
|
296
|
+
const close = text.indexOf(">", from);
|
|
297
|
+
return close >= 0 && close < braces.end ? close : braces.end;
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
/**
|
|
301
|
+
* The start tag whose `<` is at `from`, to its `>`. A quoted or braced value may hold `>`.
|
|
302
|
+
* Past an unmatched `{`, the tag also ends before a `<script` start tag that begins a line.
|
|
303
|
+
*/
|
|
304
|
+
function readStartTag(text: string, from: number, nameStop: number, dialect: Dialect): StartTag {
|
|
239
305
|
const name = text.slice(from + 1, nameStop).toLowerCase();
|
|
240
306
|
const attributes: Attribute[] = [];
|
|
241
307
|
let at = nameStop;
|
|
242
308
|
let selfClosing = false;
|
|
309
|
+
let unmatched = false;
|
|
310
|
+
/** The braces opening at `at`, and whether they make a braced value rather than text. */
|
|
311
|
+
const bracesAt = (): { readonly braces: Braces; readonly value: boolean } | undefined => {
|
|
312
|
+
if (text.charCodeAt(at) !== 0x7b) {
|
|
313
|
+
return undefined;
|
|
314
|
+
}
|
|
315
|
+
const braces = scanBraces(text, at);
|
|
316
|
+
return { braces, value: braces.closed || !dialect.braceIsText };
|
|
317
|
+
};
|
|
318
|
+
const bracedEnd = (braces: Braces): number =>
|
|
319
|
+
braces.closed ? braces.end : unmatchedBraceEnd(text, at, braces);
|
|
243
320
|
while (at < text.length) {
|
|
244
321
|
const code = text.charCodeAt(at);
|
|
245
322
|
if (code === 0x3e) {
|
|
246
323
|
return { name, end: at + 1, selfClosing, attributes };
|
|
247
324
|
}
|
|
325
|
+
if (unmatched && scriptTagAt(text, at)) {
|
|
326
|
+
return { name, end: at, selfClosing, attributes };
|
|
327
|
+
}
|
|
248
328
|
if (isSpace(code)) {
|
|
249
329
|
at += 1;
|
|
250
330
|
continue;
|
|
@@ -256,12 +336,13 @@ function readStartTag(text: string, from: number, nameStop: number): StartTag {
|
|
|
256
336
|
}
|
|
257
337
|
selfClosing = false;
|
|
258
338
|
const start = at;
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
339
|
+
const bare = bracesAt();
|
|
340
|
+
unmatched ||= bare?.braces.closed === false;
|
|
341
|
+
if (bare?.value === true) {
|
|
342
|
+
at = bracedEnd(bare.braces);
|
|
262
343
|
attributes.push({
|
|
263
344
|
name: "",
|
|
264
|
-
value: text.slice(start + 1, at - 1),
|
|
345
|
+
value: text.slice(start + 1, bare.braces.closed ? at - 1 : at),
|
|
265
346
|
start,
|
|
266
347
|
end: at,
|
|
267
348
|
valueStart: start + 1,
|
|
@@ -287,16 +368,18 @@ function readStartTag(text: string, from: number, nameStop: number): StartTag {
|
|
|
287
368
|
at += 1;
|
|
288
369
|
}
|
|
289
370
|
const opening = text.charCodeAt(at);
|
|
371
|
+
const braced = bracesAt();
|
|
372
|
+
unmatched ||= braced?.braces.closed === false;
|
|
290
373
|
if (opening === 0x22 || opening === 0x27) {
|
|
291
|
-
|
|
374
|
+
// An attribute value has no escapes: a backslash before its quote is text.
|
|
375
|
+
const close = text.indexOf(text.charAt(at), at + 1);
|
|
292
376
|
valueStart = at + 1;
|
|
293
|
-
value = text.slice(at + 1,
|
|
294
|
-
at =
|
|
295
|
-
} else if (
|
|
296
|
-
const
|
|
297
|
-
const stop = past < 0 ? text.length : past;
|
|
377
|
+
value = text.slice(at + 1, close < 0 ? text.length : close);
|
|
378
|
+
at = close < 0 ? text.length : close + 1;
|
|
379
|
+
} else if (braced?.value === true) {
|
|
380
|
+
const stop = bracedEnd(braced.braces);
|
|
298
381
|
valueStart = at + 1;
|
|
299
|
-
value = text.slice(at + 1,
|
|
382
|
+
value = text.slice(at + 1, braced.braces.closed ? stop - 1 : stop);
|
|
300
383
|
at = stop;
|
|
301
384
|
} else {
|
|
302
385
|
valueStart = at;
|
|
@@ -410,9 +493,11 @@ function scriptsInside(text: string, from: number, to: number): number[] {
|
|
|
410
493
|
* The scan keeps a stack of open elements. A comment, a doctype or processing instruction and
|
|
411
494
|
* an expression in braces are text, and an end tag closes back to the nearest open element of
|
|
412
495
|
* its name. A raw-text element at the top level runs to its own end tag, and only a `script`
|
|
413
|
-
* element whose start tag begins a line at the top level is a block.
|
|
496
|
+
* element whose start tag begins a line at the top level is a block. The file's extension
|
|
497
|
+
* names the framework whose reading of a comment and of a stray brace applies.
|
|
414
498
|
*/
|
|
415
|
-
export function readComponent(text: string): ComponentFile {
|
|
499
|
+
export function readComponent(text: string, extension: string): ComponentFile {
|
|
500
|
+
const dialect = dialectOf(extension);
|
|
416
501
|
const blocks: Block[] = [];
|
|
417
502
|
const imports: SourceImport[] = [];
|
|
418
503
|
const warnings: ComponentWarning[] = [];
|
|
@@ -442,8 +527,7 @@ export function readComponent(text: string): ComponentFile {
|
|
|
442
527
|
}
|
|
443
528
|
const lt = at;
|
|
444
529
|
if (text.startsWith("<!--", lt)) {
|
|
445
|
-
|
|
446
|
-
at = close < 0 ? text.length : close + 3;
|
|
530
|
+
at = pastComment(text, lt, dialect);
|
|
447
531
|
continue;
|
|
448
532
|
}
|
|
449
533
|
const next = text.charCodeAt(lt + 1);
|
|
@@ -467,7 +551,7 @@ export function readComponent(text: string): ComponentFile {
|
|
|
467
551
|
at = lt + 1;
|
|
468
552
|
continue;
|
|
469
553
|
}
|
|
470
|
-
const tag = readStartTag(text, lt, nameStop);
|
|
554
|
+
const tag = readStartTag(text, lt, nameStop, dialect);
|
|
471
555
|
const rawText = open.length === 0 && RAW_TEXT.has(tag.name);
|
|
472
556
|
for (const attribute of rawText ? [] : tag.attributes) {
|
|
473
557
|
if (attribute.value !== "") {
|
|
@@ -542,9 +626,9 @@ export function readComponent(text: string): ComponentFile {
|
|
|
542
626
|
}
|
|
543
627
|
|
|
544
628
|
/**
|
|
545
|
-
* Whether the module's text exports a default at its top level: an `export default
|
|
546
|
-
* or an export clause naming `default`, outside every comment,
|
|
547
|
-
* regular expression, and outside every bracket.
|
|
629
|
+
* Whether the module's text exports a default at its top level: an `export default`, an
|
|
630
|
+
* `export * as default`, or an export clause naming `default`, outside every comment,
|
|
631
|
+
* string, template and regular expression, and outside every bracket.
|
|
548
632
|
*/
|
|
549
633
|
export function exportsDefault(text: string): boolean {
|
|
550
634
|
const words: string[] = [];
|
|
@@ -633,7 +717,8 @@ export function exportsDefault(text: string): boolean {
|
|
|
633
717
|
const matched = word.exec(text);
|
|
634
718
|
if (matched !== null) {
|
|
635
719
|
const name = matched[0];
|
|
636
|
-
|
|
720
|
+
const starAs = words.at(-3) === "export" && words.at(-2) === "*" && words.at(-1) === "as";
|
|
721
|
+
if (depth === 0 && (words.at(-1) === "export" || starAs) && name === "default") {
|
|
637
722
|
return true;
|
|
638
723
|
}
|
|
639
724
|
if (clause && name === "default") {
|
|
@@ -662,8 +747,8 @@ export function exportsDefault(text: string): boolean {
|
|
|
662
747
|
}
|
|
663
748
|
|
|
664
749
|
/** The module one component file's text is read as, and the span map back to the file. */
|
|
665
|
-
export function componentModule(text: string): ComponentModule {
|
|
666
|
-
const { blocks, imports } = readComponent(text);
|
|
750
|
+
export function componentModule(text: string, fileExtension: string): ComponentModule {
|
|
751
|
+
const { blocks, imports } = readComponent(text, fileExtension);
|
|
667
752
|
const out: string[] = [];
|
|
668
753
|
let at = 0;
|
|
669
754
|
let language: BlockLanguage | undefined;
|
package/src/components.ts
CHANGED
|
@@ -208,12 +208,12 @@ function linesSpanned(symbols: readonly (InventorySymbol | undefined)[]): number
|
|
|
208
208
|
}
|
|
209
209
|
|
|
210
210
|
/**
|
|
211
|
-
* The dead declarations by subgraph place (`at`, and `position` per graph declaration),
|
|
212
|
-
*
|
|
213
|
-
*
|
|
214
|
-
*
|
|
215
|
-
*
|
|
216
|
-
* test
|
|
211
|
+
* The dead declarations by subgraph place (`at`, and `position` per graph declaration), a
|
|
212
|
+
* test file's only where it tests dead code or nothing the tests reach. `adjacent` holds each
|
|
213
|
+
* reference one makes to another, but an unreferenced test-file declaration's to production
|
|
214
|
+
* code, which decides nothing; an edge each way between a dead member and its dead container;
|
|
215
|
+
* and one back from each non-ambient production target of an admitted test, which puts the
|
|
216
|
+
* test in its subject's component. `references` holds the first alone.
|
|
217
217
|
*/
|
|
218
218
|
function deadSubgraph(
|
|
219
219
|
graph: Graph,
|
|
@@ -248,6 +248,7 @@ function deadSubgraph(
|
|
|
248
248
|
if (
|
|
249
249
|
testOfDeadCode[index] === true &&
|
|
250
250
|
graph.test[edge.to] !== true &&
|
|
251
|
+
graph.ambient[edge.to] !== true &&
|
|
251
252
|
graph.subject[edge.to] === true
|
|
252
253
|
) {
|
|
253
254
|
adjacent[to]?.push(from);
|
package/src/config.ts
CHANGED
|
@@ -175,6 +175,8 @@ export interface TSSection {
|
|
|
175
175
|
readonly injectionRegistrations: readonly DeclarationEntry[];
|
|
176
176
|
readonly lifecycleContracts: readonly LifecycleContract[];
|
|
177
177
|
readonly serializers: readonly DeclarationEntry[];
|
|
178
|
+
/** Whether the maintainer declares the roots the analysis finds every root the target has. */
|
|
179
|
+
readonly rootsComplete: boolean;
|
|
178
180
|
}
|
|
179
181
|
|
|
180
182
|
/**
|
|
@@ -253,6 +255,7 @@ export function defaultConfig(): Config {
|
|
|
253
255
|
injectionRegistrations: [],
|
|
254
256
|
lifecycleContracts: [],
|
|
255
257
|
serializers: [],
|
|
258
|
+
rootsComplete: false,
|
|
256
259
|
},
|
|
257
260
|
};
|
|
258
261
|
}
|
|
@@ -8,10 +8,13 @@
|
|
|
8
8
|
*/
|
|
9
9
|
|
|
10
10
|
import {
|
|
11
|
+
isCallExpression,
|
|
11
12
|
isExportDeclaration,
|
|
13
|
+
isIdentifier,
|
|
12
14
|
isImportDeclaration,
|
|
13
15
|
isNoSubstitutionTemplateLiteral,
|
|
14
16
|
isStringLiteral,
|
|
17
|
+
SyntaxKind,
|
|
15
18
|
type Node,
|
|
16
19
|
type SourceFile,
|
|
17
20
|
} from "@typescript/native/ast";
|
|
@@ -110,7 +113,7 @@ interface ConfigurationDocument {
|
|
|
110
113
|
export type ParseFile = (fileName: string, text: string) => SourceFile;
|
|
111
114
|
|
|
112
115
|
/** One file no program holds that a configuration module outside the program leads to. */
|
|
113
|
-
interface OutsideModule {
|
|
116
|
+
export interface OutsideModule {
|
|
114
117
|
/** The file, parsed alone. */
|
|
115
118
|
readonly file: SourceFile;
|
|
116
119
|
/** The directories of the manifests of the configuration files the file was reached from. */
|
|
@@ -278,8 +281,15 @@ function readDocument(host: Host, path: string): unknown {
|
|
|
278
281
|
|
|
279
282
|
/** The configuration files one project holds and sits beside. */
|
|
280
283
|
interface ConfigurationFiles {
|
|
281
|
-
/**
|
|
282
|
-
|
|
284
|
+
/**
|
|
285
|
+
* Every own file that is a configuration module, with the form its name has and the
|
|
286
|
+
* directory of the manifest whose dependencies its strings are read against.
|
|
287
|
+
*/
|
|
288
|
+
readonly modules: readonly {
|
|
289
|
+
readonly file: SourceFile;
|
|
290
|
+
readonly form: string;
|
|
291
|
+
readonly manifestDir: string;
|
|
292
|
+
}[];
|
|
283
293
|
/**
|
|
284
294
|
* Every configuration module in a directory of the project that the program does not
|
|
285
295
|
* hold, and every file such a file imports or re-exports by a relative specifier that
|
|
@@ -345,12 +355,13 @@ export function configurationFiles(
|
|
|
345
355
|
return held;
|
|
346
356
|
};
|
|
347
357
|
|
|
348
|
-
const modules: { file: SourceFile; form: string }[] = [];
|
|
358
|
+
const modules: { file: SourceFile; form: string; manifestDir: string }[] = [];
|
|
349
359
|
const dirs = new Set<string>([root]);
|
|
350
360
|
for (const file of files.byPath.values()) {
|
|
351
361
|
const form = configurationModuleForm(basename(file.fileName));
|
|
352
|
-
|
|
353
|
-
|
|
362
|
+
const fileDir = dirnamePath(file.fileName);
|
|
363
|
+
if (form !== undefined && holdsManifest(fileDir)) {
|
|
364
|
+
modules.push({ file, form, manifestDir: fileDir });
|
|
354
365
|
}
|
|
355
366
|
for (
|
|
356
367
|
let dir = dirnamePath(file.fileName);
|
|
@@ -383,7 +394,7 @@ export function configurationFiles(
|
|
|
383
394
|
if (path.endsWith(".json")) {
|
|
384
395
|
pending.push({ path, manifestDir: dir });
|
|
385
396
|
} else if (held !== undefined) {
|
|
386
|
-
modules.push({ file: held, form: "a script's configuration flag" });
|
|
397
|
+
modules.push({ file: held, form: "a script's configuration flag", manifestDir: dir });
|
|
387
398
|
} else {
|
|
388
399
|
named.push({ path, manifestDir: dir });
|
|
389
400
|
}
|
|
@@ -431,19 +442,70 @@ export function configurationFiles(
|
|
|
431
442
|
};
|
|
432
443
|
}
|
|
433
444
|
|
|
434
|
-
/** The
|
|
435
|
-
function
|
|
436
|
-
|
|
437
|
-
|
|
445
|
+
/** The literal module specifier one `require(...)` or `import(...)` call names, if any. */
|
|
446
|
+
function calledSpecifier(node: Node): Node | undefined {
|
|
447
|
+
if (!isCallExpression(node) || node.arguments.length !== 1) {
|
|
448
|
+
return undefined;
|
|
449
|
+
}
|
|
450
|
+
const callee = node.expression;
|
|
451
|
+
const loads =
|
|
452
|
+
callee.kind === SyntaxKind.ImportKeyword || (isIdentifier(callee) && callee.text === "require");
|
|
453
|
+
return loads ? node.arguments[0] : undefined;
|
|
454
|
+
}
|
|
455
|
+
|
|
456
|
+
/**
|
|
457
|
+
* The relative specifiers one file's imports and re-exports name, and those its
|
|
458
|
+
* `require(...)` and `import(...)` calls name by a literal.
|
|
459
|
+
*/
|
|
460
|
+
export function relativeSpecifiers(file: SourceFile): readonly string[] {
|
|
461
|
+
const known = specifiersOf.get(file);
|
|
462
|
+
if (known !== undefined) {
|
|
463
|
+
return known;
|
|
464
|
+
}
|
|
465
|
+
const found: string[] = [];
|
|
466
|
+
const named = (specifier: Node | undefined): void => {
|
|
467
|
+
if (
|
|
468
|
+
specifier !== undefined &&
|
|
469
|
+
(isStringLiteral(specifier) || isNoSubstitutionTemplateLiteral(specifier)) &&
|
|
470
|
+
RELATIVE_PREFIXES.some((prefix) => specifier.text.startsWith(prefix))
|
|
471
|
+
) {
|
|
472
|
+
found.push(specifier.text);
|
|
473
|
+
}
|
|
474
|
+
};
|
|
475
|
+
const visit = (node: Node): void => {
|
|
476
|
+
named(calledSpecifier(node));
|
|
477
|
+
node.forEachChild(visit);
|
|
478
|
+
};
|
|
479
|
+
for (const statement of file.statements) {
|
|
480
|
+
named(
|
|
438
481
|
isImportDeclaration(statement) || isExportDeclaration(statement)
|
|
439
482
|
? statement.moduleSpecifier
|
|
440
|
-
: undefined
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
483
|
+
: undefined,
|
|
484
|
+
);
|
|
485
|
+
visit(statement);
|
|
486
|
+
}
|
|
487
|
+
specifiersOf.set(file, found);
|
|
488
|
+
return found;
|
|
489
|
+
}
|
|
490
|
+
|
|
491
|
+
/** What {@link relativeSpecifiers} found in each file, which every project of a run reads alike. */
|
|
492
|
+
const specifiersOf = new WeakMap<SourceFile, readonly string[]>();
|
|
493
|
+
|
|
494
|
+
/**
|
|
495
|
+
* The files no program holds that a chain from `seeds` reads: each seed, and each file
|
|
496
|
+
* below the target root one imports, re-exports or loads by a relative specifier that no
|
|
497
|
+
* program holds either, until no further file joins, each read against the manifest
|
|
498
|
+
* directory its seed names. A file that cannot be read is skipped.
|
|
499
|
+
*/
|
|
500
|
+
export function outsideChain(
|
|
501
|
+
host: Host,
|
|
502
|
+
targetRoot: string,
|
|
503
|
+
files: SourceFiles,
|
|
504
|
+
parse: ParseFile,
|
|
505
|
+
seeds: readonly { readonly path: string; readonly manifestDir: string }[],
|
|
506
|
+
): readonly OutsideModule[] {
|
|
507
|
+
const root = resolvePath(host.workingDirectory(), targetRoot);
|
|
508
|
+
return outsideModules(host, root, [], files, () => false, parse, seeds);
|
|
447
509
|
}
|
|
448
510
|
|
|
449
511
|
/**
|
|
@@ -20,7 +20,7 @@ import { SymbolFlags, type NodeHandle, type Symbol as TSSymbol } from "@typescri
|
|
|
20
20
|
import type { AliasChains } from "./alias-chain.ts";
|
|
21
21
|
import { resolvedTargets } from "./calls.ts";
|
|
22
22
|
import type { Config, DeclarationEntry } from "./config.ts";
|
|
23
|
-
import type
|
|
23
|
+
import { declaredComponent, type Inventory } from "./inventory.ts";
|
|
24
24
|
import { must, Unanswerable } from "./query.ts";
|
|
25
25
|
import { nameComponent } from "./ref.ts";
|
|
26
26
|
import type { ProjectView } from "./session.ts";
|
|
@@ -118,15 +118,44 @@ function candidatesOf<Brand>(
|
|
|
118
118
|
];
|
|
119
119
|
}
|
|
120
120
|
|
|
121
|
-
/**
|
|
121
|
+
/**
|
|
122
|
+
* The properties of the type one container declares, or of its constructor's type for a
|
|
123
|
+
* static member. A member a symbol keys is bound to that type later than the container's
|
|
124
|
+
* symbol table is built, so the table does not hold it.
|
|
125
|
+
*/
|
|
126
|
+
function typeMembersOf<Brand>(
|
|
127
|
+
project: ProjectView<Brand>,
|
|
128
|
+
container: TSSymbol,
|
|
129
|
+
isStatic: boolean,
|
|
130
|
+
): readonly TSSymbol[] {
|
|
131
|
+
const queries = project.queries;
|
|
132
|
+
const type = must(isStatic ? queries.typeOfSymbol(container) : queries.declaredTypeOf(container));
|
|
133
|
+
return type === undefined ? [] : must(queries.propertiesOf(type));
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
/**
|
|
137
|
+
* The member of one container a path component spells, followed through any alias. A
|
|
138
|
+
* component in brackets is a computed key, compared with the key each declaration of a
|
|
139
|
+
* candidate writes: the checker names a member a symbol keys by no spelling of its own.
|
|
140
|
+
*/
|
|
122
141
|
function memberOf<Brand>(
|
|
123
142
|
project: ProjectView<Brand>,
|
|
124
143
|
container: TSSymbol,
|
|
125
144
|
component: string,
|
|
126
145
|
isStatic: boolean,
|
|
127
146
|
): TSSymbol | undefined {
|
|
128
|
-
const
|
|
129
|
-
|
|
147
|
+
const computed = component.startsWith("[");
|
|
148
|
+
const candidates = candidatesOf(project, container, isStatic);
|
|
149
|
+
const member = (
|
|
150
|
+
computed ? [...candidates, ...typeMembersOf(project, container, isStatic)] : candidates
|
|
151
|
+
).find(
|
|
152
|
+
(candidate) =>
|
|
153
|
+
nameComponent(candidate.name) === component ||
|
|
154
|
+
(computed &&
|
|
155
|
+
candidate.declarations.some((handle) => {
|
|
156
|
+
const node = project.declarationAt(handle)?.node;
|
|
157
|
+
return node !== undefined && declaredComponent(node) === component;
|
|
158
|
+
})),
|
|
130
159
|
);
|
|
131
160
|
return member === undefined ? undefined : unaliased(project, member);
|
|
132
161
|
}
|
|
@@ -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: "
|
|
5
|
+
corpusVersion: "6.0.0",
|
|
6
6
|
result: "pass",
|
|
7
|
-
digest: "sha256:
|
|
7
|
+
digest: "sha256:76c07ac14bda6e302444918d832b01844bac6f29b6f22a742d538804480c0769",
|
|
8
8
|
},
|
|
9
9
|
gaps: [],
|
|
10
10
|
} as const;
|
package/src/contextual-uses.ts
CHANGED
|
@@ -319,14 +319,15 @@ export function contextualUses<Brand>(
|
|
|
319
319
|
return found;
|
|
320
320
|
};
|
|
321
321
|
|
|
322
|
-
const calls = new Map<number, Called
|
|
322
|
+
const calls = new Map<number, readonly Called[]>();
|
|
323
323
|
/**
|
|
324
|
-
* The parameter and return types of a type's
|
|
325
|
-
*
|
|
324
|
+
* The parameter and return types of each of a type's call signatures, or of each of its
|
|
325
|
+
* construct signatures where it has no call signature, read once per type.
|
|
326
326
|
*/
|
|
327
|
-
const
|
|
328
|
-
|
|
329
|
-
|
|
327
|
+
const callsOf = (type: Type): readonly Called[] => {
|
|
328
|
+
const known = calls.get(type.id);
|
|
329
|
+
if (known !== undefined) {
|
|
330
|
+
return known;
|
|
330
331
|
}
|
|
331
332
|
lookups += 1;
|
|
332
333
|
let kind = SignatureKind.Call;
|
|
@@ -336,31 +337,29 @@ export function contextualUses<Brand>(
|
|
|
336
337
|
kind = SignatureKind.Construct;
|
|
337
338
|
signatures = queries.signaturesOf(type, kind);
|
|
338
339
|
}
|
|
339
|
-
|
|
340
|
-
const [signature, ...more] = isAnswered(signatures) ? signatures : [];
|
|
341
|
-
if (signature !== undefined && more.length === 0) {
|
|
340
|
+
const called = (isAnswered(signatures) ? signatures : []).map((signature): Called => {
|
|
342
341
|
const parameters = signature.parameters.map((_parameter, index) =>
|
|
343
342
|
queries.parameterType(signature, index),
|
|
344
343
|
);
|
|
345
344
|
const result = queries.returnTypeOf(signature);
|
|
346
345
|
lookups += parameters.length + 1;
|
|
347
|
-
|
|
346
|
+
return {
|
|
348
347
|
kind,
|
|
349
348
|
parameters: parameters.map((one) => (isAnswered(one) ? one : undefined)),
|
|
350
349
|
result: isAnswered(result) ? result : undefined,
|
|
351
350
|
};
|
|
352
|
-
}
|
|
351
|
+
});
|
|
353
352
|
calls.set(type.id, called);
|
|
354
353
|
return called;
|
|
355
354
|
};
|
|
356
355
|
|
|
357
356
|
/**
|
|
358
357
|
* Each object type a value of `source` is checked as against the object types of
|
|
359
|
-
* `target`: the value's own, and through array elements,
|
|
360
|
-
* checked the other way,
|
|
361
|
-
*
|
|
362
|
-
* A source that is one of the target's own object types matches
|
|
363
|
-
* pair is compared once, which ends the walk over a recursive type.
|
|
358
|
+
* `target`: the value's own, and through array elements, every pair of signatures
|
|
359
|
+
* (parameters checked the other way, then results), which reads more than the overload
|
|
360
|
+
* the checker picks, and the type arguments of two instantiations by place, as an array
|
|
361
|
+
* reaches `Iterable<T>`. A source that is one of the target's own object types matches
|
|
362
|
+
* it as it stands. Each pair is compared once, which ends the walk over a recursive type.
|
|
364
363
|
*/
|
|
365
364
|
const compared = (
|
|
366
365
|
source: Type | undefined,
|
|
@@ -385,7 +384,7 @@ export function contextualUses<Brand>(
|
|
|
385
384
|
into.push({ source: one, targets, outside: true });
|
|
386
385
|
}
|
|
387
386
|
const element = elementOf(one);
|
|
388
|
-
const called = element === undefined ?
|
|
387
|
+
const called = element === undefined ? callsOf(one) : [];
|
|
389
388
|
const sourceArguments = typeArgumentsOf(one);
|
|
390
389
|
for (const part of targets) {
|
|
391
390
|
const partElement = elementOf(part);
|
|
@@ -399,17 +398,17 @@ export function contextualUses<Brand>(
|
|
|
399
398
|
sourceArguments.slice(0, targetArguments.length).forEach((argument, index) => {
|
|
400
399
|
compared(argument, targetArguments[index], into, seen);
|
|
401
400
|
});
|
|
402
|
-
if (element !== undefined) {
|
|
401
|
+
if (element !== undefined || called.length === 0) {
|
|
403
402
|
continue;
|
|
404
403
|
}
|
|
405
|
-
const other
|
|
406
|
-
|
|
407
|
-
|
|
404
|
+
for (const other of callsOf(part)) {
|
|
405
|
+
for (const signature of called.filter((one) => one.kind === other.kind)) {
|
|
406
|
+
other.parameters.forEach((parameter, index) => {
|
|
407
|
+
compared(parameter, signature.parameters[index], into, seen);
|
|
408
|
+
});
|
|
409
|
+
compared(signature.result, other.result, into, seen);
|
|
410
|
+
}
|
|
408
411
|
}
|
|
409
|
-
other.parameters.forEach((parameter, index) => {
|
|
410
|
-
compared(parameter, called.parameters[index], into, seen);
|
|
411
|
-
});
|
|
412
|
-
compared(called.result, other.result, into, seen);
|
|
413
412
|
}
|
|
414
413
|
}
|
|
415
414
|
};
|
|
@@ -431,7 +430,7 @@ export function contextualUses<Brand>(
|
|
|
431
430
|
if (element !== undefined) {
|
|
432
431
|
return mayCarry(element, seen);
|
|
433
432
|
}
|
|
434
|
-
return
|
|
433
|
+
return callsOf(part).length > 0 || typeArgumentsOf(part).some((one) => mayCarry(one, seen));
|
|
435
434
|
});
|
|
436
435
|
};
|
|
437
436
|
|
|
@@ -467,11 +466,12 @@ export function contextualUses<Brand>(
|
|
|
467
466
|
continue;
|
|
468
467
|
}
|
|
469
468
|
for (const [name, member] of sourceMembers) {
|
|
469
|
+
// A member the position shares with the source is no read of it. A member with no
|
|
470
|
+
// declaration, as a mapped type over literal keys has, is never one it shares.
|
|
470
471
|
const read = tables.some((table) => {
|
|
471
472
|
const declared = table.get(name);
|
|
472
473
|
return (
|
|
473
474
|
declared !== undefined &&
|
|
474
|
-
declared.handles.size > 0 &&
|
|
475
475
|
![...declared.handles].some((handle) => member.handles.has(handle))
|
|
476
476
|
);
|
|
477
477
|
});
|