@cplieger/deadset-ts 8.0.0-dev.20 → 8.0.0-dev.22
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/src/alias-chain.js +33 -2
- package/dist/src/analysis.js +18 -77
- package/dist/src/conformance-record.js +2 -2
- package/dist/src/contract/contract.generated.js +5 -4
- package/dist/src/dependencies.js +1 -1
- package/dist/src/exemptions/argument-places.js +131 -0
- package/dist/src/{calls.js → exemptions/calls.js} +126 -179
- package/dist/src/{exempt-classes.js → exemptions/classes.js} +1 -1
- package/dist/src/{decorator.js → exemptions/decorator.js} +19 -30
- package/dist/src/exemptions/detectors.js +48 -0
- package/dist/src/{enum-group.js → exemptions/enum-group.js} +7 -7
- package/dist/src/{erased-stores.js → exemptions/erased-stores.js} +4 -6
- package/dist/src/{framework-lifecycle.js → exemptions/framework-lifecycle.js} +31 -36
- package/dist/src/{generated-file.js → exemptions/generated-file.js} +4 -4
- package/dist/src/{injection-container.js → exemptions/injection-container.js} +19 -25
- package/dist/src/exemptions/installed-globals.js +173 -0
- package/dist/src/{interface-satisfaction.js → exemptions/interface-satisfaction.js} +52 -121
- package/dist/src/exemptions/lifecycle.js +150 -0
- package/dist/src/{reflective-lookup.js → exemptions/reflective-lookup.js} +3 -3
- package/dist/src/{serialization-contract.js → exemptions/serialization-contract.js} +415 -329
- package/dist/src/exemptions/sites.js +182 -0
- package/dist/src/{template-field.js → exemptions/template-field.js} +7 -7
- package/dist/src/{value-reach.js → exemptions/value-reach.js} +14 -7
- package/dist/src/findings/intra-function.js +50 -37
- package/dist/src/index/sites.js +1 -1
- package/dist/src/index/values.js +124 -0
- package/dist/src/intra-function-parts.js +1149 -222
- package/dist/src/liveness/sweep.js +2 -2
- package/dist/src/query.js +48 -0
- package/dist/src/roots/configured-declarations.js +1 -1
- package/dist/src/verbs/print-retained.js +23 -1
- package/jsr.json +1 -1
- package/package.json +2 -2
- package/src/alias-chain.ts +38 -2
- package/src/analysis.ts +19 -100
- package/src/bindings.ts +1 -1
- package/src/conformance-record.ts +2 -2
- package/src/contract/contract.generated.ts +5 -4
- package/src/dependencies.ts +1 -1
- package/src/exemptions/argument-places.ts +198 -0
- package/src/{calls.ts → exemptions/calls.ts} +190 -224
- package/src/{exempt-classes.ts → exemptions/classes.ts} +1 -1
- package/src/exemptions/decorator.ts +56 -0
- package/src/exemptions/detectors.ts +75 -0
- package/src/{enum-group.ts → exemptions/enum-group.ts} +9 -9
- package/src/{erased-stores.ts → exemptions/erased-stores.ts} +5 -8
- package/src/{framework-lifecycle.ts → exemptions/framework-lifecycle.ts} +39 -48
- package/src/{generated-file.ts → exemptions/generated-file.ts} +6 -6
- package/src/{injection-container.ts → exemptions/injection-container.ts} +28 -33
- package/src/exemptions/installed-globals.ts +255 -0
- package/src/{interface-satisfaction.ts → exemptions/interface-satisfaction.ts} +69 -160
- package/src/exemptions/lifecycle.ts +286 -0
- package/src/{reflective-lookup.ts → exemptions/reflective-lookup.ts} +5 -5
- package/src/{serialization-contract.ts → exemptions/serialization-contract.ts} +563 -429
- package/src/exemptions/sites.ts +293 -0
- package/src/{template-field.ts → exemptions/template-field.ts} +11 -11
- package/src/{value-reach.ts → exemptions/value-reach.ts} +16 -8
- package/src/facts.ts +1 -1
- package/src/findings/intra-function.ts +59 -37
- package/src/index/sites.ts +1 -1
- package/src/index/values.ts +170 -0
- package/src/intra-function-parts.ts +1435 -247
- package/src/liveness/sweep.ts +2 -2
- package/src/query.ts +109 -10
- package/src/roots/configured-declarations.ts +1 -2
- package/src/stores.ts +1 -1
- package/src/verbs/print-retained.ts +29 -1
- package/dist/src/exempt.js +0 -164
- package/dist/src/value-flow.js +0 -74
- package/src/decorator.ts +0 -78
- package/src/exempt.ts +0 -295
- package/src/value-flow.ts +0 -104
package/dist/src/alias-chain.js
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Where an import or export chain leads in one project: from one symbol to each
|
|
3
3
|
* declaration of the target it is, and along the chain of aliases it heads to each
|
|
4
|
-
* declaration every link of that chain is.
|
|
4
|
+
* declaration every link of that chain is, and what a name resolves to through them.
|
|
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
8
|
import { isParameterProperty, nodeKey } from "./inventory.js";
|
|
9
|
-
import { UNANSWERED } from "./query.js";
|
|
9
|
+
import { must, UNANSWERED } from "./query.js";
|
|
10
10
|
/**
|
|
11
11
|
* The chain walks over one project, against the declarations of `held`, the same
|
|
12
12
|
* project's inventory.
|
|
@@ -145,3 +145,34 @@ export function aliasChains(project, held) {
|
|
|
145
145
|
nextDeclared,
|
|
146
146
|
};
|
|
147
147
|
}
|
|
148
|
+
/**
|
|
149
|
+
* What each of a batch of nodes resolves to, every alias followed to the declaration
|
|
150
|
+
* it stands for. A node the checker resolves to nothing, and an alias that resolves
|
|
151
|
+
* to nothing, are absent from the answer. Each alias is followed once. A question the
|
|
152
|
+
* checker does not answer stops the pass, as {@link must} stops.
|
|
153
|
+
*/
|
|
154
|
+
export function resolvedTargets(project, nodes) {
|
|
155
|
+
const followed = new Map();
|
|
156
|
+
const targets = new Map();
|
|
157
|
+
project
|
|
158
|
+
.symbolsAt(nodes.map((node) => project.handle(node)))
|
|
159
|
+
.map(must)
|
|
160
|
+
.forEach((symbol, index) => {
|
|
161
|
+
const node = nodes[index];
|
|
162
|
+
if (symbol === undefined || node === undefined) {
|
|
163
|
+
return;
|
|
164
|
+
}
|
|
165
|
+
let target = symbol;
|
|
166
|
+
if ((symbol.flags & SymbolFlags.Alias) !== 0) {
|
|
167
|
+
if (!followed.has(symbol.id)) {
|
|
168
|
+
const aliased = must(project.queries.aliased(symbol));
|
|
169
|
+
followed.set(symbol.id, aliased.declarations.length === 0 ? undefined : aliased);
|
|
170
|
+
}
|
|
171
|
+
target = followed.get(symbol.id);
|
|
172
|
+
}
|
|
173
|
+
if (target !== undefined) {
|
|
174
|
+
targets.set(node, target);
|
|
175
|
+
}
|
|
176
|
+
});
|
|
177
|
+
return targets;
|
|
178
|
+
}
|
package/dist/src/analysis.js
CHANGED
|
@@ -10,7 +10,6 @@ import { lineOf, readComponent } from "./component-files.js";
|
|
|
10
10
|
import { kindOf } from "./config.js";
|
|
11
11
|
import { consumerReferences } from "./consumers.js";
|
|
12
12
|
import { configuredDeclarations, configuredRef, declarationsNamed, } from "./roots/configured-declarations.js";
|
|
13
|
-
import { decorator } from "./decorator.js";
|
|
14
13
|
import { configurationNeeds, dependenciesOf, projectNeeds } from "./dependencies.js";
|
|
15
14
|
import { carriersOf, NO_CARRIERS } from "./exposure.js";
|
|
16
15
|
import { deprecatedDeclarations } from "./deprecation.js";
|
|
@@ -19,19 +18,16 @@ import { loadRun } from "./load/load.js";
|
|
|
19
18
|
import { readSources } from "./entries/sources.js";
|
|
20
19
|
import { readWorkflows } from "./entries/workflow-steps.js";
|
|
21
20
|
import { readEdgeSides } from "./edges.js";
|
|
22
|
-
import { enumGroup } from "./enum-group.js";
|
|
23
21
|
import { bindingsOf, distinctBindings } from "./bindings.js";
|
|
24
|
-
import {
|
|
22
|
+
import { generatedFilesOf } from "./exemptions/generated-file.js";
|
|
25
23
|
import { inOperandNames } from "./findings/in-operands.js";
|
|
26
24
|
import { sourceFilesOf } from "./source-files.js";
|
|
27
|
-
import {
|
|
25
|
+
import { DETECTORS, detection } from "./exemptions/detectors.js";
|
|
26
|
+
import { exemptionsOf, settle } from "./exemptions/lifecycle.js";
|
|
28
27
|
import { heldByInclusion, sourcePaths, sourceTree } from "./file-facts.js";
|
|
29
28
|
import { findingsPass, recordedFindings, rowKey } from "./findings-pass.js";
|
|
30
29
|
import { unmatchedRootFindings } from "./findings/self-check.js";
|
|
31
|
-
import { frameworkLifecycle } from "./framework-lifecycle.js";
|
|
32
30
|
import { implementations, mergeImplementations } from "./findings/implementations.js";
|
|
33
|
-
import { injectionContainer } from "./injection-container.js";
|
|
34
|
-
import { interfaceSatisfaction, settledSatisfaction } from "./interface-satisfaction.js";
|
|
35
31
|
import { intraFunctionFacts, projectParts } from "./intra-function-parts.js";
|
|
36
32
|
import {} from "./inventory.js";
|
|
37
33
|
import { inventory } from "./inventory/enumerate.js";
|
|
@@ -40,10 +36,9 @@ import { matrixOf, referenceKey } from "./liveness/matrix.js";
|
|
|
40
36
|
import { ancestors, dirnamePath, extensionOf, joinPath, relativePath, resolvePath, } from "./paths.js";
|
|
41
37
|
import { byPosition, isComponentFile } from "./position.js";
|
|
42
38
|
import { skippedUnits } from "./type-errors.js";
|
|
43
|
-
import { reflectiveLookup } from "./reflective-lookup.js";
|
|
44
39
|
import { references } from "./references.js";
|
|
45
40
|
import { rootSet, unmatchedEverywhere, } from "./roots/root-set.js";
|
|
46
|
-
import { decodedMembers
|
|
41
|
+
import { decodedMembers } from "./exemptions/serialization-contract.js";
|
|
47
42
|
import { outsideTestReferences, testFilesAt } from "./tests/outside.js";
|
|
48
43
|
import { formedForNode } from "./load/synthesize.js";
|
|
49
44
|
import { GLOBALS_RUNNER, namesTestGlobals } from "./load/test-globals.js";
|
|
@@ -54,7 +49,6 @@ import { isPartKind } from "./suppress.js";
|
|
|
54
49
|
import { bindRows, readBaseline, readIgnoreFile } from "./suppress-file.js";
|
|
55
50
|
import { inlineDirectives, inlineSuppressions } from "./suppress-inline.js";
|
|
56
51
|
import { sweep } from "./liveness/sweep.js";
|
|
57
|
-
import { readTemplates, templateField } from "./template-field.js";
|
|
58
52
|
import { bytewise } from "./text/compare.js";
|
|
59
53
|
import { holdsManifest, manifestAt } from "./fs/manifest.js";
|
|
60
54
|
import { runDirectories } from "./entries/run-directories.js";
|
|
@@ -230,18 +224,6 @@ export function productionMode(config) {
|
|
|
230
224
|
consumerTestsProduction: config.analysis.consumer_tests === "production",
|
|
231
225
|
};
|
|
232
226
|
}
|
|
233
|
-
/** The exemption classes this analyzer detects, each by its detector; any other retains nothing. */
|
|
234
|
-
export const DETECTORS = new Map([
|
|
235
|
-
["interface-satisfaction", interfaceSatisfaction],
|
|
236
|
-
["enum-group", enumGroup],
|
|
237
|
-
["generated-file", generatedFile],
|
|
238
|
-
["template-field", templateField],
|
|
239
|
-
["reflective-lookup", reflectiveLookup],
|
|
240
|
-
["decorator", decorator],
|
|
241
|
-
["injection-container", injectionContainer],
|
|
242
|
-
["framework-lifecycle", frameworkLifecycle],
|
|
243
|
-
["serialization-contract", serializationContract],
|
|
244
|
-
]);
|
|
245
227
|
/** The manifest section of a package test evidence counts, and the sections that keep one out of it. */
|
|
246
228
|
const DEV_SECTION = "devDependencies";
|
|
247
229
|
const OTHER_SECTIONS = ["dependencies", "peerDependencies", "optionalDependencies"];
|
|
@@ -288,11 +270,8 @@ function testEvidence(host, targetRoot, projects) {
|
|
|
288
270
|
*/
|
|
289
271
|
function readRun(engine, host, scope, config, mode, detectors, extra) {
|
|
290
272
|
const targetRoot = scope.target.path;
|
|
291
|
-
const disabled = disabledClasses(config);
|
|
292
|
-
const templates = disabled.has("template-field")
|
|
293
|
-
? { delimiters: config.analysis.template_delimiters, files: [] }
|
|
294
|
-
: readTemplates(host, targetRoot, config.analysis);
|
|
295
273
|
const consumers = scope.consumers.map((consumer) => consumer.path);
|
|
274
|
+
const detect = detection(host, targetRoot, config, consumers, detectors);
|
|
296
275
|
const directories = runDirectories(host, targetRoot, (fileName, text) => engine.parseSourceFile(fileName, text));
|
|
297
276
|
const read = readProjects(engine, host, scope, config, (project, opened) => {
|
|
298
277
|
const projectRead = opened.read();
|
|
@@ -303,17 +282,7 @@ function readRun(engine, host, scope, config, mode, detectors, extra) {
|
|
|
303
282
|
formedForNode: projectRead.formedForNode,
|
|
304
283
|
});
|
|
305
284
|
const testFiles = testFilePaths(project, targetRoot, testFile);
|
|
306
|
-
const evidence =
|
|
307
|
-
project,
|
|
308
|
-
held: projectRead.held,
|
|
309
|
-
targetRoot,
|
|
310
|
-
templates,
|
|
311
|
-
ts: config.ts,
|
|
312
|
-
consumers,
|
|
313
|
-
...(config.analysis.generated_files === "include"
|
|
314
|
-
? {}
|
|
315
|
-
: { generated: projectRead.generated }),
|
|
316
|
-
}, detectors, disabled);
|
|
285
|
+
const evidence = detect({ project, held: projectRead.held, generated: projectRead.generated });
|
|
317
286
|
const components = project.ownSourceFiles().filter(isComponentFile);
|
|
318
287
|
const injected = project.program
|
|
319
288
|
.getSourceFileNames()
|
|
@@ -378,27 +347,26 @@ function readRun(engine, host, scope, config, mode, detectors, extra) {
|
|
|
378
347
|
...one.decoded.copied,
|
|
379
348
|
]),
|
|
380
349
|
]);
|
|
381
|
-
const
|
|
382
|
-
|
|
383
|
-
evidence: evidence[index] ?? [],
|
|
350
|
+
const settled = settle(withOutside.map((one) => ({
|
|
351
|
+
records: one.evidence,
|
|
384
352
|
held: one.projectRead.held,
|
|
385
|
-
|
|
353
|
+
testFiles: new Set([...one.testFiles, ...support]),
|
|
354
|
+
})), {
|
|
355
|
+
references: [...withOutside.flatMap((one) => one.references), ...read.result.references],
|
|
356
|
+
written,
|
|
357
|
+
mode,
|
|
358
|
+
});
|
|
386
359
|
const answers = withOutside.map((one, index) => {
|
|
387
360
|
const { projectRead } = one;
|
|
388
|
-
const
|
|
389
|
-
const
|
|
390
|
-
mode,
|
|
391
|
-
testFiles: new Set([...one.testFiles, ...support]),
|
|
392
|
-
});
|
|
393
|
-
const exempt = detected.records;
|
|
394
|
-
const made = [...one.references, ...(satisfied?.references ?? [])];
|
|
361
|
+
const exempt = settled[index]?.records ?? [];
|
|
362
|
+
const made = [...one.references, ...(settled[index]?.references ?? [])];
|
|
395
363
|
const held = new Set(projectRead.held.symbols.map((symbol) => symbol.position.path));
|
|
396
364
|
const supportFiles = [...support].filter((path) => held.has(path)).sort(bytewise);
|
|
397
365
|
const testUsedFiles = [...testUsed].filter((path) => held.has(path)).sort(bytewise);
|
|
398
366
|
const configured = {
|
|
399
367
|
configuration: projectRead.configuration,
|
|
400
368
|
symbols: projectRead.held.symbols,
|
|
401
|
-
references:
|
|
369
|
+
references: made,
|
|
402
370
|
roots: projectRead.rooted.liveUnderReachability,
|
|
403
371
|
testFiles: [...one.testFiles, ...supportFiles],
|
|
404
372
|
...(supportFiles.length === 0 ? {} : { supportFiles }),
|
|
@@ -420,7 +388,7 @@ function readRun(engine, host, scope, config, mode, detectors, extra) {
|
|
|
420
388
|
...projectRead.rooted.unanswered,
|
|
421
389
|
...made.filter((reference) => reference.guessed === true).map((reference) => reference.to),
|
|
422
390
|
...one.held,
|
|
423
|
-
...exempt.filter(
|
|
391
|
+
...exempt.filter((record) => record.unanswered === true).map((record) => record.id),
|
|
424
392
|
...one.decoded.unanswered,
|
|
425
393
|
],
|
|
426
394
|
filledOutside: one.decoded.filled,
|
|
@@ -470,33 +438,6 @@ function readRun(engine, host, scope, config, mode, detectors, extra) {
|
|
|
470
438
|
workflows: read.result.workflows,
|
|
471
439
|
};
|
|
472
440
|
}
|
|
473
|
-
/**
|
|
474
|
-
* The test references exemption evidence written in a test file makes: each is a read of
|
|
475
|
-
* the declaration it would retain, made by the test file at the evidence's site.
|
|
476
|
-
*/
|
|
477
|
-
function evidenceReferences(symbols, inTestFiles) {
|
|
478
|
-
if (inTestFiles.length === 0) {
|
|
479
|
-
return [];
|
|
480
|
-
}
|
|
481
|
-
const files = new Map(symbols
|
|
482
|
-
.filter((symbol) => symbol.kind === "file")
|
|
483
|
-
.map((symbol) => [symbol.position.path, symbol.id]));
|
|
484
|
-
return inTestFiles.flatMap((record) => {
|
|
485
|
-
const from = files.get(record.site.path);
|
|
486
|
-
return from === undefined
|
|
487
|
-
? []
|
|
488
|
-
: [
|
|
489
|
-
{
|
|
490
|
-
from,
|
|
491
|
-
to: record.id,
|
|
492
|
-
position: record.site,
|
|
493
|
-
use: "read",
|
|
494
|
-
resolution: "syntax",
|
|
495
|
-
test: true,
|
|
496
|
-
},
|
|
497
|
-
];
|
|
498
|
-
});
|
|
499
|
-
}
|
|
500
441
|
/**
|
|
501
442
|
* The declarations a write the mode counts stores into, a write a test-support file
|
|
502
443
|
* makes being a test's.
|
|
@@ -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: "6.
|
|
5
|
+
corpusVersion: "6.1.0",
|
|
6
6
|
result: "pass",
|
|
7
|
-
digest: "sha256:
|
|
7
|
+
digest: "sha256:fcfdf88a7d45e1ac96545ef10c8427462260f4e81641d4d808b80086256bb6b7",
|
|
8
8
|
},
|
|
9
9
|
gaps: [],
|
|
10
10
|
};
|
|
@@ -3,7 +3,7 @@ export const CONTRACT_VERSION = "8.0.0";
|
|
|
3
3
|
export const SCHEMA_VERSIONS = ["9.0.0"];
|
|
4
4
|
export const SCHEMA_VERSION = "9.0.0";
|
|
5
5
|
export const FACTS_VERSION = "1.0.0";
|
|
6
|
-
export const CORPUS_VERSION = "6.
|
|
6
|
+
export const CORPUS_VERSION = "6.1.0";
|
|
7
7
|
export const CODE_PREFIX = "DS";
|
|
8
8
|
export const FAMILY_KEY_LENGTH = 4;
|
|
9
9
|
export const LANGUAGES = ["go", "ts", "proto"];
|
|
@@ -266,7 +266,7 @@ export const KIND_ROWS = [
|
|
|
266
266
|
subject: "part",
|
|
267
267
|
holdsLive: false,
|
|
268
268
|
rule: "A parameter with no reference inside its function body, on a function whose signature is free to change.",
|
|
269
|
-
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 go-fuzz entry point, 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. In Go a function literal that is the callee of the call it is written in, the call of a `go` or a `defer` statement included, is not used as a value, wherever the literal is written. 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 for deletion when its body reads no parameter after it, and an unread parameter before the last read one is reported for a rename to its name with a leading underscore, which keeps every argument in place and which the compilers' unused-parameter rules read as declared unread, unless its name already starts with one. A parameter of a TypeScript function expression or arrow function that the compiler gives a contextual type, and that carries a type annotation, is not reported for deletion where the inference of the call holding the function may read the annotation, being where the callee is generic and its parameter type at the function's place names a type parameter at the parameter's place, or where the function is written inside an object literal such a call is passed, and it is reported for deletion at `probable` where it carries an annotation no such inference reads, because nothing in the program shows that an outside reader ignores it. A name an object binding pattern of a parameter of such a function binds, annotated or not, that the body never reads is reported for deletion at `probable`, because the pattern reads the property it names from the argument and the code that hands the argument may act on that read, which nothing in the program shows. 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. A TypeScript name a parameter binds, whole or through a binding pattern, that starts with an underscore is never reported, because a leading underscore is the spelling unused-parameter rules read as declared unread, and no other name exempts a parameter. A Go parameter named by the blank identifier declares no name a body could read and is never reported.",
|
|
269
|
+
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 go-fuzz entry point, 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. In Go a function literal that is the callee of the call it is written in, the call of a `go` or a `defer` statement included, is not used as a value, wherever the literal is written. 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 for deletion when its body reads no parameter after it, and an unread parameter before the last read one is reported for a rename to its name with a leading underscore, which keeps every argument in place and which the compilers' unused-parameter rules read as declared unread, unless its name already starts with one. A function's `length` counts each parameter before the first one with a default or a rest, and code holding the function may read it, so in TypeScript an unread parameter that `length` counts is reported at `probable` where the function is used as a value or is a root, which a library's published API is and which code outside the program calls, and a function whose `length` the program reads through its own name reports no parameter. A parameter of a TypeScript function expression or arrow function that the compiler gives a contextual type, and that carries a type annotation, is not reported for deletion where the inference of the call holding the function may read the annotation, being where the callee is generic and its parameter type at the function's place names a type parameter at the parameter's place, or where the function is written inside an object literal such a call is passed, and it is reported for deletion at `probable` where it carries an annotation no such inference reads, because nothing in the program shows that an outside reader ignores it. A name an object binding pattern of a parameter of such a function binds, annotated or not, that the body never reads is reported for deletion at `probable`, because the pattern reads the property it names from the argument and the code that hands the argument may act on that read, which nothing in the program shows. 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. A TypeScript name a parameter binds, whole or through a binding pattern, that starts with an underscore is never reported, because a leading underscore is the spelling unused-parameter rules read as declared unread, and no other name exempts a parameter. A Go parameter named by the blank identifier declares no name a body could read and is never reported.",
|
|
270
270
|
details: { overlap: "required" },
|
|
271
271
|
defaultEnabled: true,
|
|
272
272
|
defaultSeverity: "warn",
|
|
@@ -371,7 +371,7 @@ export const KIND_ROWS = [
|
|
|
371
371
|
languages: ["go", "ts"],
|
|
372
372
|
subject: "declaration",
|
|
373
373
|
holdsLive: true,
|
|
374
|
-
rule: "An exported symbol whose every reference is inside the symbol's own package or module, reported as a candidate for unexporting. The subject is a package-level declaration or a method.
|
|
374
|
+
rule: "An exported symbol whose every reference is inside the symbol's own package or module, reported as a candidate for unexporting. The subject is a package-level declaration or a method. Four subjects are excluded. An interface method is excluded because its exportedness is the contract of the interface that declares it. A struct field is excluded because an encoder reads it by name. A method that satisfies an interface some symbol uses as a type is excluded because it cannot be unexported without its type ceasing to satisfy that interface, and so is 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, because that interface fixes them whether or not the program converts the method's type to it. A Go function a driver finds by its exported name is excluded, whatever else refers to it, because unexporting it stops the driver from running it: a test, a benchmark, a fuzz test or an example of a test file, TestMain, a test method a suite runner calls, and a go-fuzz entry point. The finding names the narrower visibility the references support. A declared cross-language edge counts as an out-of-package reference. Where the edge's other side is unknown to the analyzer, the finding is emitted pending. A reference from a Go external test package, a test file whose package clause names its directory's package with the suffix _test, is a reference from outside the package, because that test builds as another package and needs the export.",
|
|
375
375
|
precondition: "Closed world only. A Go main package, and an internal/ directory tree for importers outside its parent, are closed worlds whatever the configuration declares, because no other program can import them, so this finding is reported there always. A published package is a closed world only when the configuration declares the consumer set complete and every declared consumer loads. A library with no consumer loaded and no complete consumer set declared gets this finding on its internal/ tree and its main packages and never on its published API. A symbol the rule of DS1301 holds for is not reported under this code, whatever severity the configuration gives DS1301, because the write-only finding names the defect to fix first. A type is not reported when a reference from outside the package reaches its values through its own package's exported declarations: a reference to an exported function or method whose parameters or results name the type, to an exported variable or constant whose type names it, to an exported field or property whose type names it, an embedded field included, or to an exported member of the type itself, a field promoted through an embedding included. A type a reference from outside the package names, or whose values such a reference reaches, passes the reach on to each type its definition names other than through an unexported member, whether it is exported or not, so an unexported type an exported function returns passes it to the types its exported fields name. A type names another through every composite that spells it: a pointer, an array, a slice, a map key or value, a channel, a function signature and a type argument. The referencing code holds values of the type, so unexporting it would leave an exported declaration naming a type its users cannot name. A package-level declaration whose name a string literal spells carries the class grammar/analysis/confidence.md gives it.",
|
|
376
376
|
details: { narrower_visibility: "required", name_literal: "optional" },
|
|
377
377
|
defaultEnabled: true,
|
|
@@ -386,7 +386,7 @@ export const KIND_ROWS = [
|
|
|
386
386
|
languages: ["go"],
|
|
387
387
|
subject: "declaration",
|
|
388
388
|
holdsLive: true,
|
|
389
|
-
rule: "An exported symbol of a non-internal package whose every reference is inside the target module, reported as a candidate for relocation behind an internal boundary. The subject is a package-level declaration or a method.
|
|
389
|
+
rule: "An exported symbol of a non-internal package whose every reference is inside the target module, reported as a candidate for relocation behind an internal boundary. The subject is a package-level declaration or a method. Four subjects are excluded. An interface method is excluded because its exportedness is the contract of the interface that declares it. A struct field is excluded because an encoder reads it by name. A method that satisfies an interface some symbol uses as a type is excluded because it cannot be unexported without its type ceasing to satisfy that interface, and so is 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, because that interface fixes them whether or not the program converts the method's type to it. A Go function a driver finds by its exported name is excluded, whatever else refers to it, because unexporting it stops the driver from running it: a test, a benchmark, a fuzz test or an example of a test file, TestMain, a test method a suite runner calls, and a go-fuzz entry point. The finding names the narrower visibility the references support. A declared cross-language edge counts as an out-of-package reference. Where the edge's other side is unknown to the analyzer, the finding is emitted pending. A reference from a Go external test package, a test file whose package clause names its directory's package with the suffix _test, is a reference from outside the package, because that test builds as another package and needs the export.",
|
|
390
390
|
precondition: "Closed world only. Reported always for a main package and an internal/ directory tree, and for a published package only when the configuration declares the consumer set complete and every declared consumer loads. A library with no consumer loaded and no complete consumer set declared gets this finding on its internal/ tree and its main packages and never on its published API. A symbol the rule of DS1301 holds for is not reported under this code, whatever severity the configuration gives DS1301, because the write-only finding names the defect to fix first. A type is not reported when a reference from outside the target module reaches its values through its own package's exported declarations, as DS1501 defines that reach, because the referencing code holds values of the type, and moving it behind an internal boundary would leave an exported declaration naming a type its users cannot name.",
|
|
391
391
|
details: { narrower_visibility: "required", name_literal: "optional" },
|
|
392
392
|
defaultEnabled: true,
|
|
@@ -1361,6 +1361,7 @@ export const FAILURE_CLASSES = {
|
|
|
1361
1361
|
"unwritten-output": "run",
|
|
1362
1362
|
"unreportable-path": "run",
|
|
1363
1363
|
"memory-exhausted": "run",
|
|
1364
|
+
"missing-toolchain": "run",
|
|
1364
1365
|
"missing-analyzer": "run",
|
|
1365
1366
|
"unadmitted-analyzer": "run",
|
|
1366
1367
|
"unresolved-edge": "run",
|
package/dist/src/dependencies.js
CHANGED
|
@@ -67,7 +67,7 @@ export function packageOfSpecifier(specifier) {
|
|
|
67
67
|
return second === undefined || second === "" || first === "@" ? undefined : `${first}/${second}`;
|
|
68
68
|
}
|
|
69
69
|
/** The package an installed file belongs to, read below the last dependency directory of its path. */
|
|
70
|
-
function packageOfPath(path) {
|
|
70
|
+
export function packageOfPath(path) {
|
|
71
71
|
const at = path.lastIndexOf(MODULES_SEGMENT);
|
|
72
72
|
return at < 0 ? undefined : packageOfSpecifier(path.slice(at + MODULES_SEGMENT.length));
|
|
73
73
|
}
|
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Which parameter place each argument of a call binds, the one reading every question
|
|
3
|
+
* about a call's arguments asks. A plain argument fills the next place, and a spread or
|
|
4
|
+
* an `apply` list typed as a tuple whose elements are all required fills one place per
|
|
5
|
+
* element. Any other spread or list has no known length: from its first place on, each of
|
|
6
|
+
* its element types, and every later argument, may fill any place, so a value is never
|
|
7
|
+
* read as missing a parameter only because its place could not be read. The type at each
|
|
8
|
+
* place is the checker's answer for it, so this reading names places and never types them.
|
|
9
|
+
*/
|
|
10
|
+
import { isSpreadElement } from "@typescript/native/ast";
|
|
11
|
+
import { ElementFlags } from "@typescript/native/sync";
|
|
12
|
+
import { must } from "../query.js";
|
|
13
|
+
/** The element flags of a tuple type; none for another type. */
|
|
14
|
+
function elementFlags(project, type) {
|
|
15
|
+
const target = type.isTupleType() ? must(project.queries.targetOf(type)) : undefined;
|
|
16
|
+
return target?.isTupleTypeTarget() === true ? target.elementFlags : [];
|
|
17
|
+
}
|
|
18
|
+
/** What a spread argument puts at the places it fills, and how many it fills at least. */
|
|
19
|
+
function spread(project, type) {
|
|
20
|
+
const { queries } = project;
|
|
21
|
+
if (type === undefined) {
|
|
22
|
+
return { types: [undefined], fixed: false, least: 0 };
|
|
23
|
+
}
|
|
24
|
+
if (type.isTupleType()) {
|
|
25
|
+
const flags = elementFlags(project, type);
|
|
26
|
+
return {
|
|
27
|
+
types: must(queries.typeArguments(type)).slice(0, flags.length),
|
|
28
|
+
fixed: flags.every((flag) => (flag & ElementFlags.Required) !== 0),
|
|
29
|
+
least: flags.filter((flag) => (flag & ElementFlags.Required) !== 0).length,
|
|
30
|
+
};
|
|
31
|
+
}
|
|
32
|
+
if (must(queries.isArray(type))) {
|
|
33
|
+
return { types: must(queries.typeArguments(type)), fixed: false, least: 0 };
|
|
34
|
+
}
|
|
35
|
+
// Another iterable's elements are read off its type arguments, as `Set<T>` holds `T`.
|
|
36
|
+
return { types: [type, ...must(queries.typeArguments(type))], fixed: false, least: 0 };
|
|
37
|
+
}
|
|
38
|
+
/** The element types of a tuple of `count` required elements; none for another type. */
|
|
39
|
+
export function knownElements(project, type, count) {
|
|
40
|
+
if (!type.isTupleType()) {
|
|
41
|
+
return undefined;
|
|
42
|
+
}
|
|
43
|
+
const elements = spread(project, type);
|
|
44
|
+
return elements.fixed && elements.types.length === count ? elements.types : undefined;
|
|
45
|
+
}
|
|
46
|
+
/**
|
|
47
|
+
* The places each argument fills, `types` holding the type of each argument's value; it
|
|
48
|
+
* may be empty where no argument is a spread and no given type is read. `listed` reads
|
|
49
|
+
* the last argument as a list whose elements the places bind.
|
|
50
|
+
*/
|
|
51
|
+
export function spansOf(project, args, listed, types) {
|
|
52
|
+
const spans = [];
|
|
53
|
+
let place = 0;
|
|
54
|
+
let open = false;
|
|
55
|
+
for (const [at, argument] of args.entries()) {
|
|
56
|
+
const type = types[at];
|
|
57
|
+
if (!isSpreadElement(argument) && !(listed && at === args.length - 1)) {
|
|
58
|
+
spans.push({ argument: at, first: place, given: [type], open, spread: false });
|
|
59
|
+
place += 1;
|
|
60
|
+
continue;
|
|
61
|
+
}
|
|
62
|
+
const elements = spread(project, type);
|
|
63
|
+
open ||= !elements.fixed;
|
|
64
|
+
spans.push({ argument: at, first: place, given: elements.types, open, spread: true });
|
|
65
|
+
place += elements.least;
|
|
66
|
+
}
|
|
67
|
+
return spans;
|
|
68
|
+
}
|
|
69
|
+
/**
|
|
70
|
+
* The places of one signature as the checker counts them: one per parameter, and in
|
|
71
|
+
* place of a rest as many as the `length` the checker answers for its type, where that
|
|
72
|
+
* is a number literal or a union of them. Any other `length`, an array's or an array-like
|
|
73
|
+
* interface's `number` among them, leaves the rest open.
|
|
74
|
+
*/
|
|
75
|
+
export function layoutOf(project, signature) {
|
|
76
|
+
const { queries } = project;
|
|
77
|
+
const fixed = signature.parameters.length;
|
|
78
|
+
const rest = must(queries.restTypeOf(signature));
|
|
79
|
+
if (rest === undefined) {
|
|
80
|
+
return { count: fixed, repeats: false };
|
|
81
|
+
}
|
|
82
|
+
const length = must(queries.propertyTypeOf(rest, "length"));
|
|
83
|
+
const lengths = length === undefined ? [] : must(queries.constituents(length));
|
|
84
|
+
const bounds = lengths.flatMap((one) => (one.isNumberLiteralType() ? [one.value] : []));
|
|
85
|
+
if (bounds.length > 0 && bounds.length === lengths.length) {
|
|
86
|
+
return { count: fixed - 1 + Math.max(...bounds), repeats: false };
|
|
87
|
+
}
|
|
88
|
+
return { count: fixed, repeats: true };
|
|
89
|
+
}
|
|
90
|
+
/**
|
|
91
|
+
* The parameter place each argument binds in a signature of `layout`, with the type it
|
|
92
|
+
* gives there. A span of unknown length binds every place from its first to the last the
|
|
93
|
+
* layout counts, and past them, where the rest is open, one `rest` placement for every
|
|
94
|
+
* later place, or else its first place alone. A place past every place of a layout that
|
|
95
|
+
* does not repeat is `uncovered`.
|
|
96
|
+
*/
|
|
97
|
+
export function placesIn(spans, layout) {
|
|
98
|
+
const placed = [];
|
|
99
|
+
const add = (span, place, rest) => {
|
|
100
|
+
const uncovered = place >= layout.count && !layout.repeats;
|
|
101
|
+
const elements = span.open
|
|
102
|
+
? span.given.map((given, element) => ({ given, element }))
|
|
103
|
+
: [{ given: span.given[place - span.first], element: place - span.first }];
|
|
104
|
+
placed.push(...elements.map((one) => ({ argument: span.argument, place, uncovered, rest, ...one })));
|
|
105
|
+
};
|
|
106
|
+
for (const span of spans) {
|
|
107
|
+
const open = span.open && layout.repeats;
|
|
108
|
+
const end = !span.open
|
|
109
|
+
? span.first + span.given.length
|
|
110
|
+
: Math.max(layout.count, open ? span.first : span.first + 1);
|
|
111
|
+
for (let place = span.first; place < end; place += 1) {
|
|
112
|
+
add(span, place, false);
|
|
113
|
+
}
|
|
114
|
+
if (open) {
|
|
115
|
+
add(span, Math.max(layout.count, span.first), true);
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
return placed;
|
|
119
|
+
}
|
|
120
|
+
/**
|
|
121
|
+
* Whether a span fills the parameter declared at `index`, a rest parameter every place
|
|
122
|
+
* from it on; with `element`, whether that element of a span that is not open does.
|
|
123
|
+
*/
|
|
124
|
+
export function fills(span, index, rest, element) {
|
|
125
|
+
if (element !== undefined && !span.open) {
|
|
126
|
+
const place = span.first + element;
|
|
127
|
+
return rest ? place >= index : place === index;
|
|
128
|
+
}
|
|
129
|
+
const last = span.open ? Number.POSITIVE_INFINITY : span.first + span.given.length - 1;
|
|
130
|
+
return rest ? last >= index : span.first <= index && index <= last;
|
|
131
|
+
}
|