@cplieger/deadset-ts 7.2.0-dev.4 → 8.0.0-dev.2

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 (122) hide show
  1. package/dist/src/analysis.js +108 -10
  2. package/dist/src/augmentations.js +62 -18
  3. package/dist/src/automatic-types.js +189 -0
  4. package/dist/src/base-url.js +18 -0
  5. package/dist/src/components.js +7 -6
  6. package/dist/src/config.js +1 -0
  7. package/dist/src/configuration-files.js +50 -12
  8. package/dist/src/conformance-record.js +2 -2
  9. package/dist/src/convention-rows.js +98 -0
  10. package/dist/src/conventions.js +101 -5
  11. package/dist/src/decorator.js +21 -18
  12. package/dist/src/dependencies.js +183 -28
  13. package/dist/src/discover.js +132 -11
  14. package/dist/src/entry-point-gaps.js +7 -10
  15. package/dist/src/entry-points.js +112 -11
  16. package/dist/src/findings/completion.js +172 -40
  17. package/dist/src/findings/dependencies-and-module-machinery.js +71 -25
  18. package/dist/src/findings/emitters.js +1 -1
  19. package/dist/src/findings/intra-function.js +44 -13
  20. package/dist/src/findings/non-code-artifacts.js +66 -33
  21. package/dist/src/findings/unused-declarations.js +30 -5
  22. package/dist/src/findings/visibility-narrowing.js +29 -5
  23. package/dist/src/generated-file.js +11 -0
  24. package/dist/src/git-ignore.js +86 -0
  25. package/dist/src/graph.js +4 -1
  26. package/dist/src/import-bindings.js +97 -0
  27. package/dist/src/indexed-access.js +130 -28
  28. package/dist/src/interface-satisfaction.js +30 -12
  29. package/dist/src/intra-function-parts.js +297 -45
  30. package/dist/src/inventory.js +52 -8
  31. package/dist/src/kinds.js +13 -13
  32. package/dist/src/matrix.js +57 -10
  33. package/dist/src/mdx-entries.js +197 -0
  34. package/dist/src/module-declarations.js +9 -13
  35. package/dist/src/overrides.js +41 -10
  36. package/dist/src/print.js +1 -0
  37. package/dist/src/ref.js +10 -0
  38. package/dist/src/references.js +38 -4
  39. package/dist/src/report.js +4 -2
  40. package/dist/src/resolve.js +2 -0
  41. package/dist/src/roots.js +110 -38
  42. package/dist/src/schema.js +1 -0
  43. package/dist/src/serialization-contract.js +54 -8
  44. package/dist/src/session.js +7 -0
  45. package/dist/src/source-files.js +39 -14
  46. package/dist/src/suppress-file.js +10 -2
  47. package/dist/src/suppress-inline.js +18 -2
  48. package/dist/src/suppress.js +8 -5
  49. package/dist/src/sweep.js +27 -10
  50. package/dist/src/symbol-keys.js +103 -2
  51. package/dist/src/test-support.js +119 -18
  52. package/dist/src/type-errors.js +34 -7
  53. package/dist/src/type-parameter-reads.js +172 -0
  54. package/dist/src/verbs/analyze.js +16 -0
  55. package/dist/src/verbs/explain.js +4 -2
  56. package/dist/src/version.js +1 -1
  57. package/dist/src/workflow-steps.js +119 -22
  58. package/dist/src/workspace-programs.js +7 -3
  59. package/dist/src/workspace-resolution.js +53 -10
  60. package/jsr.json +1 -1
  61. package/package.json +2 -2
  62. package/src/analysis.ts +146 -11
  63. package/src/augmentations.ts +76 -22
  64. package/src/automatic-types.ts +246 -0
  65. package/src/base-url.ts +24 -0
  66. package/src/components.ts +7 -6
  67. package/src/config.ts +3 -0
  68. package/src/configuration-files.ts +66 -12
  69. package/src/conformance-record.ts +2 -2
  70. package/src/convention-rows.ts +128 -0
  71. package/src/conventions.ts +121 -7
  72. package/src/decorator.ts +28 -18
  73. package/src/dependencies.ts +247 -41
  74. package/src/discover.ts +156 -13
  75. package/src/entry-point-gaps.ts +8 -11
  76. package/src/entry-points.ts +133 -11
  77. package/src/finding.ts +15 -0
  78. package/src/findings/completion.ts +222 -46
  79. package/src/findings/dependencies-and-module-machinery.ts +90 -28
  80. package/src/findings/emitters.ts +1 -1
  81. package/src/findings/intra-function.ts +56 -14
  82. package/src/findings/non-code-artifacts.ts +81 -41
  83. package/src/findings/unused-declarations.ts +36 -5
  84. package/src/findings/visibility-narrowing.ts +40 -5
  85. package/src/generated-file.ts +16 -0
  86. package/src/git-ignore.ts +98 -0
  87. package/src/graph.ts +6 -0
  88. package/src/host.ts +5 -9
  89. package/src/import-bindings.ts +128 -0
  90. package/src/indexed-access.ts +152 -30
  91. package/src/interface-satisfaction.ts +33 -13
  92. package/src/intra-function-parts.ts +394 -59
  93. package/src/inventory.ts +71 -7
  94. package/src/kinds.ts +13 -13
  95. package/src/matrix.ts +104 -15
  96. package/src/mdx-entries.ts +241 -0
  97. package/src/module-declarations.ts +11 -13
  98. package/src/overrides.ts +51 -12
  99. package/src/print.ts +1 -0
  100. package/src/ref.ts +12 -0
  101. package/src/references.ts +48 -3
  102. package/src/report.ts +16 -3
  103. package/src/resolve.ts +3 -0
  104. package/src/roots.ts +163 -45
  105. package/src/schema.ts +1 -0
  106. package/src/serialization-contract.ts +55 -4
  107. package/src/session.ts +7 -0
  108. package/src/source-files.ts +66 -16
  109. package/src/suppress-file.ts +12 -1
  110. package/src/suppress-inline.ts +18 -1
  111. package/src/suppress.ts +10 -5
  112. package/src/sweep.ts +32 -11
  113. package/src/symbol-keys.ts +129 -2
  114. package/src/test-support.ts +155 -18
  115. package/src/type-errors.ts +38 -6
  116. package/src/type-parameter-reads.ts +227 -0
  117. package/src/verbs/analyze.ts +22 -0
  118. package/src/verbs/explain.ts +5 -2
  119. package/src/version.ts +1 -1
  120. package/src/workflow-steps.ts +127 -22
  121. package/src/workspace-programs.ts +7 -3
  122. package/src/workspace-resolution.ts +62 -10
package/dist/src/ref.js CHANGED
@@ -45,9 +45,11 @@ export const REF_EXPRESSIONS = [
45
45
  `^${SCOPE}#$`,
46
46
  `^${SCOPE}#${TS_HEAD}$`,
47
47
  `^${SCOPE}#(?:${TS_QUOTED}\\.)?${TS_HEAD}:alias$`,
48
+ `^${SCOPE}#${TS_HEAD}:import$`,
48
49
  `^${SCOPE}#${TS_HEAD}(?:\\.${TS_MEMBER})+$`,
49
50
  `^${SCOPE}#${TS_HEAD}(?:\\.${TS_MEMBER})+:static$`,
50
51
  `^${SCOPE}#${TS_HEAD}(?:\\.${TS_MEMBER})*(?::static)?<${TS_IDENT}>$`,
52
+ `^${SCOPE}#(?:${TS_HEAD}(?:\\.${TS_MEMBER})*(?::static)?)?\\(${TS_IDENT}\\)$`,
51
53
  `^${MANIFEST_SCOPE}#${TS_PACKAGE}:(?:dependency|dev-dependency|peer-dependency)$`,
52
54
  ];
53
55
  const REF_FORMS = REF_EXPRESSIONS.map((expression) => new RegExp(expression, "u"));
@@ -86,6 +88,14 @@ function quoted(name) {
86
88
  export function computedComponent(text) {
87
89
  return `[${text.replaceAll(ASCII_WHITESPACE, "")}]`;
88
90
  }
91
+ /** The reference of an import binding, read against the reference of the module that holds it. */
92
+ export function importRef(moduleRef, name) {
93
+ return `${moduleRef}${nameComponent(name)}:import`;
94
+ }
95
+ /** The reference of a parameter, read against the reference of the declaration that holds it. */
96
+ export function parameterRef(holderRef, name) {
97
+ return `${holderRef}(${name})`;
98
+ }
89
99
  /** The fragment of an export alias, inside the `declare module` block named, if one is. */
90
100
  export function aliasFragment(name, block) {
91
101
  const head = block === undefined ? "" : `${quoted(block)}.`;
@@ -1,4 +1,4 @@
1
- import { isArrayLiteralExpression, isAsExpression, isBinaryExpression, isCallExpression, isDecorator, isDeleteExpression, isElementAccessExpression, isExportDeclaration, isExpressionStatement, isExternalModuleReference, isForInStatement, isForOfStatement, isForStatement, isIdentifier, isImportDeclaration, isImportEqualsDeclaration, isMetaProperty, isNewExpression, isNamespaceExport, isNoSubstitutionTemplateLiteral, isObjectLiteralExpression, isParenthesizedExpression, isPostfixUnaryExpression, isPrefixUnaryExpression, isPropertyAccessExpression, isPropertyAssignment, isShorthandPropertyAssignment, isSpreadAssignment, isSpreadElement, isStringLiteral, isVoidExpression, SyntaxKind, } from "@typescript/native/ast";
1
+ import { isArrayLiteralExpression, isAsExpression, isBinaryExpression, isCallExpression, isDecorator, isDeleteExpression, isElementAccessExpression, isExportDeclaration, isExpressionStatement, isExternalModuleReference, isForInStatement, isForOfStatement, isForStatement, isIdentifier, isImportDeclaration, isImportEqualsDeclaration, isInterfaceDeclaration, isMetaProperty, isModuleBlock, isModuleDeclaration, isNewExpression, isNamespaceExport, isNoSubstitutionTemplateLiteral, isObjectLiteralExpression, isParenthesizedExpression, isPostfixUnaryExpression, isPrefixUnaryExpression, isPropertyAccessExpression, isPropertyAssignment, isShorthandPropertyAssignment, isSpreadAssignment, isSpreadElement, isStringLiteral, isVoidExpression, SyntaxKind, } from "@typescript/native/ast";
2
2
  import { aliasChains } from "./alias-chain.js";
3
3
  import { contextualUses } from "./contextual-uses.js";
4
4
  import { destructuring, propertyReadsOf } from "./destructuring.js";
@@ -6,7 +6,7 @@ import { specifierCandidates } from "./configuration-files.js";
6
6
  import { namespaceUses } from "./namespace-objects.js";
7
7
  import { globExpression } from "./glob.js";
8
8
  import { declarationsByName, isParameterProperty, nodeKey, } from "./inventory.js";
9
- import { augmentationReferences } from "./augmentations.js";
9
+ import { ambientGlobalInterfaces, augmentationReferences } from "./augmentations.js";
10
10
  import { elementAccessReferences } from "./element-access.js";
11
11
  import { indexedAccessReferences } from "./indexed-access.js";
12
12
  import { keyofComparisonReferences } from "./keyof-comparisons.js";
@@ -16,8 +16,9 @@ import { overrideReferences } from "./overrides.js";
16
16
  import { dirnamePath, queriedModulePath, relativePath, resolvePath } from "./paths.js";
17
17
  import { byPosition, isComponentFile, renderPosition } from "./position.js";
18
18
  import { DEFAULT_BATCH_CAP, propertyTables, UNANSWERED } from "./query.js";
19
+ import { typeParameterReads } from "./type-parameter-reads.js";
19
20
  import { typeQueryAliases } from "./type-query-alias.js";
20
- import { computedKeyNames, keyNameOf, keyedUses } from "./symbol-keys.js";
21
+ import { computedKeyNames, iterationReferences, keyNameOf, keyedUses, } from "./symbol-keys.js";
21
22
  import { handedOn, valuesOf } from "./value-flow.js";
22
23
  /**
23
24
  * The operators whose left side one assignment stores into. A compound assignment is
@@ -1134,6 +1135,37 @@ export function references(project, held, targetRoot, options) {
1134
1135
  }
1135
1136
  }
1136
1137
  }
1138
+ /** The ambient global interfaces of the walked files that merge into a declaration outside the project's own files. */
1139
+ const libraryMerged = (scripts) => {
1140
+ const names = new Map();
1141
+ const visit = (node) => {
1142
+ if (isInterfaceDeclaration(node)) {
1143
+ const id = declaredAt(node);
1144
+ if (id !== undefined) {
1145
+ names.set(id, node.name);
1146
+ }
1147
+ }
1148
+ else if (isModuleDeclaration(node) || isModuleBlock(node)) {
1149
+ node.forEachChild(visit);
1150
+ }
1151
+ };
1152
+ for (const { file } of walked) {
1153
+ file.forEachChild(visit);
1154
+ }
1155
+ const global = new Set(ambientGlobalInterfaces(held, [...names.keys()], scripts));
1156
+ const asked = [...names].filter(([id]) => global.has(id));
1157
+ if (asked.length === 0) {
1158
+ return new Set();
1159
+ }
1160
+ const answers = project.symbolsAt(asked.map(([, name]) => project.handle(name)));
1161
+ return new Set(asked.flatMap(([id], at) => {
1162
+ const symbol = answers[at];
1163
+ return typeof symbol === "object" &&
1164
+ symbol.declarations.some((handle) => !ownPaths.has(handle.path))
1165
+ ? [id]
1166
+ : [];
1167
+ }));
1168
+ };
1137
1169
  const usesOf = new Map(walked.map((one) => [one.file, one.uses]));
1138
1170
  for (const [file, writes] of valued) {
1139
1171
  const uses = usesOf.get(file) ?? [];
@@ -1151,12 +1183,14 @@ export function references(project, held, targetRoot, options) {
1151
1183
  found.push(...indexedAccessReferences(project, held, chains, root, new Set(tests)));
1152
1184
  found.push(...elementAccessReferences(project, held, chains, root, new Set(tests)));
1153
1185
  found.push(...keyofComparisonReferences(project, held, chains, root, new Set(tests)));
1186
+ found.push(...iterationReferences(project, held, chains, root, new Set(tests)));
1187
+ found.push(...typeParameterReads(project, held, chains, root, new Set(tests)));
1154
1188
  found.push(...unimplementedCalleeReads(project, held, chains, root, new Set(tests)));
1155
1189
  found.push(...moduleDeclarationReferences(project, held, root, new Set(tests)));
1156
1190
  typedLookups = (options.requestCount?.() ?? 0) - before;
1157
1191
  }
1158
1192
  const scripts = new Set(walked.filter((one) => one.file.externalModuleIndicator === undefined).map((one) => one.fileId));
1159
- found.push(...augmentationReferences(held, found, scripts));
1193
+ found.push(...augmentationReferences(held, found, scripts, libraryMerged(scripts)));
1160
1194
  found.sort((a, b) => byPosition(a.position, b.position) ||
1161
1195
  compare(a.to, b.to) ||
1162
1196
  compare(a.from, b.from) ||
@@ -4,7 +4,7 @@
4
4
  * renders this document, so no two renderings disagree about which findings exist.
5
5
  */
6
6
  /** The version of the report schema this analyzer writes a report to. */
7
- const SCHEMA_VERSION = "8.2.0";
7
+ const SCHEMA_VERSION = "9.0.0";
8
8
  /** Every report schema version this analyzer reads: the one the Contract it implements admits. */
9
9
  export const SCHEMA_VERSIONS_ACCEPTED = [SCHEMA_VERSION];
10
10
  /** The name this analyzer writes every document under and mints every identifier with. */
@@ -247,7 +247,9 @@ export function buildReport(input) {
247
247
  ]
248
248
  .sort(([a, x], [b, y]) => compare(x.path, y.path) || x.line - y.line || compare(a, b))
249
249
  .map(([, skip]) => skip),
250
- notes: [],
250
+ notes: [...new Map((input.notes ?? []).map((one) => [JSON.stringify(one), one])).entries()]
251
+ .sort(([a, x], [b, y]) => compare(x.kind, y.kind) || compare(x.path, y.path) || compare(a, b))
252
+ .map(([, note]) => note),
251
253
  unanswered_questions: [...input.unanswered]
252
254
  .sort((a, b) => compare(a.configuration, b.configuration) ||
253
255
  compare(JSON.stringify(a), JSON.stringify(b)))
@@ -567,6 +567,7 @@ function readDoc(value, label) {
567
567
  injectionRegistrations: readDeclarations(ts, "injection_registrations", "ts.injection_registrations", label),
568
568
  lifecycleContracts: readLifecycleContracts(ts, label),
569
569
  serializers: readDeclarations(ts, "serializers", "ts.serializers", label),
570
+ rootsComplete: readBoolean(ts, "roots_complete", "ts.roots_complete", label),
570
571
  };
571
572
  }
572
573
  /** Parses one document's text and walks it against the closed key list. */
@@ -776,6 +777,7 @@ function settleAll(inputs) {
776
777
  injectionRegistrations: at("ts.injection_registrations", defaults.ts.injectionRegistrations, (doc) => doc.injectionRegistrations),
777
778
  lifecycleContracts: at("ts.lifecycle_contracts", defaults.ts.lifecycleContracts, (doc) => doc.lifecycleContracts),
778
779
  serializers: at("ts.serializers", defaults.ts.serializers, (doc) => doc.serializers),
780
+ rootsComplete: at("ts.roots_complete", defaults.ts.rootsComplete, (doc) => doc.rootsComplete),
779
781
  },
780
782
  };
781
783
  return { config, provenance };
package/dist/src/roots.js CHANGED
@@ -9,11 +9,12 @@
9
9
  */
10
10
  import { isExpressionWithTypeArguments, isFunctionLikeDeclaration, isPropertyAccessExpression, isQualifiedName, isTypeQueryNode, isTypeReferenceNode, isPropertyDeclaration, isVariableDeclaration, isVariableStatement, } from "@typescript/native/ast";
11
11
  import { aliasChains } from "./alias-chain.js";
12
+ import { filesNamedBy, outsideChain, relativeSpecifiers, } from "./configuration-files.js";
12
13
  import { conventionEntries } from "./conventions.js";
13
14
  import { entryPoints } from "./entry-points.js";
14
15
  import { globExpression } from "./glob.js";
15
16
  import { nodeKey } from "./inventory.js";
16
- import { relativePath, resolvePath } from "./paths.js";
17
+ import { dirnamePath, joinPath, relativePath, resolvePath } from "./paths.js";
17
18
  import { byPosition } from "./position.js";
18
19
  import { UNANSWERED } from "./query.js";
19
20
  import { sourceFilesOf } from "./source-files.js";
@@ -43,11 +44,22 @@ const READ_BACK_RULES = new Set([
43
44
  const OUTPUT_EXTENSION = /\.(?:d\.ts|d\.mts|d\.cts|js|mjs|cjs|jsx)$/u;
44
45
  /** The extension a source file's name drops. */
45
46
  const SOURCE_EXTENSION = /\.(?:d\.)?[cm]?[jt]sx?$/u;
47
+ /** Whether no `package.json` sits between one file and the manifest's directory, which holds it. */
48
+ function ofPackage(host, dir, file) {
49
+ for (let at = dirnamePath(file); at !== dir; at = dirnamePath(at)) {
50
+ if (relativePath(dir, at) === undefined ||
51
+ host.kindOf(joinPath(at, "package.json")) === "file") {
52
+ return false;
53
+ }
54
+ }
55
+ return true;
56
+ }
46
57
  /**
47
58
  * The rooted files one manifest target reads back to by name: its path below the
48
59
  * manifest's directory, past its first directory where it names one, without its output
49
- * extension, is the name, and a rooted file is one whose path below the same directory,
50
- * without its source extension, ends with that name at a directory boundary.
60
+ * extension, is the name, and a rooted file of the manifest's own package is one whose
61
+ * path below the same directory, without its source extension, ends with that name at a
62
+ * directory boundary.
51
63
  */
52
64
  function readBackByName(host, entry, rooted) {
53
65
  const dir = resolvePath(host.workingDirectory(), entry.dir);
@@ -63,12 +75,41 @@ function readBackByName(host, entry, rooted) {
63
75
  const found = new Map();
64
76
  for (const file of rooted) {
65
77
  const path = relativePath(dir, file.fileName)?.replace(SOURCE_EXTENSION, "");
66
- if (path !== undefined && (path === name || path.endsWith(`/${name}`))) {
78
+ if (path !== undefined &&
79
+ (path === name || path.endsWith(`/${name}`)) &&
80
+ ofPackage(host, dir, file.fileName)) {
67
81
  found.set(file.fileName, file);
68
82
  }
69
83
  }
70
84
  return [...found.values()];
71
85
  }
86
+ /**
87
+ * The program files a manifest target no program holds reaches: the target is a file
88
+ * read as a configuration file outside the program is, and each file of the chain it
89
+ * starts loads the program files its relative specifiers name, read through the emit
90
+ * mapping, as a command's shim loads the compiled entry it runs.
91
+ */
92
+ function throughOutside(options, targetRoot, files, entry) {
93
+ const cwd = options.host.workingDirectory();
94
+ const path = resolvePath(cwd, entry.path);
95
+ if (options.parse === undefined ||
96
+ options.runFiles?.has(path) === true ||
97
+ options.host.kindOf(path) !== "file") {
98
+ return [];
99
+ }
100
+ const chain = outsideChain(options.host, targetRoot, files, options.parse, [
101
+ { path, manifestDir: resolvePath(cwd, entry.dir) },
102
+ ]);
103
+ const found = new Map();
104
+ for (const { file } of chain) {
105
+ for (const specifier of relativeSpecifiers(file)) {
106
+ for (const named of filesNamedBy(files, dirnamePath(file.fileName), specifier)) {
107
+ found.set(named.fileName, named);
108
+ }
109
+ }
110
+ }
111
+ return [...found.values()];
112
+ }
72
113
  /** Whether a configured string carries either special character. */
73
114
  function isPattern(text) {
74
115
  return text.includes("*") || text.includes("?");
@@ -168,8 +209,8 @@ export function roots(project, held, targetRoot, options) {
168
209
  found.set([id, kind, source].join("\u0000"), { id, kind, source });
169
210
  };
170
211
  const entered = [];
171
- const enter = (file, kind, source, exports) => {
172
- entered.push({ file, kind, source, exports });
212
+ const enter = (file, kind, source, exports, names) => {
213
+ entered.push({ file, kind, source, exports, ...(names === undefined ? {} : { names }) });
173
214
  };
174
215
  const points = entryPoints(project, files, options.testFiles, options.host, targetRoot, options.parse, options.conventions?.selected);
175
216
  const published = [];
@@ -191,11 +232,28 @@ export function roots(project, held, targetRoot, options) {
191
232
  ...scripted,
192
233
  ...points.filter((point) => READ_BACK_RULES.has(point.rule)).map((point) => point.file),
193
234
  ];
235
+ const entryFilesOf = new Map();
194
236
  const entryFiles = (entry) => {
237
+ const known = entryFilesOf.get(entry);
238
+ if (known !== undefined) {
239
+ return known;
240
+ }
241
+ const found = readEntryFiles(entry);
242
+ entryFilesOf.set(entry, found);
243
+ return found;
244
+ };
245
+ const readEntryFiles = (entry) => {
195
246
  const named = files.named(entry.path);
196
- return named.length > 0 || entry.role === "script"
197
- ? named
198
- : readBackByName(options.host, entry, readBackTo);
247
+ if (named.length > 0 || entry.role === "script") {
248
+ return named;
249
+ }
250
+ const cwd = options.host.workingDirectory();
251
+ const shipped = files.namedAsOutput(resolvePath(cwd, entry.dir), resolvePath(cwd, entry.path));
252
+ if (shipped.length > 0) {
253
+ return shipped;
254
+ }
255
+ const byName = readBackByName(options.host, entry, readBackTo);
256
+ return byName.length > 0 ? byName : throughOutside(options, targetRoot, files, entry);
199
257
  };
200
258
  for (const { entry, publishes } of entries) {
201
259
  if (publishes) {
@@ -203,27 +261,39 @@ export function roots(project, held, targetRoot, options) {
203
261
  }
204
262
  }
205
263
  const publishedNames = new Set(published.map((file) => file.fileName));
206
- for (const { entry } of entries) {
207
- for (const file of entryFiles(entry)) {
208
- const kind = ENTRY_KINDS[entry.role];
209
- enter(file, kind, entry.member, !publishedNames.has(file.fileName));
210
- }
211
- }
264
+ const patterned = [];
212
265
  for (const pattern of options.entryFiles) {
213
266
  const expression = globExpression(pattern);
214
267
  for (const [path, file] of files.byPath) {
215
268
  if (expression.test(path)) {
216
- enter(file, "entry-file", pattern, true);
269
+ patterned.push({ file, pattern });
217
270
  }
218
271
  }
219
272
  }
273
+ const unmapped = [];
274
+ const mapped = patterned.map(({ file }) => file);
275
+ for (const { entry } of entries) {
276
+ const named = entryFiles(entry);
277
+ if (named.length === 0 && entry.role !== "script") {
278
+ unmapped.push(entry);
279
+ }
280
+ mapped.push(...named);
281
+ for (const file of named) {
282
+ const kind = ENTRY_KINDS[entry.role];
283
+ enter(file, kind, entry.member, !publishedNames.has(file.fileName));
284
+ }
285
+ }
286
+ const unmappedEntries = unmapped.filter((entry) => readBackByName(options.host, entry, mapped).length === 0);
287
+ for (const { file, pattern } of patterned) {
288
+ enter(file, "entry-file", pattern, true);
289
+ }
220
290
  for (const { file, row } of options.conventions === undefined
221
291
  ? []
222
292
  : conventionEntries(options.conventions, files)) {
223
293
  enter(file, "convention", row, true);
224
294
  }
225
295
  for (const point of points) {
226
- enter(point.file, point.rule, point.source, point.exports);
296
+ enter(point.file, point.rule, point.source, point.exports, point.names);
227
297
  }
228
298
  for (const file of files.byPath.values()) {
229
299
  for (const alias of typeQueryAliases(file)) {
@@ -236,7 +306,7 @@ export function roots(project, held, targetRoot, options) {
236
306
  }
237
307
  }
238
308
  }
239
- const { tables: exportsOf, answered } = exportTables(project, held, [
309
+ const { tables: exportsOf, named, answered, } = exportTables(project, held, [
240
310
  ...entered.filter((entry) => entry.exports).map((entry) => entry.file),
241
311
  ...published,
242
312
  ]);
@@ -247,8 +317,12 @@ export function roots(project, held, targetRoot, options) {
247
317
  }
248
318
  add(id, entry.kind, entry.source);
249
319
  if (entry.exports) {
250
- for (const exported of exportsOf.get(entry.file.fileName) ?? []) {
251
- add(exported, entry.kind, entry.source);
320
+ const byName = named.get(entry.file.fileName);
321
+ const exported = entry.names === undefined || byName === undefined
322
+ ? (exportsOf.get(entry.file.fileName) ?? [])
323
+ : entry.names.flatMap((name) => byName.get(name) ?? []);
324
+ for (const one of exported) {
325
+ add(one, entry.kind, entry.source);
252
326
  }
253
327
  }
254
328
  }
@@ -271,6 +345,7 @@ export function roots(project, held, targetRoot, options) {
271
345
  liveUnderReachability: ordered,
272
346
  unmatched,
273
347
  unanswered: answered ? api.guessed : held.symbols.map((symbol) => symbol.id),
348
+ unmappedEntries,
274
349
  };
275
350
  }
276
351
  /**
@@ -284,9 +359,10 @@ export function roots(project, held, targetRoot, options) {
284
359
  function exportTables(project, held, wanted) {
285
360
  const unique = [...new Map(wanted.map((file) => [file.fileName, file])).values()];
286
361
  const tables = new Map();
362
+ const named = new Map();
287
363
  let answered = true;
288
364
  if (unique.length === 0) {
289
- return { tables, answered };
365
+ return { tables, named, answered };
290
366
  }
291
367
  const modules = project.symbolsAt(unique.map((file) => project.handle(file)));
292
368
  const chains = aliasChains(project, held);
@@ -307,15 +383,18 @@ function exportTables(project, held, wanted) {
307
383
  return;
308
384
  }
309
385
  const ids = new Set();
386
+ const byName = new Map();
310
387
  for (const exported of table) {
311
388
  for (const target of chains.chainOf(exported)) {
312
389
  answered &&= !target.guessed;
313
390
  ids.add(target.id);
391
+ byName.set(exported.name, [...(byName.get(exported.name) ?? []), target.id]);
314
392
  }
315
393
  }
316
394
  tables.set(file.fileName, [...ids]);
395
+ named.set(file.fileName, byName);
317
396
  });
318
- return { tables, answered };
397
+ return { tables, named, answered };
319
398
  }
320
399
  /** A release tag of a documentation comment: `@` and its name, between white space or the ends. */
321
400
  const RELEASE_TAG = /(?:^|\s)@(?:public|beta|alpha)(?=\s|$)/u;
@@ -370,11 +449,13 @@ function publishedAPI(project, published, exportsOf, held, byPath, tagged) {
370
449
  const nodes = declarationNodes(held, byPath);
371
450
  const chains = aliasChains(project, held);
372
451
  const reached = new Set();
452
+ const entered = new Set();
373
453
  const guessed = new Set();
374
454
  const pending = [];
375
455
  const reach = (id) => {
376
- if (!reached.has(id) && byId.get(id)?.kind !== "file") {
377
- reached.add(id);
456
+ const file = byId.get(id)?.kind === "file";
457
+ if (!(file ? entered : reached).has(id)) {
458
+ (file ? entered : reached).add(id);
378
459
  pending.push(id);
379
460
  }
380
461
  };
@@ -383,12 +464,7 @@ function publishedAPI(project, published, exportsOf, held, byPath, tagged) {
383
464
  }
384
465
  for (const file of published) {
385
466
  for (const id of exportsOf.get(file.fileName) ?? []) {
386
- if (byId.get(id)?.kind === "file") {
387
- pending.push(id);
388
- }
389
- else {
390
- reach(id);
391
- }
467
+ reach(id);
392
468
  }
393
469
  }
394
470
  while (pending.length > 0) {
@@ -411,8 +487,7 @@ function publishedAPI(project, published, exportsOf, held, byPath, tagged) {
411
487
  });
412
488
  for (const symbol of inferredTypeSymbols(project, round.flatMap((id) => {
413
489
  const symbol = byId.get(id);
414
- const node = symbol === undefined ? undefined : nodes.nodeOf(symbol);
415
- return node === undefined ? [] : [node];
490
+ return symbol === undefined ? [] : nodes.nodesOf(symbol);
416
491
  }))) {
417
492
  for (const target of chains.chainOf(symbol)) {
418
493
  reach(target.id);
@@ -449,9 +524,7 @@ function declarationNodes(held, byPath) {
449
524
  const id = held.declarations.get(nodeKey(file, node));
450
525
  if (id !== undefined) {
451
526
  found.nodes.add(node);
452
- if (!found.byId.has(id)) {
453
- found.byId.set(id, node);
454
- }
527
+ found.byId.set(id, [...(found.byId.get(id) ?? []), node]);
455
528
  }
456
529
  node.forEachChild(visit);
457
530
  };
@@ -463,10 +536,9 @@ function declarationNodes(held, byPath) {
463
536
  return {
464
537
  typeNamesOf: (symbol) => {
465
538
  const { byId, nodes } = nodesIn(symbol.position.path);
466
- const declaration = byId.get(symbol.id);
467
- return declaration === undefined ? [] : typeNamesOf(declaration, nodes);
539
+ return (byId.get(symbol.id) ?? []).flatMap((declaration) => typeNamesOf(declaration, nodes));
468
540
  },
469
- nodeOf: (symbol) => nodesIn(symbol.position.path).byId.get(symbol.id),
541
+ nodesOf: (symbol) => nodesIn(symbol.position.path).byId.get(symbol.id) ?? [],
470
542
  };
471
543
  }
472
544
  /** How deep the type arguments and constituents of an inferred type are followed. */
@@ -82,6 +82,7 @@ export const SCHEMA_ROOT = {
82
82
  members: { components: declarations, bases: declarations, members: leaf },
83
83
  },
84
84
  serializers: declarations,
85
+ roots_complete: leaf,
85
86
  },
86
87
  },
87
88
  provenance: { kind: "map" },
@@ -1,10 +1,10 @@
1
1
  /**
2
2
  * The serialization-contract class: the data members and `toJSON` of every type a value's
3
3
  * static type reaches, where the value reaches `JSON.stringify` or a call `ts.serializers`
4
- * names, and `toString` too where it reaches an `unknown` or `any` parameter of a function
5
- * outside the analysis or an erased property of a type the program does not declare. A
6
- * function of the project handing such a parameter to a destination is one, to a fixpoint,
7
- * and a function invoked through `call`, `apply` or `bind` is the callee.
4
+ * names, and `toString` too where it reaches an erased parameter of a function outside the
5
+ * analysis, or an erased property of a type the program does not declare. A function of
6
+ * the project handing such a parameter to a destination is one, to a fixpoint, and a
7
+ * function invoked through `call`, `apply` or `bind` is the callee.
8
8
  */
9
9
  import { isArrayLiteralExpression, isArrowFunction, isAsExpression, isCallExpression, isClassDeclaration, isClassExpression, isClassStaticBlockDeclaration, isFunctionDeclaration, isFunctionExpression, isGetAccessorDeclaration, isIdentifier, isInterfaceDeclaration, isMethodDeclaration, isNoSubstitutionTemplateLiteral, isNumericLiteral, isObjectLiteralExpression, isParenthesizedExpression, isPropertyAccessExpression, isPropertyDeclaration, isSatisfiesExpression, isSetAccessorDeclaration, isSpreadElement, isStringLiteral, isTypeLiteralNode, isVariableDeclaration, ModifierFlags, NodeFlags, SyntaxKind, } from "@typescript/native/ast";
10
10
  import { SignatureKind, SymbolFlags, TypeFlags, } from "@typescript/native/sync";
@@ -25,6 +25,8 @@ const CONVERSIONS = new Set(["toJSON", "toString"]);
25
25
  const SERIALIZED_CONVERSION = "toJSON";
26
26
  /** The parameter types that keep nothing of the value they are given. */
27
27
  const ERASED = TypeFlags.Any | TypeFlags.Unknown;
28
+ /** How deep a callee's result is searched for a type parameter: `Promise<Box<T>>` is two levels. */
29
+ const MENTION_DEPTH = 3;
28
30
  /** The wider of two reaches, where either may be absent. */
29
31
  function wider(a, b) {
30
32
  return a === "conversions" || b === "conversions" ? "conversions" : (a ?? b);
@@ -381,6 +383,28 @@ function destinations(input, chains, walked) {
381
383
  });
382
384
  return containing.length === 0 ? target.declarations : containing;
383
385
  };
386
+ /** Whether a type names a type parameter, through constituents, type arguments and call signatures. */
387
+ const mentions = (type, parameter, depth) => {
388
+ // A conditional type an alias names, `Awaited<T>` among them, carries its arguments
389
+ // only on the alias the client's type object reports.
390
+ const aliased = type
391
+ .aliasTypeArguments;
392
+ if (type.id === parameter.id || aliased?.includes(parameter.id) === true) {
393
+ return true;
394
+ }
395
+ if (depth >= MENTION_DEPTH) {
396
+ return false;
397
+ }
398
+ const inner = [
399
+ ...(type.isUnionType() || type.isIntersectionType() ? must(queries.constituents(type)) : []),
400
+ ...must(queries.typeArguments(type)),
401
+ ...must(queries.signaturesOf(type, SignatureKind.Call)).flatMap((signature) => [
402
+ ...signature.parameters.map((_parameter, at) => must(queries.typeAtPosition(signature, at))),
403
+ ...[must(queries.returnTypeOf(signature))].filter((one) => one !== undefined),
404
+ ]),
405
+ ];
406
+ return inner.some((one) => mentions(one, parameter, depth + 1));
407
+ };
384
408
  const erasedArguments = new Map();
385
409
  const erasedAt = (call, argument) => {
386
410
  let indexes = erasedArguments.get(call);
@@ -394,10 +418,32 @@ function destinations(input, chains, walked) {
394
418
  ? []
395
419
  : must(queries.signaturesOf(type, SignatureKind.Call))
396
420
  : [resolvedOf(call)].filter((one) => one !== undefined);
397
- indexes = new Set(invoked.arguments.flatMap((_unused, index) => signatures.some((signature) => {
398
- const parameter = must(queries.parameterType(signature, index));
399
- return parameter !== undefined && (parameter.flags & ERASED) !== 0;
400
- })
421
+ // A type parameter of the callee that nothing constrains and its result does not name
422
+ // keeps nothing of the value either: the instantiated signature names the value's own
423
+ // type there instead, and a result that names it hands the value back.
424
+ const generic = invoked.indirect ? undefined : resolvedOf(call);
425
+ const declared = generic === undefined ? undefined : must(queries.genericOf(generic));
426
+ const unconstrained = (index) => {
427
+ const parameter = declared === undefined ? undefined : must(queries.parameterType(declared, index));
428
+ const parts = parameter === undefined
429
+ ? []
430
+ : parameter.isUnionType()
431
+ ? must(queries.constituents(parameter))
432
+ : [parameter];
433
+ const result = declared === undefined ? undefined : must(queries.returnTypeOf(declared));
434
+ return parts.some((part) => {
435
+ if (!part.isTypeParameter() || (result !== undefined && mentions(result, part, 0))) {
436
+ return false;
437
+ }
438
+ const constraint = must(queries.constraintOf(part));
439
+ return constraint === undefined || (constraint.flags & ERASED) !== 0;
440
+ });
441
+ };
442
+ indexes = new Set(invoked.arguments.flatMap((_unused, index) => unconstrained(index) ||
443
+ signatures.some((signature) => {
444
+ const parameter = must(queries.parameterType(signature, index));
445
+ return parameter !== undefined && (parameter.flags & ERASED) !== 0;
446
+ })
401
447
  ? [index]
402
448
  : []));
403
449
  erasedArguments.set(call, indexes);
@@ -31,6 +31,7 @@ export function openEngine(options) {
31
31
  ...(mapper === undefined ? {} : { runExternalCode: true }),
32
32
  });
33
33
  let closed = false;
34
+ const parsed = new Map();
34
35
  return {
35
36
  parseConfigFile: (file) => api.parseConfigFile(file),
36
37
  parseConfigJson: (json, configFile) => api.parseJsonConfigFileContent(json, { configFileName: configFile }),
@@ -54,8 +55,14 @@ export function openEngine(options) {
54
55
  moduleResolutions: { fallback: "resolve", entries: [...entries] },
55
56
  }),
56
57
  parseSourceFile: (fileName, text) => {
58
+ // Every project of a run reads the same files outside its program, so one parse serves all.
59
+ const known = parsed.get(fileName);
60
+ if (known?.text === text) {
61
+ return known.file;
62
+ }
57
63
  const retained = api.createSourceFile(fileName, text);
58
64
  try {
65
+ parsed.set(fileName, { text, file: retained.sourceFile });
59
66
  return retained.sourceFile;
60
67
  }
61
68
  finally {
@@ -4,9 +4,13 @@
4
4
  * compiler emits from it.
5
5
  */
6
6
  import { commonDirectory, emittedFrom } from "./emit-map.js";
7
- import { dirnamePath, relativePath, resolvePath } from "./paths.js";
7
+ import { dirnamePath, joinPath, relativePath, resolvePath } from "./paths.js";
8
8
  /** The one character a subpath pattern of a manifest's `exports` holds. */
9
9
  const SUBPATH_WILDCARD = "*";
10
+ /** The extension a source file's name drops when the project writes it out. */
11
+ const SOURCE_EXTENSION = /\.[cm]?[jt]sx?$/u;
12
+ /** The extensions the project may write a source file's output under. */
13
+ const OUTPUT_EXTENSIONS = [".js", ".mjs", ".cjs", ".jsx", ".d.ts", ".d.mts", ".d.cts"];
10
14
  /**
11
15
  * Whether one path is what a subpath pattern stands for: the text before the
12
16
  * wildcard, any run of characters including the solidus, then the text after it.
@@ -17,6 +21,27 @@ function matchesSubpath(pattern, path) {
17
21
  const after = pattern.slice(at + SUBPATH_WILDCARD.length);
18
22
  return (path.length >= before.length + after.length && path.startsWith(before) && path.endsWith(after));
19
23
  }
24
+ /** The first own file of the paths given that the project holds, or none. */
25
+ function heldAt(byName, paths) {
26
+ for (const candidate of paths) {
27
+ const file = byName.get(candidate);
28
+ if (file !== undefined) {
29
+ return [file];
30
+ }
31
+ }
32
+ return [];
33
+ }
34
+ /** Where one project writes its output, and the directory it writes it from, absolute. */
35
+ function layoutOf(project, fileNames) {
36
+ const options = project.program.getCompilerOptions();
37
+ const configDir = dirnamePath(project.configFile);
38
+ const at = (option) => option === undefined ? undefined : resolvePath(configDir, option);
39
+ return {
40
+ outDir: at(options.outDir),
41
+ declarationDir: at(options.declarationDir),
42
+ sourceDir: at(options.rootDir) ?? commonDirectory(fileNames),
43
+ };
44
+ }
20
45
  /**
21
46
  * The lookups over one project's own files.
22
47
  *
@@ -36,12 +61,7 @@ export function sourceFilesOf(project, targetRoot) {
36
61
  byPath.set(path, file);
37
62
  }
38
63
  }
39
- const options = project.program.getCompilerOptions();
40
- const configDir = dirnamePath(project.configFile);
41
- const at = (option) => option === undefined ? undefined : resolvePath(configDir, option);
42
- const outDir = at(options.outDir);
43
- const declarationDir = at(options.declarationDir);
44
- const sourceDir = at(options.rootDir) ?? commonDirectory([...byName.keys()]);
64
+ const { outDir, declarationDir, sourceDir } = layoutOf(project, [...byName.keys()]);
45
65
  const layout = { outDir, declarationDir, sourceDirs: [sourceDir] };
46
66
  /** The source paths one written path may stand for, the path itself first. */
47
67
  const candidates = (path) => [path, ...emittedFrom(path, layout)];
@@ -49,15 +69,20 @@ export function sourceFilesOf(project, targetRoot) {
49
69
  byPath,
50
70
  byName,
51
71
  outputDirs: [...new Set([outDir, declarationDir])].filter((dir) => dir !== undefined),
72
+ writtenAs: (file) => {
73
+ const below = relativePath(sourceDir, file.fileName);
74
+ if (below === undefined) {
75
+ return [];
76
+ }
77
+ const written = [outDir, declarationDir].flatMap((dir) => dir === undefined
78
+ ? []
79
+ : OUTPUT_EXTENSIONS.map((extension) => joinPath(dir, below.replace(SOURCE_EXTENSION, extension))));
80
+ return [...new Set(written)].filter((path) => heldAt(byName, candidates(path))[0] === file);
81
+ },
82
+ namedAsOutput: (outputDir, path) => heldAt(byName, emittedFrom(path, { outDir: outputDir, sourceDirs: [sourceDir] })),
52
83
  named: (path) => {
53
84
  if (!path.includes(SUBPATH_WILDCARD)) {
54
- for (const candidate of candidates(path)) {
55
- const held = byName.get(candidate);
56
- if (held !== undefined) {
57
- return [held];
58
- }
59
- }
60
- return [];
85
+ return heldAt(byName, candidates(path));
61
86
  }
62
87
  const patterns = candidates(path).filter((candidate) => candidate.includes(SUBPATH_WILDCARD));
63
88
  return [...byName.values()].filter((file) => patterns.some((pattern) => matchesSubpath(pattern, file.fileName)));