@cplieger/deadset-ts 5.1.0 → 5.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (73) hide show
  1. package/README.md +1 -1
  2. package/dist/src/analysis.js +79 -37
  3. package/dist/src/config.js +3 -0
  4. package/dist/src/configuration-files.js +24 -25
  5. package/dist/src/conformance-record.js +2 -2
  6. package/dist/src/contextual-uses.js +46 -18
  7. package/dist/src/convention-rows.js +7 -0
  8. package/dist/src/conventions.js +97 -33
  9. package/dist/src/dependencies.js +8 -0
  10. package/dist/src/entry-point-gaps.js +10 -26
  11. package/dist/src/entry-points.js +4 -0
  12. package/dist/src/exempt.js +23 -8
  13. package/dist/src/file-facts.js +23 -20
  14. package/dist/src/findings/completion.js +27 -11
  15. package/dist/src/findings/interfaces.js +2 -6
  16. package/dist/src/findings/intra-function.js +15 -3
  17. package/dist/src/findings/unused-declarations.js +5 -6
  18. package/dist/src/findings/visibility-narrowing.js +6 -1
  19. package/dist/src/generated-file.js +72 -12
  20. package/dist/src/graph.js +6 -5
  21. package/dist/src/html-entries.js +137 -0
  22. package/dist/src/inventory.js +13 -3
  23. package/dist/src/kinds.js +2 -2
  24. package/dist/src/manifest.js +34 -5
  25. package/dist/src/matrix.js +9 -3
  26. package/dist/src/namespace-objects.js +158 -0
  27. package/dist/src/outside-tests.js +139 -0
  28. package/dist/src/overrides.js +104 -0
  29. package/dist/src/paths.js +17 -0
  30. package/dist/src/references.js +150 -4
  31. package/dist/src/roots.js +172 -39
  32. package/dist/src/session.js +4 -3
  33. package/dist/src/sweep.js +17 -0
  34. package/dist/src/template-field.js +60 -3
  35. package/dist/src/test-support.js +35 -23
  36. package/dist/src/version.js +1 -1
  37. package/jsr.json +1 -1
  38. package/package.json +1 -1
  39. package/src/analysis.ts +122 -49
  40. package/src/config.ts +3 -0
  41. package/src/configuration-files.ts +29 -25
  42. package/src/conformance-record.ts +2 -2
  43. package/src/contextual-uses.ts +51 -20
  44. package/src/convention-rows.ts +15 -0
  45. package/src/conventions.ts +119 -30
  46. package/src/dependencies.ts +9 -0
  47. package/src/entry-point-gaps.ts +11 -28
  48. package/src/entry-points.ts +7 -1
  49. package/src/exempt.ts +29 -12
  50. package/src/file-facts.ts +25 -21
  51. package/src/findings/completion.ts +31 -11
  52. package/src/findings/interfaces.ts +2 -9
  53. package/src/findings/intra-function.ts +17 -3
  54. package/src/findings/unused-declarations.ts +5 -8
  55. package/src/findings/visibility-narrowing.ts +7 -1
  56. package/src/generated-file.ts +80 -12
  57. package/src/graph.ts +9 -8
  58. package/src/html-entries.ts +170 -0
  59. package/src/inventory.ts +15 -3
  60. package/src/kinds.ts +2 -2
  61. package/src/manifest.ts +49 -7
  62. package/src/matrix.ts +21 -3
  63. package/src/namespace-objects.ts +216 -0
  64. package/src/outside-tests.ts +191 -0
  65. package/src/overrides.ts +136 -0
  66. package/src/paths.ts +18 -0
  67. package/src/references.ts +206 -9
  68. package/src/roots.ts +210 -53
  69. package/src/session.ts +4 -2
  70. package/src/sweep.ts +19 -0
  71. package/src/template-field.ts +65 -3
  72. package/src/test-support.ts +34 -22
  73. package/src/version.ts +1 -1
package/src/references.ts CHANGED
@@ -12,8 +12,11 @@ import {
12
12
  isForInStatement,
13
13
  isForOfStatement,
14
14
  isForStatement,
15
+ isIdentifier,
15
16
  isImportDeclaration,
16
17
  isImportEqualsDeclaration,
18
+ isMetaProperty,
19
+ isNewExpression,
17
20
  isNamespaceExport,
18
21
  isNoSubstitutionTemplateLiteral,
19
22
  isObjectLiteralExpression,
@@ -35,9 +38,12 @@ import type { Symbol as TSSymbol } from "@typescript/native/unstable/sync";
35
38
  import { aliasChains, type ChainTarget } from "./alias-chain.ts";
36
39
  import { contextualUses, type FlowAt, type LiteralAt } from "./contextual-uses.ts";
37
40
  import { destructuring, propertyReadsOf, type PropertyRead } from "./destructuring.ts";
41
+ import { specifierCandidates } from "./configuration-files.ts";
42
+ import { namespaceUses } from "./namespace-objects.ts";
38
43
  import { globExpression } from "./glob.ts";
39
44
  import { declarationsByName, nodeKey, type Inventory, type InventorySymbol } from "./inventory.ts";
40
- import { relativePath } from "./paths.ts";
45
+ import { overrideReferences } from "./overrides.ts";
46
+ import { dirnamePath, queriedModulePath, relativePath, resolvePath } from "./paths.ts";
41
47
  import { byPosition, isComponentFile, renderPosition, type Position } from "./position.ts";
42
48
  import { DEFAULT_BATCH_CAP, propertyTables, UNANSWERED, type Answer } from "./query.ts";
43
49
  import type { ProjectView } from "./session.ts";
@@ -71,12 +77,12 @@ import { handedOn, valuesOf } from "./value-flow.ts";
71
77
  export type Use = "read" | "write" | "evaluation" | "decorator";
72
78
 
73
79
  /**
74
- * Which path answered for one reference: `batch`, the batched lookup over a file's names;
75
- * `resolved-symbol`, the per-node lookup for a batch's residue; `shorthand`, the value
76
- * `{ a }` reads; `alias`, a step along an import or export chain; `syntax`, the tree alone
77
- * (a decorator's target, a component's markup); `destructured`, the property a pattern
78
- * names; `contextual`, the member a literal property writes or a value's position reads;
79
- * `by-name`, every declaration spelled as the site is, where the checker failed.
80
+ * Which path answered for one reference: `batch`, a file's batched lookup; `resolved-symbol`,
81
+ * the per-node lookup for its residue; `shorthand`, the value `{ a }` reads; `alias`, a step
82
+ * along an import or export chain; `syntax`, the tree alone (a decorator's target, a
83
+ * component's markup); `destructured`, the property a pattern names; `contextual`, the member
84
+ * a literal property writes or a value's position reads; `override`, the override a base
85
+ * member's call dispatches to; `by-name`, every declaration so spelled, where the checker failed.
80
86
  */
81
87
  export type Resolution =
82
88
  | "batch"
@@ -86,6 +92,7 @@ export type Resolution =
86
92
  | "syntax"
87
93
  | "destructured"
88
94
  | "contextual"
95
+ | "override"
89
96
  | "by-name";
90
97
 
91
98
  /** One use of one declaration by one declaration. */
@@ -112,6 +119,8 @@ export interface Reference {
112
119
  * the consumer's root.
113
120
  */
114
121
  readonly consumer?: string;
122
+ /** Made by a target file no project of the run holds, so it enters as a consumer's reference does. */
123
+ readonly unheld?: true;
115
124
  /**
116
125
  * On a write, the local names of the writing file's import bindings that only the
117
126
  * values its file writes into the same declaration use, which deleting the
@@ -420,6 +429,27 @@ interface Evaluation {
420
429
  readonly from: string;
421
430
  }
422
431
 
432
+ /**
433
+ * One module URL a source file builds: `new URL(<literal>, import.meta.url)` or
434
+ * `import.meta.resolve(<literal>)`, the literal relative. A runtime starts the file it
435
+ * names as a module, so it is an import of that file for its effects.
436
+ */
437
+ interface ModuleURL {
438
+ readonly specifier: string;
439
+ /** The `URL` the constructor names, which must name no declaration of the target. */
440
+ readonly constructor: Node | undefined;
441
+ readonly at: Node;
442
+ }
443
+
444
+ /** One use of a module namespace object that may read every export of the module. */
445
+ interface WholeRead {
446
+ /** The module specifier, whose symbol is the module read. */
447
+ readonly specifier: Node;
448
+ /** The use, whose position the reference carries. */
449
+ readonly at: Node;
450
+ readonly from: string;
451
+ }
452
+
423
453
  /** One decorator expression, and the declaration it is attached to. */
424
454
  interface Decorated {
425
455
  readonly decorator: Node;
@@ -439,9 +469,55 @@ interface FileSites {
439
469
  readonly reads: readonly Read[];
440
470
  readonly links: readonly Link[];
441
471
  readonly evaluations: readonly Evaluation[];
472
+ readonly wholes: readonly WholeRead[];
473
+ readonly urls: readonly ModuleURL[];
442
474
  readonly decorated: readonly Decorated[];
443
475
  }
444
476
 
477
+ /** Whether one node is `import.meta.url`. */
478
+ function isImportMetaURL(node: Node): boolean {
479
+ const at = unparenthesized(node);
480
+ return (
481
+ isPropertyAccessExpression(at) &&
482
+ isMetaProperty(at.expression) &&
483
+ at.expression.keywordToken === SyntaxKind.ImportKeyword &&
484
+ at.name.text === "url"
485
+ );
486
+ }
487
+
488
+ /** The module URL one node builds, where it builds one. */
489
+ function moduleURLOf(node: Node): ModuleURL | undefined {
490
+ const relative = (argument: Node | undefined): string | undefined => {
491
+ const at = argument === undefined ? undefined : unparenthesized(argument);
492
+ return at !== undefined &&
493
+ (isStringLiteral(at) || isNoSubstitutionTemplateLiteral(at)) &&
494
+ /^\.\.?\//u.test(at.text)
495
+ ? at.text
496
+ : undefined;
497
+ };
498
+ if (isNewExpression(node) && isIdentifier(node.expression) && node.expression.text === "URL") {
499
+ const [reference, base] = node.arguments ?? [];
500
+ const specifier = relative(reference);
501
+ return specifier !== undefined && base !== undefined && isImportMetaURL(base)
502
+ ? { specifier, constructor: node.expression, at: node }
503
+ : undefined;
504
+ }
505
+ if (isCallExpression(node)) {
506
+ const callee = unparenthesized(node.expression);
507
+ const specifier = relative(node.arguments[0]);
508
+ if (
509
+ specifier !== undefined &&
510
+ isPropertyAccessExpression(callee) &&
511
+ isMetaProperty(callee.expression) &&
512
+ callee.expression.keywordToken === SyntaxKind.ImportKeyword &&
513
+ callee.name.text === "resolve"
514
+ ) {
515
+ return { specifier, constructor: undefined, at: node };
516
+ }
517
+ }
518
+ return undefined;
519
+ }
520
+
445
521
  /** Whether one node is a string literal or a template literal with no substitution. */
446
522
  function isLiteralText(node: Node | undefined): node is Node {
447
523
  return node !== undefined && (isStringLiteral(node) || isNoSubstitutionTemplateLiteral(node));
@@ -476,6 +552,24 @@ function evaluatedBy(node: Node): Node | undefined {
476
552
  return undefined;
477
553
  }
478
554
 
555
+ /**
556
+ * The own file a module specifier with a query evaluates ({@link queriedModulePath}): the
557
+ * checker resolves the whole specifier to whatever module declaration matches it.
558
+ */
559
+ function queriedFile(
560
+ file: SourceFile,
561
+ specifier: Node,
562
+ fileIdByName: ReadonlyMap<string, string>,
563
+ ): string | undefined {
564
+ if (!isStringLiteral(specifier) && !isNoSubstitutionTemplateLiteral(specifier)) {
565
+ return undefined;
566
+ }
567
+ const path = queriedModulePath(specifier.text);
568
+ return path === undefined
569
+ ? undefined
570
+ : fileIdByName.get(resolvePath(dirnamePath(file.fileName), path));
571
+ }
572
+
479
573
  /**
480
574
  * The re-exports one node writes, if it is an export declaration. A specifier and a
481
575
  * namespace export are declarations whose names are where they are written, so no use is
@@ -562,7 +656,10 @@ function sitesOf(
562
656
  const reads: Read[] = [];
563
657
  const links: Link[] = [];
564
658
  const evaluations: Evaluation[] = [];
659
+ const wholes: WholeRead[] = [];
660
+ const urls: ModuleURL[] = [];
565
661
  const decorated: Decorated[] = [];
662
+ const namespaces = namespaceUses(file);
566
663
  const literals: LiteralAt[] = [];
567
664
  const flows: FlowAt[] = [];
568
665
  const writes = new Set<number>();
@@ -593,6 +690,12 @@ function sitesOf(
593
690
  if (unread.has(node)) {
594
691
  return;
595
692
  }
693
+ const namespace = namespaces.get(node);
694
+ if (namespace?.kind === "whole") {
695
+ wholes.push({ specifier: namespace.specifier, at: node, from: enclosing });
696
+ } else if (namespace?.kind === "indexed") {
697
+ found.push({ node: namespace.literal, shorthand: undefined, from: enclosing, use: "read" });
698
+ }
596
699
  if (isName(node)) {
597
700
  if (!declared.has(node.pos)) {
598
701
  const site = { node, shorthand: shorthands.get(node.pos), from: enclosing };
@@ -622,6 +725,10 @@ function sitesOf(
622
725
  if (evaluated !== undefined) {
623
726
  evaluations.push({ node: evaluated, from: enclosing });
624
727
  }
728
+ const url = moduleURLOf(node);
729
+ if (url !== undefined) {
730
+ urls.push(url);
731
+ }
625
732
  const held = declarations.get(nodeKey(file, node));
626
733
  const outer = enclosing;
627
734
  if (held !== undefined) {
@@ -637,7 +744,7 @@ function sitesOf(
637
744
  };
638
745
 
639
746
  file.forEachChild(visit);
640
- return { uses: found, literals, flows, reads, links, evaluations, decorated };
747
+ return { uses: found, literals, flows, reads, links, evaluations, wholes, urls, decorated };
641
748
  }
642
749
 
643
750
  /** The declaration kinds of a property an object literal declares. */
@@ -900,6 +1007,36 @@ export function references<Brand>(
900
1007
  return topLevel.get(fileId) ?? [];
901
1008
  };
902
1009
 
1010
+ const exportsCache = new Map<number, readonly ChainTarget[]>();
1011
+ /**
1012
+ * Every declaration one module exports, each re-export walked along its chain. A module
1013
+ * the checker left unanswered may be any file, so it reads every file's exports.
1014
+ */
1015
+ const exportsRead = (module: Answer<TSSymbol>): readonly ChainTarget[] => {
1016
+ if (module === UNANSWERED) {
1017
+ return [...fileIds].flatMap((id) =>
1018
+ declaredIn(id)
1019
+ .filter((symbol) => symbol.exported)
1020
+ .map((symbol) => ({ id: symbol.id, stepped: false, guessed: true })),
1021
+ );
1022
+ }
1023
+ const known = exportsCache.get(module.id);
1024
+ if (known !== undefined) {
1025
+ return known;
1026
+ }
1027
+ const table = project.queries.exportsOfModule(module);
1028
+ const read =
1029
+ table === UNANSWERED
1030
+ ? chains.declarationsOf(module).flatMap((id) =>
1031
+ declaredIn(id)
1032
+ .filter((symbol) => symbol.exported)
1033
+ .map((symbol) => ({ id: symbol.id, stepped: false, guessed: true })),
1034
+ )
1035
+ : table.flatMap((exported) => chains.chainOf(exported));
1036
+ exportsCache.set(module.id, read);
1037
+ return read;
1038
+ };
1039
+
903
1040
  /** The identifier of the declaration one node of this program is, where it is one. */
904
1041
  const declaredAt = (node: Node): string | undefined =>
905
1042
  declarations.get(nodeKey(node.getSourceFile(), node));
@@ -1000,6 +1137,9 @@ export function references<Brand>(
1000
1137
  const fileId = declaredAt(file) ?? "";
1001
1138
  return { file, fileId, ...sitesOf(file, declarations, fileId, unread) };
1002
1139
  });
1140
+ const fileIdByName = new Map<string, string>(
1141
+ walked.filter((one) => fileIds.has(one.fileId)).map((one) => [one.file.fileName, one.fileId]),
1142
+ );
1003
1143
  const contextual = contextualUses(
1004
1144
  project,
1005
1145
  held,
@@ -1087,7 +1227,7 @@ export function references<Brand>(
1087
1227
  }
1088
1228
  }
1089
1229
 
1090
- for (const { file, fileId, uses, reads, links, evaluations, decorated } of walked) {
1230
+ for (const { file, fileId, uses, reads, links, evaluations, wholes, urls, decorated } of walked) {
1091
1231
  const test = testFile.get(file) === true;
1092
1232
  // A component file's markup may use any binding its blocks declare at the top level.
1093
1233
  const markup = isComponentFile(file);
@@ -1117,6 +1257,8 @@ export function references<Brand>(
1117
1257
  ...uses.filter((site) => site.shorthand === undefined).map((site) => site.node),
1118
1258
  ...links.map((link) => link.node),
1119
1259
  ...evaluations.map((evaluation) => evaluation.node),
1260
+ ...wholes.map((whole) => whole.specifier),
1261
+ ...urls.flatMap((url) => url.constructor ?? []),
1120
1262
  ...bindings.map((binding) => binding.node),
1121
1263
  ]),
1122
1264
  ];
@@ -1160,6 +1302,17 @@ export function references<Brand>(
1160
1302
  }
1161
1303
 
1162
1304
  for (const evaluation of evaluations) {
1305
+ const queried = queriedFile(file, evaluation.node, fileIdByName);
1306
+ if (queried !== undefined) {
1307
+ found.push({
1308
+ from: evaluation.from,
1309
+ to: queried,
1310
+ position: renderPosition(file, root, evaluation.node.getStart()),
1311
+ use: "evaluation",
1312
+ resolution: "syntax",
1313
+ test,
1314
+ });
1315
+ }
1163
1316
  const module = answered.get(evaluation.node);
1164
1317
  if (module === undefined) {
1165
1318
  continue;
@@ -1181,6 +1334,47 @@ export function references<Brand>(
1181
1334
  }
1182
1335
  }
1183
1336
 
1337
+ for (const url of urls) {
1338
+ const constructor = url.constructor === undefined ? undefined : answered.get(url.constructor);
1339
+ if (
1340
+ typeof constructor === "object" &&
1341
+ constructor.declarations.some((handle) => ownPaths.has(handle.path))
1342
+ ) {
1343
+ continue;
1344
+ }
1345
+ const loaded = specifierCandidates(dirnamePath(file.fileName), url.specifier)
1346
+ .map((path) => fileIdByName.get(path))
1347
+ .find((id) => id !== undefined);
1348
+ if (loaded !== undefined) {
1349
+ found.push({
1350
+ from: fileId,
1351
+ to: loaded,
1352
+ position: renderPosition(file, root, url.at.getStart()),
1353
+ use: "evaluation",
1354
+ resolution: "syntax",
1355
+ test,
1356
+ });
1357
+ }
1358
+ }
1359
+
1360
+ for (const whole of wholes) {
1361
+ const module = answered.get(whole.specifier);
1362
+ if (module === undefined) {
1363
+ continue;
1364
+ }
1365
+ const position = renderPosition(file, root, whole.at.getStart());
1366
+ for (const target of exportsRead(module)) {
1367
+ found.push({
1368
+ from: whole.from,
1369
+ to: target.id,
1370
+ position,
1371
+ use: "read",
1372
+ resolution: target.guessed ? "by-name" : "batch",
1373
+ test,
1374
+ });
1375
+ }
1376
+ }
1377
+
1184
1378
  if (markup) {
1185
1379
  for (const symbol of declaredIn(fileId)) {
1186
1380
  found.push({
@@ -1298,6 +1492,9 @@ export function references<Brand>(
1298
1492
  }
1299
1493
  }
1300
1494
 
1495
+ if (consumer === undefined) {
1496
+ found.push(...overrideReferences(project, held, chains, new Set(tests)));
1497
+ }
1301
1498
  found.sort(
1302
1499
  (a, b) =>
1303
1500
  byPosition(a.position, b.position) ||