@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
|
@@ -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
|
} as const;
|
package/src/decorator.ts
CHANGED
|
@@ -14,7 +14,12 @@
|
|
|
14
14
|
import type { Node } from "@typescript/native/ast";
|
|
15
15
|
import { decoratorsOf, spelledDecorator } from "./calls.ts";
|
|
16
16
|
import type { DetectorInput, Evidence } from "./exempt.ts";
|
|
17
|
-
import {
|
|
17
|
+
import {
|
|
18
|
+
isParameterProperty,
|
|
19
|
+
nodeKey,
|
|
20
|
+
type InventorySymbol,
|
|
21
|
+
type SymbolKind,
|
|
22
|
+
} from "./inventory.ts";
|
|
18
23
|
import { renderPosition } from "./position.ts";
|
|
19
24
|
|
|
20
25
|
/** The kinds of declaration a decorator is attached to and holds back. */
|
|
@@ -44,7 +49,8 @@ export function decorator<Brand>(input: DetectorInput<Brand>): readonly Evidence
|
|
|
44
49
|
const visit = (node: Node): void => {
|
|
45
50
|
const id = held.declarations.get(nodeKey(file, node));
|
|
46
51
|
const symbol = id === undefined ? undefined : byId.get(id);
|
|
47
|
-
|
|
52
|
+
// A parameter property's decorator is attached to its parameter, and to no member.
|
|
53
|
+
if (symbol !== undefined && DECORATED.has(symbol.kind) && !isParameterProperty(node)) {
|
|
48
54
|
for (const attached of decoratorsOf(node)) {
|
|
49
55
|
const detail = `decorated by ${spelledDecorator(attached)}`;
|
|
50
56
|
const site = renderPosition(file, targetRoot, attached.getStart());
|
package/src/dependencies.ts
CHANGED
|
@@ -616,15 +616,11 @@ function skipWhitespace(text: string, at: number): number {
|
|
|
616
616
|
*/
|
|
617
617
|
function membersAt(text: string, at: number): Map<string, { key: number; value: number }> {
|
|
618
618
|
const found = new Map<string, { key: number; value: number }>();
|
|
619
|
-
if (text.charAt(at) !== "{") {
|
|
620
|
-
return found;
|
|
621
|
-
}
|
|
622
619
|
let cursor = skipWhitespace(text, at + 1);
|
|
623
620
|
while (text.charAt(cursor) === '"') {
|
|
624
621
|
const keyEnd = valueEnd(text, cursor);
|
|
625
622
|
const key = JSON.parse(text.slice(cursor, keyEnd)) as string;
|
|
626
623
|
const value = skipWhitespace(text, skipWhitespace(text, keyEnd) + 1);
|
|
627
|
-
found.delete(key);
|
|
628
624
|
found.set(key, { key: cursor, value });
|
|
629
625
|
cursor = skipWhitespace(text, valueEnd(text, value));
|
|
630
626
|
if (text.charAt(cursor) === ",") {
|
package/src/deprecation.ts
CHANGED
|
@@ -73,7 +73,7 @@ export function deprecatedDeclarations<Brand>(
|
|
|
73
73
|
}
|
|
74
74
|
}
|
|
75
75
|
const id = held.declarations.get(nodeKey(file, node));
|
|
76
|
-
if (id !== undefined
|
|
76
|
+
if (id !== undefined) {
|
|
77
77
|
named.push({ id, handle: project.handle(nameNode(node)) });
|
|
78
78
|
}
|
|
79
79
|
node.forEachChild(visit);
|
|
@@ -181,10 +181,7 @@ function findingComponents(
|
|
|
181
181
|
},
|
|
182
182
|
];
|
|
183
183
|
})
|
|
184
|
-
.sort(
|
|
185
|
-
(a, b) =>
|
|
186
|
-
byPosition(a.position, b.position) || (a.ref === b.ref ? 0 : a.ref < b.ref ? -1 : 1),
|
|
187
|
-
);
|
|
184
|
+
.sort((a, b) => byPosition(a.position, b.position));
|
|
188
185
|
return (id) => {
|
|
189
186
|
const component = held.get(id);
|
|
190
187
|
return component === undefined
|
|
@@ -19,14 +19,6 @@ function positionOf(symbol: InventorySymbol): FindingPosition {
|
|
|
19
19
|
return { ...symbol.position, endLine: Math.max(symbol.position.line, symbol.endLine) };
|
|
20
20
|
}
|
|
21
21
|
|
|
22
|
-
/** Two strings ordered bytewise. */
|
|
23
|
-
function compare(a: string, b: string): number {
|
|
24
|
-
if (a === b) {
|
|
25
|
-
return 0;
|
|
26
|
-
}
|
|
27
|
-
return a < b ? -1 : 1;
|
|
28
|
-
}
|
|
29
|
-
|
|
30
22
|
/** One run's declarations and candidates, indexed for the two kinds. */
|
|
31
23
|
function indexed(input: EmitterInput): {
|
|
32
24
|
readonly symbolOf: (id: string) => InventorySymbol | undefined;
|
|
@@ -49,7 +41,7 @@ function indexed(input: EmitterInput): {
|
|
|
49
41
|
.map(symbolOf)
|
|
50
42
|
.filter((symbol): symbol is InventorySymbol => symbol !== undefined)
|
|
51
43
|
.map((symbol) => ({ ref: symbol.ref, name: symbol.name, position: positionOf(symbol) }))
|
|
52
|
-
.sort((a, b) => byPosition(a.position, b.position)
|
|
44
|
+
.sort((a, b) => byPosition(a.position, b.position)),
|
|
53
45
|
};
|
|
54
46
|
}
|
|
55
47
|
|
|
@@ -52,8 +52,6 @@ interface Judged {
|
|
|
52
52
|
readonly symbol: InventorySymbol;
|
|
53
53
|
/** Whether a test file declares it. */
|
|
54
54
|
readonly test: boolean;
|
|
55
|
-
/** Whether its container is itself dead, so it falls with the container. */
|
|
56
|
-
readonly containerDead: boolean;
|
|
57
55
|
readonly deprecated: boolean;
|
|
58
56
|
/** Whether the unreachable-export kind reports it. */
|
|
59
57
|
readonly unreachable: boolean;
|
|
@@ -61,30 +59,26 @@ interface Judged {
|
|
|
61
59
|
|
|
62
60
|
/**
|
|
63
61
|
* The code one candidate is reported under, or none, each symbol once under the most
|
|
64
|
-
* specific code: a test of dead code first; no other test-file declaration
|
|
65
|
-
*
|
|
66
|
-
*
|
|
67
|
-
*
|
|
62
|
+
* specific code: a test of dead code first; no other test-file declaration or enum
|
|
63
|
+
* member nothing names; a test-support declaration test code references as test-only
|
|
64
|
+
* use; then deprecation, a test reference, and the unreferenced codes, an unimportable
|
|
65
|
+
* unused export being the unreachable kind's.
|
|
68
66
|
*/
|
|
69
67
|
function codeOf(judged: Judged): string | undefined {
|
|
70
68
|
const { candidate, symbol } = judged;
|
|
71
69
|
if (candidate.testOfDeadCode) {
|
|
72
70
|
return "DS1005";
|
|
73
71
|
}
|
|
74
|
-
if (
|
|
75
|
-
(judged.test && !candidate.unreferencedTest) ||
|
|
76
|
-
judged.containerDead ||
|
|
77
|
-
NOT_THIS_FAMILY.has(symbol.kind)
|
|
78
|
-
) {
|
|
72
|
+
if ((judged.test && !candidate.unreferencedTest) || NOT_THIS_FAMILY.has(symbol.kind)) {
|
|
79
73
|
return undefined;
|
|
80
74
|
}
|
|
81
|
-
if (candidate.unreferencedTest) {
|
|
82
|
-
return unreferencedCode(symbol);
|
|
83
|
-
}
|
|
84
75
|
const referenced = candidate.productionRefs + candidate.testRefs > 0;
|
|
85
76
|
if (symbol.kind === "enum-member" && !referenced) {
|
|
86
77
|
return undefined;
|
|
87
78
|
}
|
|
79
|
+
if (candidate.unreferencedTest) {
|
|
80
|
+
return unreferencedCode(symbol);
|
|
81
|
+
}
|
|
88
82
|
if (candidate.heldByTests === candidate.id) {
|
|
89
83
|
return "DS1004";
|
|
90
84
|
}
|
|
@@ -233,7 +227,6 @@ function familyFindings(input: EmitterInput, fallen: boolean): Finding[] {
|
|
|
233
227
|
candidate,
|
|
234
228
|
symbol,
|
|
235
229
|
test: union.test[at] === true,
|
|
236
|
-
containerDead: containerDead && !fallen,
|
|
237
230
|
deprecated: input.deprecated.has(symbol.id),
|
|
238
231
|
unreachable: unreachable.has(symbol.id),
|
|
239
232
|
};
|
package/src/html-entries.ts
CHANGED
|
@@ -1,9 +1,10 @@
|
|
|
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
|
|
|
9
10
|
import {
|
|
@@ -14,6 +15,7 @@ import {
|
|
|
14
15
|
isPropertyDeclaration,
|
|
15
16
|
isSetAccessorDeclaration,
|
|
16
17
|
type CallExpression,
|
|
18
|
+
type Decorator,
|
|
17
19
|
type Node,
|
|
18
20
|
} from "@typescript/native/ast";
|
|
19
21
|
import { aliasChains } from "./alias-chain.ts";
|
|
@@ -27,7 +29,7 @@ import {
|
|
|
27
29
|
type ResolvedEntry,
|
|
28
30
|
} from "./configured-declarations.ts";
|
|
29
31
|
import type { DetectorInput, Evidence } from "./exempt.ts";
|
|
30
|
-
import { nodeKey } from "./inventory.ts";
|
|
32
|
+
import { isParameterProperty, nodeKey } from "./inventory.ts";
|
|
31
33
|
import { renderPosition } from "./position.ts";
|
|
32
34
|
|
|
33
35
|
/** One class a container constructs, by the registration that says so. */
|
|
@@ -37,13 +39,14 @@ interface Registration {
|
|
|
37
39
|
readonly detail: string;
|
|
38
40
|
}
|
|
39
41
|
|
|
40
|
-
/** Whether one member declaration is a property or
|
|
42
|
+
/** Whether one member declaration is a parameter property, or a property or accessor carrying a decorator. */
|
|
41
43
|
function isInjected(node: Node): boolean {
|
|
42
44
|
return (
|
|
43
|
-
(
|
|
45
|
+
isParameterProperty(node) ||
|
|
46
|
+
((isPropertyDeclaration(node) ||
|
|
44
47
|
isGetAccessorDeclaration(node) ||
|
|
45
48
|
isSetAccessorDeclaration(node)) &&
|
|
46
|
-
|
|
49
|
+
decoratorsOf(node).length > 0)
|
|
47
50
|
);
|
|
48
51
|
}
|
|
49
52
|
|
|
@@ -77,21 +80,24 @@ function registrations<Brand>(input: DetectorInput<Brand>): readonly Registratio
|
|
|
77
80
|
});
|
|
78
81
|
}
|
|
79
82
|
|
|
80
|
-
/**
|
|
81
|
-
|
|
83
|
+
/**
|
|
84
|
+
* The classes of the target a parameter of whose constructor carries a decorator, each
|
|
85
|
+
* with the first such decorator.
|
|
86
|
+
*/
|
|
87
|
+
function constructedForParameters<Brand>(
|
|
88
|
+
input: DetectorInput<Brand>,
|
|
89
|
+
): ReadonlyMap<string, Decorator> {
|
|
82
90
|
const { project, held } = input;
|
|
83
|
-
const found = new
|
|
91
|
+
const found = new Map<string, Decorator>();
|
|
84
92
|
for (const file of project.ownSourceFiles()) {
|
|
85
93
|
const visit = (node: Node): void => {
|
|
86
94
|
if (isClassDeclaration(node) || isClassExpression(node)) {
|
|
87
95
|
const id = held.declarations.get(nodeKey(file, node));
|
|
88
|
-
const asks = node.members.
|
|
89
|
-
(member)
|
|
90
|
-
isConstructorDeclaration(member) &&
|
|
91
|
-
member.parameters.some((parameter) => decoratorsOf(parameter).length > 0),
|
|
96
|
+
const [asks] = node.members.flatMap((member) =>
|
|
97
|
+
isConstructorDeclaration(member) ? member.parameters.flatMap(decoratorsOf) : [],
|
|
92
98
|
);
|
|
93
|
-
if (id !== undefined && asks) {
|
|
94
|
-
found.
|
|
99
|
+
if (id !== undefined && asks !== undefined) {
|
|
100
|
+
found.set(id, asks);
|
|
95
101
|
}
|
|
96
102
|
}
|
|
97
103
|
node.forEachChild(visit);
|
|
@@ -110,7 +116,7 @@ export function injectionContainer<Brand>(input: DetectorInput<Brand>): readonly
|
|
|
110
116
|
const { project, held, targetRoot } = input;
|
|
111
117
|
const registered = registrations(input);
|
|
112
118
|
const constructed = constructedForParameters(input);
|
|
113
|
-
const classes = new Set([...registered.map((one) => one.id), ...constructed]);
|
|
119
|
+
const classes = new Set([...registered.map((one) => one.id), ...constructed.keys()]);
|
|
114
120
|
if (classes.size === 0) {
|
|
115
121
|
return [];
|
|
116
122
|
}
|
|
@@ -125,16 +131,15 @@ export function injectionContainer<Brand>(input: DetectorInput<Brand>): readonly
|
|
|
125
131
|
found.push({ id: member.id, detail: registration.detail, site: at(registration.site) });
|
|
126
132
|
}
|
|
127
133
|
}
|
|
128
|
-
for (const id of constructed) {
|
|
134
|
+
for (const [id, asks] of constructed) {
|
|
129
135
|
for (const member of injected(id)) {
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
}
|
|
136
|
+
// An undecorated parameter property is passed because a sibling parameter asks.
|
|
137
|
+
const [decorator = asks] = decoratorsOf(member.node);
|
|
138
|
+
found.push({
|
|
139
|
+
id: member.id,
|
|
140
|
+
detail: `injected by ${spelledDecorator(decorator)}`,
|
|
141
|
+
site: at(decorator),
|
|
142
|
+
});
|
|
138
143
|
}
|
|
139
144
|
}
|
|
140
145
|
return found;
|
|
@@ -19,6 +19,7 @@ import {
|
|
|
19
19
|
isCatchClause,
|
|
20
20
|
isClassDeclaration,
|
|
21
21
|
isClassExpression,
|
|
22
|
+
isConstructorDeclaration,
|
|
22
23
|
isContinueStatement,
|
|
23
24
|
isDecorator,
|
|
24
25
|
isDefaultClause,
|
|
@@ -37,6 +38,7 @@ import {
|
|
|
37
38
|
isMethodDeclaration,
|
|
38
39
|
isModuleDeclaration,
|
|
39
40
|
isNamespaceImport,
|
|
41
|
+
isNewExpression,
|
|
40
42
|
isNoSubstitutionTemplateLiteral,
|
|
41
43
|
isNumericLiteral,
|
|
42
44
|
isObjectBindingPattern,
|
|
@@ -61,6 +63,7 @@ import {
|
|
|
61
63
|
type Block,
|
|
62
64
|
type CallExpression,
|
|
63
65
|
type CatchClause,
|
|
66
|
+
type ConstructorDeclaration,
|
|
64
67
|
type FunctionExpression,
|
|
65
68
|
type Identifier,
|
|
66
69
|
type Node,
|
|
@@ -68,7 +71,7 @@ import {
|
|
|
68
71
|
type Statement,
|
|
69
72
|
} from "@typescript/native/ast";
|
|
70
73
|
import type { FindingPosition } from "./finding.ts";
|
|
71
|
-
import { nodeKey, type Inventory } from "./inventory.ts";
|
|
74
|
+
import { isParameterProperty, nodeKey, type Inventory } from "./inventory.ts";
|
|
72
75
|
import { positionKey, renderPosition } from "./position.ts";
|
|
73
76
|
import { isAnswered, UNANSWERED, type Answer } from "./query.ts";
|
|
74
77
|
import type { Reference } from "./references.ts";
|
|
@@ -117,8 +120,10 @@ interface SignatureFact {
|
|
|
117
120
|
readonly declaration: string;
|
|
118
121
|
/** Whether the body is empty or does nothing but throw. */
|
|
119
122
|
readonly stub: boolean;
|
|
120
|
-
/** Whether a class hierarchy fixes the signature
|
|
123
|
+
/** Whether a class hierarchy fixes the signature, or a decorator a constructor's. */
|
|
121
124
|
readonly fixed: boolean;
|
|
125
|
+
/** Whether the signature is a constructor's, which its class's identifier stands for. */
|
|
126
|
+
readonly constructor: boolean;
|
|
122
127
|
}
|
|
123
128
|
|
|
124
129
|
/** What one project answers about the parts of its declarations. */
|
|
@@ -129,6 +134,8 @@ export interface ProjectParts {
|
|
|
129
134
|
readonly calls: ReadonlyMap<string, CallUse>;
|
|
130
135
|
/** Each name an import or an export specifier writes, by position, which uses no value. */
|
|
131
136
|
readonly specifiers: ReadonlySet<string>;
|
|
137
|
+
/** Each name of {@link constructionNames}, by position, which uses no constructor as a value. */
|
|
138
|
+
readonly constructions: ReadonlySet<string>;
|
|
132
139
|
}
|
|
133
140
|
|
|
134
141
|
/** The codes of the parts this module reads. */
|
|
@@ -391,6 +398,50 @@ function hierarchyFixes(declared: Node, extended: ReadonlySet<string>): boolean
|
|
|
391
398
|
return owner.name !== undefined && extended.has(owner.name.text);
|
|
392
399
|
}
|
|
393
400
|
|
|
401
|
+
/** Whether a decorator on a constructor's class or on one of its parameters fixes its signature. */
|
|
402
|
+
function decoratorFixes(constructor: ConstructorDeclaration): boolean {
|
|
403
|
+
const owner = constructor.parent;
|
|
404
|
+
return (
|
|
405
|
+
((owner as { readonly modifiers?: readonly Node[] }).modifiers ?? []).some(isDecorator) ||
|
|
406
|
+
constructor.parameters.some((parameter) =>
|
|
407
|
+
((parameter as { readonly modifiers?: readonly Node[] }).modifiers ?? []).some(isDecorator),
|
|
408
|
+
)
|
|
409
|
+
);
|
|
410
|
+
}
|
|
411
|
+
|
|
412
|
+
/** The name an expression spells a class by: an identifier, or the member a property access names. */
|
|
413
|
+
function spelledName(expression: Node): Identifier | undefined {
|
|
414
|
+
const node = unwrapped(expression);
|
|
415
|
+
if (isIdentifier(node)) {
|
|
416
|
+
return node;
|
|
417
|
+
}
|
|
418
|
+
return isPropertyAccessExpression(node) && isIdentifier(node.name) ? node.name : undefined;
|
|
419
|
+
}
|
|
420
|
+
|
|
421
|
+
/**
|
|
422
|
+
* The names one node writes that use a class without using its constructor as a value:
|
|
423
|
+
* the class a `new` expression calls, the class an `extends` or `implements` clause
|
|
424
|
+
* names, and the name a type reference spells.
|
|
425
|
+
*/
|
|
426
|
+
function constructionNames(node: Node): readonly Identifier[] {
|
|
427
|
+
if (isNewExpression(node)) {
|
|
428
|
+
return [spelledName(node.expression) ?? []].flat();
|
|
429
|
+
}
|
|
430
|
+
if (
|
|
431
|
+
isHeritageClause(node) &&
|
|
432
|
+
(isClassDeclaration(node.parent) || isClassExpression(node.parent))
|
|
433
|
+
) {
|
|
434
|
+
return node.types.flatMap((type) =>
|
|
435
|
+
isTypeReferenceNode(type) ? [] : (spelledName(type.expression) ?? []),
|
|
436
|
+
);
|
|
437
|
+
}
|
|
438
|
+
if (isTypeReferenceNode(node)) {
|
|
439
|
+
const named = isQualifiedName(node.typeName) ? node.typeName.right : node.typeName;
|
|
440
|
+
return isIdentifier(named) ? [named] : [];
|
|
441
|
+
}
|
|
442
|
+
return [];
|
|
443
|
+
}
|
|
444
|
+
|
|
394
445
|
/** One declaration of the inventory that declares a function, and that function. */
|
|
395
446
|
interface Declared {
|
|
396
447
|
readonly id: string;
|
|
@@ -478,19 +529,18 @@ export function projectParts<Brand>(
|
|
|
478
529
|
): ProjectParts {
|
|
479
530
|
const files = project.ownSourceFiles();
|
|
480
531
|
const extended = extendedNames(files);
|
|
481
|
-
const kinds = new Map(held.symbols.map((symbol) => [symbol.id, symbol.kind]));
|
|
482
532
|
const parts: PartFact[] = [];
|
|
483
533
|
const signatures: SignatureFact[] = [];
|
|
484
534
|
const calls = new Map<string, CallUse>();
|
|
485
535
|
const specifiers = new Set<string>();
|
|
536
|
+
const constructions = new Set<string>();
|
|
486
537
|
const sites: { readonly file: SourceFile; readonly declared: Declared }[] = [];
|
|
487
538
|
const contextual: ContextualParameter[] = [];
|
|
488
539
|
|
|
489
540
|
for (const file of files) {
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
};
|
|
541
|
+
// A parameter property's own parts stay with the parameter when its member goes.
|
|
542
|
+
const declarationAt = (node: Node): string | undefined =>
|
|
543
|
+
isParameterProperty(node) ? undefined : held.declarations.get(nodeKey(file, node));
|
|
494
544
|
// A part no declaration holds belongs to the file, which is judged while the file is live.
|
|
495
545
|
const fileId = held.declarations.get(nodeKey(file, file));
|
|
496
546
|
const holderOf = (node: Node): string | undefined => {
|
|
@@ -543,6 +593,18 @@ export function projectParts<Brand>(
|
|
|
543
593
|
walk.declared.push({ id, node, signature });
|
|
544
594
|
covered.add(signature);
|
|
545
595
|
}
|
|
596
|
+
// A constructor is no declaration, so the class it belongs to holds its parts.
|
|
597
|
+
const constructs =
|
|
598
|
+
isConstructorDeclaration(node) && node.body !== undefined
|
|
599
|
+
? declarationAt(node.parent)
|
|
600
|
+
: undefined;
|
|
601
|
+
if (constructs !== undefined && isConstructorDeclaration(node)) {
|
|
602
|
+
walk.declared.push({ id: constructs, node, signature: node });
|
|
603
|
+
covered.add(node);
|
|
604
|
+
}
|
|
605
|
+
for (const name of constructionNames(node)) {
|
|
606
|
+
constructions.add(position(name));
|
|
607
|
+
}
|
|
546
608
|
const value = covered.has(node) ? undefined : valueSignature(node);
|
|
547
609
|
const valueHolder = id ?? holderOf(node);
|
|
548
610
|
if (value !== undefined && valueHolder !== undefined && !isStub(value)) {
|
|
@@ -628,11 +690,15 @@ export function projectParts<Brand>(
|
|
|
628
690
|
const ids = resolvedIds(project, [...new Set(asked)]);
|
|
629
691
|
|
|
630
692
|
for (const one of walk.declared) {
|
|
631
|
-
|
|
693
|
+
const constructor = isConstructorDeclaration(one.node);
|
|
694
|
+
if (!constructor) {
|
|
695
|
+
sites.push({ file, declared: one });
|
|
696
|
+
}
|
|
632
697
|
signatures.push({
|
|
633
698
|
declaration: one.id,
|
|
634
699
|
stub: isStub(one.signature),
|
|
635
|
-
fixed: hierarchyFixes(one.node, extended),
|
|
700
|
+
fixed: constructor ? decoratorFixes(one.node) : hierarchyFixes(one.node, extended),
|
|
701
|
+
constructor,
|
|
636
702
|
});
|
|
637
703
|
const reader = readers.get(one.signature);
|
|
638
704
|
if (reader !== undefined) {
|
|
@@ -699,7 +765,13 @@ export function projectParts<Brand>(
|
|
|
699
765
|
}
|
|
700
766
|
|
|
701
767
|
parts.push(...resultParts(project, targetRoot, sites, references, calls));
|
|
702
|
-
return {
|
|
768
|
+
return {
|
|
769
|
+
parts: withContextualRead(project, parts, contextual),
|
|
770
|
+
signatures,
|
|
771
|
+
calls,
|
|
772
|
+
specifiers,
|
|
773
|
+
constructions,
|
|
774
|
+
};
|
|
703
775
|
}
|
|
704
776
|
|
|
705
777
|
/**
|
|
@@ -722,16 +794,12 @@ function valueSignature(node: Node): SignatureNode | undefined {
|
|
|
722
794
|
|
|
723
795
|
/**
|
|
724
796
|
* Every name one function's parameters bind, a binding pattern's names one by one. A
|
|
725
|
-
* `this` parameter receives no argument
|
|
726
|
-
*
|
|
797
|
+
* `this` parameter receives no argument, a decorated one is read by its decorator, and a
|
|
798
|
+
* parameter property is read by the write to its member, so none binds a subject.
|
|
727
799
|
*/
|
|
728
800
|
function parameterNames(signature: SignatureNode): ParameterName[] {
|
|
729
801
|
const found: ParameterName[] = [];
|
|
730
|
-
const patternNames = (pattern: BindingName, at: number): void => {
|
|
731
|
-
if (isIdentifier(pattern)) {
|
|
732
|
-
found.push({ name: pattern, at, pattern: true, exempt: false });
|
|
733
|
-
return;
|
|
734
|
-
}
|
|
802
|
+
const patternNames = (pattern: Exclude<BindingName, Identifier>, at: number): void => {
|
|
735
803
|
const rest =
|
|
736
804
|
isObjectBindingPattern(pattern) &&
|
|
737
805
|
pattern.elements.some((element) => element.dotDotDotToken !== undefined);
|
|
@@ -750,7 +818,8 @@ function parameterNames(signature: SignatureNode): ParameterName[] {
|
|
|
750
818
|
signature.parameters.forEach((parameter, at) => {
|
|
751
819
|
const name = parameter.name;
|
|
752
820
|
if (
|
|
753
|
-
((parameter as { readonly modifiers?: readonly Node[] }).modifiers ?? []).some(isDecorator)
|
|
821
|
+
((parameter as { readonly modifiers?: readonly Node[] }).modifiers ?? []).some(isDecorator) ||
|
|
822
|
+
isParameterProperty(parameter)
|
|
754
823
|
) {
|
|
755
824
|
return;
|
|
756
825
|
}
|
|
@@ -961,10 +1030,7 @@ function deadStores(
|
|
|
961
1030
|
for (const write of writes) {
|
|
962
1031
|
// The rest of the write's own statement runs after it: a later declarator may read the local.
|
|
963
1032
|
const own = statements[write.at];
|
|
964
|
-
const sameStatement =
|
|
965
|
-
own !== undefined && write.target === local.name
|
|
966
|
-
? usesIn(own).filter((use) => use !== local.name)
|
|
967
|
-
: [];
|
|
1033
|
+
const sameStatement = own !== undefined && write.target === local.name ? usesIn(own) : [];
|
|
968
1034
|
if (sameStatement.length > 0) {
|
|
969
1035
|
continue;
|
|
970
1036
|
}
|
|
@@ -1096,7 +1162,7 @@ export function intraFunctionFacts(
|
|
|
1096
1162
|
for (const [at, value] of one.calls) {
|
|
1097
1163
|
calls.set(at, value);
|
|
1098
1164
|
}
|
|
1099
|
-
for (const at of one.specifiers) {
|
|
1165
|
+
for (const at of [...one.specifiers, ...one.constructions]) {
|
|
1100
1166
|
specifiers.add(at);
|
|
1101
1167
|
}
|
|
1102
1168
|
}
|
|
@@ -1127,10 +1193,16 @@ export function intraFunctionFacts(
|
|
|
1127
1193
|
.map((one) => one.declaration)
|
|
1128
1194
|
.filter((id) => !fixed.has(id) && !exempted.has(id) && !valued.has(id)),
|
|
1129
1195
|
);
|
|
1196
|
+
// A class used as a value fixes its constructor's whole signature, whatever reads follow.
|
|
1197
|
+
const constructors = new Set(
|
|
1198
|
+
signatures.filter((one) => one.constructor).map((one) => one.declaration),
|
|
1199
|
+
);
|
|
1130
1200
|
const freeButValued = new Set(
|
|
1131
1201
|
signatures
|
|
1132
1202
|
.map((one) => one.declaration)
|
|
1133
|
-
.filter(
|
|
1203
|
+
.filter(
|
|
1204
|
+
(id) => !fixed.has(id) && !exempted.has(id) && valued.has(id) && !constructors.has(id),
|
|
1205
|
+
),
|
|
1134
1206
|
);
|
|
1135
1207
|
// Awaiting a thenable result sequences on it, so a call that awaits one uses it.
|
|
1136
1208
|
const sequenced = new Set(
|
package/src/inventory.ts
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import {
|
|
2
2
|
isClassDeclaration,
|
|
3
3
|
isComputedPropertyName,
|
|
4
|
+
isConstructorDeclaration,
|
|
4
5
|
isEnumDeclaration,
|
|
5
6
|
isEnumMember,
|
|
6
7
|
isExportAssignment,
|
|
@@ -15,6 +16,7 @@ import {
|
|
|
15
16
|
isModuleBlock,
|
|
16
17
|
isModuleDeclaration,
|
|
17
18
|
isNamespaceExport,
|
|
19
|
+
isParameterDeclaration,
|
|
18
20
|
isParenthesizedTypeNode,
|
|
19
21
|
isPrivateIdentifier,
|
|
20
22
|
isPropertyDeclaration,
|
|
@@ -26,6 +28,7 @@ import {
|
|
|
26
28
|
isUnionTypeNode,
|
|
27
29
|
isVariableStatement,
|
|
28
30
|
ModifierFlags,
|
|
31
|
+
type ClassDeclaration,
|
|
29
32
|
type Node,
|
|
30
33
|
type SourceFile,
|
|
31
34
|
} from "@typescript/native/ast";
|
|
@@ -490,6 +493,26 @@ function unnamedStart(node: Node): number {
|
|
|
490
493
|
return prefix === null ? start : start + prefix[0].length;
|
|
491
494
|
}
|
|
492
495
|
|
|
496
|
+
/**
|
|
497
|
+
* Whether one node is a parameter property: a constructor parameter carrying an
|
|
498
|
+
* accessibility, `readonly` or `override` modifier, which also declares an instance
|
|
499
|
+
* member of the class, with the parameter's node as the member's declaration.
|
|
500
|
+
*/
|
|
501
|
+
export function isParameterProperty(node: Node): boolean {
|
|
502
|
+
return (
|
|
503
|
+
isParameterDeclaration(node) &&
|
|
504
|
+
isConstructorDeclaration(node.parent) &&
|
|
505
|
+
(modifiersOf(node) & ModifierFlags.ParameterPropertyModifier) !== 0
|
|
506
|
+
);
|
|
507
|
+
}
|
|
508
|
+
|
|
509
|
+
/** The parameter properties every constructor of one class declares. */
|
|
510
|
+
function parameterPropertiesOf(node: ClassDeclaration): readonly Node[] {
|
|
511
|
+
return node.members.flatMap((member) =>
|
|
512
|
+
isConstructorDeclaration(member) ? member.parameters.filter(isParameterProperty) : [],
|
|
513
|
+
);
|
|
514
|
+
}
|
|
515
|
+
|
|
493
516
|
/** The kind a class element takes: a method, or a property or accessor. */
|
|
494
517
|
function classMemberKind(node: Node): SymbolKind {
|
|
495
518
|
return isMethodDeclaration(node) ? "method" : "class-member";
|
|
@@ -647,7 +670,7 @@ export function inventory<Brand>(
|
|
|
647
670
|
owner: record,
|
|
648
671
|
declaration: node,
|
|
649
672
|
nameNode: node.name,
|
|
650
|
-
members: node.members,
|
|
673
|
+
members: [...node.members, ...parameterPropertiesOf(node)],
|
|
651
674
|
memberKind: "class-member",
|
|
652
675
|
isClass: true,
|
|
653
676
|
membersAreExports: false,
|
|
@@ -974,7 +997,8 @@ export function inventory<Brand>(
|
|
|
974
997
|
isPropertyDeclaration(node) ||
|
|
975
998
|
isMethodDeclaration(node) ||
|
|
976
999
|
isGetAccessorDeclaration(node) ||
|
|
977
|
-
isSetAccessorDeclaration(node)
|
|
1000
|
+
isSetAccessorDeclaration(node) ||
|
|
1001
|
+
isParameterProperty(node)
|
|
978
1002
|
) {
|
|
979
1003
|
return classMemberKind(node);
|
|
980
1004
|
}
|
|
@@ -1018,9 +1042,7 @@ export function inventory<Brand>(
|
|
|
1018
1042
|
}
|
|
1019
1043
|
|
|
1020
1044
|
const byId = new Map(building.map((record) => [positionKey(record.position), record]));
|
|
1021
|
-
const symbols = building
|
|
1022
|
-
.map(render)
|
|
1023
|
-
.sort((a, b) => byPosition(a.position, b.position) || (a.ref < b.ref ? -1 : 1));
|
|
1045
|
+
const symbols = building.map(render).sort((a, b) => byPosition(a.position, b.position));
|
|
1024
1046
|
|
|
1025
1047
|
return {
|
|
1026
1048
|
symbols,
|
package/src/kinds.ts
CHANGED
|
@@ -191,7 +191,7 @@ const ROWS: readonly KindRow[] = [
|
|
|
191
191
|
code: "DS1301",
|
|
192
192
|
name: "write-only-symbol",
|
|
193
193
|
languages: ["go", "ts"],
|
|
194
|
-
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.",
|
|
194
|
+
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.",
|
|
195
195
|
defaultSeverity: "deny",
|
|
196
196
|
maxClass: "certain",
|
|
197
197
|
fixability: "deletable",
|
|
@@ -332,7 +332,7 @@ const ROWS: readonly KindRow[] = [
|
|
|
332
332
|
languages: ["go", "ts"],
|
|
333
333
|
rule: "A parameter with no reference inside its function body, on a function whose signature is free to change.",
|
|
334
334
|
precondition:
|
|
335
|
-
"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.",
|
|
335
|
+
"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.",
|
|
336
336
|
defaultSeverity: "warn",
|
|
337
337
|
maxClass: "certain",
|
|
338
338
|
fixability: "manual",
|