@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.
- package/dist/bin/mapper-connection.js +7 -4
- package/dist/src/analysis.js +116 -16
- package/dist/src/augmentations.js +21 -8
- package/dist/src/automatic-types.js +189 -0
- package/dist/src/base-url.js +18 -0
- package/dist/src/component-files.js +96 -31
- package/dist/src/components.js +7 -6
- package/dist/src/config.js +1 -0
- package/dist/src/configuration-files.js +54 -15
- package/dist/src/configured-declarations.js +24 -2
- package/dist/src/conformance-record.js +2 -2
- package/dist/src/contextual-uses.js +26 -26
- package/dist/src/convention-rows.js +75 -0
- package/dist/src/conventions.js +101 -5
- package/dist/src/decorator.js +21 -18
- package/dist/src/dependencies.js +185 -30
- package/dist/src/destructuring.js +77 -2
- package/dist/src/discover.js +188 -85
- package/dist/src/edges.js +1 -1
- package/dist/src/entry-point-gaps.js +7 -10
- package/dist/src/entry-points.js +112 -11
- package/dist/src/findings/completion.js +116 -21
- package/dist/src/findings/dependencies-and-module-machinery.js +71 -25
- package/dist/src/findings/emitters.js +1 -1
- package/dist/src/findings/intra-function.js +44 -13
- package/dist/src/findings/non-code-artifacts.js +66 -33
- package/dist/src/findings/unused-declarations.js +21 -1
- package/dist/src/findings/visibility-narrowing.js +22 -3
- package/dist/src/generated-file.js +11 -0
- package/dist/src/git-ignore.js +86 -0
- package/dist/src/graph.js +4 -1
- package/dist/src/import-bindings.js +97 -0
- package/dist/src/indexed-access.js +130 -28
- package/dist/src/interface-satisfaction.js +30 -12
- package/dist/src/intra-function-parts.js +313 -50
- package/dist/src/inventory.js +63 -8
- package/dist/src/json-document.js +5 -5
- package/dist/src/kinds.js +13 -13
- package/dist/src/matrix.js +18 -9
- package/dist/src/mdx-entries.js +197 -0
- package/dist/src/module-declarations.js +9 -13
- package/dist/src/namespace-objects.js +28 -18
- package/dist/src/overrides.js +45 -9
- package/dist/src/paths.js +9 -0
- package/dist/src/print.js +1 -0
- package/dist/src/ref.js +10 -0
- package/dist/src/references.js +29 -13
- package/dist/src/reflective-lookup.js +8 -6
- package/dist/src/report.js +4 -2
- package/dist/src/reporters/template.js +22 -13
- package/dist/src/resolve.js +2 -0
- package/dist/src/roots.js +110 -38
- package/dist/src/schema.js +1 -0
- package/dist/src/serialization-contract.js +97 -20
- package/dist/src/session.js +26 -1
- package/dist/src/source-files.js +39 -14
- package/dist/src/spelled-names.js +58 -0
- package/dist/src/suppress-file.js +12 -4
- package/dist/src/suppress-inline.js +21 -3
- package/dist/src/suppress.js +7 -5
- package/dist/src/sweep.js +27 -10
- package/dist/src/symbol-keys.js +103 -2
- package/dist/src/template-field.js +9 -6
- package/dist/src/test-support.js +121 -17
- package/dist/src/type-errors.js +116 -35
- package/dist/src/type-parameter-reads.js +172 -0
- package/dist/src/value-reach.js +17 -5
- package/dist/src/verbs/analyze.js +19 -1
- package/dist/src/verbs/explain.js +4 -2
- package/dist/src/version-range.js +10 -5
- package/dist/src/version.js +1 -1
- package/dist/src/workflow-steps.js +119 -22
- package/dist/src/workspace-programs.js +7 -3
- package/dist/src/workspace-resolution.js +72 -13
- package/dist/src/workspace.js +1 -1
- package/jsr.json +1 -1
- package/package.json +2 -2
- package/src/analysis.ts +175 -16
- package/src/augmentations.ts +21 -10
- package/src/automatic-types.ts +246 -0
- package/src/base-url.ts +24 -0
- package/src/component-files.ts +116 -31
- package/src/components.ts +7 -6
- package/src/config.ts +3 -0
- package/src/configuration-files.ts +80 -18
- package/src/configured-declarations.ts +33 -4
- package/src/conformance-record.ts +2 -2
- package/src/contextual-uses.ts +27 -27
- package/src/convention-rows.ts +103 -0
- package/src/conventions.ts +121 -7
- package/src/decorator.ts +28 -18
- package/src/dependencies.ts +249 -43
- package/src/destructuring.ts +93 -2
- package/src/discover.ts +234 -86
- package/src/edges.ts +1 -1
- package/src/entry-point-gaps.ts +8 -11
- package/src/entry-points.ts +133 -11
- package/src/finding.ts +10 -0
- package/src/findings/completion.ts +147 -24
- package/src/findings/dependencies-and-module-machinery.ts +90 -28
- package/src/findings/emitters.ts +1 -1
- package/src/findings/intra-function.ts +56 -14
- package/src/findings/non-code-artifacts.ts +81 -41
- package/src/findings/unused-declarations.ts +25 -1
- package/src/findings/visibility-narrowing.ts +32 -3
- package/src/generated-file.ts +16 -0
- package/src/git-ignore.ts +98 -0
- package/src/graph.ts +6 -0
- package/src/host.ts +5 -9
- package/src/import-bindings.ts +128 -0
- package/src/indexed-access.ts +152 -30
- package/src/interface-satisfaction.ts +33 -13
- package/src/intra-function-parts.ts +415 -64
- package/src/inventory.ts +83 -7
- package/src/json-document.ts +7 -7
- package/src/kinds.ts +13 -13
- package/src/matrix.ts +32 -14
- package/src/mdx-entries.ts +241 -0
- package/src/module-declarations.ts +11 -13
- package/src/namespace-objects.ts +34 -21
- package/src/overrides.ts +56 -9
- package/src/paths.ts +10 -0
- package/src/print.ts +1 -0
- package/src/ref.ts +12 -0
- package/src/references.ts +38 -17
- package/src/reflective-lookup.ts +9 -7
- package/src/report.ts +16 -3
- package/src/reporters/template.ts +21 -13
- package/src/resolve.ts +3 -0
- package/src/roots.ts +163 -45
- package/src/schema.ts +1 -0
- package/src/serialization-contract.ts +108 -17
- package/src/session.ts +29 -1
- package/src/source-files.ts +66 -16
- package/src/spelled-names.ts +102 -0
- package/src/suppress-file.ts +14 -3
- package/src/suppress-inline.ts +21 -2
- package/src/suppress.ts +9 -5
- package/src/sweep.ts +32 -11
- package/src/symbol-keys.ts +129 -2
- package/src/template-field.ts +9 -6
- package/src/test-support.ts +149 -16
- package/src/type-errors.ts +132 -41
- package/src/type-parameter-reads.ts +227 -0
- package/src/value-reach.ts +19 -6
- package/src/verbs/analyze.ts +28 -1
- package/src/verbs/explain.ts +5 -2
- package/src/version-range.ts +10 -5
- package/src/version.ts +1 -1
- package/src/workflow-steps.ts +127 -22
- package/src/workspace-programs.ts +7 -3
- package/src/workspace-resolution.ts +86 -14
- package/src/workspace.ts +1 -1
package/dist/src/type-errors.js
CHANGED
|
@@ -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.
|
|
6
|
-
*
|
|
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
|
-
/**
|
|
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
|
|
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
|
-
/**
|
|
54
|
-
|
|
55
|
-
|
|
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
|
|
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
|
-
|
|
109
|
-
|
|
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
|
-
|
|
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
|
|
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
|
|
128
|
-
return
|
|
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,
|
|
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
|
|
147
|
-
|
|
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
|
|
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
|
-
/**
|
|
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
|
|
248
|
-
|
|
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
|
+
}
|
package/dist/src/value-reach.js
CHANGED
|
@@ -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
|
-
/**
|
|
14
|
-
|
|
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
|
-
|
|
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
|
|
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 &
|
|
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,
|
|
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 &&
|
|
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
|
|
41
|
+
if (prerelease === undefined) {
|
|
42
42
|
return undefined;
|
|
43
43
|
}
|
|
44
|
-
|
|
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
|
-
/**
|
|
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 [
|
|
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
|
-
: [
|
|
173
|
+
: [];
|
|
169
174
|
}
|
|
170
175
|
if (minor !== undefined && patch !== undefined) {
|
|
171
176
|
return [{ operator, version: lowest(partial) }];
|
package/dist/src/version.js
CHANGED