@cplieger/deadset-ts 7.2.0-dev.2 → 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 +5 -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 +2 -2
  35. package/src/analysis.ts +32 -6
  36. package/src/component-files.ts +116 -31
  37. package/src/configuration-files.ts +15 -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
@@ -6,6 +6,7 @@
6
6
  * https://github.com/microsoft/TypeScript/blob/f9f8d01292562242b6e7c7142e46ed7e470926f9/tsc/internal/contentmapper/hostimpl.go
7
7
  */
8
8
  import { componentModule } from "../src/component-files.js";
9
+ import { extensionOf } from "../src/paths.js";
9
10
  /** The JSON-RPC code of a method this mapper does not serve. */
10
11
  const METHOD_NOT_FOUND = -32601;
11
12
  /** The JSON-RPC code of a request whose parameters are not the protocol's. */
@@ -42,13 +43,15 @@ function answer(message) {
42
43
  case "closeProject":
43
44
  return reply(null);
44
45
  case "transform": {
45
- const content = typeof message.params === "object" && message.params !== null
46
- ? message.params.content
47
- : undefined;
46
+ const params = typeof message.params === "object" && message.params !== null ? message.params : {};
47
+ const { content, fileName } = params;
48
48
  if (typeof content !== "string") {
49
49
  return fail(INVALID_PARAMS, "transform: params.content is not a string");
50
50
  }
51
- const module = componentModule(content);
51
+ if (typeof fileName !== "string") {
52
+ return fail(INVALID_PARAMS, "transform: params.fileName is not a string");
53
+ }
54
+ const module = componentModule(content, extensionOf(fileName));
52
55
  return reply({ text: module.text, extension: module.extension, mappings: module.mappings });
53
56
  }
54
57
  default:
@@ -32,7 +32,7 @@ import { intraFunctionFacts, projectParts } from "./intra-function-parts.js";
32
32
  import { inventory } from "./inventory.js";
33
33
  import { packageEntries, readManifest } from "./manifest.js";
34
34
  import { liveFilesAt, matrixOf, referenceKey, sweepMatrix, } from "./matrix.js";
35
- import { dirnamePath, joinPath, normalizePath, relativePath, resolvePath } from "./paths.js";
35
+ import { dirnamePath, extensionOf, joinPath, normalizePath, relativePath, resolvePath, } from "./paths.js";
36
36
  import { byPosition, isComponentFile } from "./position.js";
37
37
  import { LANGUAGE } from "./report.js";
38
38
  import { SetupError, setupLine } from "./setup-failure.js";
@@ -175,6 +175,7 @@ function readProjects(engine, host, scope, config, stage) {
175
175
  const failures = [];
176
176
  const unbuildable = [];
177
177
  const setupDropped = [];
178
+ const librariesUnfound = [];
178
179
  const consumed = new Map();
179
180
  const heldFiles = new Set();
180
181
  let outsideTests = [];
@@ -227,10 +228,6 @@ function readProjects(engine, host, scope, config, stage) {
227
228
  : resolvePath(host.workingDirectory(), consumer.path),
228
229
  paths: pathsOf(engine, project.configFile),
229
230
  });
230
- if (reading.failing.length > 0) {
231
- failures.push(...reading.failing);
232
- return undefined;
233
- }
234
231
  if (consumer !== undefined && reading.missing.length > 0) {
235
232
  unbuildable.push(...reading.missing.map((one) => ({
236
233
  setupClass: "missing-consumer",
@@ -258,6 +255,10 @@ function readProjects(engine, host, scope, config, stage) {
258
255
  }
259
256
  return undefined;
260
257
  }
258
+ librariesUnfound.push(...raw.unfound.map((one) => ({
259
+ configFile: project.configFile,
260
+ error: renderDiagnostic(one, (path) => relativePath(absoluteRoot, path) ?? path),
261
+ })));
261
262
  const configuration = ids.get(project.configFile) ?? project.configFile;
262
263
  const read = () => {
263
264
  const held = inventory(project, host, targetRoot);
@@ -326,6 +327,7 @@ function readProjects(engine, host, scope, config, stage) {
326
327
  notBuilt,
327
328
  unanswered: session.unanswered,
328
329
  componentsUnread: workspace?.componentsUnread ?? [],
330
+ librariesUnfound,
329
331
  conventionsApplied: conventions.applied,
330
332
  conventionUses: conventions.uses,
331
333
  memberDirs,
@@ -439,7 +441,7 @@ function readRun(engine, host, scope, config, mode, detectors, extra) {
439
441
  testFiles: resolved.testFilePaths,
440
442
  evidence,
441
443
  componentFiles: components.map(pathOf),
442
- componentWarnings: components.flatMap((file) => readComponent(file.originalText).warnings.map((warning) => ({
444
+ componentWarnings: components.flatMap((file) => readComponent(file.originalText, extensionOf(file.fileName)).warnings.map((warning) => ({
443
445
  path: pathOf(file),
444
446
  line: lineOf(file.originalText, warning.offset),
445
447
  reason: warning.reason,
@@ -528,6 +530,7 @@ function readRun(engine, host, scope, config, mode, detectors, extra) {
528
530
  notBuilt: read.notBuilt,
529
531
  unanswered: read.unanswered,
530
532
  componentsUnread: read.componentsUnread,
533
+ librariesUnfound: read.librariesUnfound,
531
534
  conventionsApplied: read.conventionsApplied,
532
535
  conventionUses: read.conventionUses,
533
536
  memberDirs: read.memberDirs,
@@ -796,6 +799,7 @@ function readAnalysis(engine, host, scope, config, provenance, mode, detectors)
796
799
  notBuilt: read.notBuilt,
797
800
  unanswered: read.unanswered,
798
801
  componentsUnread: read.componentsUnread,
802
+ librariesUnfound: read.librariesUnfound,
799
803
  conventionsApplied: read.conventionsApplied,
800
804
  },
801
805
  configured: read.projects.map((one) => one.configured),
@@ -109,17 +109,17 @@ function scriptTagAt(text, offset) {
109
109
  /^<script[\s/>]/iu.test(text.slice(offset, offset + 8)));
110
110
  }
111
111
  /**
112
- * The offset past the `}` matching the `{` at `from`, quoted strings skipped, or -1 where
113
- * none matches before the end or before a `<script` start tag that begins a line, so a
114
- * stray brace in markup never hides a block.
112
+ * The braces opening at `from`, quoted strings skipped. The scan stops unclosed at the
113
+ * end, or before a `<script` start tag that begins a line, so a stray brace in markup
114
+ * never hides a block.
115
115
  */
116
- function pastBraces(text, from) {
116
+ function scanBraces(text, from) {
117
117
  let depth = 0;
118
118
  let at = from;
119
119
  while (at < text.length) {
120
120
  const code = text.charCodeAt(at);
121
121
  if (scriptTagAt(text, at)) {
122
- return -1;
122
+ return { end: at, closed: false };
123
123
  }
124
124
  if (code === 0x22 || code === 0x27 || code === 0x60) {
125
125
  at = pastQuoted(text, at);
@@ -131,24 +131,84 @@ function pastBraces(text, from) {
131
131
  else if (code === 0x7d) {
132
132
  depth -= 1;
133
133
  if (depth === 0) {
134
- return at + 1;
134
+ return { end: at + 1, closed: true };
135
135
  }
136
136
  }
137
137
  at += 1;
138
138
  }
139
- return -1;
139
+ return { end: text.length, closed: false };
140
+ }
141
+ /** The offset past the `}` matching the `{` at `from`, or -1 where {@link scanBraces} finds none. */
142
+ function pastBraces(text, from) {
143
+ const braces = scanBraces(text, from);
144
+ return braces.closed ? braces.end : -1;
145
+ }
146
+ /**
147
+ * The dialect each framework's parser reads: Vue's tokenizer, Svelte's parser and Astro's
148
+ * tokenizer, the last for any other extension too. `.vue` also closes `--!>`, which Vue
149
+ * does not, so a block Vue drops there is still read.
150
+ * https://github.com/vuejs/core/blob/main/packages/compiler-core/src/tokenizer.ts
151
+ * https://github.com/sveltejs/svelte/blob/main/packages/svelte/src/compiler/phases/1-parse/state/element.js
152
+ * https://github.com/withastro/compiler/blob/main/internal/token.go
153
+ */
154
+ function dialectOf(extension) {
155
+ switch (extension.toLowerCase()) {
156
+ case ".svelte":
157
+ return { shortComments: false, braceIsText: false };
158
+ case ".vue":
159
+ return { shortComments: true, braceIsText: true };
160
+ default:
161
+ return { shortComments: true, braceIsText: false };
162
+ }
163
+ }
164
+ /** The offset past the comment whose `<!--` is at `lt`, or the text's length where none ends it. */
165
+ function pastComment(text, lt, dialect) {
166
+ if (!dialect.shortComments) {
167
+ const close = text.indexOf("-->", lt + 4);
168
+ return close < 0 ? text.length : close + 3;
169
+ }
170
+ const dashes = text.indexOf("-->", lt + 2);
171
+ const bang = text.indexOf("--!>", lt + 2);
172
+ if (bang >= 0 && (dashes < 0 || bang < dashes)) {
173
+ return bang + 4;
174
+ }
175
+ return dashes < 0 ? text.length : dashes + 3;
176
+ }
177
+ /**
178
+ * Where an unmatched `{` of a start tag ends its braced value: at the first `>`, or where
179
+ * the brace scan stopped where that comes first.
180
+ */
181
+ function unmatchedBraceEnd(text, from, braces) {
182
+ const close = text.indexOf(">", from);
183
+ return close >= 0 && close < braces.end ? close : braces.end;
140
184
  }
141
- /** The start tag whose `<` is at `from`, to its `>`. A quoted or braced value may hold `>`. */
142
- function readStartTag(text, from, nameStop) {
185
+ /**
186
+ * The start tag whose `<` is at `from`, to its `>`. A quoted or braced value may hold `>`.
187
+ * Past an unmatched `{`, the tag also ends before a `<script` start tag that begins a line.
188
+ */
189
+ function readStartTag(text, from, nameStop, dialect) {
143
190
  const name = text.slice(from + 1, nameStop).toLowerCase();
144
191
  const attributes = [];
145
192
  let at = nameStop;
146
193
  let selfClosing = false;
194
+ let unmatched = false;
195
+ /** The braces opening at `at`, and whether they make a braced value rather than text. */
196
+ const bracesAt = () => {
197
+ if (text.charCodeAt(at) !== 0x7b) {
198
+ return undefined;
199
+ }
200
+ const braces = scanBraces(text, at);
201
+ return { braces, value: braces.closed || !dialect.braceIsText };
202
+ };
203
+ const bracedEnd = (braces) => braces.closed ? braces.end : unmatchedBraceEnd(text, at, braces);
147
204
  while (at < text.length) {
148
205
  const code = text.charCodeAt(at);
149
206
  if (code === 0x3e) {
150
207
  return { name, end: at + 1, selfClosing, attributes };
151
208
  }
209
+ if (unmatched && scriptTagAt(text, at)) {
210
+ return { name, end: at, selfClosing, attributes };
211
+ }
152
212
  if (isSpace(code)) {
153
213
  at += 1;
154
214
  continue;
@@ -160,12 +220,13 @@ function readStartTag(text, from, nameStop) {
160
220
  }
161
221
  selfClosing = false;
162
222
  const start = at;
163
- if (code === 0x7b) {
164
- const past = pastBraces(text, at);
165
- at = past < 0 ? text.length : past;
223
+ const bare = bracesAt();
224
+ unmatched ||= bare?.braces.closed === false;
225
+ if (bare?.value === true) {
226
+ at = bracedEnd(bare.braces);
166
227
  attributes.push({
167
228
  name: "",
168
- value: text.slice(start + 1, at - 1),
229
+ value: text.slice(start + 1, bare.braces.closed ? at - 1 : at),
169
230
  start,
170
231
  end: at,
171
232
  valueStart: start + 1,
@@ -191,17 +252,19 @@ function readStartTag(text, from, nameStop) {
191
252
  at += 1;
192
253
  }
193
254
  const opening = text.charCodeAt(at);
255
+ const braced = bracesAt();
256
+ unmatched ||= braced?.braces.closed === false;
194
257
  if (opening === 0x22 || opening === 0x27) {
195
- const past = pastQuoted(text, at);
258
+ // An attribute value has no escapes: a backslash before its quote is text.
259
+ const close = text.indexOf(text.charAt(at), at + 1);
196
260
  valueStart = at + 1;
197
- value = text.slice(at + 1, Math.max(past - 1, at + 1));
198
- at = past;
261
+ value = text.slice(at + 1, close < 0 ? text.length : close);
262
+ at = close < 0 ? text.length : close + 1;
199
263
  }
200
- else if (opening === 0x7b) {
201
- const past = pastBraces(text, at);
202
- const stop = past < 0 ? text.length : past;
264
+ else if (braced?.value === true) {
265
+ const stop = bracedEnd(braced.braces);
203
266
  valueStart = at + 1;
204
- value = text.slice(at + 1, Math.max(stop - 1, at + 1));
267
+ value = text.slice(at + 1, braced.braces.closed ? stop - 1 : stop);
205
268
  at = stop;
206
269
  }
207
270
  else {
@@ -305,9 +368,11 @@ function scriptsInside(text, from, to) {
305
368
  * The scan keeps a stack of open elements. A comment, a doctype or processing instruction and
306
369
  * an expression in braces are text, and an end tag closes back to the nearest open element of
307
370
  * its name. A raw-text element at the top level runs to its own end tag, and only a `script`
308
- * element whose start tag begins a line at the top level is a block.
371
+ * element whose start tag begins a line at the top level is a block. The file's extension
372
+ * names the framework whose reading of a comment and of a stray brace applies.
309
373
  */
310
- export function readComponent(text) {
374
+ export function readComponent(text, extension) {
375
+ const dialect = dialectOf(extension);
311
376
  const blocks = [];
312
377
  const imports = [];
313
378
  const warnings = [];
@@ -337,8 +402,7 @@ export function readComponent(text) {
337
402
  }
338
403
  const lt = at;
339
404
  if (text.startsWith("<!--", lt)) {
340
- const close = text.indexOf("-->", lt + 4);
341
- at = close < 0 ? text.length : close + 3;
405
+ at = pastComment(text, lt, dialect);
342
406
  continue;
343
407
  }
344
408
  const next = text.charCodeAt(lt + 1);
@@ -362,7 +426,7 @@ export function readComponent(text) {
362
426
  at = lt + 1;
363
427
  continue;
364
428
  }
365
- const tag = readStartTag(text, lt, nameStop);
429
+ const tag = readStartTag(text, lt, nameStop, dialect);
366
430
  const rawText = open.length === 0 && RAW_TEXT.has(tag.name);
367
431
  for (const attribute of rawText ? [] : tag.attributes) {
368
432
  if (attribute.value !== "") {
@@ -435,9 +499,9 @@ export function readComponent(text) {
435
499
  }
436
500
  }
437
501
  /**
438
- * Whether the module's text exports a default at its top level: an `export default`
439
- * or an export clause naming `default`, outside every comment, string, template and
440
- * regular expression, and outside every bracket.
502
+ * Whether the module's text exports a default at its top level: an `export default`, an
503
+ * `export * as default`, or an export clause naming `default`, outside every comment,
504
+ * string, template and regular expression, and outside every bracket.
441
505
  */
442
506
  export function exportsDefault(text) {
443
507
  const words = [];
@@ -525,7 +589,8 @@ export function exportsDefault(text) {
525
589
  const matched = word.exec(text);
526
590
  if (matched !== null) {
527
591
  const name = matched[0];
528
- if (depth === 0 && words.at(-1) === "export" && name === "default") {
592
+ const starAs = words.at(-3) === "export" && words.at(-2) === "*" && words.at(-1) === "as";
593
+ if (depth === 0 && (words.at(-1) === "export" || starAs) && name === "default") {
529
594
  return true;
530
595
  }
531
596
  if (clause && name === "default") {
@@ -554,8 +619,8 @@ export function exportsDefault(text) {
554
619
  return false;
555
620
  }
556
621
  /** The module one component file's text is read as, and the span map back to the file. */
557
- export function componentModule(text) {
558
- const { blocks, imports } = readComponent(text);
622
+ export function componentModule(text, fileExtension) {
623
+ const { blocks, imports } = readComponent(text, fileExtension);
559
624
  const out = [];
560
625
  let at = 0;
561
626
  let language;
@@ -76,6 +76,7 @@ const CONFIG_FLAGS = new Set(["--config", "-c"]);
76
76
  /** The extensions of a module a configuration flag may name. */
77
77
  const MODULE_EXTENSIONS = [".js", ".mjs", ".cjs", ".ts", ".mts", ".cts"];
78
78
  /** The characters that end a word outside quotes and separate nothing: blanks. */
79
+ // Stryker disable next-line Regex: its \v to \V mutant is no valid escape under the u flag, so the whole module fails to parse
79
80
  const BLANK = /[ \t\r\f\v]/u;
80
81
  /** The characters that end a word outside quotes and begin an operator: newline included. */
81
82
  const OPERATOR = /[\n;&|()<>]/u;
@@ -269,8 +270,9 @@ export function configurationFiles(host, targetRoot, files, parse) {
269
270
  const dirs = new Set([root]);
270
271
  for (const file of files.byPath.values()) {
271
272
  const form = configurationModuleForm(basename(file.fileName));
272
- if (form !== undefined && holdsManifest(dirnamePath(file.fileName))) {
273
- modules.push({ file, form });
273
+ const fileDir = dirnamePath(file.fileName);
274
+ if (form !== undefined && holdsManifest(fileDir)) {
275
+ modules.push({ file, form, manifestDir: fileDir });
274
276
  }
275
277
  for (let dir = dirnamePath(file.fileName); relativePath(root, dir) !== undefined && !dirs.has(dir); dir = dirnamePath(dir)) {
276
278
  dirs.add(dir);
@@ -300,7 +302,7 @@ export function configurationFiles(host, targetRoot, files, parse) {
300
302
  pending.push({ path, manifestDir: dir });
301
303
  }
302
304
  else if (held !== undefined) {
303
- modules.push({ file: held, form: "a script's configuration flag" });
305
+ modules.push({ file: held, form: "a script's configuration flag", manifestDir: dir });
304
306
  }
305
307
  else {
306
308
  named.push({ path, manifestDir: dir });
@@ -7,6 +7,7 @@
7
7
  import { isCallExpression, isExportDeclaration, isExternalModuleReference, isImportDeclaration, isImportEqualsDeclaration, isNoSubstitutionTemplateLiteral, isStringLiteral, SyntaxKind, } from "@typescript/native/ast";
8
8
  import { SymbolFlags } from "@typescript/native/sync";
9
9
  import { resolvedTargets } from "./calls.js";
10
+ import { declaredComponent } from "./inventory.js";
10
11
  import { must, Unanswerable } from "./query.js";
11
12
  import { nameComponent } from "./ref.js";
12
13
  /** The meanings a name of the global scope is looked up under: any of them. */
@@ -80,9 +81,30 @@ function candidatesOf(project, container, isStatic) {
80
81
  ...exported.filter((member) => !isClass || !isClassMember(member)),
81
82
  ];
82
83
  }
83
- /** The member of one container a path component spells, followed through any alias. */
84
+ /**
85
+ * The properties of the type one container declares, or of its constructor's type for a
86
+ * static member. A member a symbol keys is bound to that type later than the container's
87
+ * symbol table is built, so the table does not hold it.
88
+ */
89
+ function typeMembersOf(project, container, isStatic) {
90
+ const queries = project.queries;
91
+ const type = must(isStatic ? queries.typeOfSymbol(container) : queries.declaredTypeOf(container));
92
+ return type === undefined ? [] : must(queries.propertiesOf(type));
93
+ }
94
+ /**
95
+ * The member of one container a path component spells, followed through any alias. A
96
+ * component in brackets is a computed key, compared with the key each declaration of a
97
+ * candidate writes: the checker names a member a symbol keys by no spelling of its own.
98
+ */
84
99
  function memberOf(project, container, component, isStatic) {
85
- const member = candidatesOf(project, container, isStatic).find((candidate) => nameComponent(candidate.name) === component);
100
+ const computed = component.startsWith("[");
101
+ const candidates = candidatesOf(project, container, isStatic);
102
+ const member = (computed ? [...candidates, ...typeMembersOf(project, container, isStatic)] : candidates).find((candidate) => nameComponent(candidate.name) === component ||
103
+ (computed &&
104
+ candidate.declarations.some((handle) => {
105
+ const node = project.declarationAt(handle)?.node;
106
+ return node !== undefined && declaredComponent(node) === component;
107
+ })));
86
108
  return member === undefined ? undefined : unaliased(project, member);
87
109
  }
88
110
  /** The declaration a path names from one starting symbol, component by component. */
@@ -4,7 +4,7 @@ export const RECORD = {
4
4
  conformance: {
5
5
  corpusVersion: "5.2.0",
6
6
  result: "pass",
7
- digest: "sha256:4050d1b67fb757bc12a60f8ece7317a3e8485d3383ec52919cd24b9dfd5509dc",
7
+ digest: "sha256:012872542679539ec105f49b5ee502b0a9fa35c6bbd9ef3983b5f6e53af83d8e",
8
8
  },
9
9
  gaps: [],
10
10
  };
@@ -196,12 +196,13 @@ export function contextualUses(project, held, chains, literals, flows, cap, prop
196
196
  };
197
197
  const calls = new Map();
198
198
  /**
199
- * The parameter and return types of a type's one call signature, or of its one construct
200
- * signature where it has no call signature, read once per type.
199
+ * The parameter and return types of each of a type's call signatures, or of each of its
200
+ * construct signatures where it has no call signature, read once per type.
201
201
  */
202
- const callOf = (type) => {
203
- if (calls.has(type.id)) {
204
- return calls.get(type.id);
202
+ const callsOf = (type) => {
203
+ const known = calls.get(type.id);
204
+ if (known !== undefined) {
205
+ return known;
205
206
  }
206
207
  lookups += 1;
207
208
  let kind = SignatureKind.Call;
@@ -211,28 +212,26 @@ export function contextualUses(project, held, chains, literals, flows, cap, prop
211
212
  kind = SignatureKind.Construct;
212
213
  signatures = queries.signaturesOf(type, kind);
213
214
  }
214
- let called;
215
- const [signature, ...more] = isAnswered(signatures) ? signatures : [];
216
- if (signature !== undefined && more.length === 0) {
215
+ const called = (isAnswered(signatures) ? signatures : []).map((signature) => {
217
216
  const parameters = signature.parameters.map((_parameter, index) => queries.parameterType(signature, index));
218
217
  const result = queries.returnTypeOf(signature);
219
218
  lookups += parameters.length + 1;
220
- called = {
219
+ return {
221
220
  kind,
222
221
  parameters: parameters.map((one) => (isAnswered(one) ? one : undefined)),
223
222
  result: isAnswered(result) ? result : undefined,
224
223
  };
225
- }
224
+ });
226
225
  calls.set(type.id, called);
227
226
  return called;
228
227
  };
229
228
  /**
230
229
  * Each object type a value of `source` is checked as against the object types of
231
- * `target`: the value's own, and through array elements, a function's parameters,
232
- * checked the other way, and result, and the type arguments two instantiations share
233
- * by place, an array among them, as an array reaches `Iterable<T>`.
234
- * A source that is one of the target's own object types matches it as it stands. Each
235
- * pair is compared once, which ends the walk over a recursive type.
230
+ * `target`: the value's own, and through array elements, every pair of signatures
231
+ * (parameters checked the other way, then results), which reads more than the overload
232
+ * the checker picks, and the type arguments of two instantiations by place, as an array
233
+ * reaches `Iterable<T>`. A source that is one of the target's own object types matches
234
+ * it as it stands. Each pair is compared once, which ends the walk over a recursive type.
236
235
  */
237
236
  const compared = (source, target, into, seen) => {
238
237
  const key = `${String(source?.id)}>${String(target?.id)}`;
@@ -253,7 +252,7 @@ export function contextualUses(project, held, chains, literals, flows, cap, prop
253
252
  into.push({ source: one, targets, outside: true });
254
253
  }
255
254
  const element = elementOf(one);
256
- const called = element === undefined ? callOf(one) : undefined;
255
+ const called = element === undefined ? callsOf(one) : [];
257
256
  const sourceArguments = typeArgumentsOf(one);
258
257
  for (const part of targets) {
259
258
  const partElement = elementOf(part);
@@ -267,17 +266,17 @@ export function contextualUses(project, held, chains, literals, flows, cap, prop
267
266
  sourceArguments.slice(0, targetArguments.length).forEach((argument, index) => {
268
267
  compared(argument, targetArguments[index], into, seen);
269
268
  });
270
- if (element !== undefined) {
269
+ if (element !== undefined || called.length === 0) {
271
270
  continue;
272
271
  }
273
- const other = called === undefined ? undefined : callOf(part);
274
- if (called === undefined || other?.kind !== called.kind) {
275
- continue;
272
+ for (const other of callsOf(part)) {
273
+ for (const signature of called.filter((one) => one.kind === other.kind)) {
274
+ other.parameters.forEach((parameter, index) => {
275
+ compared(parameter, signature.parameters[index], into, seen);
276
+ });
277
+ compared(signature.result, other.result, into, seen);
278
+ }
276
279
  }
277
- other.parameters.forEach((parameter, index) => {
278
- compared(parameter, called.parameters[index], into, seen);
279
- });
280
- compared(called.result, other.result, into, seen);
281
280
  }
282
281
  }
283
282
  };
@@ -298,7 +297,7 @@ export function contextualUses(project, held, chains, literals, flows, cap, prop
298
297
  if (element !== undefined) {
299
298
  return mayCarry(element, seen);
300
299
  }
301
- return callOf(part) !== undefined || typeArgumentsOf(part).some((one) => mayCarry(one, seen));
300
+ return callsOf(part).length > 0 || typeArgumentsOf(part).some((one) => mayCarry(one, seen));
302
301
  });
303
302
  };
304
303
  const sourced = carried.flatMap((flow, index) => {
@@ -330,10 +329,11 @@ export function contextualUses(project, held, chains, literals, flows, cap, prop
330
329
  continue;
331
330
  }
332
331
  for (const [name, member] of sourceMembers) {
332
+ // A member the position shares with the source is no read of it. A member with no
333
+ // declaration, as a mapped type over literal keys has, is never one it shares.
333
334
  const read = tables.some((table) => {
334
335
  const declared = table.get(name);
335
336
  return (declared !== undefined &&
336
- declared.handles.size > 0 &&
337
337
  ![...declared.handles].some((handle) => member.handles.has(handle)));
338
338
  });
339
339
  if (read) {
@@ -244,8 +244,8 @@ export function projectNeeds(project, held, host, targetRoot, parse) {
244
244
  return names;
245
245
  };
246
246
  const configuration = configurationFiles(host, targetRoot, sourceFilesOf(project, targetRoot), parse);
247
- for (const { file } of configuration.modules) {
248
- const names = declaredAt(file.fileName);
247
+ for (const { file, manifestDir } of configuration.modules) {
248
+ const names = declaredAt(joinPath(manifestDir, MANIFEST));
249
249
  for (const { node, text } of moduleStrings(file)) {
250
250
  use(ownersOf(file, held, node), dependenciesNamed(text, names), file.fileName);
251
251
  }
@@ -6,8 +6,10 @@
6
6
  * it. A rest element names no property: what it copies is read where the copy is.
7
7
  */
8
8
  import { isArrayLiteralExpression, isBinaryExpression, isForOfStatement, isIdentifier, isNoSubstitutionTemplateLiteral, isNumericLiteral, isObjectBindingPattern, isObjectLiteralExpression, isParameterDeclaration, isParenthesizedExpression, isPropertyAssignment, isShorthandPropertyAssignment, isStringLiteral, SyntaxKind, } from "@typescript/native/ast";
9
- import { TypeFlags } from "@typescript/native/sync";
9
+ import { SignatureKind, TypeFlags, } from "@typescript/native/sync";
10
10
  import { isAnswered, propertyTables, UNANSWERED, } from "./query.js";
11
+ /** The types of an iteration result's `value` that carry no element of the iterable. */
12
+ const NO_ELEMENT = TypeFlags.AnyOrUnknown | TypeFlags.Undefined | TypeFlags.Void | TypeFlags.Null | TypeFlags.Never;
11
13
  /** The text a property name spells, or `undefined` for a computed name. */
12
14
  function nameText(node) {
13
15
  if (node === undefined) {
@@ -207,11 +209,84 @@ export function destructuring(project, cap, properties = propertyTables(project.
207
209
  const elements = queries.typeArguments(type);
208
210
  return isAnswered(elements) ? elements[0] : UNANSWERED;
209
211
  };
212
+ /** The type of the one property of a type that `matches` accepts, unanswered where two do. */
213
+ const propertyTypeOf = (owner, matches) => {
214
+ const table = tableOf(owner);
215
+ if (!isAnswered(table)) {
216
+ return UNANSWERED;
217
+ }
218
+ const [property, ...more] = [...table.values()].filter(matches);
219
+ if (property === undefined) {
220
+ return undefined;
221
+ }
222
+ if (more.length > 0) {
223
+ return UNANSWERED;
224
+ }
225
+ patternLookups += 1;
226
+ return queries.typeOfSymbol(property);
227
+ };
228
+ const resultOf = (callee) => {
229
+ if (callee === undefined || callee === UNANSWERED) {
230
+ return callee;
231
+ }
232
+ patternLookups += 1;
233
+ const signatures = queries.signaturesOf(callee, SignatureKind.Call);
234
+ if (!isAnswered(signatures)) {
235
+ return UNANSWERED;
236
+ }
237
+ const [signature] = signatures;
238
+ if (signature === undefined) {
239
+ return undefined;
240
+ }
241
+ patternLookups += 1;
242
+ return queries.returnTypeOf(signature);
243
+ };
244
+ const iterations = new Map();
245
+ /**
246
+ * The type a `for…of` takes from one iterable that is no array, read through the
247
+ * iteration protocol: the `value` of what the `next()` of its `[Symbol.iterator]()`
248
+ * answers. A constituent of that answer whose value is {@link NO_ELEMENT} carries no
249
+ * element; where more than one carries one, the element is left unanswered, so a read
250
+ * through it is resolved by name.
251
+ */
252
+ const iteratedOf = (type) => {
253
+ if (iterations.has(type.id)) {
254
+ return iterations.get(type.id);
255
+ }
256
+ // The checker keys a member a unique symbol names `__@<name>@<symbol id>`, and escapes
257
+ // a written key that starts with `__` with one more `_`, so no string key matches; a
258
+ // unique symbol of the program's own named `iterator` does, which leaves two matches:
259
+ // https://github.com/microsoft/typescript-go/blob/89d5d5b2849a0db0957065889ca58536fa6d2e4a/internal/checker/checker.go#L23096-L23108
260
+ // https://github.com/microsoft/typescript-go/blob/89d5d5b2849a0db0957065889ca58536fa6d2e4a/internal/ast/symbol.go#L91-L103
261
+ const iterator = resultOf(propertyTypeOf(type, (one) => one.escapedName.startsWith("__@iterator@")));
262
+ const result = iterator === undefined || iterator === UNANSWERED
263
+ ? iterator
264
+ : resultOf(propertyTypeOf(iterator, (one) => one.name === "next"));
265
+ let found = result;
266
+ if (result !== undefined && result !== UNANSWERED) {
267
+ let parts = [result];
268
+ if (result.isUnionType()) {
269
+ patternLookups += 1;
270
+ parts = queries.constituents(result);
271
+ }
272
+ const values = isAnswered(parts)
273
+ ? parts.map((part) => propertyTypeOf(part, (one) => one.name === "value"))
274
+ : [UNANSWERED];
275
+ const carried = values.filter((value) => value === UNANSWERED || (value !== undefined && (value.flags & NO_ELEMENT) === 0));
276
+ const [only, ...more] = carried;
277
+ found = more.length > 0 ? UNANSWERED : only;
278
+ }
279
+ iterations.set(type.id, found);
280
+ return found;
281
+ };
210
282
  const step = (type, next) => {
211
283
  const defined = definedOf(type);
212
284
  if (defined === undefined || defined === UNANSWERED) {
213
285
  return defined;
214
286
  }
287
+ if (next.kind === "iterated") {
288
+ return elementOf(defined, undefined) ?? iteratedOf(defined);
289
+ }
215
290
  if (next.kind === "property") {
216
291
  const table = tableOf(defined);
217
292
  if (!isAnswered(table)) {
@@ -224,7 +299,7 @@ export function destructuring(project, cap, properties = propertyTables(project.
224
299
  patternLookups += 1;
225
300
  return queries.typeOfSymbol(property);
226
301
  }
227
- return elementOf(defined, next.kind === "element" ? next.index : undefined);
302
+ return elementOf(defined, next.index);
228
303
  };
229
304
  const resolve = (reads) => {
230
305
  const patterns = [...new Set(reads.map((read) => read.pattern))];