@cplieger/deadset-ts 6.1.1-dev.1 → 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
|
@@ -6,6 +6,7 @@
|
|
|
6
6
|
*/
|
|
7
7
|
|
|
8
8
|
import {
|
|
9
|
+
isArrayBindingPattern,
|
|
9
10
|
isArrowFunction,
|
|
10
11
|
isAwaitExpression,
|
|
11
12
|
isBigIntLiteral,
|
|
@@ -55,10 +56,12 @@ import {
|
|
|
55
56
|
isVoidExpression,
|
|
56
57
|
NodeFlags,
|
|
57
58
|
SyntaxKind,
|
|
59
|
+
type ArrowFunction,
|
|
58
60
|
type BindingName,
|
|
59
61
|
type Block,
|
|
60
62
|
type CallExpression,
|
|
61
63
|
type CatchClause,
|
|
64
|
+
type FunctionExpression,
|
|
62
65
|
type Identifier,
|
|
63
66
|
type Node,
|
|
64
67
|
type SourceFile,
|
|
@@ -473,6 +476,7 @@ export function projectParts<Brand>(
|
|
|
473
476
|
const calls = new Map<string, boolean>();
|
|
474
477
|
const specifiers = new Set<string>();
|
|
475
478
|
const sites: { readonly file: SourceFile; readonly declared: Declared }[] = [];
|
|
479
|
+
const contextual: ContextualParameter[] = [];
|
|
476
480
|
|
|
477
481
|
for (const file of files) {
|
|
478
482
|
const declarationAt = (node: Node): string | undefined => {
|
|
@@ -624,15 +628,25 @@ export function projectParts<Brand>(
|
|
|
624
628
|
});
|
|
625
629
|
const reader = readers.get(one.signature);
|
|
626
630
|
if (reader !== undefined) {
|
|
627
|
-
|
|
631
|
+
const found = parameterParts(file, targetRoot, one.id, one.signature, reader, ids, false);
|
|
632
|
+
parts.push(...found.parts);
|
|
633
|
+
contextual.push(...found.contextual);
|
|
628
634
|
}
|
|
629
635
|
}
|
|
630
636
|
for (const one of walk.values) {
|
|
631
637
|
const reader = readers.get(one.signature);
|
|
632
638
|
if (reader !== undefined) {
|
|
633
|
-
|
|
634
|
-
|
|
639
|
+
const found = parameterParts(
|
|
640
|
+
file,
|
|
641
|
+
targetRoot,
|
|
642
|
+
one.holder,
|
|
643
|
+
one.signature,
|
|
644
|
+
reader,
|
|
645
|
+
ids,
|
|
646
|
+
true,
|
|
635
647
|
);
|
|
648
|
+
parts.push(...found.parts);
|
|
649
|
+
contextual.push(...found.contextual);
|
|
636
650
|
}
|
|
637
651
|
}
|
|
638
652
|
walk.catches.forEach(({ name, holder }, at) => {
|
|
@@ -677,7 +691,7 @@ export function projectParts<Brand>(
|
|
|
677
691
|
}
|
|
678
692
|
|
|
679
693
|
parts.push(...resultParts(project, targetRoot, sites, references, calls));
|
|
680
|
-
return { parts, signatures, calls, specifiers };
|
|
694
|
+
return { parts: withContextualRead(project, parts, contextual), signatures, calls, specifiers };
|
|
681
695
|
}
|
|
682
696
|
|
|
683
697
|
/**
|
|
@@ -743,9 +757,38 @@ function parameterNames(signature: SignatureNode): ParameterName[] {
|
|
|
743
757
|
return found;
|
|
744
758
|
}
|
|
745
759
|
|
|
760
|
+
/** An unread name of a parameter that a contextual type of its function expression keeps. */
|
|
761
|
+
interface ContextualParameter {
|
|
762
|
+
readonly part: PartFact;
|
|
763
|
+
readonly signature: ArrowFunction | FunctionExpression;
|
|
764
|
+
}
|
|
765
|
+
|
|
766
|
+
/**
|
|
767
|
+
* The parts with each name of `contextual` read where the compiler gives its function a
|
|
768
|
+
* contextual type: the inference of the call holding the function may read a parameter's
|
|
769
|
+
* annotation, and the code handing the argument may act on what an object pattern reads.
|
|
770
|
+
* A question the checker leaves unanswered keeps the name read.
|
|
771
|
+
*/
|
|
772
|
+
function withContextualRead<Brand>(
|
|
773
|
+
project: ProjectView<Brand>,
|
|
774
|
+
parts: readonly PartFact[],
|
|
775
|
+
contextual: readonly ContextualParameter[],
|
|
776
|
+
): readonly PartFact[] {
|
|
777
|
+
if (contextual.length === 0) {
|
|
778
|
+
return parts;
|
|
779
|
+
}
|
|
780
|
+
const signatures = [...new Set(contextual.map((one) => one.signature))];
|
|
781
|
+
const answers = project.queries.contextualTypes(signatures);
|
|
782
|
+
const typed = new Set(signatures.filter((_signature, at) => answers[at] !== undefined));
|
|
783
|
+
const read = new Set(contextual.filter((one) => typed.has(one.signature)).map((one) => one.part));
|
|
784
|
+
return parts.map((part) => (read.has(part) ? { ...part, used: true } : part));
|
|
785
|
+
}
|
|
786
|
+
|
|
746
787
|
/**
|
|
747
|
-
* Each name one function's parameters bind and whether the function reads it
|
|
748
|
-
*
|
|
788
|
+
* Each name one function's parameters bind and whether the function reads it, and the
|
|
789
|
+
* unread names a contextual type of a function expression may keep: those of a parameter
|
|
790
|
+
* that carries a type annotation or is an object binding pattern. A binding pattern counts
|
|
791
|
+
* as read where its function reads any name it binds.
|
|
749
792
|
*/
|
|
750
793
|
function parameterParts(
|
|
751
794
|
file: SourceFile,
|
|
@@ -755,7 +798,7 @@ function parameterParts(
|
|
|
755
798
|
reader: { readonly names: readonly ParameterName[]; readonly uses: readonly Identifier[] },
|
|
756
799
|
ids: ReadonlyMap<Node, Answer<number>>,
|
|
757
800
|
inValue: boolean,
|
|
758
|
-
): PartFact[] {
|
|
801
|
+
): { readonly parts: PartFact[]; readonly contextual: ContextualParameter[] } {
|
|
759
802
|
// A function that reads `arguments` reads every parameter by position.
|
|
760
803
|
const readsArguments =
|
|
761
804
|
!isArrowFunction(signature) && identifiersNaming(signature, new Set(["arguments"])).length > 0;
|
|
@@ -766,11 +809,14 @@ function parameterParts(
|
|
|
766
809
|
);
|
|
767
810
|
const lastRead = Math.max(-1, ...reader.names.filter((_, at) => read[at]).map((one) => one.at));
|
|
768
811
|
const found: PartFact[] = [];
|
|
812
|
+
const contextual: ContextualParameter[] = [];
|
|
813
|
+
const expression =
|
|
814
|
+
isArrowFunction(signature) || isFunctionExpression(signature) ? signature : undefined;
|
|
769
815
|
reader.names.forEach(({ name, at, pattern, exempt }, index) => {
|
|
770
816
|
if (exempt) {
|
|
771
817
|
return;
|
|
772
818
|
}
|
|
773
|
-
|
|
819
|
+
const part: PartFact = {
|
|
774
820
|
code: UNUSED_PARAMETER,
|
|
775
821
|
declaration,
|
|
776
822
|
kind: "parameter",
|
|
@@ -779,9 +825,19 @@ function parameterParts(
|
|
|
779
825
|
message: `parameter ${name.text} is never read in the body`,
|
|
780
826
|
used: read[index] === true,
|
|
781
827
|
parameter: { beforeARead: !pattern && at < lastRead, inValue },
|
|
782
|
-
}
|
|
828
|
+
};
|
|
829
|
+
found.push(part);
|
|
830
|
+
const parameter = signature.parameters[at];
|
|
831
|
+
if (
|
|
832
|
+
expression !== undefined &&
|
|
833
|
+
!part.used &&
|
|
834
|
+
parameter !== undefined &&
|
|
835
|
+
(parameter.type !== undefined || isObjectBindingPattern(parameter.name))
|
|
836
|
+
) {
|
|
837
|
+
contextual.push({ part, signature: expression });
|
|
838
|
+
}
|
|
783
839
|
});
|
|
784
|
-
return found;
|
|
840
|
+
return { parts: found, contextual };
|
|
785
841
|
}
|
|
786
842
|
|
|
787
843
|
/** One local a block declares by name, and every identifier of the block that spells it. */
|
|
@@ -792,6 +848,22 @@ interface Local {
|
|
|
792
848
|
readonly uses: readonly Identifier[];
|
|
793
849
|
}
|
|
794
850
|
|
|
851
|
+
/**
|
|
852
|
+
* The names one declaration binds that a write of its initializer stores each on its own:
|
|
853
|
+
* the declared name, or each element name of an array binding pattern at any depth.
|
|
854
|
+
*/
|
|
855
|
+
function arrayPatternNames(name: BindingName): readonly Identifier[] {
|
|
856
|
+
if (isIdentifier(name)) {
|
|
857
|
+
return [name];
|
|
858
|
+
}
|
|
859
|
+
if (!isArrayBindingPattern(name)) {
|
|
860
|
+
return [];
|
|
861
|
+
}
|
|
862
|
+
return name.elements.flatMap((element) =>
|
|
863
|
+
isBindingElement(element) && element.name !== undefined ? arrayPatternNames(element.name) : [],
|
|
864
|
+
);
|
|
865
|
+
}
|
|
866
|
+
|
|
795
867
|
/** The locals one block declares with `let` or `const`, each with the names that spell it. */
|
|
796
868
|
function localsOf(block: Block): readonly Local[] {
|
|
797
869
|
const declared: { name: Identifier; statement: number; initialized: boolean }[] = [];
|
|
@@ -807,9 +879,9 @@ function localsOf(block: Block): readonly Local[] {
|
|
|
807
879
|
return;
|
|
808
880
|
}
|
|
809
881
|
for (const declaration of list.declarations) {
|
|
810
|
-
|
|
882
|
+
for (const name of arrayPatternNames(declaration.name)) {
|
|
811
883
|
declared.push({
|
|
812
|
-
name
|
|
884
|
+
name,
|
|
813
885
|
statement: at,
|
|
814
886
|
initialized: declaration.initializer !== undefined,
|
|
815
887
|
});
|
package/src/kinds.ts
CHANGED
|
@@ -101,7 +101,7 @@ const ROWS: readonly KindRow[] = [
|
|
|
101
101
|
code: "DS1101",
|
|
102
102
|
name: "unnecessary-export",
|
|
103
103
|
languages: ["go", "ts"],
|
|
104
|
-
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.",
|
|
104
|
+
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.",
|
|
105
105
|
precondition:
|
|
106
106
|
"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.",
|
|
107
107
|
defaultSeverity: "warn",
|
|
@@ -113,7 +113,7 @@ const ROWS: readonly KindRow[] = [
|
|
|
113
113
|
code: "DS1102",
|
|
114
114
|
name: "unnecessary-exposure",
|
|
115
115
|
languages: ["go"],
|
|
116
|
-
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.",
|
|
116
|
+
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.",
|
|
117
117
|
precondition:
|
|
118
118
|
"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.",
|
|
119
119
|
defaultSeverity: "warn",
|
|
@@ -235,7 +235,7 @@ const ROWS: readonly KindRow[] = [
|
|
|
235
235
|
languages: ["go", "ts"],
|
|
236
236
|
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.",
|
|
237
237
|
precondition:
|
|
238
|
-
"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
|
|
238
|
+
"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.",
|
|
239
239
|
defaultSeverity: "deny",
|
|
240
240
|
maxClass: "certain",
|
|
241
241
|
fixability: "manual",
|
|
@@ -319,7 +319,7 @@ const ROWS: readonly KindRow[] = [
|
|
|
319
319
|
languages: ["go", "ts"],
|
|
320
320
|
rule: "A parameter with no reference inside its function body, on a function whose signature is free to change.",
|
|
321
321
|
precondition:
|
|
322
|
-
"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.",
|
|
322
|
+
"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.",
|
|
323
323
|
defaultSeverity: "warn",
|
|
324
324
|
maxClass: "certain",
|
|
325
325
|
fixability: "manual",
|
|
@@ -330,8 +330,9 @@ const ROWS: readonly KindRow[] = [
|
|
|
330
330
|
code: "DS1802",
|
|
331
331
|
name: "unused-receiver",
|
|
332
332
|
languages: ["go"],
|
|
333
|
-
rule: "A named method receiver with no reference inside the method body
|
|
334
|
-
precondition:
|
|
333
|
+
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.",
|
|
334
|
+
precondition:
|
|
335
|
+
"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.",
|
|
335
336
|
defaultSeverity: "warn",
|
|
336
337
|
maxClass: "certain",
|
|
337
338
|
fixability: "deletable",
|
package/src/references.ts
CHANGED
|
@@ -51,6 +51,7 @@ import { byPosition, isComponentFile, renderPosition, type Position } from "./po
|
|
|
51
51
|
import { DEFAULT_BATCH_CAP, propertyTables, UNANSWERED, type Answer } from "./query.ts";
|
|
52
52
|
import type { ProjectView } from "./session.ts";
|
|
53
53
|
import { typeQueryAliases } from "./type-query-alias.ts";
|
|
54
|
+
import { computedKeyNames, keyNameOf, keyedUses, type KeyedAccess } from "./symbol-keys.ts";
|
|
54
55
|
import { handedOn, valuesOf } from "./value-flow.ts";
|
|
55
56
|
|
|
56
57
|
/**
|
|
@@ -80,12 +81,12 @@ import { handedOn, valuesOf } from "./value-flow.ts";
|
|
|
80
81
|
export type Use = "read" | "write" | "evaluation" | "decorator";
|
|
81
82
|
|
|
82
83
|
/**
|
|
83
|
-
* Which path answered for one reference: `batch`, a file's batched lookup; `resolved-symbol`,
|
|
84
|
-
*
|
|
85
|
-
*
|
|
86
|
-
*
|
|
87
|
-
*
|
|
88
|
-
* member
|
|
84
|
+
* Which path answered for one reference: `batch`, a file's batched lookup; `resolved-symbol`, the
|
|
85
|
+
* per-node lookup for its residue; `shorthand`, the value `{ a }` reads; `alias`, a step along an
|
|
86
|
+
* import or export chain; `syntax`, the tree alone (a decorator's target, a component's markup);
|
|
87
|
+
* `destructured`, the property a pattern names; `contextual`, the member a literal property writes
|
|
88
|
+
* or a value's position reads; `override`, the override a base call dispatches to; `keyed`, the
|
|
89
|
+
* member a symbol key selects; `by-name`, each declaration so spelled, where the checker failed.
|
|
89
90
|
*/
|
|
90
91
|
export type Resolution =
|
|
91
92
|
| "batch"
|
|
@@ -96,6 +97,7 @@ export type Resolution =
|
|
|
96
97
|
| "destructured"
|
|
97
98
|
| "contextual"
|
|
98
99
|
| "override"
|
|
100
|
+
| "keyed"
|
|
99
101
|
| "by-name";
|
|
100
102
|
|
|
101
103
|
/** One use of one declaration by one declaration. */
|
|
@@ -297,12 +299,11 @@ function unparenthesized(node: Node): Node {
|
|
|
297
299
|
* Records the name one store writes into.
|
|
298
300
|
*
|
|
299
301
|
* A property access writes the member it names. An element access writes the collection
|
|
300
|
-
* it stores into,
|
|
301
|
-
*
|
|
302
|
-
* writes each name it binds, through
|
|
303
|
-
* in.
|
|
302
|
+
* it stores into, since a collection a module only stores into holds nothing anything
|
|
303
|
+
* reads, and joins `accesses` for the member a unique-symbol key selects. A destructuring
|
|
304
|
+
* target writes each name it binds, through its spreads and nested patterns.
|
|
304
305
|
*/
|
|
305
|
-
function markWrite(target: Node, writes: Set<number>): void {
|
|
306
|
+
function markWrite(target: Node, writes: Set<number>, accesses: Set<Node>): void {
|
|
306
307
|
const held = unparenthesized(target);
|
|
307
308
|
if (isName(held)) {
|
|
308
309
|
writes.add(held.pos);
|
|
@@ -315,37 +316,38 @@ function markWrite(target: Node, writes: Set<number>): void {
|
|
|
315
316
|
return;
|
|
316
317
|
}
|
|
317
318
|
if (isElementAccessExpression(held)) {
|
|
319
|
+
accesses.add(held);
|
|
318
320
|
// A store through a member's value lands in storage every holder of the value
|
|
319
321
|
// reads, so it reads the member; a variable only ever stored into stays written.
|
|
320
322
|
if (!isPropertyAccessExpression(unparenthesized(held.expression))) {
|
|
321
|
-
markWrite(held.expression, writes);
|
|
323
|
+
markWrite(held.expression, writes, accesses);
|
|
322
324
|
}
|
|
323
325
|
return;
|
|
324
326
|
}
|
|
325
327
|
if (isArrayLiteralExpression(held)) {
|
|
326
328
|
for (const element of held.elements) {
|
|
327
|
-
markWrite(element, writes);
|
|
329
|
+
markWrite(element, writes, accesses);
|
|
328
330
|
}
|
|
329
331
|
return;
|
|
330
332
|
}
|
|
331
333
|
if (isObjectLiteralExpression(held)) {
|
|
332
334
|
for (const property of held.properties) {
|
|
333
335
|
if (isPropertyAssignment(property)) {
|
|
334
|
-
markWrite(property.initializer, writes);
|
|
336
|
+
markWrite(property.initializer, writes, accesses);
|
|
335
337
|
continue;
|
|
336
338
|
}
|
|
337
339
|
if (isShorthandPropertyAssignment(property)) {
|
|
338
|
-
markWrite(property.name, writes);
|
|
340
|
+
markWrite(property.name, writes, accesses);
|
|
339
341
|
continue;
|
|
340
342
|
}
|
|
341
343
|
if (isSpreadAssignment(property)) {
|
|
342
|
-
markWrite(property.expression, writes);
|
|
344
|
+
markWrite(property.expression, writes, accesses);
|
|
343
345
|
}
|
|
344
346
|
}
|
|
345
347
|
return;
|
|
346
348
|
}
|
|
347
349
|
if (isSpreadElement(held)) {
|
|
348
|
-
markWrite(held.expression, writes);
|
|
350
|
+
markWrite(held.expression, writes, accesses);
|
|
349
351
|
}
|
|
350
352
|
}
|
|
351
353
|
|
|
@@ -356,16 +358,21 @@ function markWrite(target: Node, writes: Set<number>): void {
|
|
|
356
358
|
* holds. A store that reads its target as well records it in `reads`: a logical
|
|
357
359
|
* assignment, and a compound assignment, an increment or a decrement whose value is used.
|
|
358
360
|
*/
|
|
359
|
-
function markStores(
|
|
361
|
+
function markStores(
|
|
362
|
+
node: Node,
|
|
363
|
+
writes: Set<number>,
|
|
364
|
+
reads: Set<number>,
|
|
365
|
+
accesses: { readonly written: Set<Node>; readonly read: Set<Node> },
|
|
366
|
+
): void {
|
|
360
367
|
if (isBinaryExpression(node)) {
|
|
361
368
|
const operator = node.operatorToken.kind;
|
|
362
369
|
if (ASSIGNMENTS.has(operator)) {
|
|
363
|
-
markWrite(node.left, writes);
|
|
370
|
+
markWrite(node.left, writes, accesses.written);
|
|
364
371
|
if (
|
|
365
372
|
LOGICAL_ASSIGNMENTS.has(operator) ||
|
|
366
373
|
(operator !== SyntaxKind.EqualsToken && !valueDiscarded(node))
|
|
367
374
|
) {
|
|
368
|
-
markWrite(node.left, reads);
|
|
375
|
+
markWrite(node.left, reads, accesses.read);
|
|
369
376
|
}
|
|
370
377
|
}
|
|
371
378
|
return;
|
|
@@ -375,20 +382,20 @@ function markStores(node: Node, writes: Set<number>, reads: Set<number>): void {
|
|
|
375
382
|
node.operator === SyntaxKind.PlusPlusToken ||
|
|
376
383
|
node.operator === SyntaxKind.MinusMinusToken
|
|
377
384
|
) {
|
|
378
|
-
markWrite(node.operand, writes);
|
|
385
|
+
markWrite(node.operand, writes, accesses.written);
|
|
379
386
|
if (!valueDiscarded(node)) {
|
|
380
|
-
markWrite(node.operand, reads);
|
|
387
|
+
markWrite(node.operand, reads, accesses.read);
|
|
381
388
|
}
|
|
382
389
|
}
|
|
383
390
|
return;
|
|
384
391
|
}
|
|
385
392
|
if (isDeleteExpression(node)) {
|
|
386
|
-
markWrite(node.expression, writes);
|
|
393
|
+
markWrite(node.expression, writes, accesses.written);
|
|
387
394
|
return;
|
|
388
395
|
}
|
|
389
396
|
if (isForInStatement(node) || isForOfStatement(node)) {
|
|
390
397
|
if (node.initializer.kind !== SyntaxKind.VariableDeclarationList) {
|
|
391
|
-
markWrite(node.initializer, writes);
|
|
398
|
+
markWrite(node.initializer, writes, accesses.written);
|
|
392
399
|
}
|
|
393
400
|
}
|
|
394
401
|
}
|
|
@@ -475,6 +482,7 @@ interface FileSites {
|
|
|
475
482
|
readonly wholes: readonly WholeRead[];
|
|
476
483
|
readonly urls: readonly ModuleURL[];
|
|
477
484
|
readonly decorated: readonly Decorated[];
|
|
485
|
+
readonly keyed: readonly KeyedAccess[];
|
|
478
486
|
}
|
|
479
487
|
|
|
480
488
|
/** Whether one node is `import.meta.url`. */
|
|
@@ -667,6 +675,8 @@ function sitesOf(
|
|
|
667
675
|
const flows: FlowAt[] = [];
|
|
668
676
|
const writes = new Set<number>();
|
|
669
677
|
const alsoRead = new Set<number>();
|
|
678
|
+
const accesses = { written: new Set<Node>(), read: new Set<Node>() };
|
|
679
|
+
const keyed: KeyedAccess[] = [];
|
|
670
680
|
const declared = new Set<number>();
|
|
671
681
|
const shorthands = new Map<number, Node>();
|
|
672
682
|
let enclosing = fileId;
|
|
@@ -712,7 +722,17 @@ function sitesOf(
|
|
|
712
722
|
return;
|
|
713
723
|
}
|
|
714
724
|
markNames(node);
|
|
715
|
-
markStores(node, writes, alsoRead);
|
|
725
|
+
markStores(node, writes, alsoRead, accesses);
|
|
726
|
+
const key = isElementAccessExpression(node) ? keyNameOf(node.argumentExpression) : undefined;
|
|
727
|
+
if (key !== undefined && isElementAccessExpression(node)) {
|
|
728
|
+
const written = accesses.written.has(node);
|
|
729
|
+
keyed.push({
|
|
730
|
+
access: node,
|
|
731
|
+
key,
|
|
732
|
+
from: enclosing,
|
|
733
|
+
use: written ? (accesses.read.has(node) ? "both" : "write") : "read",
|
|
734
|
+
});
|
|
735
|
+
}
|
|
716
736
|
if (isObjectLiteralExpression(node) && !isAssignmentPattern(node)) {
|
|
717
737
|
literals.push({ literal: node, from: enclosing });
|
|
718
738
|
}
|
|
@@ -747,7 +767,18 @@ function sitesOf(
|
|
|
747
767
|
};
|
|
748
768
|
|
|
749
769
|
file.forEachChild(visit);
|
|
750
|
-
return {
|
|
770
|
+
return {
|
|
771
|
+
uses: found,
|
|
772
|
+
literals,
|
|
773
|
+
flows,
|
|
774
|
+
reads,
|
|
775
|
+
links,
|
|
776
|
+
evaluations,
|
|
777
|
+
wholes,
|
|
778
|
+
urls,
|
|
779
|
+
decorated,
|
|
780
|
+
keyed,
|
|
781
|
+
};
|
|
751
782
|
}
|
|
752
783
|
|
|
753
784
|
/** The declaration kinds of a property an object literal declares. */
|
|
@@ -1230,7 +1261,19 @@ export function references<Brand>(
|
|
|
1230
1261
|
}
|
|
1231
1262
|
}
|
|
1232
1263
|
|
|
1233
|
-
|
|
1264
|
+
const keyNames = computedKeyNames(held.symbols);
|
|
1265
|
+
for (const {
|
|
1266
|
+
file,
|
|
1267
|
+
fileId,
|
|
1268
|
+
uses,
|
|
1269
|
+
reads,
|
|
1270
|
+
links,
|
|
1271
|
+
evaluations,
|
|
1272
|
+
wholes,
|
|
1273
|
+
urls,
|
|
1274
|
+
decorated,
|
|
1275
|
+
keyed,
|
|
1276
|
+
} of walked) {
|
|
1234
1277
|
const test = testFile.get(file) === true;
|
|
1235
1278
|
// A component file's markup may use any binding its blocks declare at the top level.
|
|
1236
1279
|
const markup = isComponentFile(file);
|
|
@@ -1449,6 +1492,22 @@ export function references<Brand>(
|
|
|
1449
1492
|
}
|
|
1450
1493
|
});
|
|
1451
1494
|
|
|
1495
|
+
const resolvedKeys = keyed.flatMap((access) => {
|
|
1496
|
+
const symbol =
|
|
1497
|
+
isIdentifier(access.key) && keyNames.has(access.key.text)
|
|
1498
|
+
? answered.get(access.key)
|
|
1499
|
+
: undefined;
|
|
1500
|
+
return typeof symbol === "object" ? [{ access, symbol }] : [];
|
|
1501
|
+
});
|
|
1502
|
+
for (const { access, ids } of keyedUses(project, chains, resolvedKeys, tables, cap)) {
|
|
1503
|
+
const position = renderPosition(file, root, access.access.argumentExpression.getStart());
|
|
1504
|
+
for (const id of ids) {
|
|
1505
|
+
for (const use of access.use === "both" ? (["read", "write"] as const) : [access.use]) {
|
|
1506
|
+
found.push({ from: access.from, to: id, position, use, resolution: "keyed", test });
|
|
1507
|
+
}
|
|
1508
|
+
}
|
|
1509
|
+
}
|
|
1510
|
+
|
|
1452
1511
|
for (const site of uses) {
|
|
1453
1512
|
const direct: Resolution =
|
|
1454
1513
|
site.shorthand !== undefined
|