@cplieger/deadset-ts 7.1.0 → 7.2.0-dev.2
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/README.md +1 -1
- 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/dependencies.js +0 -4
- package/dist/src/deprecation.js +1 -1
- package/dist/src/findings/completion.js +1 -1
- package/dist/src/findings/interfaces.js +1 -8
- package/dist/src/findings/unused-declarations.js +8 -11
- package/dist/src/html-entries.js +0 -3
- package/dist/src/injection-container.js +28 -25
- package/dist/src/intra-function-parts.js +71 -22
- package/dist/src/inventory.js +19 -6
- package/dist/src/kinds.js +2 -2
- package/dist/src/matrix.js +1 -8
- package/dist/src/module-declarations.js +0 -3
- 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/type-errors.js +2 -1
- package/dist/src/verbs/analyze.js +7 -3
- package/dist/src/version.js +1 -1
- package/dist/src/workspace-resolution.js +5 -8
- package/dist/src/workspace.js +2 -2
- package/jsr.json +1 -1
- package/package.json +2 -2
- 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/dependencies.ts +0 -4
- package/src/deprecation.ts +1 -1
- package/src/findings/completion.ts +1 -4
- package/src/findings/interfaces.ts +1 -9
- package/src/findings/unused-declarations.ts +8 -15
- package/src/html-entries.ts +0 -3
- package/src/injection-container.ts +31 -26
- package/src/intra-function-parts.ts +96 -24
- package/src/inventory.ts +27 -5
- package/src/kinds.ts +2 -2
- package/src/matrix.ts +1 -9
- package/src/module-declarations.ts +0 -3
- 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/type-errors.ts +2 -1
- package/src/verbs/analyze.ts +7 -2
- package/src/version.ts +1 -1
- package/src/workspace-resolution.ts +5 -8
- package/src/workspace.ts +2 -2
package/README.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# deadset-ts
|
|
2
2
|
|
|
3
|
-
[](https://www.npmjs.com/package/@cplieger/deadset-ts) [](https://jsr.io/@cplieger/deadset-ts)
|
|
3
|
+
[](https://www.npmjs.com/package/@cplieger/deadset-ts) [](https://jsr.io/@cplieger/deadset-ts) [](https://github.com/cplieger/deadset-ts/issues?q=label%3Astryker-tracker)
|
|
4
4
|
|
|
5
5
|
deadset-ts finds dead code in TypeScript projects, down to class members, type members and dead stores inside functions, and writes a report your CI can fail on.
|
|
6
6
|
|
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());
|
package/dist/src/dependencies.js
CHANGED
|
@@ -466,15 +466,11 @@ function skipWhitespace(text, at) {
|
|
|
466
466
|
*/
|
|
467
467
|
function membersAt(text, at) {
|
|
468
468
|
const found = new Map();
|
|
469
|
-
if (text.charAt(at) !== "{") {
|
|
470
|
-
return found;
|
|
471
|
-
}
|
|
472
469
|
let cursor = skipWhitespace(text, at + 1);
|
|
473
470
|
while (text.charAt(cursor) === '"') {
|
|
474
471
|
const keyEnd = valueEnd(text, cursor);
|
|
475
472
|
const key = JSON.parse(text.slice(cursor, keyEnd));
|
|
476
473
|
const value = skipWhitespace(text, skipWhitespace(text, keyEnd) + 1);
|
|
477
|
-
found.delete(key);
|
|
478
474
|
found.set(key, { key: cursor, value });
|
|
479
475
|
cursor = skipWhitespace(text, valueEnd(text, value));
|
|
480
476
|
if (text.charAt(cursor) === ",") {
|
package/dist/src/deprecation.js
CHANGED
|
@@ -47,7 +47,7 @@ export function deprecatedDeclarations(project, held) {
|
|
|
47
47
|
}
|
|
48
48
|
}
|
|
49
49
|
const id = held.declarations.get(nodeKey(file, node));
|
|
50
|
-
if (id !== undefined
|
|
50
|
+
if (id !== undefined) {
|
|
51
51
|
named.push({ id, handle: project.handle(nameNode(node)) });
|
|
52
52
|
}
|
|
53
53
|
node.forEachChild(visit);
|
|
@@ -145,7 +145,7 @@ function findingComponents(swept, cascade) {
|
|
|
145
145
|
},
|
|
146
146
|
];
|
|
147
147
|
})
|
|
148
|
-
.sort((a, b) => byPosition(a.position, b.position)
|
|
148
|
+
.sort((a, b) => byPosition(a.position, b.position));
|
|
149
149
|
return (id) => {
|
|
150
150
|
const component = held.get(id);
|
|
151
151
|
return component === undefined
|
|
@@ -10,13 +10,6 @@ const UNCALLED_MESSAGE = "no call site invokes or selects the method through the
|
|
|
10
10
|
function positionOf(symbol) {
|
|
11
11
|
return { ...symbol.position, endLine: Math.max(symbol.position.line, symbol.endLine) };
|
|
12
12
|
}
|
|
13
|
-
/** Two strings ordered bytewise. */
|
|
14
|
-
function compare(a, b) {
|
|
15
|
-
if (a === b) {
|
|
16
|
-
return 0;
|
|
17
|
-
}
|
|
18
|
-
return a < b ? -1 : 1;
|
|
19
|
-
}
|
|
20
13
|
/** One run's declarations and candidates, indexed for the two kinds. */
|
|
21
14
|
function indexed(input) {
|
|
22
15
|
const union = input.swept.matrix.union;
|
|
@@ -31,7 +24,7 @@ function indexed(input) {
|
|
|
31
24
|
.map(symbolOf)
|
|
32
25
|
.filter((symbol) => symbol !== undefined)
|
|
33
26
|
.map((symbol) => ({ ref: symbol.ref, name: symbol.name, position: positionOf(symbol) }))
|
|
34
|
-
.sort((a, b) => byPosition(a.position, b.position)
|
|
27
|
+
.sort((a, b) => byPosition(a.position, b.position)),
|
|
35
28
|
};
|
|
36
29
|
}
|
|
37
30
|
/** The finding one code makes about one declaration. */
|
|
@@ -39,28 +39,26 @@ const WORDS = {
|
|
|
39
39
|
const TEST_OF_DEAD_CODE = "every target symbol this test references is reported dead";
|
|
40
40
|
/**
|
|
41
41
|
* The code one candidate is reported under, or none, each symbol once under the most
|
|
42
|
-
* specific code: a test of dead code first; no other test-file declaration
|
|
43
|
-
*
|
|
44
|
-
*
|
|
45
|
-
*
|
|
42
|
+
* specific code: a test of dead code first; no other test-file declaration or enum
|
|
43
|
+
* member nothing names; a test-support declaration test code references as test-only
|
|
44
|
+
* use; then deprecation, a test reference, and the unreferenced codes, an unimportable
|
|
45
|
+
* unused export being the unreachable kind's.
|
|
46
46
|
*/
|
|
47
47
|
function codeOf(judged) {
|
|
48
48
|
const { candidate, symbol } = judged;
|
|
49
49
|
if (candidate.testOfDeadCode) {
|
|
50
50
|
return "DS1005";
|
|
51
51
|
}
|
|
52
|
-
if ((judged.test && !candidate.unreferencedTest) ||
|
|
53
|
-
judged.containerDead ||
|
|
54
|
-
NOT_THIS_FAMILY.has(symbol.kind)) {
|
|
52
|
+
if ((judged.test && !candidate.unreferencedTest) || NOT_THIS_FAMILY.has(symbol.kind)) {
|
|
55
53
|
return undefined;
|
|
56
54
|
}
|
|
57
|
-
if (candidate.unreferencedTest) {
|
|
58
|
-
return unreferencedCode(symbol);
|
|
59
|
-
}
|
|
60
55
|
const referenced = candidate.productionRefs + candidate.testRefs > 0;
|
|
61
56
|
if (symbol.kind === "enum-member" && !referenced) {
|
|
62
57
|
return undefined;
|
|
63
58
|
}
|
|
59
|
+
if (candidate.unreferencedTest) {
|
|
60
|
+
return unreferencedCode(symbol);
|
|
61
|
+
}
|
|
64
62
|
if (candidate.heldByTests === candidate.id) {
|
|
65
63
|
return "DS1004";
|
|
66
64
|
}
|
|
@@ -192,7 +190,6 @@ function familyFindings(input, fallen) {
|
|
|
192
190
|
candidate,
|
|
193
191
|
symbol,
|
|
194
192
|
test: union.test[at] === true,
|
|
195
|
-
containerDead: containerDead && !fallen,
|
|
196
193
|
deprecated: input.deprecated.has(symbol.id),
|
|
197
194
|
unreachable: unreachable.has(symbol.id),
|
|
198
195
|
};
|
package/dist/src/html-entries.js
CHANGED
|
@@ -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.
|
|
@@ -270,18 +303,16 @@ function readsBinding(use, declared, ids) {
|
|
|
270
303
|
export function projectParts(project, held, references, targetRoot) {
|
|
271
304
|
const files = project.ownSourceFiles();
|
|
272
305
|
const extended = extendedNames(files);
|
|
273
|
-
const kinds = new Map(held.symbols.map((symbol) => [symbol.id, symbol.kind]));
|
|
274
306
|
const parts = [];
|
|
275
307
|
const signatures = [];
|
|
276
308
|
const calls = new Map();
|
|
277
309
|
const specifiers = new Set();
|
|
310
|
+
const constructions = new Set();
|
|
278
311
|
const sites = [];
|
|
279
312
|
const contextual = [];
|
|
280
313
|
for (const file of files) {
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
return id === undefined || kinds.get(id) === "file" ? undefined : id;
|
|
284
|
-
};
|
|
314
|
+
// A parameter property's own parts stay with the parameter when its member goes.
|
|
315
|
+
const declarationAt = (node) => isParameterProperty(node) ? undefined : held.declarations.get(nodeKey(file, node));
|
|
285
316
|
// A part no declaration holds belongs to the file, which is judged while the file is live.
|
|
286
317
|
const fileId = held.declarations.get(nodeKey(file, file));
|
|
287
318
|
const holderOf = (node) => {
|
|
@@ -332,6 +363,17 @@ export function projectParts(project, held, references, targetRoot) {
|
|
|
332
363
|
walk.declared.push({ id, node, signature });
|
|
333
364
|
covered.add(signature);
|
|
334
365
|
}
|
|
366
|
+
// A constructor is no declaration, so the class it belongs to holds its parts.
|
|
367
|
+
const constructs = isConstructorDeclaration(node) && node.body !== undefined
|
|
368
|
+
? declarationAt(node.parent)
|
|
369
|
+
: undefined;
|
|
370
|
+
if (constructs !== undefined && isConstructorDeclaration(node)) {
|
|
371
|
+
walk.declared.push({ id: constructs, node, signature: node });
|
|
372
|
+
covered.add(node);
|
|
373
|
+
}
|
|
374
|
+
for (const name of constructionNames(node)) {
|
|
375
|
+
constructions.add(position(name));
|
|
376
|
+
}
|
|
335
377
|
const value = covered.has(node) ? undefined : valueSignature(node);
|
|
336
378
|
const valueHolder = id ?? holderOf(node);
|
|
337
379
|
if (value !== undefined && valueHolder !== undefined && !isStub(value)) {
|
|
@@ -413,11 +455,15 @@ export function projectParts(project, held, references, targetRoot) {
|
|
|
413
455
|
}
|
|
414
456
|
const ids = resolvedIds(project, [...new Set(asked)]);
|
|
415
457
|
for (const one of walk.declared) {
|
|
416
|
-
|
|
458
|
+
const constructor = isConstructorDeclaration(one.node);
|
|
459
|
+
if (!constructor) {
|
|
460
|
+
sites.push({ file, declared: one });
|
|
461
|
+
}
|
|
417
462
|
signatures.push({
|
|
418
463
|
declaration: one.id,
|
|
419
464
|
stub: isStub(one.signature),
|
|
420
|
-
fixed: hierarchyFixes(one.node, extended),
|
|
465
|
+
fixed: constructor ? decoratorFixes(one.node) : hierarchyFixes(one.node, extended),
|
|
466
|
+
constructor,
|
|
421
467
|
});
|
|
422
468
|
const reader = readers.get(one.signature);
|
|
423
469
|
if (reader !== undefined) {
|
|
@@ -475,7 +521,13 @@ export function projectParts(project, held, references, targetRoot) {
|
|
|
475
521
|
}
|
|
476
522
|
}
|
|
477
523
|
parts.push(...resultParts(project, targetRoot, sites, references, calls));
|
|
478
|
-
return {
|
|
524
|
+
return {
|
|
525
|
+
parts: withContextualRead(project, parts, contextual),
|
|
526
|
+
signatures,
|
|
527
|
+
calls,
|
|
528
|
+
specifiers,
|
|
529
|
+
constructions,
|
|
530
|
+
};
|
|
479
531
|
}
|
|
480
532
|
/**
|
|
481
533
|
* A function written in place that no declaration declares: an arrow, a function
|
|
@@ -496,16 +548,12 @@ function valueSignature(node) {
|
|
|
496
548
|
}
|
|
497
549
|
/**
|
|
498
550
|
* Every name one function's parameters bind, a binding pattern's names one by one. A
|
|
499
|
-
* `this` parameter receives no argument
|
|
500
|
-
*
|
|
551
|
+
* `this` parameter receives no argument, a decorated one is read by its decorator, and a
|
|
552
|
+
* parameter property is read by the write to its member, so none binds a subject.
|
|
501
553
|
*/
|
|
502
554
|
function parameterNames(signature) {
|
|
503
555
|
const found = [];
|
|
504
556
|
const patternNames = (pattern, at) => {
|
|
505
|
-
if (isIdentifier(pattern)) {
|
|
506
|
-
found.push({ name: pattern, at, pattern: true, exempt: false });
|
|
507
|
-
return;
|
|
508
|
-
}
|
|
509
557
|
const rest = isObjectBindingPattern(pattern) &&
|
|
510
558
|
pattern.elements.some((element) => element.dotDotDotToken !== undefined);
|
|
511
559
|
for (const element of pattern.elements) {
|
|
@@ -523,7 +571,8 @@ function parameterNames(signature) {
|
|
|
523
571
|
};
|
|
524
572
|
signature.parameters.forEach((parameter, at) => {
|
|
525
573
|
const name = parameter.name;
|
|
526
|
-
if ((parameter.modifiers ?? []).some(isDecorator)
|
|
574
|
+
if ((parameter.modifiers ?? []).some(isDecorator) ||
|
|
575
|
+
isParameterProperty(parameter)) {
|
|
527
576
|
return;
|
|
528
577
|
}
|
|
529
578
|
if (isIdentifier(name)) {
|
|
@@ -682,9 +731,7 @@ function deadStores(file, root, block, holder, locals, ids) {
|
|
|
682
731
|
for (const write of writes) {
|
|
683
732
|
// The rest of the write's own statement runs after it: a later declarator may read the local.
|
|
684
733
|
const own = statements[write.at];
|
|
685
|
-
const sameStatement = own !== undefined && write.target === local.name
|
|
686
|
-
? usesIn(own).filter((use) => use !== local.name)
|
|
687
|
-
: [];
|
|
734
|
+
const sameStatement = own !== undefined && write.target === local.name ? usesIn(own) : [];
|
|
688
735
|
if (sameStatement.length > 0) {
|
|
689
736
|
continue;
|
|
690
737
|
}
|
|
@@ -787,7 +834,7 @@ export function intraFunctionFacts(projects, references, exempted) {
|
|
|
787
834
|
for (const [at, value] of one.calls) {
|
|
788
835
|
calls.set(at, value);
|
|
789
836
|
}
|
|
790
|
-
for (const at of one.specifiers) {
|
|
837
|
+
for (const at of [...one.specifiers, ...one.constructions]) {
|
|
791
838
|
specifiers.add(at);
|
|
792
839
|
}
|
|
793
840
|
}
|
|
@@ -815,9 +862,11 @@ export function intraFunctionFacts(projects, references, exempted) {
|
|
|
815
862
|
const free = new Set(signatures
|
|
816
863
|
.map((one) => one.declaration)
|
|
817
864
|
.filter((id) => !fixed.has(id) && !exempted.has(id) && !valued.has(id)));
|
|
865
|
+
// A class used as a value fixes its constructor's whole signature, whatever reads follow.
|
|
866
|
+
const constructors = new Set(signatures.filter((one) => one.constructor).map((one) => one.declaration));
|
|
818
867
|
const freeButValued = new Set(signatures
|
|
819
868
|
.map((one) => one.declaration)
|
|
820
|
-
.filter((id) => !fixed.has(id) && !exempted.has(id) && valued.has(id)));
|
|
869
|
+
.filter((id) => !fixed.has(id) && !exempted.has(id) && valued.has(id) && !constructors.has(id)));
|
|
821
870
|
// Awaiting a thenable result sequences on it, so a call that awaits one uses it.
|
|
822
871
|
const sequenced = new Set([...parts.values()]
|
|
823
872
|
.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;
|
|
@@ -718,9 +733,7 @@ export function inventory(project, host, targetRoot) {
|
|
|
718
733
|
});
|
|
719
734
|
}
|
|
720
735
|
const byId = new Map(building.map((record) => [positionKey(record.position), record]));
|
|
721
|
-
const symbols = building
|
|
722
|
-
.map(render)
|
|
723
|
-
.sort((a, b) => byPosition(a.position, b.position) || (a.ref < b.ref ? -1 : 1));
|
|
736
|
+
const symbols = building.map(render).sort((a, b) => byPosition(a.position, b.position));
|
|
724
737
|
return {
|
|
725
738
|
symbols,
|
|
726
739
|
declarations: new Map([...byNode].map(([key, record]) => [key, positionKey(record.position)])),
|
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/matrix.js
CHANGED
|
@@ -7,13 +7,6 @@ import { componentsOf } from "./components.js";
|
|
|
7
7
|
import { graphOf, OUTSIDE } from "./graph.js";
|
|
8
8
|
import { byPosition } from "./position.js";
|
|
9
9
|
import { reachedWithTheTests, sweep, } from "./sweep.js";
|
|
10
|
-
/** Two strings ordered bytewise. */
|
|
11
|
-
function compare(a, b) {
|
|
12
|
-
if (a === b) {
|
|
13
|
-
return 0;
|
|
14
|
-
}
|
|
15
|
-
return a < b ? -1 : 1;
|
|
16
|
-
}
|
|
17
10
|
/**
|
|
18
11
|
* What makes one reference one reference, whichever configuration made it. A consumer's
|
|
19
12
|
* position is rendered against its own root, so the consumer is part of what it names.
|
|
@@ -76,7 +69,7 @@ export function matrixOf(per) {
|
|
|
76
69
|
references.push(reference);
|
|
77
70
|
}
|
|
78
71
|
});
|
|
79
|
-
symbols.sort((a, b) => byPosition(a.position, b.position)
|
|
72
|
+
symbols.sort((a, b) => byPosition(a.position, b.position));
|
|
80
73
|
return {
|
|
81
74
|
configurations: per.map((one) => one.configuration),
|
|
82
75
|
graphs: per.map((one) => graphOf(one.symbols, one.references, one.roots, one.testFiles, one.supportFiles)),
|