@cplieger/deadset-ts 7.1.0-dev.2 → 7.2.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/src/alias-chain.js +6 -2
- package/dist/src/analysis.js +3 -1
- package/dist/src/config.js +4 -0
- package/dist/src/conformance-record.js +2 -2
- package/dist/src/decorator.js +3 -2
- package/dist/src/findings/unused-declarations.js +3 -3
- package/dist/src/injection-container.js +28 -25
- package/dist/src/intra-function-parts.js +72 -11
- package/dist/src/inventory.js +18 -3
- package/dist/src/kinds.js +2 -2
- package/dist/src/ref.js +3 -2
- package/dist/src/references.js +38 -20
- package/dist/src/report.js +1 -1
- package/dist/src/resolve.js +37 -4
- package/dist/src/schema.js +4 -1
- package/dist/src/suppress-file.js +43 -9
- package/dist/src/suppress-inline.js +35 -12
- package/dist/src/suppress.js +3 -2
- package/dist/src/verbs/analyze.js +7 -3
- package/dist/src/version.js +1 -1
- package/jsr.json +1 -1
- package/package.json +1 -1
- package/src/alias-chain.ts +7 -2
- package/src/analysis.ts +3 -3
- package/src/config.ts +9 -1
- package/src/conformance-record.ts +2 -2
- package/src/decorator.ts +8 -2
- package/src/findings/unused-declarations.ts +3 -3
- package/src/injection-container.ts +31 -26
- package/src/intra-function-parts.ts +95 -11
- package/src/inventory.ts +26 -2
- package/src/kinds.ts +2 -2
- package/src/ref.ts +3 -2
- package/src/references.ts +55 -21
- package/src/report.ts +1 -1
- package/src/resolve.ts +56 -6
- package/src/schema.ts +10 -14
- package/src/signatures.ts +6 -1
- package/src/suppress-file.ts +48 -9
- package/src/suppress-inline.ts +57 -15
- package/src/suppress.ts +3 -2
- package/src/verbs/analyze.ts +7 -2
- package/src/version.ts +1 -1
package/dist/src/alias-chain.js
CHANGED
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
*/
|
|
6
6
|
import { isExportAssignment, isExportSpecifier, isIdentifier, isImportClause, isImportSpecifier, isNamespaceExport, isNamespaceImport, } from "@typescript/native/ast";
|
|
7
7
|
import { SymbolFlags } from "@typescript/native/sync";
|
|
8
|
-
import { declarationsByName, nodeKey } from "./inventory.js";
|
|
8
|
+
import { declarationsByName, isParameterProperty, nodeKey } from "./inventory.js";
|
|
9
9
|
import { UNANSWERED } from "./query.js";
|
|
10
10
|
/**
|
|
11
11
|
* The chain walks over one project, against the declarations of `held`, the same
|
|
@@ -28,7 +28,11 @@ export function aliasChains(project, held) {
|
|
|
28
28
|
}
|
|
29
29
|
const node = project.declarationAt(handle)?.node;
|
|
30
30
|
const id = node === undefined ? undefined : held.declarations.get(nodeKey(node.getSourceFile(), node));
|
|
31
|
-
|
|
31
|
+
// A parameter property's node declares the parameter too, whose reads are no member's.
|
|
32
|
+
const parameter = node !== undefined &&
|
|
33
|
+
isParameterProperty(node) &&
|
|
34
|
+
(symbol.flags & SymbolFlags.Property) === 0;
|
|
35
|
+
if (id !== undefined && !parameter && !ids.includes(id)) {
|
|
32
36
|
ids.push(id);
|
|
33
37
|
}
|
|
34
38
|
}
|
package/dist/src/analysis.js
CHANGED
|
@@ -6,6 +6,7 @@
|
|
|
6
6
|
* another project's checker, which holds only for views the session hands out.
|
|
7
7
|
*/
|
|
8
8
|
import { lineOf, readComponent } from "./component-files.js";
|
|
9
|
+
import { kindOf } from "./config.js";
|
|
9
10
|
import { consumerLoads, consumerReferences, moduleIdentity, } from "./consumers.js";
|
|
10
11
|
import { configuredDeclarations, configuredRef, declarationsNamed, } from "./configured-declarations.js";
|
|
11
12
|
import { decorator } from "./decorator.js";
|
|
@@ -33,6 +34,7 @@ import { packageEntries, readManifest } from "./manifest.js";
|
|
|
33
34
|
import { liveFilesAt, matrixOf, referenceKey, sweepMatrix, } from "./matrix.js";
|
|
34
35
|
import { dirnamePath, joinPath, normalizePath, relativePath, resolvePath } from "./paths.js";
|
|
35
36
|
import { byPosition, isComponentFile } from "./position.js";
|
|
37
|
+
import { LANGUAGE } from "./report.js";
|
|
36
38
|
import { SetupError, setupLine } from "./setup-failure.js";
|
|
37
39
|
import { pathsOf, readErrors, skippedRoots, skippedUnits, } from "./type-errors.js";
|
|
38
40
|
import { reflectiveLookup } from "./reflective-lookup.js";
|
|
@@ -158,7 +160,7 @@ function readProjects(engine, host, scope, config, stage) {
|
|
|
158
160
|
conventions,
|
|
159
161
|
testFiles: config.ts.testFiles,
|
|
160
162
|
parse,
|
|
161
|
-
publishedAPI: config.targetKind === "library",
|
|
163
|
+
publishedAPI: config.targetKind !== "" && kindOf(config.targetKind, LANGUAGE) === "library",
|
|
162
164
|
publishedMembers: new Set((workspace?.entries ?? []).filter((entry) => entry.published).map((entry) => entry.dir)),
|
|
163
165
|
};
|
|
164
166
|
};
|
package/dist/src/config.js
CHANGED
|
@@ -5,6 +5,10 @@ import { CONTRACT_VERSION } from "./version.js";
|
|
|
5
5
|
* target supplied the setting it is about.
|
|
6
6
|
*/
|
|
7
7
|
export const REPOSITORY_DOCUMENT = "deadset.json";
|
|
8
|
+
/** The kind a target kind setting gives one language's declarations. */
|
|
9
|
+
export function kindOf(setting, language) {
|
|
10
|
+
return typeof setting === "string" ? setting : setting[language];
|
|
11
|
+
}
|
|
8
12
|
/** The severities ranked, the strongest highest. */
|
|
9
13
|
const SEVERITY_RANK = { allow: 0, warn: 1, deny: 2 };
|
|
10
14
|
/** Whether a finding at one severity fails a run under the failing severity. */
|
|
@@ -2,9 +2,9 @@
|
|
|
2
2
|
// Regenerate it with `UPDATE_GOLDEN=1 npx vitest --run src/conformance.corpus.test.ts`.
|
|
3
3
|
export const RECORD = {
|
|
4
4
|
conformance: {
|
|
5
|
-
corpusVersion: "5.
|
|
5
|
+
corpusVersion: "5.2.0",
|
|
6
6
|
result: "pass",
|
|
7
|
-
digest: "sha256:
|
|
7
|
+
digest: "sha256:4050d1b67fb757bc12a60f8ece7317a3e8485d3383ec52919cd24b9dfd5509dc",
|
|
8
8
|
},
|
|
9
9
|
gaps: [],
|
|
10
10
|
};
|
package/dist/src/decorator.js
CHANGED
|
@@ -11,7 +11,7 @@
|
|
|
11
11
|
* the inventory holds for the class, static members included.
|
|
12
12
|
*/
|
|
13
13
|
import { decoratorsOf, spelledDecorator } from "./calls.js";
|
|
14
|
-
import { nodeKey } from "./inventory.js";
|
|
14
|
+
import { isParameterProperty, nodeKey, } from "./inventory.js";
|
|
15
15
|
import { renderPosition } from "./position.js";
|
|
16
16
|
/** The kinds of declaration a decorator is attached to and holds back. */
|
|
17
17
|
const DECORATED = new Set(["class", "method", "class-member"]);
|
|
@@ -39,7 +39,8 @@ export function decorator(input) {
|
|
|
39
39
|
const visit = (node) => {
|
|
40
40
|
const id = held.declarations.get(nodeKey(file, node));
|
|
41
41
|
const symbol = id === undefined ? undefined : byId.get(id);
|
|
42
|
-
|
|
42
|
+
// A parameter property's decorator is attached to its parameter, and to no member.
|
|
43
|
+
if (symbol !== undefined && DECORATED.has(symbol.kind) && !isParameterProperty(node)) {
|
|
43
44
|
for (const attached of decoratorsOf(node)) {
|
|
44
45
|
const detail = `decorated by ${spelledDecorator(attached)}`;
|
|
45
46
|
const site = renderPosition(file, targetRoot, attached.getStart());
|
|
@@ -54,13 +54,13 @@ function codeOf(judged) {
|
|
|
54
54
|
NOT_THIS_FAMILY.has(symbol.kind)) {
|
|
55
55
|
return undefined;
|
|
56
56
|
}
|
|
57
|
-
if (candidate.unreferencedTest) {
|
|
58
|
-
return unreferencedCode(symbol);
|
|
59
|
-
}
|
|
60
57
|
const referenced = candidate.productionRefs + candidate.testRefs > 0;
|
|
61
58
|
if (symbol.kind === "enum-member" && !referenced) {
|
|
62
59
|
return undefined;
|
|
63
60
|
}
|
|
61
|
+
if (candidate.unreferencedTest) {
|
|
62
|
+
return unreferencedCode(symbol);
|
|
63
|
+
}
|
|
64
64
|
if (candidate.heldByTests === candidate.id) {
|
|
65
65
|
return "DS1004";
|
|
66
66
|
}
|
|
@@ -1,23 +1,25 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* The injection-container class: a container constructs a class passed to a call
|
|
3
3
|
* `ts.injection_registrations` names, or one a parameter of whose constructor carries a
|
|
4
|
-
* decorator,
|
|
5
|
-
*
|
|
6
|
-
*
|
|
4
|
+
* decorator, passes every argument of its constructor, and populates each of its
|
|
5
|
+
* properties and accessors that carries a decorator, a base class's included. So each
|
|
6
|
+
* parameter property is injected, decorated or not. A constructor is no declaration of
|
|
7
|
+
* the inventory, so the class stands for it and the registration referencing it keeps it.
|
|
7
8
|
*/
|
|
8
9
|
import { isClassDeclaration, isClassExpression, isConstructorDeclaration, isGetAccessorDeclaration, isPropertyDeclaration, isSetAccessorDeclaration, } from "@typescript/native/ast";
|
|
9
10
|
import { aliasChains } from "./alias-chain.js";
|
|
10
11
|
import { calleeName, callsOf, classesPassed, decoratorsOf, spelledDecorator } from "./calls.js";
|
|
11
12
|
import { classMembers } from "./class-members.js";
|
|
12
13
|
import { entriesAt, namesADeclaration, resolveEntries, spelledEntry, } from "./configured-declarations.js";
|
|
13
|
-
import { nodeKey } from "./inventory.js";
|
|
14
|
+
import { isParameterProperty, nodeKey } from "./inventory.js";
|
|
14
15
|
import { renderPosition } from "./position.js";
|
|
15
|
-
/** Whether one member declaration is a property or
|
|
16
|
+
/** Whether one member declaration is a parameter property, or a property or accessor carrying a decorator. */
|
|
16
17
|
function isInjected(node) {
|
|
17
|
-
return ((
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
18
|
+
return (isParameterProperty(node) ||
|
|
19
|
+
((isPropertyDeclaration(node) ||
|
|
20
|
+
isGetAccessorDeclaration(node) ||
|
|
21
|
+
isSetAccessorDeclaration(node)) &&
|
|
22
|
+
decoratorsOf(node).length > 0));
|
|
21
23
|
}
|
|
22
24
|
/** The classes of the target a call whose callee an entry names passes, with that call's entry. */
|
|
23
25
|
function registrations(input) {
|
|
@@ -43,18 +45,20 @@ function registrations(input) {
|
|
|
43
45
|
return uses.map((use) => ({ id: use.id, site: use.site, detail }));
|
|
44
46
|
});
|
|
45
47
|
}
|
|
46
|
-
/**
|
|
48
|
+
/**
|
|
49
|
+
* The classes of the target a parameter of whose constructor carries a decorator, each
|
|
50
|
+
* with the first such decorator.
|
|
51
|
+
*/
|
|
47
52
|
function constructedForParameters(input) {
|
|
48
53
|
const { project, held } = input;
|
|
49
|
-
const found = new
|
|
54
|
+
const found = new Map();
|
|
50
55
|
for (const file of project.ownSourceFiles()) {
|
|
51
56
|
const visit = (node) => {
|
|
52
57
|
if (isClassDeclaration(node) || isClassExpression(node)) {
|
|
53
58
|
const id = held.declarations.get(nodeKey(file, node));
|
|
54
|
-
const asks = node.members.
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
found.add(id);
|
|
59
|
+
const [asks] = node.members.flatMap((member) => isConstructorDeclaration(member) ? member.parameters.flatMap(decoratorsOf) : []);
|
|
60
|
+
if (id !== undefined && asks !== undefined) {
|
|
61
|
+
found.set(id, asks);
|
|
58
62
|
}
|
|
59
63
|
}
|
|
60
64
|
node.forEachChild(visit);
|
|
@@ -72,7 +76,7 @@ export function injectionContainer(input) {
|
|
|
72
76
|
const { project, held, targetRoot } = input;
|
|
73
77
|
const registered = registrations(input);
|
|
74
78
|
const constructed = constructedForParameters(input);
|
|
75
|
-
const classes = new Set([...registered.map((one) => one.id), ...constructed]);
|
|
79
|
+
const classes = new Set([...registered.map((one) => one.id), ...constructed.keys()]);
|
|
76
80
|
if (classes.size === 0) {
|
|
77
81
|
return [];
|
|
78
82
|
}
|
|
@@ -85,16 +89,15 @@ export function injectionContainer(input) {
|
|
|
85
89
|
found.push({ id: member.id, detail: registration.detail, site: at(registration.site) });
|
|
86
90
|
}
|
|
87
91
|
}
|
|
88
|
-
for (const id of constructed) {
|
|
92
|
+
for (const [id, asks] of constructed) {
|
|
89
93
|
for (const member of injected(id)) {
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
}
|
|
94
|
+
// An undecorated parameter property is passed because a sibling parameter asks.
|
|
95
|
+
const [decorator = asks] = decoratorsOf(member.node);
|
|
96
|
+
found.push({
|
|
97
|
+
id: member.id,
|
|
98
|
+
detail: `injected by ${spelledDecorator(decorator)}`,
|
|
99
|
+
site: at(decorator),
|
|
100
|
+
});
|
|
98
101
|
}
|
|
99
102
|
}
|
|
100
103
|
return found;
|
|
@@ -4,8 +4,8 @@
|
|
|
4
4
|
* call sites that use or discard it, each statement control flow cannot reach, each
|
|
5
5
|
* write to a local that no read reaches, and each case an earlier case covers.
|
|
6
6
|
*/
|
|
7
|
-
import { isArrayBindingPattern, isArrowFunction, isAwaitExpression, isBigIntLiteral, isBinaryExpression, isBlock, isBreakStatement, isBindingElement, isCallExpression, isCaseClause, isCatchClause, isClassDeclaration, isClassExpression, isContinueStatement, isDecorator, isDefaultClause, isEmptyStatement, isExportAssignment, isExportSpecifier, isExpressionStatement, isFunctionDeclaration, isFunctionExpression, isHeritageClause, isIdentifier, isIfStatement, isImportClause, isImportSpecifier, isInterfaceDeclaration, isMethodDeclaration, isModuleDeclaration, isNamespaceImport, isNoSubstitutionTemplateLiteral, isNumericLiteral, isObjectBindingPattern, isObjectLiteralExpression, isParenthesizedExpression, isPrefixUnaryExpression, isPropertyAccessExpression, isQualifiedName, isReturnStatement, isShorthandPropertyAssignment, isStringLiteral, isSwitchStatement, isThrowStatement, isTypeAliasDeclaration, isTypeReferenceNode, isVariableStatement, isVoidExpression, NodeFlags, SyntaxKind, } from "@typescript/native/ast";
|
|
8
|
-
import { nodeKey } from "./inventory.js";
|
|
7
|
+
import { isArrayBindingPattern, isArrowFunction, isAwaitExpression, isBigIntLiteral, isBinaryExpression, isBlock, isBreakStatement, isBindingElement, isCallExpression, isCaseClause, isCatchClause, isClassDeclaration, isClassExpression, isConstructorDeclaration, isContinueStatement, isDecorator, isDefaultClause, isEmptyStatement, isExportAssignment, isExportSpecifier, isExpressionStatement, isFunctionDeclaration, isFunctionExpression, isHeritageClause, isIdentifier, isIfStatement, isImportClause, isImportSpecifier, isInterfaceDeclaration, isMethodDeclaration, isModuleDeclaration, isNamespaceImport, isNewExpression, isNoSubstitutionTemplateLiteral, isNumericLiteral, isObjectBindingPattern, isObjectLiteralExpression, isParenthesizedExpression, isPrefixUnaryExpression, isPropertyAccessExpression, isQualifiedName, isReturnStatement, isShorthandPropertyAssignment, isStringLiteral, isSwitchStatement, isThrowStatement, isTypeAliasDeclaration, isTypeReferenceNode, isVariableStatement, isVoidExpression, NodeFlags, SyntaxKind, } from "@typescript/native/ast";
|
|
8
|
+
import { isParameterProperty, nodeKey } from "./inventory.js";
|
|
9
9
|
import { positionKey, renderPosition } from "./position.js";
|
|
10
10
|
import { isAnswered, UNANSWERED } from "./query.js";
|
|
11
11
|
import { signatureOf } from "./signatures.js";
|
|
@@ -234,6 +234,39 @@ function hierarchyFixes(declared, extended) {
|
|
|
234
234
|
}
|
|
235
235
|
return owner.name !== undefined && extended.has(owner.name.text);
|
|
236
236
|
}
|
|
237
|
+
/** Whether a decorator on a constructor's class or on one of its parameters fixes its signature. */
|
|
238
|
+
function decoratorFixes(constructor) {
|
|
239
|
+
const owner = constructor.parent;
|
|
240
|
+
return ((owner.modifiers ?? []).some(isDecorator) ||
|
|
241
|
+
constructor.parameters.some((parameter) => (parameter.modifiers ?? []).some(isDecorator)));
|
|
242
|
+
}
|
|
243
|
+
/** The name an expression spells a class by: an identifier, or the member a property access names. */
|
|
244
|
+
function spelledName(expression) {
|
|
245
|
+
const node = unwrapped(expression);
|
|
246
|
+
if (isIdentifier(node)) {
|
|
247
|
+
return node;
|
|
248
|
+
}
|
|
249
|
+
return isPropertyAccessExpression(node) && isIdentifier(node.name) ? node.name : undefined;
|
|
250
|
+
}
|
|
251
|
+
/**
|
|
252
|
+
* The names one node writes that use a class without using its constructor as a value:
|
|
253
|
+
* the class a `new` expression calls, the class an `extends` or `implements` clause
|
|
254
|
+
* names, and the name a type reference spells.
|
|
255
|
+
*/
|
|
256
|
+
function constructionNames(node) {
|
|
257
|
+
if (isNewExpression(node)) {
|
|
258
|
+
return [spelledName(node.expression) ?? []].flat();
|
|
259
|
+
}
|
|
260
|
+
if (isHeritageClause(node) &&
|
|
261
|
+
(isClassDeclaration(node.parent) || isClassExpression(node.parent))) {
|
|
262
|
+
return node.types.flatMap((type) => isTypeReferenceNode(type) ? [] : (spelledName(type.expression) ?? []));
|
|
263
|
+
}
|
|
264
|
+
if (isTypeReferenceNode(node)) {
|
|
265
|
+
const named = isQualifiedName(node.typeName) ? node.typeName.right : node.typeName;
|
|
266
|
+
return isIdentifier(named) ? [named] : [];
|
|
267
|
+
}
|
|
268
|
+
return [];
|
|
269
|
+
}
|
|
237
270
|
/**
|
|
238
271
|
* The symbol identifier each of a batch of identifiers resolves to, where the batch
|
|
239
272
|
* resolves it, and {@link UNANSWERED} where the checker did not answer.
|
|
@@ -275,12 +308,16 @@ export function projectParts(project, held, references, targetRoot) {
|
|
|
275
308
|
const signatures = [];
|
|
276
309
|
const calls = new Map();
|
|
277
310
|
const specifiers = new Set();
|
|
311
|
+
const constructions = new Set();
|
|
278
312
|
const sites = [];
|
|
279
313
|
const contextual = [];
|
|
280
314
|
for (const file of files) {
|
|
315
|
+
// A parameter property's own parts stay with the parameter when its member goes.
|
|
281
316
|
const declarationAt = (node) => {
|
|
282
317
|
const id = held.declarations.get(nodeKey(file, node));
|
|
283
|
-
return id === undefined || kinds.get(id) === "file"
|
|
318
|
+
return id === undefined || kinds.get(id) === "file" || isParameterProperty(node)
|
|
319
|
+
? undefined
|
|
320
|
+
: id;
|
|
284
321
|
};
|
|
285
322
|
// A part no declaration holds belongs to the file, which is judged while the file is live.
|
|
286
323
|
const fileId = held.declarations.get(nodeKey(file, file));
|
|
@@ -332,6 +369,17 @@ export function projectParts(project, held, references, targetRoot) {
|
|
|
332
369
|
walk.declared.push({ id, node, signature });
|
|
333
370
|
covered.add(signature);
|
|
334
371
|
}
|
|
372
|
+
// A constructor is no declaration, so the class it belongs to holds its parts.
|
|
373
|
+
const constructs = isConstructorDeclaration(node) && node.body !== undefined
|
|
374
|
+
? declarationAt(node.parent)
|
|
375
|
+
: undefined;
|
|
376
|
+
if (constructs !== undefined && isConstructorDeclaration(node)) {
|
|
377
|
+
walk.declared.push({ id: constructs, node, signature: node });
|
|
378
|
+
covered.add(node);
|
|
379
|
+
}
|
|
380
|
+
for (const name of constructionNames(node)) {
|
|
381
|
+
constructions.add(position(name));
|
|
382
|
+
}
|
|
335
383
|
const value = covered.has(node) ? undefined : valueSignature(node);
|
|
336
384
|
const valueHolder = id ?? holderOf(node);
|
|
337
385
|
if (value !== undefined && valueHolder !== undefined && !isStub(value)) {
|
|
@@ -413,11 +461,15 @@ export function projectParts(project, held, references, targetRoot) {
|
|
|
413
461
|
}
|
|
414
462
|
const ids = resolvedIds(project, [...new Set(asked)]);
|
|
415
463
|
for (const one of walk.declared) {
|
|
416
|
-
|
|
464
|
+
const constructor = isConstructorDeclaration(one.node);
|
|
465
|
+
if (!constructor) {
|
|
466
|
+
sites.push({ file, declared: one });
|
|
467
|
+
}
|
|
417
468
|
signatures.push({
|
|
418
469
|
declaration: one.id,
|
|
419
470
|
stub: isStub(one.signature),
|
|
420
|
-
fixed: hierarchyFixes(one.node, extended),
|
|
471
|
+
fixed: constructor ? decoratorFixes(one.node) : hierarchyFixes(one.node, extended),
|
|
472
|
+
constructor,
|
|
421
473
|
});
|
|
422
474
|
const reader = readers.get(one.signature);
|
|
423
475
|
if (reader !== undefined) {
|
|
@@ -475,7 +527,13 @@ export function projectParts(project, held, references, targetRoot) {
|
|
|
475
527
|
}
|
|
476
528
|
}
|
|
477
529
|
parts.push(...resultParts(project, targetRoot, sites, references, calls));
|
|
478
|
-
return {
|
|
530
|
+
return {
|
|
531
|
+
parts: withContextualRead(project, parts, contextual),
|
|
532
|
+
signatures,
|
|
533
|
+
calls,
|
|
534
|
+
specifiers,
|
|
535
|
+
constructions,
|
|
536
|
+
};
|
|
479
537
|
}
|
|
480
538
|
/**
|
|
481
539
|
* A function written in place that no declaration declares: an arrow, a function
|
|
@@ -496,8 +554,8 @@ function valueSignature(node) {
|
|
|
496
554
|
}
|
|
497
555
|
/**
|
|
498
556
|
* Every name one function's parameters bind, a binding pattern's names one by one. A
|
|
499
|
-
* `this` parameter receives no argument
|
|
500
|
-
*
|
|
557
|
+
* `this` parameter receives no argument, a decorated one is read by its decorator, and a
|
|
558
|
+
* parameter property is read by the write to its member, so none binds a subject.
|
|
501
559
|
*/
|
|
502
560
|
function parameterNames(signature) {
|
|
503
561
|
const found = [];
|
|
@@ -523,7 +581,8 @@ function parameterNames(signature) {
|
|
|
523
581
|
};
|
|
524
582
|
signature.parameters.forEach((parameter, at) => {
|
|
525
583
|
const name = parameter.name;
|
|
526
|
-
if ((parameter.modifiers ?? []).some(isDecorator)
|
|
584
|
+
if ((parameter.modifiers ?? []).some(isDecorator) ||
|
|
585
|
+
isParameterProperty(parameter)) {
|
|
527
586
|
return;
|
|
528
587
|
}
|
|
529
588
|
if (isIdentifier(name)) {
|
|
@@ -787,7 +846,7 @@ export function intraFunctionFacts(projects, references, exempted) {
|
|
|
787
846
|
for (const [at, value] of one.calls) {
|
|
788
847
|
calls.set(at, value);
|
|
789
848
|
}
|
|
790
|
-
for (const at of one.specifiers) {
|
|
849
|
+
for (const at of [...one.specifiers, ...one.constructions]) {
|
|
791
850
|
specifiers.add(at);
|
|
792
851
|
}
|
|
793
852
|
}
|
|
@@ -815,9 +874,11 @@ export function intraFunctionFacts(projects, references, exempted) {
|
|
|
815
874
|
const free = new Set(signatures
|
|
816
875
|
.map((one) => one.declaration)
|
|
817
876
|
.filter((id) => !fixed.has(id) && !exempted.has(id) && !valued.has(id)));
|
|
877
|
+
// A class used as a value fixes its constructor's whole signature, whatever reads follow.
|
|
878
|
+
const constructors = new Set(signatures.filter((one) => one.constructor).map((one) => one.declaration));
|
|
818
879
|
const freeButValued = new Set(signatures
|
|
819
880
|
.map((one) => one.declaration)
|
|
820
|
-
.filter((id) => !fixed.has(id) && !exempted.has(id) && valued.has(id)));
|
|
881
|
+
.filter((id) => !fixed.has(id) && !exempted.has(id) && valued.has(id) && !constructors.has(id)));
|
|
821
882
|
// Awaiting a thenable result sequences on it, so a call that awaits one uses it.
|
|
822
883
|
const sequenced = new Set([...parts.values()]
|
|
823
884
|
.filter((part) => part.thenable === true && awaited.has(part.declaration))
|
package/dist/src/inventory.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { isClassDeclaration, isComputedPropertyName, isEnumDeclaration, isEnumMember, isExportAssignment, isExportDeclaration, isFunctionDeclaration, isGetAccessorDeclaration, isIdentifier, isInterfaceDeclaration, isIntersectionTypeNode, isMethodDeclaration, isMethodSignatureDeclaration, isModuleBlock, isModuleDeclaration, isNamespaceExport, isParenthesizedTypeNode, isPrivateIdentifier, isPropertyDeclaration, isPropertySignatureDeclaration, isSetAccessorDeclaration, isStringLiteral, isTypeAliasDeclaration, isTypeLiteralNode, isUnionTypeNode, isVariableStatement, ModifierFlags, } from "@typescript/native/ast";
|
|
1
|
+
import { isClassDeclaration, isComputedPropertyName, isConstructorDeclaration, isEnumDeclaration, isEnumMember, isExportAssignment, isExportDeclaration, isFunctionDeclaration, isGetAccessorDeclaration, isIdentifier, isInterfaceDeclaration, isIntersectionTypeNode, isMethodDeclaration, isMethodSignatureDeclaration, isModuleBlock, isModuleDeclaration, isNamespaceExport, isParameterDeclaration, isParenthesizedTypeNode, isPrivateIdentifier, isPropertyDeclaration, isPropertySignatureDeclaration, isSetAccessorDeclaration, isStringLiteral, isTypeAliasDeclaration, isTypeLiteralNode, isUnionTypeNode, isVariableStatement, ModifierFlags, } from "@typescript/native/ast";
|
|
2
2
|
import { dirnamePath, joinPath, relativePath } from "./paths.js";
|
|
3
3
|
import { byPosition, positionKey, renderPosition, writtenLength, } from "./position.js";
|
|
4
4
|
import { computedComponent, nameComponent, renderRef } from "./ref.js";
|
|
@@ -232,6 +232,20 @@ function unnamedStart(node) {
|
|
|
232
232
|
const prefix = DEFAULT_EXPORT.exec(node.getText());
|
|
233
233
|
return prefix === null ? start : start + prefix[0].length;
|
|
234
234
|
}
|
|
235
|
+
/**
|
|
236
|
+
* Whether one node is a parameter property: a constructor parameter carrying an
|
|
237
|
+
* accessibility, `readonly` or `override` modifier, which also declares an instance
|
|
238
|
+
* member of the class, with the parameter's node as the member's declaration.
|
|
239
|
+
*/
|
|
240
|
+
export function isParameterProperty(node) {
|
|
241
|
+
return (isParameterDeclaration(node) &&
|
|
242
|
+
isConstructorDeclaration(node.parent) &&
|
|
243
|
+
(modifiersOf(node) & ModifierFlags.ParameterPropertyModifier) !== 0);
|
|
244
|
+
}
|
|
245
|
+
/** The parameter properties every constructor of one class declares. */
|
|
246
|
+
function parameterPropertiesOf(node) {
|
|
247
|
+
return node.members.flatMap((member) => isConstructorDeclaration(member) ? member.parameters.filter(isParameterProperty) : []);
|
|
248
|
+
}
|
|
235
249
|
/** The kind a class element takes: a method, or a property or accessor. */
|
|
236
250
|
function classMemberKind(node) {
|
|
237
251
|
return isMethodDeclaration(node) ? "method" : "class-member";
|
|
@@ -367,7 +381,7 @@ export function inventory(project, host, targetRoot) {
|
|
|
367
381
|
owner: record,
|
|
368
382
|
declaration: node,
|
|
369
383
|
nameNode: node.name,
|
|
370
|
-
members: node.members,
|
|
384
|
+
members: [...node.members, ...parameterPropertiesOf(node)],
|
|
371
385
|
memberKind: "class-member",
|
|
372
386
|
isClass: true,
|
|
373
387
|
membersAreExports: false,
|
|
@@ -677,7 +691,8 @@ export function inventory(project, host, targetRoot) {
|
|
|
677
691
|
if (isPropertyDeclaration(node) ||
|
|
678
692
|
isMethodDeclaration(node) ||
|
|
679
693
|
isGetAccessorDeclaration(node) ||
|
|
680
|
-
isSetAccessorDeclaration(node)
|
|
694
|
+
isSetAccessorDeclaration(node) ||
|
|
695
|
+
isParameterProperty(node)) {
|
|
681
696
|
return classMemberKind(node);
|
|
682
697
|
}
|
|
683
698
|
return undefined;
|
package/dist/src/kinds.js
CHANGED
|
@@ -153,7 +153,7 @@ const ROWS = [
|
|
|
153
153
|
code: "DS1301",
|
|
154
154
|
name: "write-only-symbol",
|
|
155
155
|
languages: ["go", "ts"],
|
|
156
|
-
rule: "A package-level or module-level variable, struct field, class member, type member or collection that production code writes and never reads. In production mode a read from a test file counts as no read. A write from a loaded consumer's code counts as a read, because the deletion the finding names would break a consumer the target does not own. A write in a consumer's test file counts as a read in the run that counts test references. It counts in a production sweep only where the configuration counts consumer tests as production. A comparison of struct values reads every field of the type: in Go, a value of a struct type used as an operand of `==` or `!=`, as a map key, or as a `switch` tag or `case` expression is a read of every field of that type, transitively through the fields of every struct type it holds, recorded at the comparison, because equality reads every field and a field that decides equality carries information. In Go a type argument the program instantiates a type parameter with, where the parameter's constraint is `comparable` or embeds it, is compared as an operand of `==` of that type is, recorded at the instantiation, so the struct values `slices.Equal`, `slices.Index` or `maps.Equal` compares have every field read. `reflect.DeepEqual` is not a comparison under this rule. In Go, a conversion the compiler accepts between two struct types, or between two pointer types that are not named types and whose base types are struct types, reads every field of the two struct types where their struct type literals are distinct, recorded at the conversion, because the conversion is legal only while their fields are identical, ignoring tags, and so no field of either can be deleted alone. It reads in the same way every field of each pair of distinct struct type literals the identity of the two types compares in turn: through each field's type, and from there through the element, key, base, parameter and result types of the type literals it reaches, through the method signatures of an interface type literal, and through the type arguments of an instantiated generic type. A field reached there has no reference of its own, as grammar/symbol-ref.md states, so it is never a subject and those reads hold no finding back. It stops at a defined type, whose fields the conversion does not read, because a defined type is identical to itself whatever fields it declares. A pair whose two sides are one struct type literal, the two struct types themselves included, as where one type is declared from the other or the conversion is to the operand's own type, reads nothing, because a field deleted from that literal leaves both sides identical. Where the operand's type or the conversion's type is a type parameter, the struct type its constraint's core type is takes that side's place, and a type parameter operand reads in the same way the fields of each struct type argument an instantiation the program holds gives it, an instantiation made through another generic declaration's own type parameters included. A conversion to any other type, an interface type among them, reads no field under this rule. In TypeScript a comparison of two object references reads no member, because `===` compares identity and never a member. In TypeScript a property of an object literal writes the member of the same name that the literal's contextual type declares, whether or not the compiler checks the literal for excess properties. Where that contextual type is a union or an intersection, the property writes the member of that name on each constituent that declares one. So a literal that a declaration annotates, a function returns, a call passes or a `satisfies` expression checks writes the members of the type it is given. An object literal handed to a callee the target does not implement has its members read, as grammar/analysis.md states. A store through the value a field or a member holds reads that field or member rather than writing it, because the store lands in storage every other holder of the value reads. In Go that store is an assignment, an increment, a compound assignment or a `delete` whose target is reached through a struct field holding a map, a slice or a pointer. Examples are `s.f[k] = v`, `s.f.x = v` and `delete(s.f, k)`. The same store through a field holding an array or a struct is a store into the field itself. TypeScript holds every object by reference, so an element or property store through a member's value reads the member. The finding names each write position, so the deletion set is visible. Deleting the subject and its writes also deletes every declaration whose only uses they hold. Such a declaration is a type parameter of the subject's declaring type that the subject's type alone names, an import binding the writes alone use, or a declaration whose every use is in the type annotation of the subject or of another subject this rule reports, or in the writes those findings name. The finding's message names each such declaration, and no finding is reported about the declaration itself.",
|
|
156
|
+
rule: "A package-level or module-level variable, struct field, class member, type member or collection that production code writes and never reads. In production mode a read from a test file counts as no read. A write from a loaded consumer's code counts as a read, because the deletion the finding names would break a consumer the target does not own. A write in a consumer's test file counts as a read in the run that counts test references. It counts in a production sweep only where the configuration counts consumer tests as production. A comparison of struct values reads every field of the type: in Go, a value of a struct type used as an operand of `==` or `!=`, as a map key, or as a `switch` tag or `case` expression is a read of every field of that type, transitively through the fields of every struct type it holds, recorded at the comparison, because equality reads every field and a field that decides equality carries information. In Go a type argument the program instantiates a type parameter with, where the parameter's constraint is `comparable` or embeds it, is compared as an operand of `==` of that type is, recorded at the instantiation, so the struct values `slices.Equal`, `slices.Index` or `maps.Equal` compares have every field read. `reflect.DeepEqual` is not a comparison under this rule. In Go, a conversion the compiler accepts between two struct types, or between two pointer types that are not named types and whose base types are struct types, reads every field of the two struct types where their struct type literals are distinct, recorded at the conversion, because the conversion is legal only while their fields are identical, ignoring tags, and so no field of either can be deleted alone. It reads in the same way every field of each pair of distinct struct type literals the identity of the two types compares in turn: through each field's type, and from there through the element, key, base, parameter and result types of the type literals it reaches, through the method signatures of an interface type literal, and through the type arguments of an instantiated generic type. A field reached there has no reference of its own, as grammar/symbol-ref.md states, so it is never a subject and those reads hold no finding back. It stops at a defined type, whose fields the conversion does not read, because a defined type is identical to itself whatever fields it declares. A pair whose two sides are one struct type literal, the two struct types themselves included, as where one type is declared from the other or the conversion is to the operand's own type, reads nothing, because a field deleted from that literal leaves both sides identical. Where the operand's type or the conversion's type is a type parameter, the struct type its constraint's core type is takes that side's place, and a type parameter operand reads in the same way the fields of each struct type argument an instantiation the program holds gives it, an instantiation made through another generic declaration's own type parameters included. A conversion to any other type, an interface type among them, reads no field under this rule. In TypeScript a comparison of two object references reads no member, because `===` compares identity and never a member. A TypeScript parameter property, a constructor parameter that carries a modifier, declares a member of its class that the constructor writes with its argument, so the member is judged under this rule, and under every rule that reads a class member, as a member the class body declares is. In TypeScript a property of an object literal writes the member of the same name that the literal's contextual type declares, whether or not the compiler checks the literal for excess properties. Where that contextual type is a union or an intersection, the property writes the member of that name on each constituent that declares one. So a literal that a declaration annotates, a function returns, a call passes or a `satisfies` expression checks writes the members of the type it is given. An object literal handed to a callee the target does not implement has its members read, as grammar/analysis.md states. A store through the value a field or a member holds reads that field or member rather than writing it, because the store lands in storage every other holder of the value reads. In Go that store is an assignment, an increment, a compound assignment or a `delete` whose target is reached through a struct field holding a map, a slice or a pointer. Examples are `s.f[k] = v`, `s.f.x = v` and `delete(s.f, k)`. The same store through a field holding an array or a struct is a store into the field itself. TypeScript holds every object by reference, so an element or property store through a member's value reads the member. The finding names each write position, so the deletion set is visible. Deleting the subject and its writes also deletes every declaration whose only uses they hold. Such a declaration is a type parameter of the subject's declaring type that the subject's type alone names, an import binding the writes alone use, or a declaration whose every use is in the type annotation of the subject or of another subject this rule reports, or in the writes those findings name. The finding's message names each such declaration, and no finding is reported about the declaration itself.",
|
|
157
157
|
defaultSeverity: "deny",
|
|
158
158
|
maxClass: "certain",
|
|
159
159
|
fixability: "deletable",
|
|
@@ -289,7 +289,7 @@ const ROWS = [
|
|
|
289
289
|
name: "unused-parameter",
|
|
290
290
|
languages: ["go", "ts"],
|
|
291
291
|
rule: "A parameter with no reference inside its function body, on a function whose signature is free to change.",
|
|
292
|
-
precondition: "The signature must be free, defined as follows: the function is not a method retained by interface satisfaction, is not a method whose name and signature are those of a method of an exported interface type declared by a package outside the target that the import closure holds, is not used as a value, is not a go:linkname or cgo target, is not a function the Go test driver runs (a test, benchmark or fuzz test of a test file, or TestMain), and is not a stub whose body is empty or only panics. A function literal or function expression that initializes a variable the program only calls, never passing, storing, returning or assigning it, is called by name and is not used as a value. A parameter its body never names is dead whatever the callers, so a published declaration of a library is reported too, with the fixability the vocabulary gives the kind: the signature change is a breaking change. In TypeScript a caller may pass a function used as a value more arguments than it declares parameters, so in such a function whose signature is free but for that use, a parameter its body never reads is reported when its body reads no parameter after it, and an unread parameter before the last read one is not. A parameter of a TypeScript function expression or arrow function that the compiler gives a contextual type is not reported when it carries a type annotation, because the inference of the call that holds the function may read the annotation, or when it is an object binding pattern, because the pattern reads each property it names from the argument and the code that hands the argument may act on that read. No name such a pattern binds is reported either. Any other TypeScript parameter that is a binding pattern is judged by the names it binds: each name the body never reads is reported wherever the pattern stands, because removing it from the pattern moves no argument, except a name beside a rest element of the same object pattern, whose removal changes what that element holds, and the pattern counts as a read parameter when the body reads any name it binds. A rest parameter is judged as the last parameter. A TypeScript `this` parameter declares the type of `this` and receives no argument, so it is never reported. No name exempts a parameter. A TypeScript parameter whose name starts with an underscore is judged as any other is, and a Go parameter named by the blank identifier declares no name a body could read and is never reported.",
|
|
292
|
+
precondition: "The signature must be free, defined as follows: the function is not a method retained by interface satisfaction, is not a method whose name and signature are those of a method of an exported interface type declared by a package outside the target that the import closure holds, is not used as a value, is not a go:linkname or cgo target, is not a function the Go test driver runs (a test, benchmark or fuzz test of a test file, or TestMain), and is not a stub whose body is empty or only panics. A function literal or function expression that initializes a variable the program only calls, never passing, storing, returning or assigning it, is called by name and is not used as a value. A parameter its body never names is dead whatever the callers, so a published declaration of a library is reported too, with the fixability the vocabulary gives the kind: the signature change is a breaking change. In TypeScript a caller may pass a function used as a value more arguments than it declares parameters, so in such a function whose signature is free but for that use, a parameter its body never reads is reported when its body reads no parameter after it, and an unread parameter before the last read one is not. A parameter of a TypeScript function expression or arrow function that the compiler gives a contextual type is not reported when it carries a type annotation, because the inference of the call that holds the function may read the annotation, or when it is an object binding pattern, because the pattern reads each property it names from the argument and the code that hands the argument may act on that read. No name such a pattern binds is reported either. Any other TypeScript parameter that is a binding pattern is judged by the names it binds: each name the body never reads is reported wherever the pattern stands, because removing it from the pattern moves no argument, except a name beside a rest element of the same object pattern, whose removal changes what that element holds, and the pattern counts as a read parameter when the body reads any name it binds. A rest parameter is judged as the last parameter. A TypeScript `this` parameter declares the type of `this` and receives no argument, so it is never reported. A TypeScript constructor is used as a value where its class is used as a value other than as the callee of a `new` expression or the class an `extends` clause names, and its signature is not free where its class carries a decorator or a parameter of the constructor carries one, because the code that decorator or a container runs may construct the class. A parameter property is read by the write to its member the constructor makes, so it is never reported under this kind, and its member is judged under DS1301. No name exempts a parameter. A TypeScript parameter whose name starts with an underscore is judged as any other is, and a Go parameter named by the blank identifier declares no name a body could read and is never reported.",
|
|
293
293
|
defaultSeverity: "warn",
|
|
294
294
|
maxClass: "certain",
|
|
295
295
|
fixability: "manual",
|
package/dist/src/ref.js
CHANGED
|
@@ -25,9 +25,10 @@ const TS_COMPUTED = String.raw `\[[^\]\r\n\t ]+\]`;
|
|
|
25
25
|
const TS_HEAD = `(?:${TS_IDENT}|${TS_QUOTED})`;
|
|
26
26
|
const TS_MEMBER = `(?:${TS_IDENT}|#${TS_IDENT}|${TS_QUOTED}|${TS_COMPUTED})`;
|
|
27
27
|
const TS_PACKAGE = String.raw `(?:@[A-Za-z0-9._-]+/)?[A-Za-z0-9._-]+`;
|
|
28
|
-
const
|
|
28
|
+
const TS_DIRS = String.raw `(?:[^/#\r\n]+/)*`;
|
|
29
|
+
const TS_FILE = String.raw `${TS_DIRS}[^/#\r\n]+(?:(?:\.d)?\.[mc]?ts|\.tsx|\.[mc]?js|\.jsx)`;
|
|
29
30
|
const SCOPE = `${PREFIX}${TS_PACKAGE}/${TS_FILE}`;
|
|
30
|
-
const MANIFEST_SCOPE = `${PREFIX}${TS_PACKAGE}
|
|
31
|
+
const MANIFEST_SCOPE = `${PREFIX}${TS_PACKAGE}/${TS_DIRS}package\\.json`;
|
|
31
32
|
/**
|
|
32
33
|
* One expression per form the grammar defines, each anchored and each the whole of
|
|
33
34
|
* a reference. Several forms share one expression because they differ in what the
|
package/dist/src/references.js
CHANGED
|
@@ -5,7 +5,7 @@ import { destructuring, propertyReadsOf } from "./destructuring.js";
|
|
|
5
5
|
import { specifierCandidates } from "./configuration-files.js";
|
|
6
6
|
import { namespaceUses } from "./namespace-objects.js";
|
|
7
7
|
import { globExpression } from "./glob.js";
|
|
8
|
-
import { declarationsByName, nodeKey } from "./inventory.js";
|
|
8
|
+
import { declarationsByName, isParameterProperty, nodeKey, } from "./inventory.js";
|
|
9
9
|
import { augmentationReferences } from "./augmentations.js";
|
|
10
10
|
import { elementAccessReferences } from "./element-access.js";
|
|
11
11
|
import { indexedAccessReferences } from "./indexed-access.js";
|
|
@@ -336,6 +336,7 @@ function sitesOf(file, declarations, fileId, unread) {
|
|
|
336
336
|
const wholes = [];
|
|
337
337
|
const urls = [];
|
|
338
338
|
const decorated = [];
|
|
339
|
+
const parameterWrites = [];
|
|
339
340
|
const namespaces = namespaceUses(file);
|
|
340
341
|
const literals = [];
|
|
341
342
|
const flows = [];
|
|
@@ -419,7 +420,16 @@ function sitesOf(file, declarations, fileId, unread) {
|
|
|
419
420
|
}
|
|
420
421
|
const held = declarations.get(nodeKey(file, node));
|
|
421
422
|
const outer = enclosing;
|
|
422
|
-
|
|
423
|
+
// A parameter property's annotation, initializer and decorators stay with the
|
|
424
|
+
// parameter when the member goes, so they are written inside the constructor's class.
|
|
425
|
+
if (held !== undefined && isParameterProperty(node)) {
|
|
426
|
+
parameterWrites.push({
|
|
427
|
+
name: node.name,
|
|
428
|
+
from: outer,
|
|
429
|
+
member: held,
|
|
430
|
+
});
|
|
431
|
+
}
|
|
432
|
+
else if (held !== undefined) {
|
|
423
433
|
enclosing = held;
|
|
424
434
|
for (const modifier of node.modifiers ?? []) {
|
|
425
435
|
if (isDecorator(modifier)) {
|
|
@@ -441,6 +451,7 @@ function sitesOf(file, declarations, fileId, unread) {
|
|
|
441
451
|
wholes,
|
|
442
452
|
urls,
|
|
443
453
|
decorated,
|
|
454
|
+
parameterWrites,
|
|
444
455
|
keyed,
|
|
445
456
|
};
|
|
446
457
|
}
|
|
@@ -523,15 +534,12 @@ function importedNames(file) {
|
|
|
523
534
|
importedCache.set(file, names);
|
|
524
535
|
return names;
|
|
525
536
|
}
|
|
526
|
-
/**
|
|
527
|
-
* The name node of every import binding one file's top level declares, and whether it
|
|
528
|
-
* binds a whole module.
|
|
529
|
-
*/
|
|
537
|
+
/** The name node of every import binding one file's top level declares. */
|
|
530
538
|
function importBindings(file) {
|
|
531
539
|
const found = [];
|
|
532
540
|
for (const statement of file.statements) {
|
|
533
541
|
if (isImportEqualsDeclaration(statement)) {
|
|
534
|
-
found.push({ node: statement.name
|
|
542
|
+
found.push({ node: statement.name });
|
|
535
543
|
continue;
|
|
536
544
|
}
|
|
537
545
|
const clause = isImportDeclaration(statement) ? statement.importClause : undefined;
|
|
@@ -539,17 +547,17 @@ function importBindings(file) {
|
|
|
539
547
|
continue;
|
|
540
548
|
}
|
|
541
549
|
if (clause.name !== undefined) {
|
|
542
|
-
found.push({ node: clause.name
|
|
550
|
+
found.push({ node: clause.name });
|
|
543
551
|
}
|
|
544
552
|
const bindings = clause.namedBindings;
|
|
545
553
|
if (bindings === undefined) {
|
|
546
554
|
continue;
|
|
547
555
|
}
|
|
548
556
|
if (bindings.kind === SyntaxKind.NamespaceImport) {
|
|
549
|
-
found.push({ node: bindings.name
|
|
557
|
+
found.push({ node: bindings.name });
|
|
550
558
|
}
|
|
551
559
|
else {
|
|
552
|
-
found.push(...bindings.elements.map((element) => ({ node: element.name
|
|
560
|
+
found.push(...bindings.elements.map((element) => ({ node: element.name })));
|
|
553
561
|
}
|
|
554
562
|
}
|
|
555
563
|
return found;
|
|
@@ -856,7 +864,7 @@ export function references(project, held, targetRoot, options) {
|
|
|
856
864
|
}
|
|
857
865
|
}
|
|
858
866
|
const keyNames = computedKeyNames(held.symbols);
|
|
859
|
-
for (const { file, fileId, uses, reads, links, evaluations, wholes, urls, decorated, keyed, } of walked) {
|
|
867
|
+
for (const { file, fileId, uses, reads, links, evaluations, wholes, urls, decorated, parameterWrites, keyed, } of walked) {
|
|
860
868
|
const test = testFile.get(file) === true;
|
|
861
869
|
// A component file's markup may use any binding its blocks declare at the top level.
|
|
862
870
|
const markup = isComponentFile(file);
|
|
@@ -874,6 +882,17 @@ export function references(project, held, targetRoot, options) {
|
|
|
874
882
|
test,
|
|
875
883
|
});
|
|
876
884
|
}
|
|
885
|
+
// The constructor stores its argument in the member a parameter property declares.
|
|
886
|
+
for (const { name, from, member } of parameterWrites) {
|
|
887
|
+
found.push({
|
|
888
|
+
from,
|
|
889
|
+
to: member,
|
|
890
|
+
position: renderPosition(file, root, name.getStart()),
|
|
891
|
+
use: "write",
|
|
892
|
+
resolution: "syntax",
|
|
893
|
+
test,
|
|
894
|
+
});
|
|
895
|
+
}
|
|
877
896
|
// One batch per capped run of the file's name nodes, the re-exports' nodes after
|
|
878
897
|
// the uses and the evaluated modules' specifiers last, each node once: a bare star
|
|
879
898
|
// re-export's specifier is a link and an evaluation both. A shorthand is left out
|
|
@@ -1011,15 +1030,14 @@ export function references(project, held, targetRoot, options) {
|
|
|
1011
1030
|
const chained = symbol === UNANSWERED
|
|
1012
1031
|
? named(binding.node).map((id) => ({ id, stepped: false, guessed: true }))
|
|
1013
1032
|
: chains.chainOf(symbol);
|
|
1014
|
-
// The markup may name any export of a module a binding
|
|
1015
|
-
|
|
1016
|
-
|
|
1017
|
-
|
|
1018
|
-
|
|
1019
|
-
|
|
1020
|
-
|
|
1021
|
-
|
|
1022
|
-
: chained;
|
|
1033
|
+
// The markup may name any export of a module a binding's chain reaches, whether a
|
|
1034
|
+
// namespace import binds it or a named or default import of a namespace re-export.
|
|
1035
|
+
const targets = chained.flatMap((target) => [
|
|
1036
|
+
target,
|
|
1037
|
+
...declaredIn(target.id)
|
|
1038
|
+
.filter((one) => one.exported)
|
|
1039
|
+
.map((one) => ({ ...target, id: one.id })),
|
|
1040
|
+
]);
|
|
1023
1041
|
for (const target of targets) {
|
|
1024
1042
|
found.push({
|
|
1025
1043
|
from: fileId,
|
package/dist/src/report.js
CHANGED
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
* renders this document, so no two renderings disagree about which findings exist.
|
|
5
5
|
*/
|
|
6
6
|
/** The version of the report schema this analyzer writes a report to. */
|
|
7
|
-
const SCHEMA_VERSION = "8.
|
|
7
|
+
const SCHEMA_VERSION = "8.2.0";
|
|
8
8
|
/** Every report schema version this analyzer reads: the one the Contract it implements admits. */
|
|
9
9
|
export const SCHEMA_VERSIONS_ACCEPTED = [SCHEMA_VERSION];
|
|
10
10
|
/** The name this analyzer writes every document under and mints every identifier with. */
|