@cplieger/deadset-ts 7.2.0-dev.3 → 8.0.0-dev.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (153) hide show
  1. package/dist/bin/mapper-connection.js +7 -4
  2. package/dist/src/analysis.js +116 -16
  3. package/dist/src/augmentations.js +21 -8
  4. package/dist/src/automatic-types.js +189 -0
  5. package/dist/src/base-url.js +18 -0
  6. package/dist/src/component-files.js +96 -31
  7. package/dist/src/components.js +7 -6
  8. package/dist/src/config.js +1 -0
  9. package/dist/src/configuration-files.js +54 -15
  10. package/dist/src/configured-declarations.js +24 -2
  11. package/dist/src/conformance-record.js +2 -2
  12. package/dist/src/contextual-uses.js +26 -26
  13. package/dist/src/convention-rows.js +75 -0
  14. package/dist/src/conventions.js +101 -5
  15. package/dist/src/decorator.js +21 -18
  16. package/dist/src/dependencies.js +185 -30
  17. package/dist/src/destructuring.js +77 -2
  18. package/dist/src/discover.js +188 -85
  19. package/dist/src/edges.js +1 -1
  20. package/dist/src/entry-point-gaps.js +7 -10
  21. package/dist/src/entry-points.js +112 -11
  22. package/dist/src/findings/completion.js +116 -21
  23. package/dist/src/findings/dependencies-and-module-machinery.js +71 -25
  24. package/dist/src/findings/emitters.js +1 -1
  25. package/dist/src/findings/intra-function.js +44 -13
  26. package/dist/src/findings/non-code-artifacts.js +66 -33
  27. package/dist/src/findings/unused-declarations.js +21 -1
  28. package/dist/src/findings/visibility-narrowing.js +22 -3
  29. package/dist/src/generated-file.js +11 -0
  30. package/dist/src/git-ignore.js +86 -0
  31. package/dist/src/graph.js +4 -1
  32. package/dist/src/import-bindings.js +97 -0
  33. package/dist/src/indexed-access.js +130 -28
  34. package/dist/src/interface-satisfaction.js +30 -12
  35. package/dist/src/intra-function-parts.js +313 -50
  36. package/dist/src/inventory.js +63 -8
  37. package/dist/src/json-document.js +5 -5
  38. package/dist/src/kinds.js +13 -13
  39. package/dist/src/matrix.js +18 -9
  40. package/dist/src/mdx-entries.js +197 -0
  41. package/dist/src/module-declarations.js +9 -13
  42. package/dist/src/namespace-objects.js +28 -18
  43. package/dist/src/overrides.js +45 -9
  44. package/dist/src/paths.js +9 -0
  45. package/dist/src/print.js +1 -0
  46. package/dist/src/ref.js +10 -0
  47. package/dist/src/references.js +29 -13
  48. package/dist/src/reflective-lookup.js +8 -6
  49. package/dist/src/report.js +4 -2
  50. package/dist/src/reporters/template.js +22 -13
  51. package/dist/src/resolve.js +2 -0
  52. package/dist/src/roots.js +110 -38
  53. package/dist/src/schema.js +1 -0
  54. package/dist/src/serialization-contract.js +97 -20
  55. package/dist/src/session.js +26 -1
  56. package/dist/src/source-files.js +39 -14
  57. package/dist/src/spelled-names.js +58 -0
  58. package/dist/src/suppress-file.js +12 -4
  59. package/dist/src/suppress-inline.js +21 -3
  60. package/dist/src/suppress.js +7 -5
  61. package/dist/src/sweep.js +27 -10
  62. package/dist/src/symbol-keys.js +103 -2
  63. package/dist/src/template-field.js +9 -6
  64. package/dist/src/test-support.js +121 -17
  65. package/dist/src/type-errors.js +116 -35
  66. package/dist/src/type-parameter-reads.js +172 -0
  67. package/dist/src/value-reach.js +17 -5
  68. package/dist/src/verbs/analyze.js +19 -1
  69. package/dist/src/verbs/explain.js +4 -2
  70. package/dist/src/version-range.js +10 -5
  71. package/dist/src/version.js +1 -1
  72. package/dist/src/workflow-steps.js +119 -22
  73. package/dist/src/workspace-programs.js +7 -3
  74. package/dist/src/workspace-resolution.js +72 -13
  75. package/dist/src/workspace.js +1 -1
  76. package/jsr.json +1 -1
  77. package/package.json +2 -2
  78. package/src/analysis.ts +175 -16
  79. package/src/augmentations.ts +21 -10
  80. package/src/automatic-types.ts +246 -0
  81. package/src/base-url.ts +24 -0
  82. package/src/component-files.ts +116 -31
  83. package/src/components.ts +7 -6
  84. package/src/config.ts +3 -0
  85. package/src/configuration-files.ts +80 -18
  86. package/src/configured-declarations.ts +33 -4
  87. package/src/conformance-record.ts +2 -2
  88. package/src/contextual-uses.ts +27 -27
  89. package/src/convention-rows.ts +103 -0
  90. package/src/conventions.ts +121 -7
  91. package/src/decorator.ts +28 -18
  92. package/src/dependencies.ts +249 -43
  93. package/src/destructuring.ts +93 -2
  94. package/src/discover.ts +234 -86
  95. package/src/edges.ts +1 -1
  96. package/src/entry-point-gaps.ts +8 -11
  97. package/src/entry-points.ts +133 -11
  98. package/src/finding.ts +10 -0
  99. package/src/findings/completion.ts +147 -24
  100. package/src/findings/dependencies-and-module-machinery.ts +90 -28
  101. package/src/findings/emitters.ts +1 -1
  102. package/src/findings/intra-function.ts +56 -14
  103. package/src/findings/non-code-artifacts.ts +81 -41
  104. package/src/findings/unused-declarations.ts +25 -1
  105. package/src/findings/visibility-narrowing.ts +32 -3
  106. package/src/generated-file.ts +16 -0
  107. package/src/git-ignore.ts +98 -0
  108. package/src/graph.ts +6 -0
  109. package/src/host.ts +5 -9
  110. package/src/import-bindings.ts +128 -0
  111. package/src/indexed-access.ts +152 -30
  112. package/src/interface-satisfaction.ts +33 -13
  113. package/src/intra-function-parts.ts +415 -64
  114. package/src/inventory.ts +83 -7
  115. package/src/json-document.ts +7 -7
  116. package/src/kinds.ts +13 -13
  117. package/src/matrix.ts +32 -14
  118. package/src/mdx-entries.ts +241 -0
  119. package/src/module-declarations.ts +11 -13
  120. package/src/namespace-objects.ts +34 -21
  121. package/src/overrides.ts +56 -9
  122. package/src/paths.ts +10 -0
  123. package/src/print.ts +1 -0
  124. package/src/ref.ts +12 -0
  125. package/src/references.ts +38 -17
  126. package/src/reflective-lookup.ts +9 -7
  127. package/src/report.ts +16 -3
  128. package/src/reporters/template.ts +21 -13
  129. package/src/resolve.ts +3 -0
  130. package/src/roots.ts +163 -45
  131. package/src/schema.ts +1 -0
  132. package/src/serialization-contract.ts +108 -17
  133. package/src/session.ts +29 -1
  134. package/src/source-files.ts +66 -16
  135. package/src/spelled-names.ts +102 -0
  136. package/src/suppress-file.ts +14 -3
  137. package/src/suppress-inline.ts +21 -2
  138. package/src/suppress.ts +9 -5
  139. package/src/sweep.ts +32 -11
  140. package/src/symbol-keys.ts +129 -2
  141. package/src/template-field.ts +9 -6
  142. package/src/test-support.ts +149 -16
  143. package/src/type-errors.ts +132 -41
  144. package/src/type-parameter-reads.ts +227 -0
  145. package/src/value-reach.ts +19 -6
  146. package/src/verbs/analyze.ts +28 -1
  147. package/src/verbs/explain.ts +5 -2
  148. package/src/version-range.ts +10 -5
  149. package/src/version.ts +1 -1
  150. package/src/workflow-steps.ts +127 -22
  151. package/src/workspace-programs.ts +7 -3
  152. package/src/workspace-resolution.ts +86 -14
  153. package/src/workspace.ts +1 -1
@@ -2,14 +2,15 @@
2
2
  * What the errors a project's own files carry do to the run. An import of a module the
3
3
  * project expects and nothing provides is a setup failure. Any other error in an own file
4
4
  * is a type error: the function, method or file-level statement holding it is skipped,
5
- * which only ever withholds a finding. An error the compiler places in no file is read as
6
- * a project that cannot be analyzed.
5
+ * which only ever withholds a finding. `projectErrors` decides, before any of these, which
6
+ * errors the compiler places in no file leave the project unloaded.
7
7
  */
8
- import { isClassDeclaration, isNoSubstitutionTemplateLiteral, isPropertyAccessExpression, isStringLiteral, } from "@typescript/native/ast";
8
+ import { isClassDeclaration, isFunctionLikeDeclaration, isNoSubstitutionTemplateLiteral, isPropertyAccessExpression, isStringLiteral, } from "@typescript/native/ast";
9
9
  import { packageOfSpecifier } from "./dependencies.js";
10
10
  import { nodeKey } from "./inventory.js";
11
11
  import { dirnamePath, isAbsolutePath, joinPath, relativePath, resolvePath } from "./paths.js";
12
12
  import { isAnswered } from "./query.js";
13
+ import { findWorkspace } from "./workspace.js";
13
14
  /** The compiler's codes for an import whose module it cannot find. */
14
15
  const MODULE_NOT_FOUND = new Set([2307, 2792]);
15
16
  const MANIFEST = "package.json";
@@ -23,7 +24,7 @@ const MANIFEST_SECTIONS = [
23
24
  export function insideSkipped(skipped, position) {
24
25
  return skipped.some((unit) => unit.path === position.path && unit.fromLine <= position.line && position.line <= unit.toLine);
25
26
  }
26
- /** The string literal holding one offset, innermost first. */
27
+ /** The string literal holding one offset, innermost first, a doc comment's included. */
27
28
  function literalAt(file, offset) {
28
29
  let found;
29
30
  const visit = (node) => {
@@ -33,32 +34,55 @@ function literalAt(file, offset) {
33
34
  if (isStringLiteral(node) || isNoSubstitutionTemplateLiteral(node)) {
34
35
  found = node.text;
35
36
  }
37
+ // `forEachChild` does not enter a doc comment, where a JavaScript file imports types.
38
+ for (const doc of node.jsDoc ?? []) {
39
+ visit(doc);
40
+ }
36
41
  node.forEachChild(visit);
37
42
  };
38
43
  file.forEachChild(visit);
39
44
  return found;
40
45
  }
41
- /** Whether one `paths` key matches one specifier: exactly, or around its one `*`. */
46
+ /**
47
+ * What one `paths` key's `*` matches in one specifier, `""` for a key with no `*` that
48
+ * is the specifier, or `undefined` where the key does not match it.
49
+ */
42
50
  function mappedBy(key, specifier) {
43
51
  const star = key.indexOf("*");
44
52
  if (star < 0) {
45
- return key === specifier;
53
+ return key === specifier ? "" : undefined;
46
54
  }
47
55
  const prefix = key.slice(0, star);
48
56
  const suffix = key.slice(star + 1);
49
- return (specifier.length >= prefix.length + suffix.length &&
57
+ return specifier.length >= prefix.length + suffix.length &&
50
58
  specifier.startsWith(prefix) &&
51
- specifier.endsWith(suffix));
59
+ specifier.endsWith(suffix)
60
+ ? specifier.slice(prefix.length, specifier.length - suffix.length)
61
+ : undefined;
52
62
  }
53
- /** Whether a relative import's path names something on disk, as written or with any extension. */
54
- function provided(host, path) {
55
- if (host.kindOf(path) !== "absent") {
63
+ /**
64
+ * One `paths` target, its first `*` replaced by `star` taken literally, as the compiler does:
65
+ * https://github.com/microsoft/typescript-go/blob/89d5d5b2849a0db0957065889ca58536fa6d2e4a/internal/module/resolver.go#L1282
66
+ */
67
+ function substituted(target, star) {
68
+ const at = target.indexOf("*");
69
+ return at < 0 ? target : target.slice(0, at) + star + target.slice(at + 1);
70
+ }
71
+ /**
72
+ * Whether a path names something on disk, as written or with any extension; under
73
+ * `fileOnly`, only a file counts.
74
+ */
75
+ function provided(host, path, fileOnly) {
76
+ const counts = (at) => fileOnly ? host.kindOf(at) === "file" : host.kindOf(at) !== "absent";
77
+ if (counts(path)) {
56
78
  return true;
57
79
  }
58
80
  const dir = dirnamePath(path);
59
81
  const stem = `${path.slice(dir.length + 1)}.`;
60
82
  try {
61
- return host.readDirectory(dir).some((entry) => entry.name.startsWith(stem));
83
+ return host
84
+ .readDirectory(dir)
85
+ .some((entry) => entry.name.startsWith(stem) && counts(joinPath(dir, entry.name)));
62
86
  }
63
87
  catch {
64
88
  return false;
@@ -90,6 +114,20 @@ function declaringManifest(host, dir, targetRoot, name) {
90
114
  }
91
115
  }
92
116
  }
117
+ /**
118
+ * Whether a package is installed for an importer in `dir`: in the `node_modules` of
119
+ * `dir` or of any ancestor, above the target root too, as Node and the compiler look.
120
+ */
121
+ function installed(host, dir, name) {
122
+ for (let at = dir;; at = dirnamePath(at)) {
123
+ if (host.kindOf(joinPath(at, "node_modules", name)) === "directory") {
124
+ return true;
125
+ }
126
+ if (dirnamePath(at) === at) {
127
+ return false;
128
+ }
129
+ }
130
+ }
93
131
  /**
94
132
  * Why one import that the compiler cannot resolve names a module the project expects,
95
133
  * with the fix, or undefined where the module is one the project does not expect.
@@ -98,34 +136,64 @@ function expectedModule(context, importer, specifier) {
98
136
  const { host, targetRoot } = context;
99
137
  const fix = "run the generator or the build that writes it";
100
138
  if (specifier.startsWith(".") || isAbsolutePath(specifier)) {
101
- return provided(host, resolvePath(dirnamePath(importer), specifier))
139
+ return provided(host, resolvePath(dirnamePath(importer), specifier), false)
102
140
  ? undefined
103
141
  : `which names no file of the target: ${fix}`;
104
142
  }
105
143
  if (specifier.startsWith("#")) {
106
144
  return `which the manifest's imports map to no file: ${fix}`;
107
145
  }
108
- if (context.paths.some((key) => mappedBy(key, specifier))) {
109
- return `which the compiler configuration's paths map to no file: ${fix}`;
110
- }
146
+ const mapped = context.paths.entries.flatMap(({ key, targets }) => {
147
+ const star = mappedBy(key, specifier);
148
+ return star === undefined
149
+ ? []
150
+ : targets.map((target) => resolvePath(context.paths.base, substituted(target, star)));
151
+ });
111
152
  const name = packageOfSpecifier(specifier);
153
+ // The compiler looks up in `node_modules` what `paths` does not resolve, so the error
154
+ // of an installed package's import is the import's own, a subpath its exports omit:
155
+ // https://github.com/microsoft/typescript-go/blob/89d5d5b2849a0db0957065889ca58536fa6d2e4a/internal/module/resolver.go#L548-L575
156
+ if (name !== undefined && installed(host, dirnamePath(importer), name)) {
157
+ return undefined;
158
+ }
159
+ if (mapped.length > 0) {
160
+ return mapped.some((path) => provided(host, path, true))
161
+ ? undefined
162
+ : `which the compiler configuration's paths map to no file: ${fix}`;
163
+ }
112
164
  const manifest = name === undefined
113
165
  ? undefined
114
166
  : declaringManifest(host, dirnamePath(importer), targetRoot, name);
115
- if (manifest === undefined) {
167
+ // No run requires a nested package that is no workspace member to be installed.
168
+ if (manifest === undefined ||
169
+ (dirnamePath(manifest) !== targetRoot && !context.member(manifest))) {
116
170
  return undefined;
117
171
  }
118
172
  const where = relativePath(targetRoot, manifest) ?? manifest;
119
173
  return `a dependency ${where} declares that is not installed: run the package manager's install`;
120
174
  }
121
175
  /**
122
- * The unit a type error at one offset skips: the class member holding it where a
123
- * top-level class does, else the top-level statement.
176
+ * The unit a type error at one offset skips: the innermost function holding it, else the
177
+ * class member holding it where a top-level class does, else the top-level statement.
124
178
  */
125
179
  function unitAt(file, offset) {
126
180
  const statement = file.statements.find((one) => one.pos <= offset && offset < one.end);
127
- if (statement === undefined || !isClassDeclaration(statement)) {
128
- return statement;
181
+ if (statement === undefined) {
182
+ return undefined;
183
+ }
184
+ let innermost;
185
+ const visit = (node) => {
186
+ if (offset < node.getStart() || offset >= node.end) {
187
+ return;
188
+ }
189
+ if (isFunctionLikeDeclaration(node)) {
190
+ innermost = node;
191
+ }
192
+ node.forEachChild(visit);
193
+ };
194
+ visit(statement);
195
+ if (innermost !== undefined || !isClassDeclaration(statement)) {
196
+ return innermost ?? statement;
129
197
  }
130
198
  return statement.members.find((one) => one.pos <= offset && offset < one.end) ?? statement;
131
199
  }
@@ -139,18 +207,22 @@ function messageOf(text, targetRoot) {
139
207
  * is no error here, and one in a file that is not the project's own is that file's
140
208
  * package's to answer for.
141
209
  */
142
- export function readErrors(project, errors, context) {
210
+ export function readErrors(project, errors, where) {
211
+ let workspace = null;
212
+ const context = {
213
+ ...where,
214
+ member: (manifest) => {
215
+ if (workspace === null) {
216
+ workspace = findWorkspace(where.host, where.targetRoot, () => undefined);
217
+ }
218
+ return workspace?.memberOf(manifest) !== undefined;
219
+ },
220
+ };
143
221
  const files = new Map(project.ownSourceFiles().map((file) => [file.fileName, file]));
144
222
  const missing = [];
145
223
  const skips = [];
146
- const failing = [];
147
- for (const located of errors) {
148
- const { diagnostic, at } = located;
149
- if (diagnostic.fileName === undefined) {
150
- failing.push(located);
151
- continue;
152
- }
153
- const file = files.get(diagnostic.fileName);
224
+ for (const { diagnostic, at } of errors) {
225
+ const file = diagnostic.fileName === undefined ? undefined : files.get(diagnostic.fileName);
154
226
  if (file === undefined || diagnostic.reportsUnnecessary === true) {
155
227
  continue;
156
228
  }
@@ -176,7 +248,7 @@ export function readErrors(project, errors, context) {
176
248
  });
177
249
  }
178
250
  }
179
- return { missing, skips, failing };
251
+ return { missing, skips };
180
252
  }
181
253
  /** The lines each skip withholds, below the target root. */
182
254
  export function skippedUnits(skips, targetRoot) {
@@ -241,13 +313,22 @@ export function skippedRoots(project, held, skips) {
241
313
  }
242
314
  return [...kept];
243
315
  }
244
- /** The keys of one compiler configuration's `paths` mapping, none where it cannot be read. */
316
+ /** One compiler configuration's `paths` mapping, empty where it cannot be read. */
245
317
  export function pathsOf(engine, configFile) {
318
+ const base = dirnamePath(configFile);
246
319
  try {
247
- const paths = engine.parseConfigFile(configFile).options.paths;
248
- return typeof paths === "object" && paths !== null ? Object.keys(paths) : [];
320
+ const { options } = engine.parseConfigFile(configFile);
321
+ const { paths } = options;
322
+ // The parse answers `pathsBasePath`, the directory of the configuration that writes
323
+ // `paths` (an extended one included), though the client's option type omits it:
324
+ // https://github.com/microsoft/typescript-go/blob/89d5d5b2849a0db0957065889ca58536fa6d2e4a/internal/tsoptions/tsconfigparsing.go#L1120
325
+ const declaredAt = options.pathsBasePath;
326
+ return {
327
+ base: typeof declaredAt === "string" ? declaredAt : base,
328
+ entries: Object.entries(paths ?? {}).map(([key, targets]) => ({ key, targets })),
329
+ };
249
330
  }
250
331
  catch {
251
- return [];
332
+ return { base, entries: [] };
252
333
  }
253
334
  }
@@ -0,0 +1,172 @@
1
+ /**
2
+ * The members a read through a value typed by a type parameter reaches: `value.kind`,
3
+ * where `value` is of a type parameter `T` of a generic function, reads `kind` on each
4
+ * object type no class declares that a call of the function passes for `T`, because the
5
+ * call's value is what the read reads, whatever member of the constraint the name resolves
6
+ * to. A class's members a constraint requires are interface satisfaction's.
7
+ */
8
+ import { isBinaryExpression, isCallExpression, isIdentifier, isNewExpression, isPropertyAccessExpression, isTypeParameterDeclaration, SyntaxKind, } from "@typescript/native/ast";
9
+ import { SymbolFlags } from "@typescript/native/sync";
10
+ import { nodeKey } from "./inventory.js";
11
+ import { renderPosition } from "./position.js";
12
+ import { isAnswered } from "./query.js";
13
+ /** How deep a parameter's type is matched against the type a call passes: `readonly T[]` is one level. */
14
+ const MATCH_DEPTH = 3;
15
+ /** The name a call reaches one generic by: its own, or that of what holds it. */
16
+ function nameOf(generic) {
17
+ const own = generic.name;
18
+ const holder = generic.parent?.name;
19
+ const name = own ?? holder;
20
+ return name !== undefined && isIdentifier(name) ? name.text : undefined;
21
+ }
22
+ /** Whether a property access is the target of a plain assignment, which writes the member. */
23
+ function assignedTo(access) {
24
+ const parent = access.parent;
25
+ return (isBinaryExpression(parent) &&
26
+ parent.operatorToken.kind === SyntaxKind.EqualsToken &&
27
+ parent.left === access);
28
+ }
29
+ /**
30
+ * The references the reads through type-parameter-typed values of one project's own files
31
+ * make to the members of the types its calls pass, each made by the declaration holding
32
+ * the read.
33
+ */
34
+ export function typeParameterReads(project, held, chains, root, testFiles) {
35
+ const queries = project.queries;
36
+ const reads = [];
37
+ const calls = [];
38
+ for (const file of project.ownSourceFiles()) {
39
+ const fileId = held.declarations.get(nodeKey(file, file));
40
+ if (fileId === undefined) {
41
+ continue;
42
+ }
43
+ const visit = (node, from, generic) => {
44
+ const holder = held.declarations.get(nodeKey(file, node)) ?? from;
45
+ const inside = generic ||
46
+ node.typeParameters !== undefined;
47
+ if (inside &&
48
+ isPropertyAccessExpression(node) &&
49
+ isIdentifier(node.name) &&
50
+ !assignedTo(node)) {
51
+ reads.push({ file, from: holder, access: node });
52
+ }
53
+ if (isCallExpression(node) || isNewExpression(node)) {
54
+ calls.push(node);
55
+ }
56
+ node.forEachChild((child) => {
57
+ visit(child, holder, inside);
58
+ });
59
+ };
60
+ file.forEachChild((child) => {
61
+ visit(child, fileId, false);
62
+ });
63
+ }
64
+ if (reads.length === 0) {
65
+ return [];
66
+ }
67
+ // The reads whose receiver the checker types as a type parameter, by the generic declaring it.
68
+ const receivers = queries.typesAt(reads.map((read) => read.access.expression));
69
+ const byGeneric = new Map();
70
+ reads.forEach((read, at) => {
71
+ const type = receivers[at];
72
+ if (type === undefined || !isAnswered(type) || !type.isTypeParameter()) {
73
+ return;
74
+ }
75
+ const symbol = queries.symbolOfType(type);
76
+ const declaration = symbol === undefined || !isAnswered(symbol)
77
+ ? undefined
78
+ : symbol.declarations
79
+ .map((handle) => project.declarationAt(handle)?.node)
80
+ .find((node) => node !== undefined && isTypeParameterDeclaration(node));
81
+ const generic = declaration?.parent;
82
+ if (generic !== undefined) {
83
+ byGeneric.set(generic, [...(byGeneric.get(generic) ?? []), { read, parameter: type }]);
84
+ }
85
+ });
86
+ if (byGeneric.size === 0) {
87
+ return [];
88
+ }
89
+ /** The types `instantiated` holds where `declared` holds `parameter`, matched part for part. */
90
+ const passedFor = (parameter, declared, instantiated, depth) => {
91
+ if (declared.id === parameter.id) {
92
+ return [instantiated];
93
+ }
94
+ if (depth >= MATCH_DEPTH) {
95
+ return [];
96
+ }
97
+ const a = queries.typeArguments(declared);
98
+ const b = queries.typeArguments(instantiated);
99
+ if (!isAnswered(a) || !isAnswered(b) || a.length !== b.length) {
100
+ return [];
101
+ }
102
+ return a.flatMap((one, at) => {
103
+ const other = b[at];
104
+ return other === undefined ? [] : passedFor(parameter, one, other, depth + 1);
105
+ });
106
+ };
107
+ // Only a call spelling a generic's name is resolved: one request each.
108
+ const spelled = new Set([...byGeneric.keys()].flatMap((generic) => nameOf(generic) ?? []));
109
+ const found = new Map();
110
+ for (const call of calls) {
111
+ const callee = call.expression;
112
+ const written = isIdentifier(callee)
113
+ ? callee.text
114
+ : isPropertyAccessExpression(callee) && isIdentifier(callee.name)
115
+ ? callee.name.text
116
+ : undefined;
117
+ if (written === undefined || !spelled.has(written)) {
118
+ continue;
119
+ }
120
+ const resolved = queries.resolvedSignature(call);
121
+ if (resolved === undefined || !isAnswered(resolved) || resolved.declaration === undefined) {
122
+ continue;
123
+ }
124
+ const generic = project.declarationAt(resolved.declaration)?.node;
125
+ const pending = generic === undefined ? undefined : byGeneric.get(generic);
126
+ const declared = pending === undefined ? undefined : queries.genericOf(resolved);
127
+ if (pending === undefined || declared === undefined || !isAnswered(declared)) {
128
+ continue;
129
+ }
130
+ const count = (generic?.parameters ?? []).length;
131
+ for (const { read, parameter } of pending) {
132
+ const passed = [];
133
+ for (let at = 0; at < count; at += 1) {
134
+ const own = queries.parameterType(declared, at);
135
+ const given = queries.typeAtPosition(resolved, at);
136
+ if (own !== undefined && isAnswered(own) && isAnswered(given)) {
137
+ passed.push(...passedFor(parameter, own, given, 0));
138
+ }
139
+ }
140
+ const name = read.access.name.text;
141
+ const position = renderPosition(read.file, root, read.access.name.getStart());
142
+ for (const type of passed) {
143
+ const parts = type.isUnionType() ? queries.constituents(type) : [type];
144
+ for (const part of isAnswered(parts) ? parts : []) {
145
+ const owner = queries.symbolOfType(part);
146
+ if (!isAnswered(owner) ||
147
+ owner === undefined ||
148
+ (owner.flags & SymbolFlags.Class) !== 0) {
149
+ continue;
150
+ }
151
+ const properties = queries.propertiesOf(part);
152
+ for (const property of isAnswered(properties) ? properties : []) {
153
+ if (property.name !== name) {
154
+ continue;
155
+ }
156
+ for (const to of chains.declarationsOf(property)) {
157
+ found.set(`${read.from}\u0000${to}\u0000${String(read.access.name.pos)}`, {
158
+ from: read.from,
159
+ to,
160
+ position,
161
+ use: "read",
162
+ resolution: "batch",
163
+ test: testFiles.has(position.path),
164
+ });
165
+ }
166
+ }
167
+ }
168
+ }
169
+ }
170
+ }
171
+ return [...found.values()];
172
+ }
@@ -10,12 +10,22 @@ import { isAsExpression, isParenthesizedExpression, isSatisfiesExpression, isTyp
10
10
  import { ObjectFlags, SymbolFlags, } from "@typescript/native/sync";
11
11
  import { classNames, typesAt } from "./class-members.js";
12
12
  import { must } from "./query.js";
13
- /** The object types whose members the walk reads whatever declares them. */
14
- const WRITTEN = ObjectFlags.ObjectLiteral | ObjectFlags.Mapped;
13
+ /**
14
+ * The object types whose members the walk reads whatever declares them. A reverse-mapped
15
+ * type has no symbol: it is the shape the checker infers from an argument the target
16
+ * writes against a homomorphic mapped type.
17
+ */
18
+ const WRITTEN = ObjectFlags.ObjectLiteral | ObjectFlags.Mapped | ObjectFlags.ReverseMapped;
15
19
  /** The symbols of an anonymous object type the target writes as a type. */
16
20
  const LITERAL_SYMBOLS = SymbolFlags.TypeLiteral | SymbolFlags.ObjectLiteral;
17
21
  /** The two conversion methods a serializer or a formatter resolves on a value's own shape. */
18
22
  const CONVERSION_NAMES = new Set(["toJSON", "toString"]);
23
+ /**
24
+ * The members that can answer a conversion: a serializer reads the name with `[[Get]]`
25
+ * and calls what it answers, so a getter answers it as a method does:
26
+ * https://tc39.es/ecma262/#sec-serializejsonproperty (step 2).
27
+ */
28
+ export const CONVERSION_MEMBERS = SymbolFlags.Method | SymbolFlags.GetAccessor;
19
29
  /**
20
30
  * The types each value is checked against by the `satisfies` expressions and type assertions
21
31
  * it is written as, through parentheses, and the innermost operand's own type where there is
@@ -92,7 +102,9 @@ export function valueReach(project, held, stored = () => []) {
92
102
  if ((type.objectFlags & ObjectFlags.Anonymous) !== 0) {
93
103
  return (symbol.flags & LITERAL_SYMBOLS) !== 0;
94
104
  }
95
- return ((symbol.flags & SymbolFlags.Interface) !== 0 &&
105
+ // A class the inventory holds is read as a class before this, so an own class
106
+ // here is one it does not hold, such as a class declared in a function.
107
+ return ((symbol.flags & (SymbolFlags.Interface | SymbolFlags.Class)) !== 0 &&
96
108
  symbol.declarations.every((handle) => own.has(handle.path)));
97
109
  };
98
110
  /** The types a value of one object type holds in its data members. */
@@ -115,9 +127,9 @@ export function valueReach(project, held, stored = () => []) {
115
127
  (target.isObjectType() && (target.objectFlags & ObjectFlags.Tuple) !== 0));
116
128
  return !isList && isWritten(type);
117
129
  };
118
- /** The data members and conversion methods of one written object type. */
130
+ /** The data members and conversion members of one written object type. */
119
131
  const carried = (type) => must(queries.propertiesOf(type)).filter((property) => (property.flags & SymbolFlags.Property) !== 0 ||
120
- ((property.flags & SymbolFlags.Method) !== 0 && CONVERSION_NAMES.has(property.name)));
132
+ ((property.flags & CONVERSION_MEMBERS) !== 0 && CONVERSION_NAMES.has(property.name)));
121
133
  /** The types a value of one type holds, read once per type. */
122
134
  const childrenOf = (type) => {
123
135
  const known = children.get(type.id);
@@ -62,6 +62,21 @@ function runRelative(invoked, path) {
62
62
  }
63
63
  return below;
64
64
  }
65
+ /**
66
+ * One note per manifest entry the run reads back to no source file: what the entry names
67
+ * reaches nothing, so the findings its file would have rooted are reported.
68
+ */
69
+ function unmappedNotes(entries, targetRoot) {
70
+ return entries.map((entry) => {
71
+ const named = relativePath(targetRoot, entry.path) ?? entry.path;
72
+ return {
73
+ kind: "unmapped-entry",
74
+ path: relativePath(targetRoot, entry.dir) ?? ".",
75
+ key: "ts.entry_files",
76
+ message: `the manifest's ${entry.member} names ${named}, which reads back to no source file of the target: name the file it is built from in ts.entry_files`,
77
+ };
78
+ });
79
+ }
65
80
  /** Each project the run dropped as a report names it, by its configuration file below the target. */
66
81
  function notBuiltOf(dropped, targetRoot) {
67
82
  return dropped.map((one) => {
@@ -161,6 +176,7 @@ export const analyzeVerb = ({ err, host, inputs, scope, openClient, option, repe
161
176
  let partial;
162
177
  let warnings;
163
178
  let unread;
179
+ let unfound;
164
180
  try {
165
181
  const root = runRelative(invoked, targetRoot);
166
182
  const mode = { production: true };
@@ -178,6 +194,7 @@ export const analyzeVerb = ({ err, host, inputs, scope, openClient, option, repe
178
194
  const { result, run } = analysis;
179
195
  warnings = run.componentWarnings;
180
196
  unread = run.componentsUnread.map(({ configFile, reason }) => `${relativePath(resolvePath(invoked, targetRoot), configFile) ?? configFile}: its component files are not read: ${reason}`);
197
+ unfound = run.librariesUnfound.map(({ configFile, error }) => `${relativePath(resolvePath(invoked, targetRoot), configFile) ?? configFile}: ${error}: the configuration is analyzed without that library`);
181
198
  partial = partialNotes({
182
199
  notBuilt: run.notBuilt,
183
200
  unanswered: run.unanswered,
@@ -205,6 +222,7 @@ export const analyzeVerb = ({ err, host, inputs, scope, openClient, option, repe
205
222
  typeErrorSkips: run.typeErrorSkips,
206
223
  unanswered: unansweredCounts(analysis),
207
224
  conventionsApplied: run.conventionsApplied,
225
+ notes: unmappedNotes(run.unmappedEntries, resolvePath(invoked, targetRoot)),
208
226
  });
209
227
  report = capFindings(sortFindings(built, config.reporters.sort), config.reporters.maxFindings);
210
228
  verdict = verdictOf(result, config);
@@ -243,7 +261,7 @@ export const analyzeVerb = ({ err, host, inputs, scope, openClient, option, repe
243
261
  for (const note of partial) {
244
262
  err.write(`deadset-ts: ${note}\n`);
245
263
  }
246
- for (const line of unread) {
264
+ for (const line of [...unread, ...unfound]) {
247
265
  err.write(`deadset-ts: ${line}\n`);
248
266
  }
249
267
  for (const warning of warnings) {
@@ -257,11 +257,13 @@ function reported(found) {
257
257
  /**
258
258
  * Every finding of the run that names the declaration by its reference and reports at
259
259
  * another position: a part of it, or a suppression record naming it. Each is published under
260
- * the reference asked about, so an explanation leaving it out would disagree with the report.
260
+ * the reference asked about, or a parameter's under one it starts, so an explanation leaving
261
+ * it out would disagree with the report.
261
262
  */
262
263
  function namedElsewhere(findings, subject) {
263
264
  return findings
264
- .filter((found) => found.symbol.ref === subject.ref && positionKey(found.position) !== subject.id)
265
+ .filter((found) => (found.symbol.holder ?? found.symbol.ref) === subject.ref &&
266
+ positionKey(found.position) !== subject.id)
265
267
  .map((found) => `also reported: ${found.code} ${found.kind}\t${positionKey(found.position)}\t${found.symbol.kind} ${found.symbol.name}\n`)
266
268
  .join("");
267
269
  }
@@ -38,10 +38,12 @@ function partialOf(text) {
38
38
  parts.push(Number(part));
39
39
  }
40
40
  const prerelease = prereleaseOf(match[4]);
41
- if (prerelease === undefined || (prerelease.length > 0 && wild)) {
41
+ if (prerelease === undefined) {
42
42
  return undefined;
43
43
  }
44
- return { major: parts[0], minor: parts[1], patch: parts[2], prerelease };
44
+ // node-semver drops the prerelease of a version with a wildcard part (`1.2.x-beta` is `1.2.x`):
45
+ // https://github.com/npm/node-semver/blob/v7.8.5/classes/range.js#L409-L430
46
+ return { major: parts[0], minor: parts[1], patch: parts[2], prerelease: wild ? [] : prerelease };
45
47
  }
46
48
  /** One exact version, or `undefined` where the text is not one. */
47
49
  function versionOf(text) {
@@ -111,11 +113,14 @@ function passes(version, comparator) {
111
113
  return order === 0;
112
114
  }
113
115
  }
114
- /** The comparators a version with missing parts stands for: any value of each. */
116
+ /**
117
+ * The comparators a version with missing parts stands for: any value of each. A wildcard
118
+ * major is no comparator, so it never refuses a 0.0.0 prerelease another comparator admits.
119
+ */
115
120
  function anyOf(partial) {
116
121
  const { major, minor } = partial;
117
122
  if (major === undefined) {
118
- return [{ operator: ">=", version: exact(0, 0, 0) }];
123
+ return [];
119
124
  }
120
125
  if (minor === undefined) {
121
126
  return [
@@ -165,7 +170,7 @@ function primitive(operator, partial) {
165
170
  if (major === undefined) {
166
171
  return operator === "<" || operator === ">"
167
172
  ? [{ operator: "<", version: floorOf(0, 0, 0) }]
168
- : [{ operator: ">=", version: exact(0, 0, 0) }];
173
+ : [];
169
174
  }
170
175
  if (minor !== undefined && patch !== undefined) {
171
176
  return [{ operator, version: lowest(partial) }];
@@ -4,4 +4,4 @@
4
4
  * independently of the analyzer's own version, which is the one the host answers
5
5
  * from the package the analyzer was installed from.
6
6
  */
7
- export const CONTRACT_VERSION = "7.2.0";
7
+ export const CONTRACT_VERSION = "8.0.0";