@cplieger/deadset-ts 6.1.0 → 7.0.0-dev.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/src/analysis.js +39 -16
- package/dist/src/conformance-record.js +2 -2
- package/dist/src/contextual-uses.js +27 -3
- package/dist/src/convention-rows.js +42 -0
- package/dist/src/conventions.js +48 -0
- package/dist/src/dependencies.js +109 -38
- package/dist/src/findings/completion.js +6 -7
- package/dist/src/findings/dependencies-and-module-machinery.js +6 -26
- package/dist/src/html-entries.js +29 -14
- package/dist/src/indexed-access.js +371 -81
- package/dist/src/intra-function-parts.js +57 -11
- package/dist/src/kinds.js +6 -6
- package/dist/src/references.js +60 -21
- package/dist/src/roots.js +96 -6
- package/dist/src/session.js +6 -1
- package/dist/src/source-files.js +1 -0
- package/dist/src/sweep.js +29 -17
- package/dist/src/symbol-keys.js +114 -0
- package/dist/src/test-joins.js +101 -0
- package/dist/src/version.js +1 -1
- package/dist/src/workflow-steps.js +21 -15
- package/dist/src/workspace-programs.js +1 -1
- package/jsr.json +1 -1
- package/package.json +1 -1
- package/src/analysis.ts +45 -17
- package/src/conformance-record.ts +2 -2
- package/src/contextual-uses.ts +36 -4
- package/src/convention-rows.ts +57 -0
- package/src/conventions.ts +59 -0
- package/src/dependencies.ts +134 -46
- package/src/finding.ts +2 -0
- package/src/findings/completion.ts +7 -7
- package/src/findings/dependencies-and-module-machinery.ts +9 -28
- package/src/html-entries.ts +33 -15
- package/src/indexed-access.ts +468 -102
- package/src/intra-function-parts.ts +84 -12
- package/src/kinds.ts +7 -6
- package/src/references.ts +86 -27
- package/src/roots.ts +129 -6
- package/src/session.ts +7 -1
- package/src/source-files.ts +3 -0
- package/src/sweep.ts +29 -17
- package/src/symbol-keys.ts +172 -0
- package/src/test-joins.ts +120 -0
- package/src/version.ts +1 -1
- package/src/workflow-steps.ts +23 -16
- package/src/workspace-programs.ts +1 -1
|
@@ -4,7 +4,7 @@
|
|
|
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 { 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/unstable/ast";
|
|
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/unstable/ast";
|
|
8
8
|
import { nodeKey } from "./inventory.js";
|
|
9
9
|
import { positionKey, renderPosition } from "./position.js";
|
|
10
10
|
import { isAnswered, UNANSWERED } from "./query.js";
|
|
@@ -277,6 +277,7 @@ export function projectParts(project, held, references, targetRoot) {
|
|
|
277
277
|
const calls = new Map();
|
|
278
278
|
const specifiers = new Set();
|
|
279
279
|
const sites = [];
|
|
280
|
+
const contextual = [];
|
|
280
281
|
for (const file of files) {
|
|
281
282
|
const declarationAt = (node) => {
|
|
282
283
|
const id = held.declarations.get(nodeKey(file, node));
|
|
@@ -421,13 +422,17 @@ export function projectParts(project, held, references, targetRoot) {
|
|
|
421
422
|
});
|
|
422
423
|
const reader = readers.get(one.signature);
|
|
423
424
|
if (reader !== undefined) {
|
|
424
|
-
|
|
425
|
+
const found = parameterParts(file, targetRoot, one.id, one.signature, reader, ids, false);
|
|
426
|
+
parts.push(...found.parts);
|
|
427
|
+
contextual.push(...found.contextual);
|
|
425
428
|
}
|
|
426
429
|
}
|
|
427
430
|
for (const one of walk.values) {
|
|
428
431
|
const reader = readers.get(one.signature);
|
|
429
432
|
if (reader !== undefined) {
|
|
430
|
-
|
|
433
|
+
const found = parameterParts(file, targetRoot, one.holder, one.signature, reader, ids, true);
|
|
434
|
+
parts.push(...found.parts);
|
|
435
|
+
contextual.push(...found.contextual);
|
|
431
436
|
}
|
|
432
437
|
}
|
|
433
438
|
walk.catches.forEach(({ name, holder }, at) => {
|
|
@@ -471,7 +476,7 @@ export function projectParts(project, held, references, targetRoot) {
|
|
|
471
476
|
}
|
|
472
477
|
}
|
|
473
478
|
parts.push(...resultParts(project, targetRoot, sites, references, calls));
|
|
474
|
-
return { parts, signatures, calls, specifiers };
|
|
479
|
+
return { parts: withContextualRead(project, parts, contextual), signatures, calls, specifiers };
|
|
475
480
|
}
|
|
476
481
|
/**
|
|
477
482
|
* A function written in place that no declaration declares: an arrow, a function
|
|
@@ -533,8 +538,26 @@ function parameterNames(signature) {
|
|
|
533
538
|
return found;
|
|
534
539
|
}
|
|
535
540
|
/**
|
|
536
|
-
*
|
|
537
|
-
*
|
|
541
|
+
* The parts with each name of `contextual` read where the compiler gives its function a
|
|
542
|
+
* contextual type: the inference of the call holding the function may read a parameter's
|
|
543
|
+
* annotation, and the code handing the argument may act on what an object pattern reads.
|
|
544
|
+
* A question the checker leaves unanswered keeps the name read.
|
|
545
|
+
*/
|
|
546
|
+
function withContextualRead(project, parts, contextual) {
|
|
547
|
+
if (contextual.length === 0) {
|
|
548
|
+
return parts;
|
|
549
|
+
}
|
|
550
|
+
const signatures = [...new Set(contextual.map((one) => one.signature))];
|
|
551
|
+
const answers = project.queries.contextualTypes(signatures);
|
|
552
|
+
const typed = new Set(signatures.filter((_signature, at) => answers[at] !== undefined));
|
|
553
|
+
const read = new Set(contextual.filter((one) => typed.has(one.signature)).map((one) => one.part));
|
|
554
|
+
return parts.map((part) => (read.has(part) ? { ...part, used: true } : part));
|
|
555
|
+
}
|
|
556
|
+
/**
|
|
557
|
+
* Each name one function's parameters bind and whether the function reads it, and the
|
|
558
|
+
* unread names a contextual type of a function expression may keep: those of a parameter
|
|
559
|
+
* that carries a type annotation or is an object binding pattern. A binding pattern counts
|
|
560
|
+
* as read where its function reads any name it binds.
|
|
538
561
|
*/
|
|
539
562
|
function parameterParts(file, root, declaration, signature, reader, ids, inValue) {
|
|
540
563
|
// A function that reads `arguments` reads every parameter by position.
|
|
@@ -543,11 +566,13 @@ function parameterParts(file, root, declaration, signature, reader, ids, inValue
|
|
|
543
566
|
reader.uses.some((use) => use.text === name.text && readsBinding(use, name, ids)));
|
|
544
567
|
const lastRead = Math.max(-1, ...reader.names.filter((_, at) => read[at]).map((one) => one.at));
|
|
545
568
|
const found = [];
|
|
569
|
+
const contextual = [];
|
|
570
|
+
const expression = isArrowFunction(signature) || isFunctionExpression(signature) ? signature : undefined;
|
|
546
571
|
reader.names.forEach(({ name, at, pattern, exempt }, index) => {
|
|
547
572
|
if (exempt) {
|
|
548
573
|
return;
|
|
549
574
|
}
|
|
550
|
-
|
|
575
|
+
const part = {
|
|
551
576
|
code: UNUSED_PARAMETER,
|
|
552
577
|
declaration,
|
|
553
578
|
kind: "parameter",
|
|
@@ -556,9 +581,30 @@ function parameterParts(file, root, declaration, signature, reader, ids, inValue
|
|
|
556
581
|
message: `parameter ${name.text} is never read in the body`,
|
|
557
582
|
used: read[index] === true,
|
|
558
583
|
parameter: { beforeARead: !pattern && at < lastRead, inValue },
|
|
559
|
-
}
|
|
584
|
+
};
|
|
585
|
+
found.push(part);
|
|
586
|
+
const parameter = signature.parameters[at];
|
|
587
|
+
if (expression !== undefined &&
|
|
588
|
+
!part.used &&
|
|
589
|
+
parameter !== undefined &&
|
|
590
|
+
(parameter.type !== undefined || isObjectBindingPattern(parameter.name))) {
|
|
591
|
+
contextual.push({ part, signature: expression });
|
|
592
|
+
}
|
|
560
593
|
});
|
|
561
|
-
return found;
|
|
594
|
+
return { parts: found, contextual };
|
|
595
|
+
}
|
|
596
|
+
/**
|
|
597
|
+
* The names one declaration binds that a write of its initializer stores each on its own:
|
|
598
|
+
* the declared name, or each element name of an array binding pattern at any depth.
|
|
599
|
+
*/
|
|
600
|
+
function arrayPatternNames(name) {
|
|
601
|
+
if (isIdentifier(name)) {
|
|
602
|
+
return [name];
|
|
603
|
+
}
|
|
604
|
+
if (!isArrayBindingPattern(name)) {
|
|
605
|
+
return [];
|
|
606
|
+
}
|
|
607
|
+
return name.elements.flatMap((element) => isBindingElement(element) && element.name !== undefined ? arrayPatternNames(element.name) : []);
|
|
562
608
|
}
|
|
563
609
|
/** The locals one block declares with `let` or `const`, each with the names that spell it. */
|
|
564
610
|
function localsOf(block) {
|
|
@@ -573,9 +619,9 @@ function localsOf(block) {
|
|
|
573
619
|
return;
|
|
574
620
|
}
|
|
575
621
|
for (const declaration of list.declarations) {
|
|
576
|
-
|
|
622
|
+
for (const name of arrayPatternNames(declaration.name)) {
|
|
577
623
|
declared.push({
|
|
578
|
-
name
|
|
624
|
+
name,
|
|
579
625
|
statement: at,
|
|
580
626
|
initialized: declaration.initializer !== undefined,
|
|
581
627
|
});
|
package/dist/src/kinds.js
CHANGED
|
@@ -70,7 +70,7 @@ const ROWS = [
|
|
|
70
70
|
code: "DS1101",
|
|
71
71
|
name: "unnecessary-export",
|
|
72
72
|
languages: ["go", "ts"],
|
|
73
|
-
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. Three 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. 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.",
|
|
73
|
+
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. Three 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. 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.",
|
|
74
74
|
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 that a parameter or a result of an exported function or method of its own package names is not reported when a reference from outside the package references that function or method, because the caller holds values of the type, and unexporting it would leave an exported signature naming a type its callers cannot name.",
|
|
75
75
|
defaultSeverity: "warn",
|
|
76
76
|
maxClass: "certain",
|
|
@@ -81,7 +81,7 @@ const ROWS = [
|
|
|
81
81
|
code: "DS1102",
|
|
82
82
|
name: "unnecessary-exposure",
|
|
83
83
|
languages: ["go"],
|
|
84
|
-
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. Three 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. 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.",
|
|
84
|
+
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. Three 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. 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.",
|
|
85
85
|
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 that a parameter or a result of an exported function or method of its own package names is not reported when a reference from outside the target module references that function or method, because the caller holds values of the type, and moving it behind an internal boundary would leave an exported signature naming a type its callers cannot name.",
|
|
86
86
|
defaultSeverity: "warn",
|
|
87
87
|
maxClass: "certain",
|
|
@@ -197,7 +197,7 @@ const ROWS = [
|
|
|
197
197
|
name: "unused-dependency",
|
|
198
198
|
languages: ["go", "ts"],
|
|
199
199
|
rule: "A directly declared dependency that no import in the target needs. On the Go side, a direct require whose module provides no package any target package or test variant imports. On the TypeScript side, a manifest dependency, development dependency or peer dependency the project's import closure does not need. A deletion finding whose fix would remove the last use of a dependency names that dependency.",
|
|
200
|
-
precondition: "On the Go side the rule is the semantics of `go mod tidy -diff` exactly: a requirement marked indirect is never reported, because it exists to pin a transitive version and removing it changes the build list. On the TypeScript side the import closure is the files of the run's projects, and
|
|
200
|
+
precondition: "On the Go side the rule is the semantics of `go mod tidy -diff` exactly: a requirement marked indirect is never reported, because it exists to pin a transitive version and removing it changes the build list. On the TypeScript side the import closure is the files of the run's projects, and a call of `import.meta.resolve` or of `require.resolve` in them, whatever `require` names, whose first argument is a string literal or a template literal with no substitution needs each dependency an import of that specifier needs. Three more dependencies are needed: one whose command a script entry or a workflow step runs, one that a string of a configuration file names, and one that a string names under a manifest key or a package key of an applied convention row, each as grammar/analysis.md states. A command no script entry and no workflow step runs needs nothing, and a peer the installed manifest of a needed dependency requires is needed only as any other dependency is, because the package manager installs a required peer itself. A dependency of a manifest whose directory holds source only in configurations the run lists in configurations_not_built is reported at the reachability class possible, because the source that may import it was not analyzed. An installed manifest is the package's manifest in the nearest node_modules directory at or above the target, and a dependency with none is decided by the import closure alone.",
|
|
201
201
|
defaultSeverity: "deny",
|
|
202
202
|
maxClass: "certain",
|
|
203
203
|
fixability: "manual",
|
|
@@ -279,7 +279,7 @@ const ROWS = [
|
|
|
279
279
|
name: "unused-parameter",
|
|
280
280
|
languages: ["go", "ts"],
|
|
281
281
|
rule: "A parameter with no reference inside its function body, on a function whose signature is free to change.",
|
|
282
|
-
precondition: "The signature must be free, defined as follows: the function is not a method retained by interface satisfaction, 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 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 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.",
|
|
282
|
+
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.",
|
|
283
283
|
defaultSeverity: "warn",
|
|
284
284
|
maxClass: "certain",
|
|
285
285
|
fixability: "manual",
|
|
@@ -290,8 +290,8 @@ const ROWS = [
|
|
|
290
290
|
code: "DS1802",
|
|
291
291
|
name: "unused-receiver",
|
|
292
292
|
languages: ["go"],
|
|
293
|
-
rule: "A named method receiver with no reference inside the method body
|
|
294
|
-
precondition: "
|
|
293
|
+
rule: "A named method receiver with no reference inside the method body. Go permits a method with no receiver name, so the fix deletes an identifier and changes no signature.",
|
|
294
|
+
precondition: "None beyond the rule. Deleting a receiver name changes no signature, so a method retained by interface satisfaction, a method used as a value and a stub are reported as any other method is.",
|
|
295
295
|
defaultSeverity: "warn",
|
|
296
296
|
maxClass: "certain",
|
|
297
297
|
fixability: "deletable",
|
package/dist/src/references.js
CHANGED
|
@@ -14,6 +14,7 @@ import { dirnamePath, queriedModulePath, relativePath, resolvePath } from "./pat
|
|
|
14
14
|
import { byPosition, isComponentFile, renderPosition } from "./position.js";
|
|
15
15
|
import { DEFAULT_BATCH_CAP, propertyTables, UNANSWERED } from "./query.js";
|
|
16
16
|
import { typeQueryAliases } from "./type-query-alias.js";
|
|
17
|
+
import { computedKeyNames, keyNameOf, keyedUses } from "./symbol-keys.js";
|
|
17
18
|
import { handedOn, valuesOf } from "./value-flow.js";
|
|
18
19
|
/**
|
|
19
20
|
* The operators whose left side one assignment stores into. A compound assignment is
|
|
@@ -84,12 +85,11 @@ function unparenthesized(node) {
|
|
|
84
85
|
* Records the name one store writes into.
|
|
85
86
|
*
|
|
86
87
|
* A property access writes the member it names. An element access writes the collection
|
|
87
|
-
* it stores into,
|
|
88
|
-
*
|
|
89
|
-
* writes each name it binds, through
|
|
90
|
-
* in.
|
|
88
|
+
* it stores into, since a collection a module only stores into holds nothing anything
|
|
89
|
+
* reads, and joins `accesses` for the member a unique-symbol key selects. A destructuring
|
|
90
|
+
* target writes each name it binds, through its spreads and nested patterns.
|
|
91
91
|
*/
|
|
92
|
-
function markWrite(target, writes) {
|
|
92
|
+
function markWrite(target, writes, accesses) {
|
|
93
93
|
const held = unparenthesized(target);
|
|
94
94
|
if (isName(held)) {
|
|
95
95
|
writes.add(held.pos);
|
|
@@ -102,37 +102,38 @@ function markWrite(target, writes) {
|
|
|
102
102
|
return;
|
|
103
103
|
}
|
|
104
104
|
if (isElementAccessExpression(held)) {
|
|
105
|
+
accesses.add(held);
|
|
105
106
|
// A store through a member's value lands in storage every holder of the value
|
|
106
107
|
// reads, so it reads the member; a variable only ever stored into stays written.
|
|
107
108
|
if (!isPropertyAccessExpression(unparenthesized(held.expression))) {
|
|
108
|
-
markWrite(held.expression, writes);
|
|
109
|
+
markWrite(held.expression, writes, accesses);
|
|
109
110
|
}
|
|
110
111
|
return;
|
|
111
112
|
}
|
|
112
113
|
if (isArrayLiteralExpression(held)) {
|
|
113
114
|
for (const element of held.elements) {
|
|
114
|
-
markWrite(element, writes);
|
|
115
|
+
markWrite(element, writes, accesses);
|
|
115
116
|
}
|
|
116
117
|
return;
|
|
117
118
|
}
|
|
118
119
|
if (isObjectLiteralExpression(held)) {
|
|
119
120
|
for (const property of held.properties) {
|
|
120
121
|
if (isPropertyAssignment(property)) {
|
|
121
|
-
markWrite(property.initializer, writes);
|
|
122
|
+
markWrite(property.initializer, writes, accesses);
|
|
122
123
|
continue;
|
|
123
124
|
}
|
|
124
125
|
if (isShorthandPropertyAssignment(property)) {
|
|
125
|
-
markWrite(property.name, writes);
|
|
126
|
+
markWrite(property.name, writes, accesses);
|
|
126
127
|
continue;
|
|
127
128
|
}
|
|
128
129
|
if (isSpreadAssignment(property)) {
|
|
129
|
-
markWrite(property.expression, writes);
|
|
130
|
+
markWrite(property.expression, writes, accesses);
|
|
130
131
|
}
|
|
131
132
|
}
|
|
132
133
|
return;
|
|
133
134
|
}
|
|
134
135
|
if (isSpreadElement(held)) {
|
|
135
|
-
markWrite(held.expression, writes);
|
|
136
|
+
markWrite(held.expression, writes, accesses);
|
|
136
137
|
}
|
|
137
138
|
}
|
|
138
139
|
/**
|
|
@@ -142,14 +143,14 @@ function markWrite(target, writes) {
|
|
|
142
143
|
* holds. A store that reads its target as well records it in `reads`: a logical
|
|
143
144
|
* assignment, and a compound assignment, an increment or a decrement whose value is used.
|
|
144
145
|
*/
|
|
145
|
-
function markStores(node, writes, reads) {
|
|
146
|
+
function markStores(node, writes, reads, accesses) {
|
|
146
147
|
if (isBinaryExpression(node)) {
|
|
147
148
|
const operator = node.operatorToken.kind;
|
|
148
149
|
if (ASSIGNMENTS.has(operator)) {
|
|
149
|
-
markWrite(node.left, writes);
|
|
150
|
+
markWrite(node.left, writes, accesses.written);
|
|
150
151
|
if (LOGICAL_ASSIGNMENTS.has(operator) ||
|
|
151
152
|
(operator !== SyntaxKind.EqualsToken && !valueDiscarded(node))) {
|
|
152
|
-
markWrite(node.left, reads);
|
|
153
|
+
markWrite(node.left, reads, accesses.read);
|
|
153
154
|
}
|
|
154
155
|
}
|
|
155
156
|
return;
|
|
@@ -157,20 +158,20 @@ function markStores(node, writes, reads) {
|
|
|
157
158
|
if (isPrefixUnaryExpression(node) || isPostfixUnaryExpression(node)) {
|
|
158
159
|
if (node.operator === SyntaxKind.PlusPlusToken ||
|
|
159
160
|
node.operator === SyntaxKind.MinusMinusToken) {
|
|
160
|
-
markWrite(node.operand, writes);
|
|
161
|
+
markWrite(node.operand, writes, accesses.written);
|
|
161
162
|
if (!valueDiscarded(node)) {
|
|
162
|
-
markWrite(node.operand, reads);
|
|
163
|
+
markWrite(node.operand, reads, accesses.read);
|
|
163
164
|
}
|
|
164
165
|
}
|
|
165
166
|
return;
|
|
166
167
|
}
|
|
167
168
|
if (isDeleteExpression(node)) {
|
|
168
|
-
markWrite(node.expression, writes);
|
|
169
|
+
markWrite(node.expression, writes, accesses.written);
|
|
169
170
|
return;
|
|
170
171
|
}
|
|
171
172
|
if (isForInStatement(node) || isForOfStatement(node)) {
|
|
172
173
|
if (node.initializer.kind !== SyntaxKind.VariableDeclarationList) {
|
|
173
|
-
markWrite(node.initializer, writes);
|
|
174
|
+
markWrite(node.initializer, writes, accesses.written);
|
|
174
175
|
}
|
|
175
176
|
}
|
|
176
177
|
}
|
|
@@ -337,6 +338,8 @@ function sitesOf(file, declarations, fileId, unread) {
|
|
|
337
338
|
const flows = [];
|
|
338
339
|
const writes = new Set();
|
|
339
340
|
const alsoRead = new Set();
|
|
341
|
+
const accesses = { written: new Set(), read: new Set() };
|
|
342
|
+
const keyed = [];
|
|
340
343
|
const declared = new Set();
|
|
341
344
|
const shorthands = new Map();
|
|
342
345
|
let enclosing = fileId;
|
|
@@ -381,7 +384,17 @@ function sitesOf(file, declarations, fileId, unread) {
|
|
|
381
384
|
return;
|
|
382
385
|
}
|
|
383
386
|
markNames(node);
|
|
384
|
-
markStores(node, writes, alsoRead);
|
|
387
|
+
markStores(node, writes, alsoRead, accesses);
|
|
388
|
+
const key = isElementAccessExpression(node) ? keyNameOf(node.argumentExpression) : undefined;
|
|
389
|
+
if (key !== undefined && isElementAccessExpression(node)) {
|
|
390
|
+
const written = accesses.written.has(node);
|
|
391
|
+
keyed.push({
|
|
392
|
+
access: node,
|
|
393
|
+
key,
|
|
394
|
+
from: enclosing,
|
|
395
|
+
use: written ? (accesses.read.has(node) ? "both" : "write") : "read",
|
|
396
|
+
});
|
|
397
|
+
}
|
|
385
398
|
if (isObjectLiteralExpression(node) && !isAssignmentPattern(node)) {
|
|
386
399
|
literals.push({ literal: node, from: enclosing });
|
|
387
400
|
}
|
|
@@ -415,7 +428,18 @@ function sitesOf(file, declarations, fileId, unread) {
|
|
|
415
428
|
enclosing = outer;
|
|
416
429
|
};
|
|
417
430
|
file.forEachChild(visit);
|
|
418
|
-
return {
|
|
431
|
+
return {
|
|
432
|
+
uses: found,
|
|
433
|
+
literals,
|
|
434
|
+
flows,
|
|
435
|
+
reads,
|
|
436
|
+
links,
|
|
437
|
+
evaluations,
|
|
438
|
+
wholes,
|
|
439
|
+
urls,
|
|
440
|
+
decorated,
|
|
441
|
+
keyed,
|
|
442
|
+
};
|
|
419
443
|
}
|
|
420
444
|
/** The declaration kinds of a property an object literal declares. */
|
|
421
445
|
const LITERAL_PROPERTY_KINDS = new Set([
|
|
@@ -827,7 +851,8 @@ export function references(project, held, targetRoot, options) {
|
|
|
827
851
|
}
|
|
828
852
|
}
|
|
829
853
|
}
|
|
830
|
-
|
|
854
|
+
const keyNames = computedKeyNames(held.symbols);
|
|
855
|
+
for (const { file, fileId, uses, reads, links, evaluations, wholes, urls, decorated, keyed, } of walked) {
|
|
831
856
|
const test = testFile.get(file) === true;
|
|
832
857
|
// A component file's markup may use any binding its blocks declare at the top level.
|
|
833
858
|
const markup = isComponentFile(file);
|
|
@@ -1029,6 +1054,20 @@ export function references(project, held, targetRoot, options) {
|
|
|
1029
1054
|
});
|
|
1030
1055
|
}
|
|
1031
1056
|
});
|
|
1057
|
+
const resolvedKeys = keyed.flatMap((access) => {
|
|
1058
|
+
const symbol = isIdentifier(access.key) && keyNames.has(access.key.text)
|
|
1059
|
+
? answered.get(access.key)
|
|
1060
|
+
: undefined;
|
|
1061
|
+
return typeof symbol === "object" ? [{ access, symbol }] : [];
|
|
1062
|
+
});
|
|
1063
|
+
for (const { access, ids } of keyedUses(project, chains, resolvedKeys, tables, cap)) {
|
|
1064
|
+
const position = renderPosition(file, root, access.access.argumentExpression.getStart());
|
|
1065
|
+
for (const id of ids) {
|
|
1066
|
+
for (const use of access.use === "both" ? ["read", "write"] : [access.use]) {
|
|
1067
|
+
found.push({ from: access.from, to: id, position, use, resolution: "keyed", test });
|
|
1068
|
+
}
|
|
1069
|
+
}
|
|
1070
|
+
}
|
|
1032
1071
|
for (const site of uses) {
|
|
1033
1072
|
const direct = site.shorthand !== undefined
|
|
1034
1073
|
? "shorthand"
|
package/dist/src/roots.js
CHANGED
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
* decides; the published API only supposes a consumer, so a published export is live
|
|
8
8
|
* under reachability and still a candidate under reference counting.
|
|
9
9
|
*/
|
|
10
|
-
import { isExpressionWithTypeArguments, isFunctionLikeDeclaration, isPropertyAccessExpression, isQualifiedName, isTypeQueryNode, isTypeReferenceNode, isVariableStatement, } from "@typescript/native/unstable/ast";
|
|
10
|
+
import { isExpressionWithTypeArguments, isFunctionLikeDeclaration, isPropertyAccessExpression, isQualifiedName, isTypeQueryNode, isTypeReferenceNode, isPropertyDeclaration, isVariableDeclaration, isVariableStatement, } from "@typescript/native/unstable/ast";
|
|
11
11
|
import { aliasChains } from "./alias-chain.js";
|
|
12
12
|
import { conventionEntries } from "./conventions.js";
|
|
13
13
|
import { entryPoints } from "./entry-points.js";
|
|
@@ -18,6 +18,15 @@ import { byPosition } from "./position.js";
|
|
|
18
18
|
import { UNANSWERED } from "./query.js";
|
|
19
19
|
import { sourceFilesOf } from "./source-files.js";
|
|
20
20
|
import { typeQueryAliases } from "./type-query-alias.js";
|
|
21
|
+
/** Whether one declaration is written below the directory of a published workspace member. */
|
|
22
|
+
function inPublishedMember(held, id, root, members) {
|
|
23
|
+
const path = held.symbols.find((symbol) => symbol.id === id)?.position.path;
|
|
24
|
+
if (path === undefined || members === undefined) {
|
|
25
|
+
return false;
|
|
26
|
+
}
|
|
27
|
+
const file = resolvePath(root, path);
|
|
28
|
+
return [...members].some((dir) => file.startsWith(`${dir}/`));
|
|
29
|
+
}
|
|
21
30
|
/** The kind of root each role of a manifest entry makes. */
|
|
22
31
|
const ENTRY_KINDS = {
|
|
23
32
|
import: "manifest-entry",
|
|
@@ -167,10 +176,15 @@ export function roots(project, held, targetRoot, options) {
|
|
|
167
176
|
const entries = [
|
|
168
177
|
...options.manifest.entries.map((entry) => ({
|
|
169
178
|
entry,
|
|
170
|
-
publishes:
|
|
179
|
+
publishes: options.publishedAPI &&
|
|
180
|
+
entry.published &&
|
|
171
181
|
(options.manifest.declaresExports ? entry.member.startsWith("exports") : true),
|
|
172
182
|
})),
|
|
173
|
-
...(options.otherPackages ?? []).map((entry) => ({
|
|
183
|
+
...(options.otherPackages ?? []).map((entry) => ({
|
|
184
|
+
entry,
|
|
185
|
+
publishes: entry.published &&
|
|
186
|
+
(options.publishedAPI || options.publishedMembers?.has(entry.dir) === true),
|
|
187
|
+
})),
|
|
174
188
|
];
|
|
175
189
|
const scripted = entries.flatMap(({ entry }) => entry.role === "script" ? files.named(entry.path) : []);
|
|
176
190
|
const readBackTo = [
|
|
@@ -184,7 +198,7 @@ export function roots(project, held, targetRoot, options) {
|
|
|
184
198
|
: readBackByName(options.host, entry, readBackTo);
|
|
185
199
|
};
|
|
186
200
|
for (const { entry, publishes } of entries) {
|
|
187
|
-
if (
|
|
201
|
+
if (publishes) {
|
|
188
202
|
published.push(...entryFiles(entry));
|
|
189
203
|
}
|
|
190
204
|
}
|
|
@@ -238,8 +252,9 @@ export function roots(project, held, targetRoot, options) {
|
|
|
238
252
|
}
|
|
239
253
|
}
|
|
240
254
|
}
|
|
241
|
-
const
|
|
242
|
-
|
|
255
|
+
const tagged = releaseTagged(held, files).filter((id) => options.publishedAPI || inPublishedMember(held, id, targetRoot, options.publishedMembers));
|
|
256
|
+
const api = published.length > 0 || tagged.length > 0
|
|
257
|
+
? publishedAPI(project, published, exportsOf, held, files.byPath, tagged)
|
|
243
258
|
: { ids: [], guessed: [] };
|
|
244
259
|
for (const id of api.ids) {
|
|
245
260
|
add(id, "published-api", "");
|
|
@@ -395,6 +410,15 @@ function publishedAPI(project, published, exportsOf, held, byPath, tagged) {
|
|
|
395
410
|
const symbol = byId.get(id);
|
|
396
411
|
return symbol === undefined || symbol.kind === "file" ? [] : nodes.typeNamesOf(symbol);
|
|
397
412
|
});
|
|
413
|
+
for (const symbol of inferredTypeSymbols(project, round.flatMap((id) => {
|
|
414
|
+
const symbol = byId.get(id);
|
|
415
|
+
const node = symbol === undefined ? undefined : nodes.nodeOf(symbol);
|
|
416
|
+
return node === undefined ? [] : [node];
|
|
417
|
+
}))) {
|
|
418
|
+
for (const target of chains.chainOf(symbol)) {
|
|
419
|
+
reach(target.id);
|
|
420
|
+
}
|
|
421
|
+
}
|
|
398
422
|
project.symbolsAt(named.map((node) => project.handle(node))).forEach((symbol, index) => {
|
|
399
423
|
if (symbol === UNANSWERED) {
|
|
400
424
|
for (const id of held.byName.get(named[index]?.getText() ?? "") ?? []) {
|
|
@@ -443,8 +467,74 @@ function declarationNodes(held, byPath) {
|
|
|
443
467
|
const declaration = byId.get(symbol.id);
|
|
444
468
|
return declaration === undefined ? [] : typeNamesOf(declaration, nodes);
|
|
445
469
|
},
|
|
470
|
+
nodeOf: (symbol) => nodesIn(symbol.position.path).byId.get(symbol.id),
|
|
446
471
|
};
|
|
447
472
|
}
|
|
473
|
+
/** How deep the type arguments and constituents of an inferred type are followed. */
|
|
474
|
+
const INFERRED_DEPTH = 4;
|
|
475
|
+
/**
|
|
476
|
+
* The declarations of the types the compiler infers where a declaration writes none: the
|
|
477
|
+
* type of a variable or a property with no annotation, and the result of a function or a
|
|
478
|
+
* method with no result annotation, each with the types its type arguments and its union
|
|
479
|
+
* or intersection constituents name, to a fixed depth. A question the checker leaves
|
|
480
|
+
* unanswered names nothing.
|
|
481
|
+
*/
|
|
482
|
+
function inferredTypeSymbols(project, declarations) {
|
|
483
|
+
const { queries } = project;
|
|
484
|
+
const valued = declarations.flatMap((node) => (isVariableDeclaration(node) || isPropertyDeclaration(node)) &&
|
|
485
|
+
node.type === undefined &&
|
|
486
|
+
!isFunctionLikeDeclaration(node.initializer ?? node)
|
|
487
|
+
? [node.name]
|
|
488
|
+
: []);
|
|
489
|
+
const types = [];
|
|
490
|
+
queries.typesAt(valued).forEach((type) => {
|
|
491
|
+
if (type !== UNANSWERED && type !== undefined) {
|
|
492
|
+
types.push(type);
|
|
493
|
+
}
|
|
494
|
+
});
|
|
495
|
+
for (const node of declarations) {
|
|
496
|
+
const signature = isFunctionLikeDeclaration(node)
|
|
497
|
+
? node
|
|
498
|
+
: isVariableDeclaration(node) &&
|
|
499
|
+
node.type === undefined &&
|
|
500
|
+
node.initializer !== undefined &&
|
|
501
|
+
isFunctionLikeDeclaration(node.initializer)
|
|
502
|
+
? node.initializer
|
|
503
|
+
: undefined;
|
|
504
|
+
if (signature === undefined || signature.type !== undefined || signature.body === undefined) {
|
|
505
|
+
continue;
|
|
506
|
+
}
|
|
507
|
+
const typed = queries.signatureOf(signature);
|
|
508
|
+
const result = typed === UNANSWERED || typed === undefined ? undefined : queries.returnTypeOf(typed);
|
|
509
|
+
if (result !== UNANSWERED && result !== undefined) {
|
|
510
|
+
types.push(result);
|
|
511
|
+
}
|
|
512
|
+
}
|
|
513
|
+
const found = new Map();
|
|
514
|
+
const seen = new Set();
|
|
515
|
+
const visit = (type, depth) => {
|
|
516
|
+
if (depth > INFERRED_DEPTH || seen.has(type)) {
|
|
517
|
+
return;
|
|
518
|
+
}
|
|
519
|
+
seen.add(type);
|
|
520
|
+
const symbol = queries.symbolOfType(type);
|
|
521
|
+
if (symbol !== UNANSWERED && symbol !== undefined) {
|
|
522
|
+
found.set(symbol.id, symbol);
|
|
523
|
+
}
|
|
524
|
+
const parts = queries.constituents(type);
|
|
525
|
+
const args = queries.typeArguments(type);
|
|
526
|
+
for (const next of [
|
|
527
|
+
...(parts === UNANSWERED || (parts.length === 1 && parts[0] === type) ? [] : parts),
|
|
528
|
+
...(args === UNANSWERED ? [] : args),
|
|
529
|
+
]) {
|
|
530
|
+
visit(next, depth + 1);
|
|
531
|
+
}
|
|
532
|
+
};
|
|
533
|
+
types.forEach((type) => {
|
|
534
|
+
visit(type, 0);
|
|
535
|
+
});
|
|
536
|
+
return [...found.values()];
|
|
537
|
+
}
|
|
448
538
|
/**
|
|
449
539
|
* The names of types one declaration's own syntax writes: in a type reference, a
|
|
450
540
|
* heritage clause or a type query. A function body is not read, and neither is a
|
package/dist/src/session.js
CHANGED
|
@@ -129,7 +129,12 @@ function viewOf(project, configFile, engine, log) {
|
|
|
129
129
|
if ((symbol.flags & SymbolFlags.Alias) === 0) {
|
|
130
130
|
return undefined;
|
|
131
131
|
}
|
|
132
|
-
|
|
132
|
+
// An alias the checker synthesizes declares nothing, the default a module that assigns
|
|
133
|
+
// its export gets under esModuleInterop, and the immediate step fails on it while the
|
|
134
|
+
// whole resolution answers.
|
|
135
|
+
const held = symbol.declarations.length === 0
|
|
136
|
+
? queries.aliased(symbol)
|
|
137
|
+
: queries.immediateAliased(symbol);
|
|
133
138
|
// An alias that resolves to nothing answers with the checker's own unknown
|
|
134
139
|
// symbol, which declares nothing; a symbol that is no alias answers with
|
|
135
140
|
// nothing at all.
|
package/dist/src/source-files.js
CHANGED
|
@@ -48,6 +48,7 @@ export function sourceFilesOf(project, targetRoot) {
|
|
|
48
48
|
return {
|
|
49
49
|
byPath,
|
|
50
50
|
byName,
|
|
51
|
+
outputDirs: [...new Set([outDir, declarationDir])].filter((dir) => dir !== undefined),
|
|
51
52
|
named: (path) => {
|
|
52
53
|
if (!path.includes(SUBPATH_WILDCARD)) {
|
|
53
54
|
for (const candidate of candidates(path)) {
|
package/dist/src/sweep.js
CHANGED
|
@@ -163,30 +163,42 @@ function reachable(graph, held, mode) {
|
|
|
163
163
|
/**
|
|
164
164
|
* Admits every test declaration whose set of referenced production declarations is
|
|
165
165
|
* non-empty and wholly dead, and returns which it admitted. The set is read from every
|
|
166
|
-
* reference the test makes,
|
|
167
|
-
*
|
|
168
|
-
*
|
|
166
|
+
* reference the test makes, which a production sweep does not count. A test-support
|
|
167
|
+
* declaration that a test referencing a live target also references is a live target of
|
|
168
|
+
* every test referencing it. A marked or exempt test is never admitted.
|
|
169
169
|
*/
|
|
170
170
|
function admitTestsOfDeadCode(graph, dead, held) {
|
|
171
|
-
const
|
|
172
|
-
graph.symbols.
|
|
173
|
-
const testCode = graph.test[at] === true || graph.support[at] === true;
|
|
174
|
-
if (!testCode || graph.subject[at] !== true || held[at] === true) {
|
|
175
|
-
return;
|
|
176
|
-
}
|
|
171
|
+
const testCode = (at) => graph.test[at] === true || graph.support[at] === true;
|
|
172
|
+
const judged = graph.symbols.map((_symbol, at) => {
|
|
177
173
|
let targets = 0;
|
|
178
174
|
let live = 0;
|
|
179
|
-
|
|
180
|
-
const
|
|
181
|
-
|
|
182
|
-
|
|
175
|
+
if (testCode(at) && graph.subject[at] === true && held[at] !== true) {
|
|
176
|
+
for (const edge of graph.out[at] ?? []) {
|
|
177
|
+
if (testCode(edge.to) || graph.subject[edge.to] !== true) {
|
|
178
|
+
continue;
|
|
179
|
+
}
|
|
180
|
+
targets += 1;
|
|
181
|
+
if (dead[edge.to] !== true) {
|
|
182
|
+
live += 1;
|
|
183
|
+
}
|
|
183
184
|
}
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
185
|
+
}
|
|
186
|
+
return { targets, live };
|
|
187
|
+
});
|
|
188
|
+
const shared = new Set();
|
|
189
|
+
judged.forEach(({ live }, at) => {
|
|
190
|
+
if (live > 0) {
|
|
191
|
+
for (const edge of graph.out[at] ?? []) {
|
|
192
|
+
if (graph.support[edge.to] === true) {
|
|
193
|
+
shared.add(edge.to);
|
|
194
|
+
}
|
|
187
195
|
}
|
|
188
196
|
}
|
|
189
|
-
|
|
197
|
+
});
|
|
198
|
+
const admitted = graph.symbols.map(() => false);
|
|
199
|
+
judged.forEach(({ targets, live }, at) => {
|
|
200
|
+
const sharing = (graph.out[at] ?? []).some((edge) => shared.has(edge.to) && edge.to !== at);
|
|
201
|
+
if (targets > 0 && live === 0 && !sharing) {
|
|
190
202
|
dead[at] = true;
|
|
191
203
|
admitted[at] = true;
|
|
192
204
|
}
|