@cplieger/deadset-ts 7.2.0-dev.3 → 7.2.0-dev.4

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 (65) hide show
  1. package/dist/bin/mapper-connection.js +7 -4
  2. package/dist/src/analysis.js +10 -6
  3. package/dist/src/component-files.js +96 -31
  4. package/dist/src/configuration-files.js +4 -3
  5. package/dist/src/configured-declarations.js +24 -2
  6. package/dist/src/conformance-record.js +1 -1
  7. package/dist/src/contextual-uses.js +26 -26
  8. package/dist/src/dependencies.js +2 -2
  9. package/dist/src/destructuring.js +77 -2
  10. package/dist/src/discover.js +68 -86
  11. package/dist/src/edges.js +1 -1
  12. package/dist/src/intra-function-parts.js +17 -6
  13. package/dist/src/inventory.js +11 -0
  14. package/dist/src/json-document.js +5 -5
  15. package/dist/src/namespace-objects.js +28 -18
  16. package/dist/src/overrides.js +7 -2
  17. package/dist/src/paths.js +9 -0
  18. package/dist/src/references.js +25 -12
  19. package/dist/src/reflective-lookup.js +8 -6
  20. package/dist/src/reporters/template.js +22 -13
  21. package/dist/src/serialization-contract.js +43 -12
  22. package/dist/src/session.js +19 -1
  23. package/dist/src/spelled-names.js +58 -0
  24. package/dist/src/suppress-file.js +2 -2
  25. package/dist/src/suppress-inline.js +3 -1
  26. package/dist/src/template-field.js +9 -6
  27. package/dist/src/type-errors.js +82 -28
  28. package/dist/src/value-reach.js +17 -5
  29. package/dist/src/verbs/analyze.js +3 -1
  30. package/dist/src/version-range.js +10 -5
  31. package/dist/src/workspace-resolution.js +19 -3
  32. package/dist/src/workspace.js +1 -1
  33. package/jsr.json +1 -1
  34. package/package.json +1 -1
  35. package/src/analysis.ts +32 -6
  36. package/src/component-files.ts +116 -31
  37. package/src/configuration-files.ts +14 -6
  38. package/src/configured-declarations.ts +33 -4
  39. package/src/conformance-record.ts +1 -1
  40. package/src/contextual-uses.ts +27 -27
  41. package/src/dependencies.ts +2 -2
  42. package/src/destructuring.ts +93 -2
  43. package/src/discover.ts +92 -87
  44. package/src/edges.ts +1 -1
  45. package/src/intra-function-parts.ts +21 -5
  46. package/src/inventory.ts +12 -0
  47. package/src/json-document.ts +7 -7
  48. package/src/namespace-objects.ts +34 -21
  49. package/src/overrides.ts +10 -2
  50. package/src/paths.ts +10 -0
  51. package/src/references.ts +28 -16
  52. package/src/reflective-lookup.ts +9 -7
  53. package/src/reporters/template.ts +21 -13
  54. package/src/serialization-contract.ts +53 -13
  55. package/src/session.ts +22 -1
  56. package/src/spelled-names.ts +102 -0
  57. package/src/suppress-file.ts +2 -2
  58. package/src/suppress-inline.ts +3 -1
  59. package/src/template-field.ts +9 -6
  60. package/src/type-errors.ts +94 -35
  61. package/src/value-reach.ts +19 -6
  62. package/src/verbs/analyze.ts +6 -1
  63. package/src/version-range.ts +10 -5
  64. package/src/workspace-resolution.ts +24 -4
  65. package/src/workspace.ts +1 -1
@@ -1,5 +1,5 @@
1
1
  import { DiagnosticCategory } from "@typescript/native/sync";
2
- import { dirnamePath, isAbsolutePath, joinPath, relativePath, resolvePath } from "./paths.js";
2
+ import { dirnamePath, joinPath, relativePath, resolvePath } from "./paths.js";
3
3
  import { SetupError } from "./setup-failure.js";
4
4
  /** Directory names discovery does not descend into. */
5
5
  export const IGNORED_DIRECTORIES = new Set(["node_modules", ".git"]);
@@ -60,12 +60,28 @@ function locatedIn(diagnostics, locate) {
60
60
  export function diagnosticErrors(sets) {
61
61
  return [...sets.configParsing, ...sets.syntactic, ...sets.semantic].filter(isError);
62
62
  }
63
+ /**
64
+ * The codes of a type library and a lib the configuration names that the compiler
65
+ * cannot find, which it reports while it gathers the program's files:
66
+ * https://github.com/microsoft/typescript-go/blob/89d5d5b2849a0db0957065889ca58536fa6d2e4a/internal/compiler/processingDiagnostic.go
67
+ */
68
+ const UNFOUND_LIBRARY = new Set([2688, 2726, 2727]);
63
69
  /** The errors one open project carries, each located in the file it names. */
64
70
  export function projectErrors(sets) {
65
- const load = sets.configParsing.filter(isError);
71
+ // The compiler numbers its semantic messages from 2000 and its option messages from
72
+ // 5000. A file-less option error, a removed option or an output layout, leaves the
73
+ // program whole, and the compiler checks it all the same:
74
+ // https://github.com/microsoft/TypeScript/wiki/Coding-guidelines#diagnostic-message-codes
75
+ const semantic = sets.unplaced.filter((diagnostic) => isError(diagnostic) && diagnostic.code >= 2000 && diagnostic.code < 3000);
76
+ const unfound = semantic.filter(({ code }) => UNFOUND_LIBRARY.has(code));
77
+ const load = [
78
+ ...sets.configParsing.filter(isError),
79
+ ...semantic.filter(({ code }) => !UNFOUND_LIBRARY.has(code)),
80
+ ];
66
81
  return {
67
82
  load: locatedIn(load, sets.locate),
68
83
  check: load.length > 0 ? [] : locatedIn(diagnosticErrors(sets), sets.locate),
84
+ unfound: load.length > 0 ? [] : locatedIn(unfound, sets.locate),
69
85
  };
70
86
  }
71
87
  /**
@@ -86,9 +102,6 @@ export function targetRelative(text, targetRoot) {
86
102
  export function isGuess(discovery, configFile) {
87
103
  return discovery.derived && !discovery.fromScope.includes(configFile);
88
104
  }
89
- function isRecord(value) {
90
- return typeof value === "object" && value !== null && !Array.isArray(value);
91
- }
92
105
  /**
93
106
  * The configuration files below `root`, in ascending path order, skipping an
94
107
  * ignored directory. A directory that cannot be read is skipped: a tree the
@@ -124,46 +137,6 @@ function configFilesUnder(host, root) {
124
137
  }
125
138
  return found.sort();
126
139
  }
127
- /**
128
- * The configuration files one configuration's `references` name, resolved against
129
- * that configuration's own directory. A reference naming a directory names the
130
- * `tsconfig.json` in it, which is the compiler's own rule.
131
- */
132
- function referencesOf(host, configFile) {
133
- let text;
134
- try {
135
- text = host.readFile(configFile);
136
- }
137
- catch {
138
- return [];
139
- }
140
- let value;
141
- try {
142
- value = JSON.parse(text);
143
- }
144
- catch {
145
- // What a configuration's contents say is refused by the compiler's own
146
- // diagnostics, which name the position; nothing is inferred from it here.
147
- return [];
148
- }
149
- if (!isRecord(value) || !Array.isArray(value["references"])) {
150
- return [];
151
- }
152
- const dir = dirnamePath(configFile);
153
- const found = [];
154
- for (const entry of value["references"]) {
155
- if (!isRecord(entry)) {
156
- continue;
157
- }
158
- const path = entry["path"];
159
- if (typeof path !== "string" || path === "") {
160
- continue;
161
- }
162
- const resolved = isAbsolutePath(path) ? path : joinPath(dir, path);
163
- found.push(resolved.endsWith(".json") ? resolved : joinPath(resolved, "tsconfig.json"));
164
- }
165
- return found;
166
- }
167
140
  /**
168
141
  * The configuration file the scope names for the target directly, where its path
169
142
  * names one rather than a directory. A target naming a directory is reached by the
@@ -177,12 +150,12 @@ function scopeConfigFiles(host, scope) {
177
150
  }
178
151
  /**
179
152
  * Every configuration file reached from `pending`, in the order discovery reads them:
180
- * each once, each followed by the configurations it references, transitively. A
181
- * configuration the compiler cannot read ends the run naming it, unless `unreadable`
182
- * is given, which is then told the configuration and why, and the walk goes on
183
- * without it.
153
+ * each once, each followed by the configurations it references that `follows` admits,
154
+ * transitively. A configuration the compiler cannot read ends the run naming it, unless
155
+ * `unreadable` is given, which is then told the configuration and why, and the walk
156
+ * goes on without it.
184
157
  */
185
- function derivedFrom(engine, host, pending, unreadable) {
158
+ function derivedFrom(engine, host, pending, follows, unreadable) {
186
159
  const seen = new Set();
187
160
  const found = [];
188
161
  while (pending.length > 0) {
@@ -195,16 +168,16 @@ function derivedFrom(engine, host, pending, unreadable) {
195
168
  continue;
196
169
  }
197
170
  seen.add(path);
198
- const reason = unreadableReason(engine, path);
199
- if (reason !== undefined) {
171
+ const read = readConfiguration(engine, path);
172
+ if ("reason" in read) {
200
173
  if (unreadable === undefined) {
201
- throw new DiscoveryError(`${path} cannot be read as a compiler configuration: ${reason}`, []);
174
+ throw new DiscoveryError(`${path} cannot be read as a compiler configuration: ${read.reason}`, []);
202
175
  }
203
- unreadable(path, reason);
176
+ unreadable(path, read.reason);
204
177
  continue;
205
178
  }
206
179
  found.push(path);
207
- pending.push(...referencesOf(host, path));
180
+ pending.push(...read.references.filter(follows));
208
181
  }
209
182
  return found;
210
183
  }
@@ -229,7 +202,7 @@ export function consumerProjects(engine, host, root) {
229
202
  if (kind !== "directory") {
230
203
  throw new DiscoveryError(`the consumer ${root} is not a directory`, []);
231
204
  }
232
- const found = derivedFrom(engine, host, configFilesUnder(host, root));
205
+ const found = derivedFrom(engine, host, configFilesUnder(host, root), () => true);
233
206
  if (found.length === 0) {
234
207
  throw new DiscoveryError(`the consumer ${root} holds no compiler configuration, so none of its references can be read`, []);
235
208
  }
@@ -242,42 +215,48 @@ export function consumerProjects(engine, host, root) {
242
215
  function derivedId(targetRoot, configFile) {
243
216
  return relativePath(targetRoot, configFile) ?? configFile;
244
217
  }
245
- /** Why the compiler cannot read one configuration file, or undefined where it can. */
246
- function unreadableReason(engine, configFile) {
218
+ /**
219
+ * The configuration files one configuration's `references` name, or why the compiler
220
+ * cannot read the configuration.
221
+ */
222
+ function readConfiguration(engine, configFile) {
247
223
  try {
248
- engine.parseConfigFile(configFile);
249
- return undefined;
224
+ const parsed = engine.parseConfigFile(configFile);
225
+ // The parse answers a directory reference as the directory; the compiler reads a
226
+ // path not ending `.json` as its `tsconfig.json`:
227
+ // https://github.com/microsoft/typescript-go/blob/89d5d5b2849a0db0957065889ca58536fa6d2e4a/internal/core/projectreference.go#L18
228
+ return {
229
+ references: (parsed.projectReferences ?? []).map(({ path }) => path.endsWith(".json") ? path : joinPath(path, "tsconfig.json")),
230
+ };
250
231
  }
251
232
  catch (error) {
252
- return error instanceof Error ? error.message : String(error);
233
+ return { reason: error instanceof Error ? error.message : String(error) };
253
234
  }
254
235
  }
255
236
  /**
256
- * Resolves one compiler configuration file, or ends the run naming it: a project
257
- * opened from a configuration the compiler cannot read would be analyzed with no
258
- * type information.
237
+ * The references of one compiler configuration file, or ends the run naming it: a
238
+ * project opened from a configuration the compiler cannot read would be analyzed with
239
+ * no type information.
259
240
  */
260
241
  function parseOrRefuse(engine, configFile, named) {
261
- const reason = unreadableReason(engine, configFile);
262
- if (reason !== undefined) {
263
- throw new DiscoveryError(`${named} cannot be read as a compiler configuration: ${reason}`, []);
242
+ const read = readConfiguration(engine, configFile);
243
+ if ("reason" in read) {
244
+ throw new DiscoveryError(`${named} cannot be read as a compiler configuration: ${read.reason}`, []);
264
245
  }
246
+ return read.references;
265
247
  }
266
248
  /**
267
- * The projects a build matrix declares, in the order it lists them. Each entry
268
- * names a file below the target root, and an entry naming a file that is absent,
269
- * that is a directory, or that the compiler cannot read ends the run naming it.
270
- *
271
- * A declared project's `references` are not followed, because the entries are the
272
- * matrix, and a reference to a configuration the matrix does not name ends the run
273
- * naming both: the compiler builds a referenced project's sources into the program
274
- * that references it without reporting their diagnostics, so the project the matrix
275
- * left out would be analyzed with its errors unread. An entry naming a file an
276
- * earlier entry named adds nothing, so the earlier identifier stands.
249
+ * The projects a build matrix declares, in its order, each entry a file below the target
250
+ * root: an entry naming an absent file, a directory or a file the compiler cannot read
251
+ * ends the run naming it, and one naming an earlier entry's file adds nothing. A
252
+ * reference below the root the matrix does not name ends the run naming both, because the
253
+ * compiler builds a referenced project's sources into the referencing program without
254
+ * reporting their diagnostics. A reference outside the root is an outside project.
277
255
  */
278
256
  function declaredProjects(engine, host, targetRoot, entries) {
279
257
  const seen = new Set();
280
258
  const projects = [];
259
+ const references = new Map();
281
260
  for (const entry of entries) {
282
261
  const configFile = joinPath(targetRoot, entry.project);
283
262
  const named = `the project ${JSON.stringify(entry.id)} (${configFile})`;
@@ -291,11 +270,13 @@ function declaredProjects(engine, host, targetRoot, entries) {
291
270
  continue;
292
271
  }
293
272
  seen.add(configFile);
294
- parseOrRefuse(engine, configFile, named);
273
+ references.set(configFile, parseOrRefuse(engine, configFile, named));
295
274
  projects.push({ id: entry.id, configFile });
296
275
  }
297
276
  for (const project of projects) {
298
- const unnamed = referencesOf(host, project.configFile).find((path) => !seen.has(path));
277
+ const unnamed = references
278
+ .get(project.configFile)
279
+ ?.find((path) => !seen.has(path) && relativePath(targetRoot, path) !== undefined);
299
280
  if (unnamed !== undefined) {
300
281
  throw new DiscoveryError(`the project ${JSON.stringify(project.id)} (${project.configFile}) references ` +
301
282
  `${unnamed}, which the build matrix does not name. A referenced project is built ` +
@@ -311,18 +292,19 @@ function declaredProjects(engine, host, targetRoot, entries) {
311
292
  };
312
293
  }
313
294
  /**
314
- * The projects a run derives where the matrix declares none, in the order discovery
315
- * reads them: the paths the scope names, then every compiler configuration under
316
- * the target root that is not inside an ignored directory, then the configurations
317
- * each of those references, transitively. Each configuration appears once. A
318
- * configuration the scope names directly is the caller's own and ends the run where
319
- * it cannot be read; one the walk found is dropped and named.
295
+ * The projects a run derives where the matrix declares none, in discovery order: the
296
+ * paths the scope names, every compiler configuration under the target root outside an
297
+ * ignored directory, then each configuration below the root those reference,
298
+ * transitively, each once. A reference outside the root is an outside project, whose
299
+ * sources the compiler builds into the referencing program. A configuration the scope
300
+ * names ends the run where it cannot be read; one the walk found is dropped and named.
320
301
  */
321
302
  function derivedProjects(engine, host, scope) {
322
303
  const targetRoot = scope.target.path;
323
304
  const fromScope = scopeConfigFiles(host, scope);
324
305
  const notBuilt = [];
325
- const configFiles = derivedFrom(engine, host, [...fromScope, ...configFilesUnder(host, targetRoot)], (configFile, reason) => {
306
+ const rootDir = resolvePath(host.workingDirectory(), fromScope.length > 0 ? dirnamePath(targetRoot) : targetRoot);
307
+ const configFiles = derivedFrom(engine, host, [...fromScope, ...configFilesUnder(host, targetRoot)], (reference) => relativePath(rootDir, reference) !== undefined, (configFile, reason) => {
326
308
  if (fromScope.includes(configFile)) {
327
309
  throw new DiscoveryError(`${configFile} cannot be read as a compiler configuration: ${reason}`, []);
328
310
  }
package/dist/src/edges.js CHANGED
@@ -130,7 +130,7 @@ export function readEdgeSides(host, targetRoot) {
130
130
  }
131
131
  const sides = [];
132
132
  edges.forEach((value, index) => {
133
- const offset = walk.opened[index];
133
+ const offset = walk.starts[index];
134
134
  const at = offset === undefined ? EDGES_FILE : positionKey(positionAt(text, EDGES_FILE, offset));
135
135
  const edge = readEdge(value, at);
136
136
  const named = [
@@ -4,11 +4,12 @@
4
4
  * call sites that use or discard it, each statement control flow cannot reach, each
5
5
  * write to a local that no read reaches, and each case an earlier case covers.
6
6
  */
7
- import { isArrayBindingPattern, isArrowFunction, isAwaitExpression, isBigIntLiteral, isBinaryExpression, isBlock, isBreakStatement, isBindingElement, isCallExpression, isCaseClause, isCatchClause, isClassDeclaration, isClassExpression, isConstructorDeclaration, isContinueStatement, isDecorator, isDefaultClause, isEmptyStatement, isExportAssignment, isExportSpecifier, isExpressionStatement, isFunctionDeclaration, isFunctionExpression, isHeritageClause, isIdentifier, isIfStatement, isImportClause, isImportSpecifier, isInterfaceDeclaration, isMethodDeclaration, isModuleDeclaration, isNamespaceImport, isNewExpression, isNoSubstitutionTemplateLiteral, isNumericLiteral, isObjectBindingPattern, isObjectLiteralExpression, isParenthesizedExpression, isPrefixUnaryExpression, isPropertyAccessExpression, isQualifiedName, isReturnStatement, isShorthandPropertyAssignment, isStringLiteral, isSwitchStatement, isThrowStatement, isTypeAliasDeclaration, isTypeReferenceNode, isVariableStatement, isVoidExpression, NodeFlags, SyntaxKind, } from "@typescript/native/ast";
7
+ import { isArrayBindingPattern, isArrowFunction, isAwaitExpression, isBigIntLiteral, isBinaryExpression, isBlock, isBreakStatement, isBindingElement, isCallExpression, isCaseClause, isCatchClause, isClassDeclaration, isClassExpression, isConstructorDeclaration, isContinueStatement, isDecorator, isDefaultClause, isEmptyStatement, isExportAssignment, isExportSpecifier, isExpressionStatement, isFunctionDeclaration, isFunctionExpression, isFunctionLikeDeclaration, isHeritageClause, isIdentifier, isIfStatement, isImportClause, isImportSpecifier, isInterfaceDeclaration, isMethodDeclaration, isModuleDeclaration, isNamespaceImport, isNewExpression, isNoSubstitutionTemplateLiteral, isNumericLiteral, isObjectBindingPattern, isObjectLiteralExpression, isParenthesizedExpression, isPrefixUnaryExpression, isPropertyAccessExpression, isQualifiedName, isReturnStatement, isShorthandPropertyAssignment, isSourceFile, isStringLiteral, isSwitchStatement, isThrowStatement, isTypeAliasDeclaration, isTypeReferenceNode, isVariableStatement, isVoidExpression, NodeFlags, SyntaxKind, } from "@typescript/native/ast";
8
8
  import { isParameterProperty, nodeKey } from "./inventory.js";
9
9
  import { positionKey, renderPosition } from "./position.js";
10
10
  import { isAnswered, UNANSWERED } from "./query.js";
11
11
  import { signatureOf } from "./signatures.js";
12
+ import { spellsNoBinding } from "./spelled-names.js";
12
13
  /** The codes of the parts this module reads. */
13
14
  export const UNUSED_PARAMETER = "DS1801";
14
15
  export const UNUSED_RESULT = "DS1803";
@@ -117,13 +118,12 @@ function caseKey(expression) {
117
118
  }
118
119
  return undefined;
119
120
  }
120
- /** Every identifier below one node whose text one of the names spells, a member's name aside. */
121
+ /** Every identifier below one node whose text one of the names spells as a reference. */
121
122
  function identifiersNaming(node, names) {
122
123
  const found = [];
123
124
  const visit = (at) => {
124
125
  if (isIdentifier(at)) {
125
- const parent = at.parent;
126
- if (names.has(at.text) && !(isPropertyAccessExpression(parent) && parent.name === at)) {
126
+ if (names.has(at.text) && !spellsNoBinding(at)) {
127
127
  found.push(at);
128
128
  }
129
129
  return;
@@ -154,6 +154,15 @@ function inNestedFunction(node, outer) {
154
154
  }
155
155
  return false;
156
156
  }
157
+ /** The nearest function around one node whose `arguments` object it names: an arrow has none. */
158
+ function ownArgumentsOf(node) {
159
+ for (let at = node.parent; !isSourceFile(at); at = at.parent) {
160
+ if (isFunctionLikeDeclaration(at) && !isArrowFunction(at)) {
161
+ return at;
162
+ }
163
+ }
164
+ return undefined;
165
+ }
157
166
  /** Whether a body is a stub: it holds no statement, or nothing but `throw` statements. */
158
167
  function isStub(signature) {
159
168
  const body = signature.body;
@@ -608,8 +617,10 @@ function withContextualRead(project, parts, contextual) {
608
617
  * as read where its function reads any name it binds.
609
618
  */
610
619
  function parameterParts(file, root, declaration, signature, reader, ids, inValue) {
611
- // A function that reads `arguments` reads every parameter by position.
612
- const readsArguments = !isArrowFunction(signature) && identifiersNaming(signature, new Set(["arguments"])).length > 0;
620
+ // A function that reads its `arguments` reads every parameter by position. A nested
621
+ // function other than an arrow has an `arguments` object of its own.
622
+ const readsArguments = !isArrowFunction(signature) &&
623
+ identifiersNaming(signature, new Set(["arguments"])).some((use) => ownArgumentsOf(use) === signature);
613
624
  const read = reader.names.map(({ name }) => readsArguments ||
614
625
  reader.uses.some((use) => use.text === name.text && readsBinding(use, name, ids)));
615
626
  const lastRead = Math.max(-1, ...reader.names.filter((_, at) => read[at]).map((one) => one.at));
@@ -204,6 +204,17 @@ function visibilityOf(node, name) {
204
204
  function nameNodeOf(node) {
205
205
  return node.name;
206
206
  }
207
+ /**
208
+ * The fragment component one declaration's name spells in a reference, as the inventory
209
+ * spells it, or undefined for a name the grammar does not represent.
210
+ */
211
+ export function declaredComponent(node) {
212
+ const component = componentOf(nameNodeOf(node));
213
+ if (component === undefined) {
214
+ return undefined;
215
+ }
216
+ return component.computed ? computedComponent(component.text) : nameComponent(component.text);
217
+ }
207
218
  /**
208
219
  * One node's identity inside one program, which is how two readings of that program
209
220
  * name one declaration.
@@ -4,7 +4,7 @@
4
4
  */
5
5
  /** The token walk over a document `JSON.parse` accepted, so every token is well formed. */
6
6
  class Walk {
7
- opened = [];
7
+ starts = [];
8
8
  /** The member written twice, where one is, as the offset of its name. */
9
9
  twice;
10
10
  text;
@@ -33,11 +33,11 @@ class Walk {
33
33
  }
34
34
  value(role) {
35
35
  this.skip();
36
+ if (role === "record") {
37
+ this.starts.push(this.at);
38
+ }
36
39
  const char = this.text[this.at];
37
40
  if (char === "{") {
38
- if (role === "record") {
39
- this.opened.push(this.at);
40
- }
41
41
  this.object(role);
42
42
  }
43
43
  else if (char === "[") {
@@ -100,7 +100,7 @@ class Walk {
100
100
  export function walkDocument(text, array) {
101
101
  const walk = new Walk(text, array);
102
102
  walk.run();
103
- return { opened: walk.opened, twice: walk.twice };
103
+ return { starts: walk.starts, twice: walk.twice };
104
104
  }
105
105
  /**
106
106
  * One offset of a document as a finding carries it: the line counted from one, and the
@@ -1,11 +1,12 @@
1
1
  /**
2
2
  * Module namespace objects: the binding of a namespace import, the value an `await` of a
3
3
  * dynamic `import()` produces, the first parameter of the first callback passed to `then`
4
- * on one, and a `const` binding initialized with one of these. A use reads the exports it
4
+ * on one, and a variable binding initialized with one of these. A use reads the exports it
5
5
  * names and no other: a property access by name or by a literal index, or a destructured
6
6
  * property, names one export, and any other use may read every export.
7
7
  */
8
- import { isArrowFunction, isAwaitExpression, isBindingElement, isCallExpression, isElementAccessExpression, isExpressionStatement, isFunctionExpression, isIdentifier, isImportDeclaration, isNamespaceImport, isNoSubstitutionTemplateLiteral, isObjectBindingPattern, isParenthesizedExpression, isPropertyAccessExpression, isQualifiedName, isStringLiteral, isVariableDeclaration, isVariableDeclarationList, isVoidExpression, NodeFlags, SyntaxKind, } from "@typescript/native/ast";
8
+ import { isArrowFunction, isAwaitExpression, isCallExpression, isClassStaticBlockDeclaration, isElementAccessExpression, isExpressionStatement, isFunctionExpression, isFunctionLikeDeclaration, isIdentifier, isImportDeclaration, isModuleBlock, isNamespaceImport, isNoSubstitutionTemplateLiteral, isObjectBindingPattern, isParenthesizedExpression, isPropertyAccessExpression, isQualifiedName, isSourceFile, isStringLiteral, isVariableDeclaration, isVariableDeclarationList, isVariableStatement, isVoidExpression, NodeFlags, SyntaxKind, } from "@typescript/native/ast";
9
+ import { spellsNoBinding } from "./spelled-names.js";
9
10
  function isLiteralText(node) {
10
11
  return isStringLiteral(node) || isNoSubstitutionTemplateLiteral(node);
11
12
  }
@@ -25,21 +26,30 @@ function isLiteralImport(node) {
25
26
  const [argument] = node.arguments;
26
27
  return argument !== undefined && isLiteralText(argument);
27
28
  }
28
- /** The node a `const` binding's name is visible below: the block or file its statement is in. */
29
- function scopeOf(declaration) {
30
- const statement = declaration.parent.parent;
31
- return statement.parent;
29
+ /**
30
+ * The node the binding one declaration list declares is visible below: for `let` and
31
+ * `const`, the loop whose head declares it, or else the block or file its statement is
32
+ * in; for `var`, the function, namespace body, static block or file that holds it.
33
+ */
34
+ function scopeOf(list) {
35
+ if ((list.flags & (NodeFlags.Let | NodeFlags.Const | NodeFlags.Using)) !== 0) {
36
+ return isVariableStatement(list.parent) ? list.parent.parent : list.parent;
37
+ }
38
+ let at = list.parent;
39
+ while (!isSourceFile(at) &&
40
+ !isFunctionLikeDeclaration(at) &&
41
+ !isModuleBlock(at) &&
42
+ !isClassStaticBlockDeclaration(at)) {
43
+ at = at.parent;
44
+ }
45
+ return at;
32
46
  }
33
- /** Every identifier below `scope` that spells `name`, other than `declared` and a member's name. */
47
+ /** Every identifier below `scope` that spells `name` as a reference, other than `declared`. */
34
48
  function occurrences(scope, name, declared) {
35
49
  const found = [];
36
50
  const visit = (node) => {
37
51
  if (isIdentifier(node)) {
38
- const parent = node.parent;
39
- const member = (isPropertyAccessExpression(parent) && parent.name === node) ||
40
- (isQualifiedName(parent) && parent.right === node) ||
41
- (isBindingElement(parent) && parent.propertyName === node);
42
- if (node !== declared && node.text === name && !member) {
52
+ if (node !== declared && node.text === name && !spellsNoBinding(node)) {
43
53
  found.push(node);
44
54
  }
45
55
  return;
@@ -52,8 +62,8 @@ function occurrences(scope, name, declared) {
52
62
  /**
53
63
  * Every use of the namespace objects one file holds, keyed by the node the use is written
54
64
  * at, that reads more than a name the checker resolves: an indexed read by a literal, and
55
- * any use that may read every export. A shadowing declaration of a binding's name inside
56
- * its scope is read as the binding, which can only keep an export live.
65
+ * any use that may read every export. A use of a shadowing declaration of a binding's
66
+ * name inside its scope is read as a use of the binding, which can only keep an export live.
57
67
  */
58
68
  export function namespaceUses(file) {
59
69
  const uses = new Map();
@@ -92,11 +102,11 @@ export function namespaceUses(file) {
92
102
  }
93
103
  return;
94
104
  }
105
+ // A binding that is not `const` is followed too: each assignment to it is an
106
+ // occurrence `value` cannot narrow, so a reassigned binding still reads every export.
95
107
  const list = parent.parent;
96
- if (isIdentifier(pattern) &&
97
- isVariableDeclarationList(list) &&
98
- (list.flags & NodeFlags.Const) !== 0) {
99
- binding(pattern, scopeOf(parent), specifier, seen);
108
+ if (isIdentifier(pattern) && isVariableDeclarationList(list)) {
109
+ binding(pattern, scopeOf(list), specifier, seen);
100
110
  return;
101
111
  }
102
112
  }
@@ -12,6 +12,10 @@ import { nodeKey } from "./inventory.js";
12
12
  import { SymbolFlags } from "@typescript/native/sync";
13
13
  import { UNANSWERED } from "./query.js";
14
14
  import { nameComponent } from "./ref.js";
15
+ const CLASS_DECLARATIONS = new Set([
16
+ SyntaxKind.ClassDeclaration,
17
+ SyntaxKind.ClassExpression,
18
+ ]);
15
19
  /** The member kinds a subclass overrides. */
16
20
  const OVERRIDABLE = new Set(["method", "class-member"]);
17
21
  /** The node naming the class one `extends` entry names. */
@@ -82,8 +86,9 @@ export function overrideReferences(project, held, chains, testFiles) {
82
86
  if (inTarget.length === 0 &&
83
87
  declared !== UNANSWERED &&
84
88
  (declared.flags & SymbolFlags.Class) !== 0 &&
85
- declared.declarations.length > 0 &&
86
- declared.declarations.every((handle) => !own.has(handle.path))) {
89
+ // The target's augmentation of an outside class merges an interface into it,
90
+ // so only a class declaration says where the class is.
91
+ declared.declarations.some((handle) => CLASS_DECLARATIONS.has(handle.kind) && !own.has(handle.path))) {
87
92
  outsideBases.set(id, declared);
88
93
  }
89
94
  }
package/dist/src/paths.js CHANGED
@@ -61,6 +61,15 @@ export function dirnamePath(path) {
61
61
  }
62
62
  return cut === 0 ? SEPARATOR : normalized.slice(0, cut);
63
63
  }
64
+ /**
65
+ * The extension of one path's last segment, from its last full stop, or the empty string
66
+ * where it has none. A segment that only starts with a full stop has none.
67
+ */
68
+ export function extensionOf(path) {
69
+ const name = path.slice(path.lastIndexOf(SEPARATOR) + 1);
70
+ const dot = name.lastIndexOf(".");
71
+ return dot <= 0 ? "" : name.slice(dot);
72
+ }
64
73
  /** One path resolved against a directory, absolute where that directory is. */
65
74
  export function resolvePath(against, path) {
66
75
  return isAbsolutePath(path) ? normalizePath(path) : joinPath(against, path);
@@ -661,19 +661,29 @@ export function references(project, held, targetRoot, options) {
661
661
  relativePath(targetRoot, file.fileName) === undefined);
662
662
  const ownPaths = project.ownPaths();
663
663
  const fileIds = new Set(held.symbols.filter((symbol) => symbol.kind === "file").map((symbol) => symbol.id));
664
- let topLevel;
665
- /** The declarations one file's top level holds. */
666
- const declaredIn = (fileId) => {
667
- if (topLevel === undefined) {
668
- const byParent = new Map();
664
+ let moduleBodies;
665
+ /**
666
+ * The declarations one module holds directly: a file's top level, or the body of a
667
+ * `declare module` whose name is a string. Any other declaration holds none here.
668
+ */
669
+ const declaredIn = (moduleId) => {
670
+ if (moduleBodies === undefined) {
671
+ const byModule = new Map();
669
672
  for (const symbol of held.symbols) {
670
- if (fileIds.has(symbol.parent)) {
671
- byParent.set(symbol.parent, [...(byParent.get(symbol.parent) ?? []), symbol]);
673
+ if (!fileIds.has(symbol.parent) && !held.modules.has(symbol.parent)) {
674
+ continue;
675
+ }
676
+ const siblings = byModule.get(symbol.parent);
677
+ if (siblings === undefined) {
678
+ byModule.set(symbol.parent, [symbol]);
679
+ }
680
+ else {
681
+ siblings.push(symbol);
672
682
  }
673
683
  }
674
- topLevel = byParent;
684
+ moduleBodies = byModule;
675
685
  }
676
- return topLevel.get(fileId) ?? [];
686
+ return moduleBodies.get(moduleId) ?? [];
677
687
  };
678
688
  const exportsCache = new Map();
679
689
  /**
@@ -975,8 +985,11 @@ export function references(project, held, targetRoot, options) {
975
985
  }
976
986
  for (const url of urls) {
977
987
  const constructor = url.constructor === undefined ? undefined : answered.get(url.constructor);
988
+ // An augmentation of the global `URL` merges into it, which keeps the
989
+ // library's declaration; a `URL` the target declares has no other.
978
990
  if (typeof constructor === "object" &&
979
- constructor.declarations.some((handle) => ownPaths.has(handle.path))) {
991
+ constructor.declarations.length > 0 &&
992
+ constructor.declarations.every((handle) => ownPaths.has(handle.path))) {
980
993
  continue;
981
994
  }
982
995
  const loaded = specifierCandidates(dirnamePath(file.fileName), url.specifier)
@@ -1133,14 +1146,14 @@ export function references(project, held, targetRoot, options) {
1133
1146
  }
1134
1147
  let typedLookups = 0;
1135
1148
  if (consumer === undefined) {
1136
- found.push(...overrideReferences(project, held, chains, new Set(tests)));
1137
1149
  const before = options.requestCount?.() ?? 0;
1150
+ found.push(...overrideReferences(project, held, chains, new Set(tests)));
1138
1151
  found.push(...indexedAccessReferences(project, held, chains, root, new Set(tests)));
1139
1152
  found.push(...elementAccessReferences(project, held, chains, root, new Set(tests)));
1140
1153
  found.push(...keyofComparisonReferences(project, held, chains, root, new Set(tests)));
1141
1154
  found.push(...unimplementedCalleeReads(project, held, chains, root, new Set(tests)));
1142
- typedLookups = (options.requestCount?.() ?? 0) - before;
1143
1155
  found.push(...moduleDeclarationReferences(project, held, root, new Set(tests)));
1156
+ typedLookups = (options.requestCount?.() ?? 0) - before;
1144
1157
  }
1145
1158
  const scripts = new Set(walked.filter((one) => one.file.externalModuleIndicator === undefined).map((one) => one.fileId));
1146
1159
  found.push(...augmentationReferences(held, found, scripts));
@@ -90,8 +90,9 @@ export function reflectiveLookup(input) {
90
90
  if (lookups.length === 0) {
91
91
  return [];
92
92
  }
93
- // Whether each written `Reflect` is the global one, asked in one batch: a
94
- // declaration of that name in one of the project's own files shadows it.
93
+ // Whether each written `Reflect` is the global one, asked in one batch: a name
94
+ // declared only in the project's own files shadows it, and an augmentation of the
95
+ // global merges into it, which keeps the library's declaration.
95
96
  const ownFiles = project.ownPaths();
96
97
  const reflects = lookups.flatMap((lookup) => lookup.reflect === undefined ? [] : [lookup.reflect]);
97
98
  const global = new Set();
@@ -99,10 +100,11 @@ export function reflectiveLookup(input) {
99
100
  project.symbolsAt(reflects.map((node) => project.handle(node))).forEach((symbol, index) => {
100
101
  const node = reflects[index];
101
102
  // A name the checker did not answer for is read as the global, which retains.
102
- const declaredHere = symbol === UNANSWERED
103
- ? false
104
- : symbol?.declarations.some((declaration) => ownFiles.has(declaration.path));
105
- if (node !== undefined && declaredHere === false) {
103
+ const isGlobal = symbol === UNANSWERED ||
104
+ (symbol !== undefined &&
105
+ (symbol.declarations.length === 0 ||
106
+ symbol.declarations.some((declaration) => !ownFiles.has(declaration.path))));
107
+ if (node !== undefined && isGlobal) {
106
108
  global.add(node);
107
109
  }
108
110
  });