@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
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
import { isBindingElement, isBreakStatement, isClassDeclaration, isClassExpression, isContinueStatement, isEnumDeclaration, isEnumMember, isExportDeclaration, isExportSpecifier, isFunctionDeclaration, isFunctionExpression, isGetAccessorDeclaration, isImportClause, isImportEqualsDeclaration, isImportSpecifier, isInterfaceDeclaration, isJsxAttribute, isLabeledStatement, isMetaProperty, isMethodDeclaration, isMethodSignatureDeclaration, isModuleDeclaration, isNamespaceExport, isNamespaceExportDeclaration, isNamespaceImport, isParameterDeclaration, isPropertyAccessExpression, isPropertyAssignment, isPropertyDeclaration, isPropertySignatureDeclaration, isQualifiedName, isSetAccessorDeclaration, isTypeAliasDeclaration, isTypeParameterDeclaration, isVariableDeclaration, } from "@typescript/native/ast";
|
|
2
|
+
/**
|
|
3
|
+
* Whether one identifier spells a name without referring to a binding of it: a member's
|
|
4
|
+
* name after a dot, a property or member an object literal, a class, a type or an enum
|
|
5
|
+
* declares, the name a declaration binds, the property a binding element destructures,
|
|
6
|
+
* an import or export specifier's name in another module or under a rename, a JSX
|
|
7
|
+
* attribute's name, a statement label, or the name of a meta-property. A shorthand
|
|
8
|
+
* property and an unrenamed local export are no such spelling: each reads the binding.
|
|
9
|
+
*/
|
|
10
|
+
export function spellsNoBinding(node) {
|
|
11
|
+
const parent = node.parent;
|
|
12
|
+
if (isPropertyAccessExpression(parent) || isMetaProperty(parent)) {
|
|
13
|
+
return parent.name === node;
|
|
14
|
+
}
|
|
15
|
+
if (isQualifiedName(parent)) {
|
|
16
|
+
return parent.right === node;
|
|
17
|
+
}
|
|
18
|
+
if (isBindingElement(parent)) {
|
|
19
|
+
return parent.propertyName === node || parent.name === node;
|
|
20
|
+
}
|
|
21
|
+
if (isImportSpecifier(parent)) {
|
|
22
|
+
return true;
|
|
23
|
+
}
|
|
24
|
+
if (isExportSpecifier(parent)) {
|
|
25
|
+
return ((isExportDeclaration(parent.parent.parent) &&
|
|
26
|
+
parent.parent.parent.moduleSpecifier !== undefined) ||
|
|
27
|
+
(parent.propertyName !== undefined && parent.name === node));
|
|
28
|
+
}
|
|
29
|
+
if (isPropertyAssignment(parent) ||
|
|
30
|
+
isPropertyDeclaration(parent) ||
|
|
31
|
+
isMethodDeclaration(parent) ||
|
|
32
|
+
isPropertySignatureDeclaration(parent) ||
|
|
33
|
+
isMethodSignatureDeclaration(parent) ||
|
|
34
|
+
isGetAccessorDeclaration(parent) ||
|
|
35
|
+
isSetAccessorDeclaration(parent) ||
|
|
36
|
+
isEnumMember(parent) ||
|
|
37
|
+
isJsxAttribute(parent) ||
|
|
38
|
+
isVariableDeclaration(parent) ||
|
|
39
|
+
isParameterDeclaration(parent) ||
|
|
40
|
+
isFunctionDeclaration(parent) ||
|
|
41
|
+
isFunctionExpression(parent) ||
|
|
42
|
+
isClassDeclaration(parent) ||
|
|
43
|
+
isClassExpression(parent) ||
|
|
44
|
+
isInterfaceDeclaration(parent) ||
|
|
45
|
+
isTypeAliasDeclaration(parent) ||
|
|
46
|
+
isEnumDeclaration(parent) ||
|
|
47
|
+
isModuleDeclaration(parent) ||
|
|
48
|
+
isTypeParameterDeclaration(parent) ||
|
|
49
|
+
isImportClause(parent) ||
|
|
50
|
+
isNamespaceImport(parent) ||
|
|
51
|
+
isImportEqualsDeclaration(parent) ||
|
|
52
|
+
isNamespaceExport(parent) ||
|
|
53
|
+
isNamespaceExportDeclaration(parent)) {
|
|
54
|
+
return parent.name === node;
|
|
55
|
+
}
|
|
56
|
+
return ((isLabeledStatement(parent) || isBreakStatement(parent) || isContinueStatement(parent)) &&
|
|
57
|
+
parent.label === node);
|
|
58
|
+
}
|
|
@@ -8,7 +8,7 @@ import { jsonChunks } from "./json-chunks.js";
|
|
|
8
8
|
import { joinPath } from "./paths.js";
|
|
9
9
|
import { positionAt, walkDocument } from "./json-document.js";
|
|
10
10
|
import { declarationKey } from "./ref.js";
|
|
11
|
-
import { CODE_FORM, SUPPRESSION_WITHOUT_REASON, SuppressionError, UNSCOPED_ENTRY, blankReason, isEntryPath, isSymbolRef, siteText, } from "./suppress.js";
|
|
11
|
+
import { CODE_FORM, SUPPRESSION_WITHOUT_REASON, SuppressionError, UNSCOPED_ENTRY, blankReason, isEntryPath, isPartKind, isSymbolRef, siteText, } from "./suppress.js";
|
|
12
12
|
/** The name and the location the grammar fixes for the ignore file. */
|
|
13
13
|
export const IGNORE_FILE = "deadset-ignore.json";
|
|
14
14
|
/** The name and the location the grammar fixes for the baseline. */
|
|
@@ -51,7 +51,7 @@ function isObject(value) {
|
|
|
51
51
|
*/
|
|
52
52
|
function readRecord(value, site, byReference, shape) {
|
|
53
53
|
const refuse = (text, want) => {
|
|
54
|
-
throw new SuppressionError(shape.mechanism,
|
|
54
|
+
throw new SuppressionError(shape.mechanism, siteText(site), text, want);
|
|
55
55
|
};
|
|
56
56
|
if (!isObject(value)) {
|
|
57
57
|
return refuse(JSON.stringify(value), shape.recordWant);
|
|
@@ -107,12 +107,20 @@ function readRecord(value, site, byReference, shape) {
|
|
|
107
107
|
if (refusals.length > 0) {
|
|
108
108
|
return { records: [], refusals };
|
|
109
109
|
}
|
|
110
|
-
const named = byReference.get(declarationKey(symbol, written.path)) ??
|
|
110
|
+
const named = byReference.get(declarationKey(symbol, written.path)) ??
|
|
111
|
+
(isPartKind(code)
|
|
112
|
+
? byReference.get(declarationKey(parameterHolder(symbol), written.path))
|
|
113
|
+
: undefined) ??
|
|
114
|
+
[];
|
|
111
115
|
if (named.length === 0) {
|
|
112
116
|
return { records: [{ ...written, bound: "" }], refusals: [] };
|
|
113
117
|
}
|
|
114
118
|
return { records: named.map((one) => ({ ...written, bound: one.id })), refusals: [] };
|
|
115
119
|
}
|
|
120
|
+
/** The reference of the declaration a parameter's reference names it under, or `ref` itself. */
|
|
121
|
+
function parameterHolder(ref) {
|
|
122
|
+
return ref.endsWith(")") ? ref.slice(0, ref.lastIndexOf("(")) : ref;
|
|
123
|
+
}
|
|
116
124
|
function byReferenceOf(symbols) {
|
|
117
125
|
const byReference = new Map();
|
|
118
126
|
for (const symbol of symbols) {
|
|
@@ -159,7 +167,7 @@ function readDocument(host, targetRoot, symbols, shape) {
|
|
|
159
167
|
const records = [];
|
|
160
168
|
const refusals = [];
|
|
161
169
|
held.forEach((value, index) => {
|
|
162
|
-
const offset = walk.
|
|
170
|
+
const offset = walk.starts[index];
|
|
163
171
|
const site = offset === undefined ? documentSite(shape) : positionAt(text, shape.file, offset);
|
|
164
172
|
const read = readRecord(value, site, byReference, shape);
|
|
165
173
|
records.push(...read.records);
|
|
@@ -4,8 +4,10 @@
|
|
|
4
4
|
* below it, or on its own line where it trails a token there.
|
|
5
5
|
*/
|
|
6
6
|
import { isConstructorDeclaration, SyntaxKind, } from "@typescript/native/ast";
|
|
7
|
+
import { importBindings } from "./import-bindings.js";
|
|
7
8
|
import { isParameterProperty, nodeKey } from "./inventory.js";
|
|
8
9
|
import { byPosition, positionKey, renderPosition } from "./position.js";
|
|
10
|
+
import { importRef } from "./ref.js";
|
|
9
11
|
import { signatureOf } from "./signatures.js";
|
|
10
12
|
import { isPartKind, SUPPRESSION_WITHOUT_REASON, SuppressionError, siteText, } from "./suppress.js";
|
|
11
13
|
/**
|
|
@@ -100,7 +102,9 @@ function lineComments(file) {
|
|
|
100
102
|
const spans = [];
|
|
101
103
|
const visit = (node) => {
|
|
102
104
|
if (LITERALS.has(node.kind)) {
|
|
103
|
-
|
|
105
|
+
// JSX text carries no trivia: its first code unit is at `pos`, even a `//`.
|
|
106
|
+
const start = node.kind === SyntaxKind.JsxText ? node.pos : tokenStart(text, node.pos);
|
|
107
|
+
spans.push({ start, end: node.end });
|
|
104
108
|
}
|
|
105
109
|
node.forEachChild(visit);
|
|
106
110
|
};
|
|
@@ -142,7 +146,7 @@ function bindingKey(one) {
|
|
|
142
146
|
* function also begins, for a directive, on the line of each of its parameters, and a
|
|
143
147
|
* class, for a part kind, on the lines of its constructor and its constructor's parameters.
|
|
144
148
|
*/
|
|
145
|
-
function declarationLines(file, held, symbols) {
|
|
149
|
+
function declarationLines(file, held, symbols, targetRoot) {
|
|
146
150
|
const below = new Map();
|
|
147
151
|
const add = (offset, one) => {
|
|
148
152
|
const line = file.getLineAndCharacterOfPosition(tokenStart(file.text, offset)).line + 1;
|
|
@@ -184,6 +188,20 @@ function declarationLines(file, held, symbols) {
|
|
|
184
188
|
node.forEachChild(visit);
|
|
185
189
|
};
|
|
186
190
|
file.forEachChild(visit);
|
|
191
|
+
// An import binding is a subject of its own, bound on its line as a declaration is.
|
|
192
|
+
const fileId = held.declarations.get(nodeKey(file, file));
|
|
193
|
+
const moduleRef = held.symbols.find((symbol) => symbol.id === fileId)?.ref ?? "";
|
|
194
|
+
for (const name of moduleRef === "" ? [] : importBindings(file)) {
|
|
195
|
+
const position = renderPosition(file, targetRoot, name.getStart());
|
|
196
|
+
const one = { id: positionKey(position), ref: importRef(moduleRef, name.text), position };
|
|
197
|
+
let statement = name;
|
|
198
|
+
while (statement.parent.kind !== SyntaxKind.SourceFile) {
|
|
199
|
+
statement = statement.parent;
|
|
200
|
+
}
|
|
201
|
+
for (const at of [statement.pos, name.getStart()]) {
|
|
202
|
+
add(at, { declared: one, part: one });
|
|
203
|
+
}
|
|
204
|
+
}
|
|
187
205
|
return below;
|
|
188
206
|
}
|
|
189
207
|
/**
|
|
@@ -208,7 +226,7 @@ export function inlineDirectives(project, held, targetRoot) {
|
|
|
208
226
|
if (comments.length === 0) {
|
|
209
227
|
continue;
|
|
210
228
|
}
|
|
211
|
-
const below = declarationLines(file, held, symbols);
|
|
229
|
+
const below = declarationLines(file, held, symbols, targetRoot);
|
|
212
230
|
for (const comment of comments) {
|
|
213
231
|
const site = renderPosition(file, targetRoot, comment.offset);
|
|
214
232
|
const target = trails(file, comment.offset) ? site.line : site.line + 1;
|
package/dist/src/suppress.js
CHANGED
|
@@ -144,10 +144,11 @@ function claimKey(finding) {
|
|
|
144
144
|
}
|
|
145
145
|
/**
|
|
146
146
|
* Whether one record names one finding. A record reaches a finding in two ways: it bound
|
|
147
|
-
* the declaration at the finding's position, or it names the finding's reference
|
|
148
|
-
*
|
|
149
|
-
* finding
|
|
150
|
-
*
|
|
147
|
+
* the declaration at the finding's position, or it names the finding's reference, or the
|
|
148
|
+
* reference of the declaration holding a part that has one of its own, and the file the
|
|
149
|
+
* finding is reported in, each exactly. The second is how an entry copied from a finding
|
|
150
|
+
* about a part of a declaration reaches the part, and the only way any record reaches a
|
|
151
|
+
* dependency row.
|
|
151
152
|
*/
|
|
152
153
|
export function pairs(record, finding) {
|
|
153
154
|
if (record.code !== finding.code) {
|
|
@@ -164,7 +165,8 @@ export function pairs(record, finding) {
|
|
|
164
165
|
if (record.bound !== "" && record.bound === positionKey(finding.position)) {
|
|
165
166
|
return true;
|
|
166
167
|
}
|
|
167
|
-
return record.symbol === finding.symbol.ref
|
|
168
|
+
return ((record.symbol === finding.symbol.ref || record.symbol === finding.symbol.holder) &&
|
|
169
|
+
record.path === finding.position.path);
|
|
168
170
|
}
|
|
169
171
|
/**
|
|
170
172
|
* Decides every record against `would`, the findings the run would produce were no record
|
package/dist/src/sweep.js
CHANGED
|
@@ -70,16 +70,30 @@ export function sweep(graph, input) {
|
|
|
70
70
|
});
|
|
71
71
|
return { liveUnder: live, candidates };
|
|
72
72
|
}
|
|
73
|
+
/**
|
|
74
|
+
* The directory a test runner reads a manual mock from, whose exports it puts in place of
|
|
75
|
+
* the module's: https://jestjs.io/docs/manual-mocks
|
|
76
|
+
*/
|
|
77
|
+
const MANUAL_MOCKS = "__mocks__";
|
|
78
|
+
/** Whether a test runner calls or reads one export of a test file: a function, or any export of a manual mock. */
|
|
79
|
+
export function runnerCalls(symbol) {
|
|
80
|
+
return symbol.kind === "function" || symbol.position.path.split("/").includes(MANUAL_MOCKS);
|
|
81
|
+
}
|
|
73
82
|
/**
|
|
74
83
|
* Per declaration of the graph, whether the run that counts test references reaches it.
|
|
75
|
-
* That run's roots are the graph's own and each test file,
|
|
76
|
-
*
|
|
77
|
-
*
|
|
84
|
+
* That run's roots are the graph's own and, of each test file, its top level and every
|
|
85
|
+
* function it exports, which a test runner may call, and every export of a manual mock,
|
|
86
|
+
* which the runner reads; any other export of a test file, and test-support code, which
|
|
87
|
+
* runs where a test imports it, are judged as any file's are.
|
|
88
|
+
* `held` seeds it as a mark or an exemption seeds a sweep.
|
|
78
89
|
*/
|
|
79
90
|
export function reachedWithTheTests(graph, held) {
|
|
80
91
|
const tests = graph.symbols.flatMap((symbol, at) => {
|
|
81
|
-
const
|
|
82
|
-
|
|
92
|
+
const exported = symbol.exported && graph.symbols[graph.parent[at] ?? OUTSIDE]?.kind === "file";
|
|
93
|
+
const called = exported && runnerCalls(symbol);
|
|
94
|
+
return graph.test[at] === true &&
|
|
95
|
+
graph.support[at] !== true &&
|
|
96
|
+
(symbol.kind === "file" || called)
|
|
83
97
|
? [{ at, kind: "test-runner" }]
|
|
84
98
|
: [];
|
|
85
99
|
});
|
|
@@ -162,10 +176,11 @@ function reachable(graph, held, mode) {
|
|
|
162
176
|
}
|
|
163
177
|
/**
|
|
164
178
|
* Admits every test declaration whose set of referenced production declarations is
|
|
165
|
-
* non-empty and wholly dead, and returns which it admitted.
|
|
166
|
-
*
|
|
167
|
-
*
|
|
168
|
-
*
|
|
179
|
+
* non-empty and wholly dead, and returns which it admitted. An ambient declaration describes
|
|
180
|
+
* code the target does not implement, so it is no production declaration here. The set is
|
|
181
|
+
* read from every reference the test makes, which a production sweep does not count. A
|
|
182
|
+
* test-support declaration a test referencing a live target also references is a live
|
|
183
|
+
* target of every test referencing it. A marked or exempt test is never admitted.
|
|
169
184
|
*/
|
|
170
185
|
function admitTestsOfDeadCode(graph, dead, held) {
|
|
171
186
|
const testCode = (at) => graph.test[at] === true || graph.support[at] === true;
|
|
@@ -174,7 +189,9 @@ function admitTestsOfDeadCode(graph, dead, held) {
|
|
|
174
189
|
let live = 0;
|
|
175
190
|
if (testCode(at) && graph.subject[at] === true && held[at] !== true) {
|
|
176
191
|
for (const edge of graph.out[at] ?? []) {
|
|
177
|
-
if (testCode(edge.to) ||
|
|
192
|
+
if (testCode(edge.to) ||
|
|
193
|
+
graph.subject[edge.to] !== true ||
|
|
194
|
+
graph.ambient[edge.to] === true) {
|
|
178
195
|
continue;
|
|
179
196
|
}
|
|
180
197
|
targets += 1;
|
package/dist/src/symbol-keys.js
CHANGED
|
@@ -2,9 +2,12 @@
|
|
|
2
2
|
* The members a unique symbol keys, used through element accesses. `value[key]`, where
|
|
3
3
|
* `key` is a constant of a `unique symbol` type, reads the member of the receiver's type
|
|
4
4
|
* whose computed name is that same constant, and a store through the access writes it.
|
|
5
|
-
* No name node resolves to such a member: the key's name resolves to the constant.
|
|
5
|
+
* No name node resolves to such a member: the key's name resolves to the constant. An
|
|
6
|
+
* iteration reads the member its well-known iteration symbol keys.
|
|
6
7
|
*/
|
|
7
|
-
import { isComputedPropertyName, isIdentifier, isNoSubstitutionTemplateLiteral, isNumericLiteral, isParenthesizedExpression, isPropertyAccessExpression, isStringLiteral, } from "@typescript/native/ast";
|
|
8
|
+
import { isComputedPropertyName, isForOfStatement, isIdentifier, isNoSubstitutionTemplateLiteral, isNumericLiteral, isParenthesizedExpression, isPropertyAccessExpression, isSpreadElement, isStringLiteral, } from "@typescript/native/ast";
|
|
9
|
+
import { nodeKey } from "./inventory.js";
|
|
10
|
+
import { renderPosition } from "./position.js";
|
|
8
11
|
import { isAnswered } from "./query.js";
|
|
9
12
|
/** The identifier an element access's argument names its key by, where it is a name. */
|
|
10
13
|
export function keyNameOf(argument) {
|
|
@@ -112,3 +115,101 @@ export function keyedUses(project, chains, resolved, properties, cap) {
|
|
|
112
115
|
return ids.length === 0 ? [] : [{ access: one.access, ids: [...new Set(ids)] }];
|
|
113
116
|
});
|
|
114
117
|
}
|
|
118
|
+
/** The member name `[Symbol.<name>]` spells, where a computed name spells one. */
|
|
119
|
+
function wellKnownName(name) {
|
|
120
|
+
if (name === undefined || !isComputedPropertyName(name)) {
|
|
121
|
+
return undefined;
|
|
122
|
+
}
|
|
123
|
+
const key = name.expression;
|
|
124
|
+
return isPropertyAccessExpression(key) &&
|
|
125
|
+
isIdentifier(key.expression) &&
|
|
126
|
+
key.expression.text === "Symbol" &&
|
|
127
|
+
isIdentifier(key.name)
|
|
128
|
+
? key.name.text
|
|
129
|
+
: undefined;
|
|
130
|
+
}
|
|
131
|
+
/**
|
|
132
|
+
* The references the iterations of one project's own files make to the iteration members
|
|
133
|
+
* of the values they iterate: a `for…of` statement and a spread element read
|
|
134
|
+
* `[Symbol.iterator]`, and a `for await…of` statement reads `[Symbol.asyncIterator]` and,
|
|
135
|
+
* which it falls back to, `[Symbol.iterator]`. Each is made by the declaration holding it.
|
|
136
|
+
*/
|
|
137
|
+
export function iterationReferences(project, held, chains, root, testFiles) {
|
|
138
|
+
const queries = project.queries;
|
|
139
|
+
const ownPaths = project.ownPaths();
|
|
140
|
+
const iterated = [];
|
|
141
|
+
for (const file of project.ownSourceFiles()) {
|
|
142
|
+
const fileId = held.declarations.get(nodeKey(file, file));
|
|
143
|
+
if (fileId === undefined) {
|
|
144
|
+
continue;
|
|
145
|
+
}
|
|
146
|
+
const visit = (node, from) => {
|
|
147
|
+
const holder = held.declarations.get(nodeKey(file, node)) ?? from;
|
|
148
|
+
if (isForOfStatement(node)) {
|
|
149
|
+
const protocols = node.awaitModifier === undefined ? ["iterator"] : ["asyncIterator", "iterator"];
|
|
150
|
+
iterated.push({ file, from: holder, operand: node.expression, protocols });
|
|
151
|
+
}
|
|
152
|
+
else if (isSpreadElement(node)) {
|
|
153
|
+
iterated.push({ file, from: holder, operand: node.expression, protocols: ["iterator"] });
|
|
154
|
+
}
|
|
155
|
+
node.forEachChild((child) => {
|
|
156
|
+
visit(child, holder);
|
|
157
|
+
});
|
|
158
|
+
};
|
|
159
|
+
file.forEachChild((child) => {
|
|
160
|
+
visit(child, fileId);
|
|
161
|
+
});
|
|
162
|
+
}
|
|
163
|
+
if (iterated.length === 0) {
|
|
164
|
+
return [];
|
|
165
|
+
}
|
|
166
|
+
const types = queries.typesAt(iterated.map((one) => one.operand));
|
|
167
|
+
const members = new Map();
|
|
168
|
+
const membersOf = (type) => {
|
|
169
|
+
const known = members.get(type.id);
|
|
170
|
+
if (known !== undefined) {
|
|
171
|
+
return known;
|
|
172
|
+
}
|
|
173
|
+
const keyed = [];
|
|
174
|
+
const properties = queries.propertiesOf(type);
|
|
175
|
+
for (const property of isAnswered(properties) ? properties : []) {
|
|
176
|
+
for (const handle of property.declarations) {
|
|
177
|
+
const node = ownPaths.has(handle.path) ? project.declarationAt(handle)?.node : undefined;
|
|
178
|
+
const name = wellKnownName(node?.name);
|
|
179
|
+
if (name !== undefined) {
|
|
180
|
+
keyed.push({ name, ids: chains.declarationsOf(property) });
|
|
181
|
+
break;
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
members.set(type.id, keyed);
|
|
186
|
+
return keyed;
|
|
187
|
+
};
|
|
188
|
+
const found = [];
|
|
189
|
+
iterated.forEach((one, index) => {
|
|
190
|
+
const type = types[index];
|
|
191
|
+
if (type === undefined || !isAnswered(type)) {
|
|
192
|
+
return;
|
|
193
|
+
}
|
|
194
|
+
const parts = type.isUnionType() || type.isIntersectionType() ? queries.constituents(type) : [type];
|
|
195
|
+
const position = renderPosition(one.file, root, one.operand.getStart());
|
|
196
|
+
for (const part of isAnswered(parts) ? parts : []) {
|
|
197
|
+
for (const member of membersOf(part)) {
|
|
198
|
+
if (!one.protocols.includes(member.name)) {
|
|
199
|
+
continue;
|
|
200
|
+
}
|
|
201
|
+
for (const to of member.ids) {
|
|
202
|
+
found.push({
|
|
203
|
+
from: one.from,
|
|
204
|
+
to,
|
|
205
|
+
position,
|
|
206
|
+
use: "read",
|
|
207
|
+
resolution: "keyed",
|
|
208
|
+
test: testFiles.has(position.path),
|
|
209
|
+
});
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
});
|
|
214
|
+
return found;
|
|
215
|
+
}
|
|
@@ -14,7 +14,7 @@
|
|
|
14
14
|
import { readComponent } from "./component-files.js";
|
|
15
15
|
import { ConfigError } from "./config.js";
|
|
16
16
|
import { memberNames } from "./member-names.js";
|
|
17
|
-
import { isAbsolutePath, joinPath, normalizePath, relativePath } from "./paths.js";
|
|
17
|
+
import { extensionOf, isAbsolutePath, joinPath, normalizePath, relativePath } from "./paths.js";
|
|
18
18
|
import { isComponentFile } from "./position.js";
|
|
19
19
|
/** The kinds of declaration an action can name. */
|
|
20
20
|
const READ_BY_A_TEMPLATE = new Set([
|
|
@@ -87,7 +87,10 @@ function stringEnd(text, from) {
|
|
|
87
87
|
}
|
|
88
88
|
return at + 1;
|
|
89
89
|
}
|
|
90
|
-
/**
|
|
90
|
+
/**
|
|
91
|
+
* The end of the `${` substitution whose expression starts at `from`: past its closing brace,
|
|
92
|
+
* or one past the text's end where none closes it, so the expression is the text to its end.
|
|
93
|
+
*/
|
|
91
94
|
function substitutionEnd(text, from) {
|
|
92
95
|
let depth = 1;
|
|
93
96
|
let at = from;
|
|
@@ -112,7 +115,7 @@ function substitutionEnd(text, from) {
|
|
|
112
115
|
}
|
|
113
116
|
at += 1;
|
|
114
117
|
}
|
|
115
|
-
return
|
|
118
|
+
return text.length + 1;
|
|
116
119
|
}
|
|
117
120
|
/** The end of the template literal at `from`; each `${…}` expression goes to `substitution`. */
|
|
118
121
|
function templateEnd(text, from, substitution) {
|
|
@@ -198,8 +201,8 @@ function namesOf(text, delimiters) {
|
|
|
198
201
|
}
|
|
199
202
|
}
|
|
200
203
|
/** Every identifier the markup of one component file writes in an action. */
|
|
201
|
-
function markupNamesOf(
|
|
202
|
-
return readComponent(text).markup.flatMap((one) => identifiersOf(one.text, true).map(({ name, offset }) => ({
|
|
204
|
+
function markupNamesOf(file) {
|
|
205
|
+
return readComponent(file.text, extensionOf(file.path)).markup.flatMap((one) => identifiersOf(one.text, true).map(({ name, offset }) => ({
|
|
203
206
|
name,
|
|
204
207
|
offset: one.start + offset,
|
|
205
208
|
action: one.action,
|
|
@@ -238,7 +241,7 @@ export function templateField(input) {
|
|
|
238
241
|
names: namesOf(file.text, templates.delimiters),
|
|
239
242
|
markup: false,
|
|
240
243
|
})),
|
|
241
|
-
...components.map((file) => ({ file, names: markupNamesOf(file
|
|
244
|
+
...components.map((file) => ({ file, names: markupNamesOf(file), markup: true })),
|
|
242
245
|
];
|
|
243
246
|
for (const { file, names, markup } of scanned) {
|
|
244
247
|
for (const named of names) {
|
package/dist/src/test-support.js
CHANGED
|
@@ -1,19 +1,116 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Test-support code: the files only tests reach
|
|
3
|
-
*
|
|
4
|
-
* included, is written in a test file or in
|
|
5
|
-
* tests: what it references is referenced by
|
|
2
|
+
* Test-support code: the files only tests reach that carry test evidence of their own. A
|
|
3
|
+
* test reaches each one through a chain of references, and every reference to a
|
|
4
|
+
* declaration of one, an import for its effects included, is written in a test file or in
|
|
5
|
+
* another such file, so it is judged with the tests: what it references is referenced by
|
|
6
|
+
* test code.
|
|
6
7
|
*/
|
|
8
|
+
import { CONVENTION_ROWS } from "./convention-rows.js";
|
|
9
|
+
import { packageOfSpecifier } from "./dependencies.js";
|
|
7
10
|
/** The root kind that never takes a file out of test-support code: the skip only withholds. */
|
|
8
11
|
const SKIP_ROOT = "type-error";
|
|
12
|
+
/**
|
|
13
|
+
* The test runners and test libraries an import of which is test evidence: each runner a
|
|
14
|
+
* convention row reads test files for, the platform's own runner, and the libraries below.
|
|
15
|
+
*/
|
|
16
|
+
const TEST_PACKAGES = new Set([
|
|
17
|
+
...CONVENTION_ROWS.filter((row) => row.testFiles !== undefined).map((row) => row.package),
|
|
18
|
+
"node:test",
|
|
19
|
+
"@playwright/test",
|
|
20
|
+
]);
|
|
21
|
+
/** The scopes every package of which is a test library. */
|
|
22
|
+
const TEST_SCOPES = ["@testing-library/"];
|
|
23
|
+
/**
|
|
24
|
+
* The Node.js built-in modules a bare specifier names, which are no package. The set is
|
|
25
|
+
* closed: Node.js adds a new built-in under the `node:` scheme alone
|
|
26
|
+
* (https://nodejs.org/api/modules.html#built-in-modules-with-mandatory-node-prefix).
|
|
27
|
+
*/
|
|
28
|
+
const NODE_BUILTINS = new Set([
|
|
29
|
+
"assert",
|
|
30
|
+
"async_hooks",
|
|
31
|
+
"buffer",
|
|
32
|
+
"child_process",
|
|
33
|
+
"cluster",
|
|
34
|
+
"console",
|
|
35
|
+
"constants",
|
|
36
|
+
"crypto",
|
|
37
|
+
"dgram",
|
|
38
|
+
"diagnostics_channel",
|
|
39
|
+
"dns",
|
|
40
|
+
"domain",
|
|
41
|
+
"events",
|
|
42
|
+
"fs",
|
|
43
|
+
"http",
|
|
44
|
+
"http2",
|
|
45
|
+
"https",
|
|
46
|
+
"inspector",
|
|
47
|
+
"module",
|
|
48
|
+
"net",
|
|
49
|
+
"os",
|
|
50
|
+
"path",
|
|
51
|
+
"perf_hooks",
|
|
52
|
+
"process",
|
|
53
|
+
"punycode",
|
|
54
|
+
"querystring",
|
|
55
|
+
"readline",
|
|
56
|
+
"repl",
|
|
57
|
+
"stream",
|
|
58
|
+
"string_decoder",
|
|
59
|
+
"sys",
|
|
60
|
+
"timers",
|
|
61
|
+
"tls",
|
|
62
|
+
"trace_events",
|
|
63
|
+
"tty",
|
|
64
|
+
"url",
|
|
65
|
+
"util",
|
|
66
|
+
"v8",
|
|
67
|
+
"vm",
|
|
68
|
+
"wasi",
|
|
69
|
+
"worker_threads",
|
|
70
|
+
"zlib",
|
|
71
|
+
]);
|
|
72
|
+
/** Whether a specifier names a test runner or a test library. */
|
|
73
|
+
function namesTestPackage(specifier) {
|
|
74
|
+
const name = specifier.startsWith("node:") ? specifier : packageOfSpecifier(specifier);
|
|
75
|
+
return (name !== undefined &&
|
|
76
|
+
(TEST_PACKAGES.has(name) || TEST_SCOPES.some((scope) => name.startsWith(scope))));
|
|
77
|
+
}
|
|
78
|
+
/**
|
|
79
|
+
* Whether a file carries test evidence: an import of a test runner or a test library, or
|
|
80
|
+
* package imports, Node built-ins aside, that are all devDependencies no file imports
|
|
81
|
+
* outside the test files and `onlyTests`, the files only tests reach.
|
|
82
|
+
*/
|
|
83
|
+
function evidenceReader(tests, evidence) {
|
|
84
|
+
const importers = new Map();
|
|
85
|
+
for (const [path, specifiers] of evidence.imports) {
|
|
86
|
+
for (const specifier of specifiers) {
|
|
87
|
+
const name = packageOfSpecifier(specifier);
|
|
88
|
+
if (name !== undefined) {
|
|
89
|
+
importers.set(name, [...(importers.get(name) ?? []), path]);
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
return (onlyTests, path) => {
|
|
94
|
+
const specifiers = evidence.imports.get(path) ?? [];
|
|
95
|
+
if (specifiers.some(namesTestPackage)) {
|
|
96
|
+
return true;
|
|
97
|
+
}
|
|
98
|
+
const names = specifiers.flatMap((one) => packageOfSpecifier(one) ?? []);
|
|
99
|
+
const packages = names.filter((name) => !NODE_BUILTINS.has(name));
|
|
100
|
+
return (packages.length > 0 &&
|
|
101
|
+
packages.every((name) => evidence.devDependency(path, name) &&
|
|
102
|
+
(importers.get(name) ?? []).every((one) => tests.has(one) || onlyTests.has(one))));
|
|
103
|
+
};
|
|
104
|
+
}
|
|
9
105
|
/**
|
|
10
106
|
* The files of one project that are test-support code, below the target root: the
|
|
11
107
|
* largest set of files a test file reaches, none of them a test file or holding a root,
|
|
12
|
-
* whose every user outside the set is a test
|
|
13
|
-
* reference graph: a runtime, a tool, a
|
|
14
|
-
* published API. `testFiles` are the files
|
|
108
|
+
* each carrying test evidence of its own, whose every user outside the set is a test
|
|
109
|
+
* file. A root has a caller outside the reference graph: a runtime, a tool, a
|
|
110
|
+
* configuration, or a consumer of a library's published API. `testFiles` are the files
|
|
111
|
+
* the test-file rules classified.
|
|
15
112
|
*/
|
|
16
|
-
export function testSupportFiles(symbols, references, roots, testFiles) {
|
|
113
|
+
export function testSupportFiles(symbols, references, roots, testFiles, evidence) {
|
|
17
114
|
const fileOf = new Map(symbols.map((symbol) => [symbol.id, symbol.position.path]));
|
|
18
115
|
const users = new Map();
|
|
19
116
|
const uses = new Map();
|
|
@@ -47,17 +144,24 @@ export function testSupportFiles(symbols, references, roots, testFiles) {
|
|
|
47
144
|
}
|
|
48
145
|
}
|
|
49
146
|
}
|
|
50
|
-
const
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
147
|
+
const withoutOutsideUsers = (files) => {
|
|
148
|
+
for (let changed = true; changed;) {
|
|
149
|
+
changed = false;
|
|
150
|
+
for (const path of files) {
|
|
151
|
+
const from = users.get(path) ?? new Set();
|
|
152
|
+
if (![...from].every((one) => tests.has(one) || files.has(one))) {
|
|
153
|
+
files.delete(path);
|
|
154
|
+
changed = true;
|
|
155
|
+
}
|
|
58
156
|
}
|
|
59
157
|
}
|
|
60
|
-
|
|
158
|
+
return files;
|
|
159
|
+
};
|
|
160
|
+
// Evidence reads the files only tests reach, evidence or none, so it is fixed before the
|
|
161
|
+
// files without evidence leave the set.
|
|
162
|
+
const onlyTests = withoutOutsideUsers(new Set([...reached].filter((path) => !rooted.has(path))));
|
|
163
|
+
const carries = evidenceReader(tests, evidence);
|
|
164
|
+
const support = withoutOutsideUsers(new Set([...onlyTests].filter((path) => carries(onlyTests, path))));
|
|
61
165
|
return support;
|
|
62
166
|
}
|
|
63
167
|
/** The references with every one a test-support file makes counted as a test reference. */
|