@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/dependencies.ts
CHANGED
|
@@ -8,15 +8,19 @@
|
|
|
8
8
|
*/
|
|
9
9
|
|
|
10
10
|
import {
|
|
11
|
+
isArrayLiteralExpression,
|
|
11
12
|
isCallExpression,
|
|
12
13
|
isExportDeclaration,
|
|
14
|
+
isExpressionStatement,
|
|
13
15
|
isExternalModuleReference,
|
|
14
16
|
isIdentifier,
|
|
15
17
|
isImportDeclaration,
|
|
16
18
|
isImportEqualsDeclaration,
|
|
17
19
|
isMetaProperty,
|
|
18
20
|
isNamespaceExport,
|
|
21
|
+
isObjectLiteralExpression,
|
|
19
22
|
isPropertyAccessExpression,
|
|
23
|
+
isPropertyAssignment,
|
|
20
24
|
isShorthandPropertyAssignment,
|
|
21
25
|
isStringLiteral,
|
|
22
26
|
isNoSubstitutionTemplateLiteral,
|
|
@@ -25,11 +29,14 @@ import {
|
|
|
25
29
|
type Node,
|
|
26
30
|
type SourceFile,
|
|
27
31
|
} from "@typescript/native/ast";
|
|
32
|
+
import { automaticTypes, type AutomaticTypes } from "./automatic-types.ts";
|
|
28
33
|
import {
|
|
29
34
|
configurationFiles,
|
|
30
35
|
declaredIn as declaredBeside,
|
|
31
36
|
dependenciesNamed,
|
|
32
37
|
moduleStrings,
|
|
38
|
+
outsideChain,
|
|
39
|
+
type OutsideModule,
|
|
33
40
|
type ParseFile,
|
|
34
41
|
} from "./configuration-files.ts";
|
|
35
42
|
import type { FindingPosition } from "./finding.ts";
|
|
@@ -40,7 +47,7 @@ import { dirnamePath, isAbsolutePath, joinPath, relativePath, resolvePath } from
|
|
|
40
47
|
import type { DependencySection, Module } from "./ref.ts";
|
|
41
48
|
import { UNANSWERED } from "./query.ts";
|
|
42
49
|
import type { Handle, ProjectView } from "./session.ts";
|
|
43
|
-
import { sourceFilesOf } from "./source-files.ts";
|
|
50
|
+
import { sourceFilesOf, type SourceFiles } from "./source-files.ts";
|
|
44
51
|
import { workflowTokens } from "./workflow-steps.ts";
|
|
45
52
|
|
|
46
53
|
const MANIFEST = "package.json";
|
|
@@ -48,6 +55,9 @@ const MODULES_DIR = "node_modules";
|
|
|
48
55
|
const MODULES_SEGMENT = `/${MODULES_DIR}/`;
|
|
49
56
|
const TYPES_SCOPE = "@types/";
|
|
50
57
|
|
|
58
|
+
/** A script token naming a JavaScript file by a path no expansion changes. */
|
|
59
|
+
const SCRIPT_FILE = /^[^$`{*?[~]+\.(?:js|mjs|cjs)$/u;
|
|
60
|
+
|
|
51
61
|
/** The manifest sections a dependency is declared in, each by the selector its reference spells. */
|
|
52
62
|
const SECTIONS: readonly (readonly [string, DependencySection])[] = [
|
|
53
63
|
["dependencies", "dependency"],
|
|
@@ -72,7 +82,7 @@ interface DeclaredDependency {
|
|
|
72
82
|
export function configurationNeeds(packages: readonly string[]): ProjectNeeds {
|
|
73
83
|
return {
|
|
74
84
|
uses: new Map([[PROJECT_OWNER, new Set(packages)]]),
|
|
75
|
-
at: new Map([[ANYWHERE, new
|
|
85
|
+
at: new Map([[ANYWHERE, new Map(packages.map((one) => [one, new Set([PROJECT_OWNER])]))]]),
|
|
76
86
|
unanswered: [],
|
|
77
87
|
};
|
|
78
88
|
}
|
|
@@ -98,10 +108,12 @@ export interface ProjectNeeds {
|
|
|
98
108
|
readonly uses: ReadonlyMap<string, ReadonlySet<string>>;
|
|
99
109
|
/**
|
|
100
110
|
* Per absolute path of the file a use is written in, the packages it names, which a
|
|
101
|
-
* dependency
|
|
102
|
-
* the
|
|
111
|
+
* dependency of the nearest manifest at or above that file declaring the name serves,
|
|
112
|
+
* each with the owners {@link uses} holds it by; an import whose bindings nothing in the
|
|
113
|
+
* file uses holds its package by none. The empty key holds the uses no one file holds,
|
|
114
|
+
* which every manifest's dependency of that name serves.
|
|
103
115
|
*/
|
|
104
|
-
readonly at: ReadonlyMap<string, ReadonlySet<string
|
|
116
|
+
readonly at: ReadonlyMap<string, ReadonlyMap<string, ReadonlySet<string>>>;
|
|
105
117
|
/**
|
|
106
118
|
* The files writing a module specifier the checker left unanswered, each of which may
|
|
107
119
|
* use any dependency a manifest at or above it declares.
|
|
@@ -134,6 +146,18 @@ export interface Dependencies {
|
|
|
134
146
|
* written inside it, sorted bytewise; a candidate holding no last use has no entry.
|
|
135
147
|
*/
|
|
136
148
|
readonly lastUses: ReadonlyMap<string, readonly string[]>;
|
|
149
|
+
/**
|
|
150
|
+
* Per file, by its path below the target root, the declared dependencies whose every use
|
|
151
|
+
* in the target is written in it, sorted bytewise, which deleting the file removes.
|
|
152
|
+
*/
|
|
153
|
+
readonly fileLastUses: ReadonlyMap<string, readonly string[]>;
|
|
154
|
+
/**
|
|
155
|
+
* The needed declared dependencies, by {@link declarationKey}, each with the owners
|
|
156
|
+
* {@link ProjectNeeds.uses} holds its uses by, sorted bytewise: none that a command runs,
|
|
157
|
+
* that an unanswered specifier may need, that an import none of whose bindings is used
|
|
158
|
+
* names, or that a dependency so needed names as a peer.
|
|
159
|
+
*/
|
|
160
|
+
readonly heldBy: ReadonlyMap<string, readonly string[]>;
|
|
137
161
|
}
|
|
138
162
|
|
|
139
163
|
function isRecord(value: unknown): value is Record<string, unknown> {
|
|
@@ -202,6 +226,34 @@ function typePackages(name: string, programPackages: ReadonlySet<string>): reado
|
|
|
202
226
|
return [typed, named];
|
|
203
227
|
}
|
|
204
228
|
|
|
229
|
+
/**
|
|
230
|
+
* The strings the `extends` member of one compiler configuration file holds, read as the
|
|
231
|
+
* compiler reads the file, comments and trailing commas allowed; none where it cannot be read.
|
|
232
|
+
*/
|
|
233
|
+
function extendsOf(host: Host, path: string, parse: ParseFile): readonly string[] {
|
|
234
|
+
let text: string;
|
|
235
|
+
try {
|
|
236
|
+
text = host.readFile(path);
|
|
237
|
+
} catch {
|
|
238
|
+
return [];
|
|
239
|
+
}
|
|
240
|
+
const [statement] = parse(path, text).statements;
|
|
241
|
+
const document =
|
|
242
|
+
statement !== undefined && isExpressionStatement(statement) ? statement.expression : undefined;
|
|
243
|
+
if (document === undefined || !isObjectLiteralExpression(document)) {
|
|
244
|
+
return [];
|
|
245
|
+
}
|
|
246
|
+
const value = document.properties
|
|
247
|
+
.filter(isPropertyAssignment)
|
|
248
|
+
.findLast(
|
|
249
|
+
(property) => isLiteralText(property.name) && property.name.text === "extends",
|
|
250
|
+
)?.initializer;
|
|
251
|
+
const entries = value !== undefined && isArrayLiteralExpression(value) ? value.elements : [value];
|
|
252
|
+
return entries.flatMap((entry) =>
|
|
253
|
+
entry !== undefined && isLiteralText(entry) ? [entry.text] : [],
|
|
254
|
+
);
|
|
255
|
+
}
|
|
256
|
+
|
|
205
257
|
/**
|
|
206
258
|
* The packages the compiler configuration of one project names: its type packages,
|
|
207
259
|
* its JSX import source, the helper library it imports, its plugins, and every
|
|
@@ -212,6 +264,7 @@ function configurationPackages(
|
|
|
212
264
|
configFile: string,
|
|
213
265
|
options: Readonly<Record<string, unknown>>,
|
|
214
266
|
programPackages: ReadonlySet<string>,
|
|
267
|
+
parse: ParseFile,
|
|
215
268
|
): Set<string> {
|
|
216
269
|
const found = new Set<string>();
|
|
217
270
|
const add = (name: unknown): void => {
|
|
@@ -248,12 +301,7 @@ function configurationPackages(
|
|
|
248
301
|
continue;
|
|
249
302
|
}
|
|
250
303
|
seen.add(file);
|
|
251
|
-
const
|
|
252
|
-
const extended = isRecord(read?.value) ? read.value["extends"] : undefined;
|
|
253
|
-
for (const entry of Array.isArray(extended) ? extended : [extended]) {
|
|
254
|
-
if (typeof entry !== "string") {
|
|
255
|
-
continue;
|
|
256
|
-
}
|
|
304
|
+
for (const entry of extendsOf(host, file, parse)) {
|
|
257
305
|
if (entry.startsWith(".") || isAbsolutePath(entry)) {
|
|
258
306
|
const local = resolvePath(dirnamePath(file), entry);
|
|
259
307
|
pending.push(local.endsWith(".json") ? local : `${local}.json`);
|
|
@@ -318,7 +366,7 @@ export function projectNeeds<Brand>(
|
|
|
318
366
|
held: Inventory,
|
|
319
367
|
host: Host,
|
|
320
368
|
targetRoot: string,
|
|
321
|
-
parse
|
|
369
|
+
parse: ParseFile,
|
|
322
370
|
): ProjectNeeds {
|
|
323
371
|
const programPackages = new Set<string>();
|
|
324
372
|
for (const name of project.program.getSourceFileNames()) {
|
|
@@ -327,18 +375,19 @@ export function projectNeeds<Brand>(
|
|
|
327
375
|
programPackages.add(named);
|
|
328
376
|
}
|
|
329
377
|
}
|
|
330
|
-
const
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
project.program.getCompilerOptions() as Readonly<Record<string, unknown>>,
|
|
334
|
-
programPackages,
|
|
335
|
-
);
|
|
378
|
+
const options = project.program.getCompilerOptions() as Readonly<Record<string, unknown>>;
|
|
379
|
+
const packages = configurationPackages(host, project.configFile, options, programPackages, parse);
|
|
380
|
+
const automatic = automaticTypes(host, project.configFile, options, parse);
|
|
336
381
|
const uses = new Map<string, Set<string>>();
|
|
337
|
-
const at = new Map<string, Set<string
|
|
382
|
+
const at = new Map<string, Map<string, Set<string>>>();
|
|
338
383
|
const use = (owners: readonly string[], named: Iterable<string>, path: string): void => {
|
|
339
384
|
for (const one of named) {
|
|
340
|
-
|
|
385
|
+
const inFile = at.get(path) ?? new Map<string, Set<string>>();
|
|
386
|
+
at.set(path, inFile);
|
|
387
|
+
const holders = inFile.get(one) ?? new Set<string>();
|
|
388
|
+
inFile.set(one, holders);
|
|
341
389
|
for (const owner of owners) {
|
|
390
|
+
holders.add(owner);
|
|
342
391
|
let set = uses.get(owner);
|
|
343
392
|
if (set === undefined) {
|
|
344
393
|
set = new Set();
|
|
@@ -355,7 +404,7 @@ export function projectNeeds<Brand>(
|
|
|
355
404
|
const inFile = (owners: readonly string[], named: Iterable<string>): void => {
|
|
356
405
|
use(owners, named, file.fileName);
|
|
357
406
|
};
|
|
358
|
-
if (fileNeeds(project, held, file, programPackages, inFile)) {
|
|
407
|
+
if (fileNeeds(project, held, file, { programPackages, automatic }, inFile)) {
|
|
359
408
|
unanswered.push(file.fileName);
|
|
360
409
|
}
|
|
361
410
|
}
|
|
@@ -370,14 +419,10 @@ export function projectNeeds<Brand>(
|
|
|
370
419
|
}
|
|
371
420
|
return names;
|
|
372
421
|
};
|
|
373
|
-
const
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
parse,
|
|
378
|
-
);
|
|
379
|
-
for (const { file } of configuration.modules) {
|
|
380
|
-
const names = declaredAt(file.fileName);
|
|
422
|
+
const files = sourceFilesOf(project, targetRoot);
|
|
423
|
+
const configuration = configurationFiles(host, targetRoot, files, parse);
|
|
424
|
+
for (const { file, manifestDir } of configuration.modules) {
|
|
425
|
+
const names = declaredAt(joinPath(manifestDir, MANIFEST));
|
|
381
426
|
for (const { node, text } of moduleStrings(file)) {
|
|
382
427
|
use(ownersOf(file, held, node), dependenciesNamed(text, names), file.fileName);
|
|
383
428
|
}
|
|
@@ -390,7 +435,14 @@ export function projectNeeds<Brand>(
|
|
|
390
435
|
}
|
|
391
436
|
}
|
|
392
437
|
}
|
|
393
|
-
|
|
438
|
+
const ranFiles: readonly OutsideModule[] = outsideChain(
|
|
439
|
+
host,
|
|
440
|
+
targetRoot,
|
|
441
|
+
files,
|
|
442
|
+
parse,
|
|
443
|
+
scriptRunFiles(host, targetRoot, files),
|
|
444
|
+
);
|
|
445
|
+
for (const { file, manifestDirs } of [...configuration.outside, ...ranFiles]) {
|
|
394
446
|
for (const manifestDir of manifestDirs) {
|
|
395
447
|
const names = declaredAt(joinPath(manifestDir, "package.json"));
|
|
396
448
|
for (const { text } of moduleStrings(file)) {
|
|
@@ -401,6 +453,55 @@ export function projectNeeds<Brand>(
|
|
|
401
453
|
return { uses, at, unanswered };
|
|
402
454
|
}
|
|
403
455
|
|
|
456
|
+
/**
|
|
457
|
+
* The JavaScript files no program holds that a script of a manifest at or above one of the
|
|
458
|
+
* project's files runs, each with the directory of that manifest, and those a workflow
|
|
459
|
+
* step or git hook runs, with the target root's: a token naming such a file below the
|
|
460
|
+
* target root and in no `node_modules`, read against the manifest's directory.
|
|
461
|
+
*/
|
|
462
|
+
function scriptRunFiles(
|
|
463
|
+
host: Host,
|
|
464
|
+
targetRoot: string,
|
|
465
|
+
files: SourceFiles,
|
|
466
|
+
): readonly { readonly path: string; readonly manifestDir: string }[] {
|
|
467
|
+
const root = resolvePath(host.workingDirectory(), targetRoot);
|
|
468
|
+
const dirs = new Set<string>([root]);
|
|
469
|
+
for (const name of files.byName.keys()) {
|
|
470
|
+
for (let dir = dirnamePath(name); !dirs.has(dir); dir = dirnamePath(dir)) {
|
|
471
|
+
if (relativePath(root, dir) === undefined) {
|
|
472
|
+
break;
|
|
473
|
+
}
|
|
474
|
+
dirs.add(dir);
|
|
475
|
+
}
|
|
476
|
+
}
|
|
477
|
+
const tokens = new Map<string, ReadonlySet<string>>([
|
|
478
|
+
[root, new Set(workflowTokens(host, root))],
|
|
479
|
+
]);
|
|
480
|
+
for (const dir of dirs) {
|
|
481
|
+
const read = readJSON(host, joinPath(dir, MANIFEST));
|
|
482
|
+
if (read !== undefined && isRecord(read.value)) {
|
|
483
|
+
const own = scriptTokensOf(read.value);
|
|
484
|
+
tokens.set(dir, dir === root ? new Set([...own, ...(tokens.get(root) ?? [])]) : own);
|
|
485
|
+
}
|
|
486
|
+
}
|
|
487
|
+
const found: { path: string; manifestDir: string }[] = [];
|
|
488
|
+
for (const [manifestDir, held] of tokens) {
|
|
489
|
+
for (const token of held) {
|
|
490
|
+
const path = SCRIPT_FILE.test(token) ? resolvePath(manifestDir, token) : undefined;
|
|
491
|
+
const below = path === undefined ? undefined : relativePath(root, path);
|
|
492
|
+
if (
|
|
493
|
+
path !== undefined &&
|
|
494
|
+
below?.split("/").includes(MODULES_DIR) === false &&
|
|
495
|
+
!files.byName.has(path) &&
|
|
496
|
+
host.kindOf(path) === "file"
|
|
497
|
+
) {
|
|
498
|
+
found.push({ path, manifestDir });
|
|
499
|
+
}
|
|
500
|
+
}
|
|
501
|
+
}
|
|
502
|
+
return found;
|
|
503
|
+
}
|
|
504
|
+
|
|
404
505
|
/** The declarations of the inventory a use written at one node belongs to. */
|
|
405
506
|
function ownersOf(file: SourceFile, held: Inventory, node: Node): readonly string[] {
|
|
406
507
|
const parent = node.parent as Node | undefined;
|
|
@@ -425,12 +526,47 @@ function ownersOf(file: SourceFile, held: Inventory, node: Node): readonly strin
|
|
|
425
526
|
return [PROJECT_OWNER];
|
|
426
527
|
}
|
|
427
528
|
|
|
428
|
-
/**
|
|
529
|
+
/**
|
|
530
|
+
* Whether one identifier stands for a value or type of the scope it is written in, rather
|
|
531
|
+
* than naming a member, a property or a label.
|
|
532
|
+
*/
|
|
533
|
+
function isScopeName(node: Identifier): boolean {
|
|
534
|
+
const parent = node.parent as Node | undefined;
|
|
535
|
+
if (parent === undefined || isShorthandPropertyAssignment(parent)) {
|
|
536
|
+
return true;
|
|
537
|
+
}
|
|
538
|
+
const named = parent as {
|
|
539
|
+
readonly name?: Node;
|
|
540
|
+
readonly right?: Node;
|
|
541
|
+
readonly propertyName?: Node;
|
|
542
|
+
readonly label?: Node;
|
|
543
|
+
};
|
|
544
|
+
return (
|
|
545
|
+
named.name !== node &&
|
|
546
|
+
(parent.kind !== SyntaxKind.QualifiedName || named.right !== node) &&
|
|
547
|
+
named.propertyName !== node &&
|
|
548
|
+
named.label !== node
|
|
549
|
+
);
|
|
550
|
+
}
|
|
551
|
+
|
|
552
|
+
/** What the packages a project's configuration brings in hold, which a file's needs are read against. */
|
|
553
|
+
interface ProgramTypes {
|
|
554
|
+
/** The packages the program holds a file of. */
|
|
555
|
+
readonly programPackages: ReadonlySet<string>;
|
|
556
|
+
/** What the type packages a compiler would include automatically declare. */
|
|
557
|
+
readonly automatic: AutomaticTypes;
|
|
558
|
+
}
|
|
559
|
+
|
|
560
|
+
/**
|
|
561
|
+
* Records the needs of one file, and answers whether a specifier of it went unanswered. A
|
|
562
|
+
* module specifier or a name of the file's scope that resolves to nothing, and that an
|
|
563
|
+
* automatically included type package declares, uses that package.
|
|
564
|
+
*/
|
|
429
565
|
function fileNeeds<Brand>(
|
|
430
566
|
project: ProjectView<Brand>,
|
|
431
567
|
held: Inventory,
|
|
432
568
|
file: SourceFile,
|
|
433
|
-
programPackages:
|
|
569
|
+
{ programPackages, automatic }: ProgramTypes,
|
|
434
570
|
use: (owners: readonly string[], named: Iterable<string>) => void,
|
|
435
571
|
): boolean {
|
|
436
572
|
const specifiers = [...file.imports, ...file.moduleAugmentations].filter(isLiteralText);
|
|
@@ -449,17 +585,22 @@ function fileNeeds<Brand>(
|
|
|
449
585
|
}
|
|
450
586
|
const names = new Set(bindings.map(({ node }) => node.text));
|
|
451
587
|
const candidates: Identifier[] = [];
|
|
588
|
+
const globals: Identifier[] = [];
|
|
452
589
|
const visit = (node: Node): void => {
|
|
453
|
-
if (isIdentifier(node) &&
|
|
454
|
-
|
|
590
|
+
if (isIdentifier(node) && !bound.has(node)) {
|
|
591
|
+
if (names.has(node.text)) {
|
|
592
|
+
candidates.push(node);
|
|
593
|
+
} else if (automatic.globals.has(node.text) && isScopeName(node)) {
|
|
594
|
+
globals.push(node);
|
|
595
|
+
}
|
|
455
596
|
}
|
|
456
597
|
node.forEachChild(visit);
|
|
457
598
|
};
|
|
458
|
-
if (names.size > 0) {
|
|
599
|
+
if (names.size > 0 || automatic.globals.size > 0) {
|
|
459
600
|
visit(file);
|
|
460
601
|
}
|
|
461
602
|
|
|
462
|
-
const asked = [...specifiers, ...bindings.map(({ node }) => node), ...candidates];
|
|
603
|
+
const asked = [...specifiers, ...bindings.map(({ node }) => node), ...candidates, ...globals];
|
|
463
604
|
const handles: Handle<Brand>[] = asked.map((node) => project.handle(node));
|
|
464
605
|
const resolved = project.symbolsAt(handles);
|
|
465
606
|
|
|
@@ -471,6 +612,9 @@ function fileNeeds<Brand>(
|
|
|
471
612
|
named.add(byName);
|
|
472
613
|
}
|
|
473
614
|
const symbol = resolved[index];
|
|
615
|
+
if (symbol === undefined) {
|
|
616
|
+
automatic.modules.get(specifier.text)?.forEach((one) => named.add(one));
|
|
617
|
+
}
|
|
474
618
|
for (const declaration of typeof symbol === "object" ? symbol.declarations : []) {
|
|
475
619
|
const byPath = packageOfPath(declaration.path);
|
|
476
620
|
if (byPath !== undefined) {
|
|
@@ -508,7 +652,7 @@ function fileNeeds<Brand>(
|
|
|
508
652
|
let symbol = resolved[offset + index];
|
|
509
653
|
const parent = node.parent as Node | undefined;
|
|
510
654
|
if (parent !== undefined && isShorthandPropertyAssignment(parent) && parent.name === node) {
|
|
511
|
-
symbol = project.shorthandValueAt(project.handle(
|
|
655
|
+
symbol = project.shorthandValueAt(project.handle(parent));
|
|
512
656
|
}
|
|
513
657
|
const named =
|
|
514
658
|
symbol === UNANSWERED || (unresolvedNames.has(node.text) && symbol === undefined)
|
|
@@ -521,6 +665,13 @@ function fileNeeds<Brand>(
|
|
|
521
665
|
use(ownersOf(file, held, node), named);
|
|
522
666
|
}
|
|
523
667
|
});
|
|
668
|
+
const unresolvedAt = offset + candidates.length;
|
|
669
|
+
globals.forEach((node, index) => {
|
|
670
|
+
const symbol = resolved[unresolvedAt + index];
|
|
671
|
+
if (symbol === undefined || symbol === UNANSWERED) {
|
|
672
|
+
use(ownersOf(file, held, node), automatic.globals.get(node.text) ?? []);
|
|
673
|
+
}
|
|
674
|
+
});
|
|
524
675
|
|
|
525
676
|
for (const reference of file.typeReferenceDirectives) {
|
|
526
677
|
use(ownersOf(file, held, file), typePackages(reference.fileName, programPackages));
|
|
@@ -731,6 +882,22 @@ function manifestDir(dependency: DeclaredDependency): string {
|
|
|
731
882
|
return dir === "." ? "" : dir;
|
|
732
883
|
}
|
|
733
884
|
|
|
885
|
+
/**
|
|
886
|
+
* The declarations of one name a use written at `below` needs: those of the nearest
|
|
887
|
+
* manifest at or above it that declares the name, or every one for a use no file holds.
|
|
888
|
+
*/
|
|
889
|
+
function nearestDeclaring(
|
|
890
|
+
declarations: readonly DeclaredDependency[],
|
|
891
|
+
below: string | undefined,
|
|
892
|
+
): readonly DeclaredDependency[] {
|
|
893
|
+
if (below === undefined) {
|
|
894
|
+
return declarations;
|
|
895
|
+
}
|
|
896
|
+
const enclosing = declarations.filter((dependency) => isInside(below, manifestDir(dependency)));
|
|
897
|
+
const nearest = Math.max(-1, ...enclosing.map((dependency) => manifestDir(dependency).length));
|
|
898
|
+
return enclosing.filter((dependency) => manifestDir(dependency).length === nearest);
|
|
899
|
+
}
|
|
900
|
+
|
|
734
901
|
/**
|
|
735
902
|
* Adds to `needed` each declared dependency that the installed manifest of a needed or
|
|
736
903
|
* run declared dependency names as a peer, until no further one joins: the declaration of
|
|
@@ -829,6 +996,8 @@ export function dependenciesOf(
|
|
|
829
996
|
// A declaration several projects compile is one user, and a use any project makes is
|
|
830
997
|
// a use, so the uses are merged by declaration before anything is counted.
|
|
831
998
|
const needed = new Set<string>();
|
|
999
|
+
const live = new Set<string>();
|
|
1000
|
+
const holders = new Map<string, Set<string>>();
|
|
832
1001
|
const byName = new Map<string, DeclaredDependency[]>();
|
|
833
1002
|
for (const dependency of declared) {
|
|
834
1003
|
byName.set(dependency.name, [...(byName.get(dependency.name) ?? []), dependency]);
|
|
@@ -841,7 +1010,10 @@ export function dependenciesOf(
|
|
|
841
1010
|
const unknownDeclared = declared.filter((dependency) =>
|
|
842
1011
|
unanswered.some((file) => isInside(file, manifestDir(dependency))),
|
|
843
1012
|
);
|
|
844
|
-
|
|
1013
|
+
for (const dependency of unknownDeclared) {
|
|
1014
|
+
needed.add(declarationKey(dependency));
|
|
1015
|
+
live.add(declarationKey(dependency));
|
|
1016
|
+
}
|
|
845
1017
|
const unknown = unknownDeclared.map((dependency) => dependency.name);
|
|
846
1018
|
const held = unknown.length === 0 ? [] : [[PROJECT_OWNER, new Set(unknown)] as const];
|
|
847
1019
|
for (const [id, used] of [...project.uses, ...held]) {
|
|
@@ -851,16 +1023,34 @@ export function dependenciesOf(
|
|
|
851
1023
|
}
|
|
852
1024
|
for (const [path, named] of project.at) {
|
|
853
1025
|
const below = path === ANYWHERE ? undefined : relativePath(root, path);
|
|
854
|
-
for (const one of named) {
|
|
855
|
-
|
|
856
|
-
|
|
857
|
-
|
|
1026
|
+
for (const [one, owners] of named) {
|
|
1027
|
+
// An import none of whose bindings is used is no evidence about the code using it.
|
|
1028
|
+
const unheld = owners.size === 0;
|
|
1029
|
+
for (const dependency of nearestDeclaring(byName.get(one) ?? [], below)) {
|
|
1030
|
+
const key = declarationKey(dependency);
|
|
1031
|
+
needed.add(key);
|
|
1032
|
+
if (unheld) {
|
|
1033
|
+
live.add(key);
|
|
1034
|
+
} else {
|
|
1035
|
+
const held = holders.get(key) ?? new Set<string>();
|
|
1036
|
+
owners.forEach((owner) => held.add(owner));
|
|
1037
|
+
holders.set(key, held);
|
|
858
1038
|
}
|
|
859
1039
|
}
|
|
860
1040
|
}
|
|
861
1041
|
}
|
|
862
1042
|
}
|
|
863
1043
|
addNeededPeers(declared, peers, needed, ran);
|
|
1044
|
+
addNeededPeers(declared, peers, live, ran);
|
|
1045
|
+
const heldBy = new Map<string, readonly string[]>();
|
|
1046
|
+
for (const [key, held] of holders) {
|
|
1047
|
+
if (!live.has(key) && !ran.has(key)) {
|
|
1048
|
+
heldBy.set(
|
|
1049
|
+
key,
|
|
1050
|
+
[...held].sort((a, b) => (a < b ? -1 : 1)),
|
|
1051
|
+
);
|
|
1052
|
+
}
|
|
1053
|
+
}
|
|
864
1054
|
const users = new Map<string, number>();
|
|
865
1055
|
for (const used of uses.values()) {
|
|
866
1056
|
used.forEach((one) => users.set(one, (users.get(one) ?? 0) + 1));
|
|
@@ -876,5 +1066,21 @@ export function dependenciesOf(
|
|
|
876
1066
|
);
|
|
877
1067
|
}
|
|
878
1068
|
}
|
|
879
|
-
|
|
1069
|
+
const owners = new Map<string, Set<string>>();
|
|
1070
|
+
for (const [owner, used] of uses) {
|
|
1071
|
+
for (const one of used) {
|
|
1072
|
+
if (names.has(one)) {
|
|
1073
|
+
owners.set(one, (owners.get(one) ?? new Set<string>()).add(owner));
|
|
1074
|
+
}
|
|
1075
|
+
}
|
|
1076
|
+
}
|
|
1077
|
+
const fileLastUses = new Map<string, string[]>();
|
|
1078
|
+
for (const [one, held] of [...owners].sort(([a], [b]) => (a < b ? -1 : 1))) {
|
|
1079
|
+
const paths = new Set([...held].map((owner) => owner.replace(/:\d+:\d+$/u, "")));
|
|
1080
|
+
const [path] = paths;
|
|
1081
|
+
if (paths.size === 1 && path !== undefined && path !== PROJECT_OWNER) {
|
|
1082
|
+
fileLastUses.set(path, [...(fileLastUses.get(path) ?? []), one]);
|
|
1083
|
+
}
|
|
1084
|
+
}
|
|
1085
|
+
return { declared, needed, ran, unbuilt, lastUses, fileLastUses, heldBy };
|
|
880
1086
|
}
|
package/src/destructuring.ts
CHANGED
|
@@ -24,7 +24,12 @@ import {
|
|
|
24
24
|
type BinaryExpression,
|
|
25
25
|
type Node,
|
|
26
26
|
} from "@typescript/native/ast";
|
|
27
|
-
import {
|
|
27
|
+
import {
|
|
28
|
+
SignatureKind,
|
|
29
|
+
TypeFlags,
|
|
30
|
+
type Symbol as TSSymbol,
|
|
31
|
+
type Type,
|
|
32
|
+
} from "@typescript/native/sync";
|
|
28
33
|
import {
|
|
29
34
|
isAnswered,
|
|
30
35
|
propertyTables,
|
|
@@ -34,6 +39,10 @@ import {
|
|
|
34
39
|
} from "./query.ts";
|
|
35
40
|
import type { ProjectView } from "./session.ts";
|
|
36
41
|
|
|
42
|
+
/** The types of an iteration result's `value` that carry no element of the iterable. */
|
|
43
|
+
const NO_ELEMENT =
|
|
44
|
+
TypeFlags.AnyOrUnknown | TypeFlags.Undefined | TypeFlags.Void | TypeFlags.Null | TypeFlags.Never;
|
|
45
|
+
|
|
37
46
|
/** One property one object pattern reads. */
|
|
38
47
|
export interface PropertyRead {
|
|
39
48
|
/** The pattern, whose destructured value declares the property. */
|
|
@@ -298,11 +307,93 @@ export function destructuring<Brand>(
|
|
|
298
307
|
return isAnswered(elements) ? elements[0] : UNANSWERED;
|
|
299
308
|
};
|
|
300
309
|
|
|
310
|
+
/** The type of the one property of a type that `matches` accepts, unanswered where two do. */
|
|
311
|
+
const propertyTypeOf = (owner: Type, matches: (property: TSSymbol) => boolean): Found => {
|
|
312
|
+
const table = tableOf(owner);
|
|
313
|
+
if (!isAnswered(table)) {
|
|
314
|
+
return UNANSWERED;
|
|
315
|
+
}
|
|
316
|
+
const [property, ...more] = [...table.values()].filter(matches);
|
|
317
|
+
if (property === undefined) {
|
|
318
|
+
return undefined;
|
|
319
|
+
}
|
|
320
|
+
if (more.length > 0) {
|
|
321
|
+
return UNANSWERED;
|
|
322
|
+
}
|
|
323
|
+
patternLookups += 1;
|
|
324
|
+
return queries.typeOfSymbol(property);
|
|
325
|
+
};
|
|
326
|
+
|
|
327
|
+
const resultOf = (callee: Found): Found => {
|
|
328
|
+
if (callee === undefined || callee === UNANSWERED) {
|
|
329
|
+
return callee;
|
|
330
|
+
}
|
|
331
|
+
patternLookups += 1;
|
|
332
|
+
const signatures = queries.signaturesOf(callee, SignatureKind.Call);
|
|
333
|
+
if (!isAnswered(signatures)) {
|
|
334
|
+
return UNANSWERED;
|
|
335
|
+
}
|
|
336
|
+
const [signature] = signatures;
|
|
337
|
+
if (signature === undefined) {
|
|
338
|
+
return undefined;
|
|
339
|
+
}
|
|
340
|
+
patternLookups += 1;
|
|
341
|
+
return queries.returnTypeOf(signature);
|
|
342
|
+
};
|
|
343
|
+
|
|
344
|
+
const iterations = new Map<number, Found>();
|
|
345
|
+
/**
|
|
346
|
+
* The type a `for…of` takes from one iterable that is no array, read through the
|
|
347
|
+
* iteration protocol: the `value` of what the `next()` of its `[Symbol.iterator]()`
|
|
348
|
+
* answers. A constituent of that answer whose value is {@link NO_ELEMENT} carries no
|
|
349
|
+
* element; where more than one carries one, the element is left unanswered, so a read
|
|
350
|
+
* through it is resolved by name.
|
|
351
|
+
*/
|
|
352
|
+
const iteratedOf = (type: Type): Found => {
|
|
353
|
+
if (iterations.has(type.id)) {
|
|
354
|
+
return iterations.get(type.id);
|
|
355
|
+
}
|
|
356
|
+
// The checker keys a member a unique symbol names `__@<name>@<symbol id>`, and escapes
|
|
357
|
+
// a written key that starts with `__` with one more `_`, so no string key matches; a
|
|
358
|
+
// unique symbol of the program's own named `iterator` does, which leaves two matches:
|
|
359
|
+
// https://github.com/microsoft/typescript-go/blob/89d5d5b2849a0db0957065889ca58536fa6d2e4a/internal/checker/checker.go#L23096-L23108
|
|
360
|
+
// https://github.com/microsoft/typescript-go/blob/89d5d5b2849a0db0957065889ca58536fa6d2e4a/internal/ast/symbol.go#L91-L103
|
|
361
|
+
const iterator = resultOf(
|
|
362
|
+
propertyTypeOf(type, (one) => one.escapedName.startsWith("__@iterator@")),
|
|
363
|
+
);
|
|
364
|
+
const result =
|
|
365
|
+
iterator === undefined || iterator === UNANSWERED
|
|
366
|
+
? iterator
|
|
367
|
+
: resultOf(propertyTypeOf(iterator, (one) => one.name === "next"));
|
|
368
|
+
let found: Found = result;
|
|
369
|
+
if (result !== undefined && result !== UNANSWERED) {
|
|
370
|
+
let parts: Answer<readonly Type[]> = [result];
|
|
371
|
+
if (result.isUnionType()) {
|
|
372
|
+
patternLookups += 1;
|
|
373
|
+
parts = queries.constituents(result);
|
|
374
|
+
}
|
|
375
|
+
const values: readonly Found[] = isAnswered(parts)
|
|
376
|
+
? parts.map((part) => propertyTypeOf(part, (one) => one.name === "value"))
|
|
377
|
+
: [UNANSWERED];
|
|
378
|
+
const carried = values.filter(
|
|
379
|
+
(value): value is Answer<Type> =>
|
|
380
|
+
value === UNANSWERED || (value !== undefined && (value.flags & NO_ELEMENT) === 0),
|
|
381
|
+
);
|
|
382
|
+
const [only, ...more] = carried;
|
|
383
|
+
found = more.length > 0 ? UNANSWERED : only;
|
|
384
|
+
}
|
|
385
|
+
iterations.set(type.id, found);
|
|
386
|
+
return found;
|
|
387
|
+
};
|
|
388
|
+
|
|
301
389
|
const step = (type: Type, next: Step): Found => {
|
|
302
390
|
const defined = definedOf(type);
|
|
303
391
|
if (defined === undefined || defined === UNANSWERED) {
|
|
304
392
|
return defined;
|
|
305
393
|
}
|
|
394
|
+
if (next.kind === "iterated") {
|
|
395
|
+
return elementOf(defined, undefined) ?? iteratedOf(defined);
|
|
396
|
+
}
|
|
306
397
|
if (next.kind === "property") {
|
|
307
398
|
const table = tableOf(defined);
|
|
308
399
|
if (!isAnswered(table)) {
|
|
@@ -315,7 +406,7 @@ export function destructuring<Brand>(
|
|
|
315
406
|
patternLookups += 1;
|
|
316
407
|
return queries.typeOfSymbol(property);
|
|
317
408
|
}
|
|
318
|
-
return elementOf(defined, next.
|
|
409
|
+
return elementOf(defined, next.index);
|
|
319
410
|
};
|
|
320
411
|
|
|
321
412
|
const resolve = (reads: readonly PropertyRead[]): readonly Answer<TSSymbol | undefined>[] => {
|