@cplieger/deadset-ts 8.0.0-dev.2 → 8.0.0-dev.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +1 -1
- package/dist/src/analysis.js +61 -10
- package/dist/src/augmentations.js +63 -1
- package/dist/src/build-scripts.js +437 -0
- package/dist/src/conformance-record.js +1 -1
- package/dist/src/contextual-uses.js +56 -9
- package/dist/src/convention-rows.js +39 -2
- package/dist/src/conventions.js +15 -0
- package/dist/src/dependencies.js +98 -1
- package/dist/src/discover.js +14 -82
- package/dist/src/entry-points.js +9 -18
- package/dist/src/exempt.js +8 -0
- package/dist/src/findings/completion.js +66 -28
- package/dist/src/findings/intra-function.js +4 -2
- package/dist/src/findings/reads-and-writes.js +7 -6
- package/dist/src/findings/unused-declarations.js +4 -1
- package/dist/src/graph.js +5 -3
- package/dist/src/intra-function-parts.js +45 -17
- package/dist/src/kinds.js +8 -8
- package/dist/src/manifest.js +18 -0
- package/dist/src/matrix.js +38 -4
- package/dist/src/query.js +1 -0
- package/dist/src/report.js +23 -4
- package/dist/src/reporters/annotations.js +15 -8
- package/dist/src/reporters/sarif.js +12 -8
- package/dist/src/roots.js +197 -42
- package/dist/src/serialization-contract.js +590 -12
- package/dist/src/sweep.js +7 -9
- package/dist/src/test-globals.js +234 -0
- package/dist/src/type-errors.js +11 -3
- package/dist/src/unseen-loads.js +107 -0
- package/dist/src/verbs/analyze.js +24 -3
- package/dist/src/workflow-steps.js +32 -15
- package/dist/src/yaml-document.js +355 -0
- package/jsr.json +1 -1
- package/package.json +2 -2
- package/src/analysis.ts +109 -12
- package/src/augmentations.ts +77 -2
- package/src/build-scripts.ts +564 -0
- package/src/conformance-record.ts +1 -1
- package/src/contextual-uses.ts +64 -12
- package/src/convention-rows.ts +47 -2
- package/src/conventions.ts +16 -0
- package/src/dependencies.ts +110 -1
- package/src/discover.ts +14 -94
- package/src/entry-points.ts +10 -25
- package/src/exempt.ts +16 -0
- package/src/file-facts.ts +3 -0
- package/src/findings/boundary.ts +2 -0
- package/src/findings/completion.ts +76 -30
- package/src/findings/intra-function.ts +4 -2
- package/src/findings/reads-and-writes.ts +7 -6
- package/src/findings/unused-declarations.ts +6 -1
- package/src/graph.ts +5 -3
- package/src/intra-function-parts.ts +55 -16
- package/src/kinds.ts +8 -8
- package/src/manifest.ts +21 -3
- package/src/matrix.ts +51 -4
- package/src/query.ts +11 -0
- package/src/report.ts +56 -16
- package/src/reporters/annotations.ts +21 -10
- package/src/reporters/reporter.ts +1 -3
- package/src/reporters/sarif.ts +16 -8
- package/src/roots.ts +226 -46
- package/src/serialization-contract.ts +758 -12
- package/src/sweep.ts +23 -9
- package/src/test-globals.ts +275 -0
- package/src/type-errors.ts +14 -3
- package/src/unseen-loads.ts +145 -0
- package/src/verbs/analyze.ts +28 -3
- package/src/workflow-steps.ts +35 -17
- package/src/yaml-document.ts +377 -0
package/src/roots.ts
CHANGED
|
@@ -9,8 +9,17 @@
|
|
|
9
9
|
*/
|
|
10
10
|
|
|
11
11
|
import {
|
|
12
|
+
isArrowFunction,
|
|
13
|
+
isAssertionExpression,
|
|
12
14
|
isExpressionWithTypeArguments,
|
|
13
15
|
isFunctionLikeDeclaration,
|
|
16
|
+
isIdentifier,
|
|
17
|
+
isMethodDeclaration,
|
|
18
|
+
isObjectLiteralExpression,
|
|
19
|
+
isParenthesizedExpression,
|
|
20
|
+
isPropertyAssignment,
|
|
21
|
+
isReturnStatement,
|
|
22
|
+
isSatisfiesExpression,
|
|
14
23
|
isPropertyAccessExpression,
|
|
15
24
|
isQualifiedName,
|
|
16
25
|
isTypeQueryNode,
|
|
@@ -18,10 +27,14 @@ import {
|
|
|
18
27
|
isPropertyDeclaration,
|
|
19
28
|
isVariableDeclaration,
|
|
20
29
|
isVariableStatement,
|
|
30
|
+
SyntaxKind,
|
|
31
|
+
type Expression,
|
|
21
32
|
type Node,
|
|
33
|
+
type ObjectLiteralExpression,
|
|
22
34
|
type SourceFile,
|
|
23
35
|
} from "@typescript/native/ast";
|
|
24
36
|
import { aliasChains } from "./alias-chain.ts";
|
|
37
|
+
import { isGlobalBlock } from "./augmentations.ts";
|
|
25
38
|
import {
|
|
26
39
|
filesNamedBy,
|
|
27
40
|
outsideChain,
|
|
@@ -67,6 +80,8 @@ export type RootKind =
|
|
|
67
80
|
| "pattern"
|
|
68
81
|
/** A declaration of a function or statement a type error skipped, and what it may have selected. */
|
|
69
82
|
| "type-error"
|
|
83
|
+
/** An ambient global interface merging into one declared outside the target, and its holders. */
|
|
84
|
+
| "global-augmentation"
|
|
70
85
|
/** A file a tool or the platform enters by its own convention, and what it exports where the tool reads it. */
|
|
71
86
|
| EntryRule;
|
|
72
87
|
|
|
@@ -446,6 +461,12 @@ export function roots<Brand>(
|
|
|
446
461
|
published.push(...entryFiles(entry));
|
|
447
462
|
}
|
|
448
463
|
}
|
|
464
|
+
if (options.publishedAPI) {
|
|
465
|
+
// A library's bundle hands what its entry exports to every program that loads it.
|
|
466
|
+
published.push(
|
|
467
|
+
...points.filter((point) => point.rule === "bundler-entry").map((point) => point.file),
|
|
468
|
+
);
|
|
469
|
+
}
|
|
449
470
|
const publishedNames = new Set(published.map((file) => file.fileName));
|
|
450
471
|
const patterned: { file: SourceFile; pattern: string }[] = [];
|
|
451
472
|
for (const pattern of options.entryFiles) {
|
|
@@ -660,9 +681,11 @@ const EXPORTING_CONTAINERS: ReadonlySet<string> = new Set(["file", "namespace"])
|
|
|
660
681
|
* Every declaration a consumer of the library can name: what the published files export,
|
|
661
682
|
* and below each the members a consumer writes, a namespace's exported ones and any other
|
|
662
683
|
* declaration's public and protected ones. A declaration a published one's type names is
|
|
663
|
-
* published too, and so is an exported declaration a release tag marks
|
|
664
|
-
*
|
|
665
|
-
*
|
|
684
|
+
* published too, and so is an exported declaration a release tag marks, and every
|
|
685
|
+
* declaration a `declare global` block of a published file, or of a file holding a published
|
|
686
|
+
* declaration, holds: the consumer's compiler loads that file's typings and their global
|
|
687
|
+
* scope with it. A type name the checker leaves unanswered publishes every declaration
|
|
688
|
+
* spelled as it is, as `guessed`. A published file is not itself published.
|
|
666
689
|
*/
|
|
667
690
|
function publishedAPI<Brand>(
|
|
668
691
|
project: ProjectView<Brand>,
|
|
@@ -695,48 +718,84 @@ function publishedAPI<Brand>(
|
|
|
695
718
|
reach(id);
|
|
696
719
|
}
|
|
697
720
|
}
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
|
|
721
|
+
const files = new Set(held.symbols.filter((one) => one.kind === "file").map((one) => one.id));
|
|
722
|
+
const blocks = held.symbols.filter((one) => isGlobalBlock(one, held, files));
|
|
723
|
+
const fileOf = (id: string): string | undefined => {
|
|
724
|
+
let at = byId.get(id);
|
|
725
|
+
while (at !== undefined && at.kind !== "file") {
|
|
726
|
+
at = byId.get(at.parent);
|
|
727
|
+
}
|
|
728
|
+
return at?.id;
|
|
729
|
+
};
|
|
730
|
+
const publishedFiles = published.flatMap(
|
|
731
|
+
(file) => held.declarations.get(nodeKey(file, file)) ?? [],
|
|
732
|
+
);
|
|
733
|
+
const globalsReached = (): boolean => {
|
|
734
|
+
const typed = new Set([...publishedFiles, ...[...reached].flatMap((id) => fileOf(id) ?? [])]);
|
|
735
|
+
const before = pending.length;
|
|
736
|
+
for (const block of blocks.filter((one) => typed.has(one.parent))) {
|
|
737
|
+
reach(block.id);
|
|
738
|
+
for (const member of children.get(block.id) ?? []) {
|
|
739
|
+
if (member.kind !== "type-parameter") {
|
|
708
740
|
reach(member.id);
|
|
709
741
|
}
|
|
710
742
|
}
|
|
711
743
|
}
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
const
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
|
|
723
|
-
|
|
724
|
-
|
|
725
|
-
|
|
726
|
-
|
|
727
|
-
project.symbolsAt(named.map((node) => project.handle(node))).forEach((symbol, index) => {
|
|
728
|
-
if (symbol === UNANSWERED) {
|
|
729
|
-
for (const id of held.byName.get(named[index]?.getText() ?? "") ?? []) {
|
|
730
|
-
guessed.add(id);
|
|
731
|
-
reach(id);
|
|
744
|
+
return pending.length > before;
|
|
745
|
+
};
|
|
746
|
+
do {
|
|
747
|
+
while (pending.length > 0) {
|
|
748
|
+
const round: string[] = [];
|
|
749
|
+
for (let next = pending.shift(); next !== undefined; next = pending.shift()) {
|
|
750
|
+
round.push(next);
|
|
751
|
+
const exporting = EXPORTING_CONTAINERS.has(byId.get(next)?.kind ?? "");
|
|
752
|
+
for (const member of children.get(next) ?? []) {
|
|
753
|
+
const reachable = exporting
|
|
754
|
+
? member.exported
|
|
755
|
+
: member.visibility !== "private" && member.visibility !== "private-name";
|
|
756
|
+
if (member.kind !== "type-parameter" && reachable) {
|
|
757
|
+
reach(member.id);
|
|
758
|
+
}
|
|
732
759
|
}
|
|
733
|
-
}
|
|
760
|
+
}
|
|
761
|
+
const named = round.flatMap((id) => {
|
|
762
|
+
const symbol = byId.get(id);
|
|
763
|
+
return symbol === undefined || symbol.kind === "file" ? [] : nodes.typeNamesOf(symbol);
|
|
764
|
+
});
|
|
765
|
+
const inferred = inferredTypeSymbols(
|
|
766
|
+
project,
|
|
767
|
+
round.flatMap((id) => {
|
|
768
|
+
const symbol = byId.get(id);
|
|
769
|
+
return symbol === undefined ? [] : nodes.nodesOf(symbol);
|
|
770
|
+
}),
|
|
771
|
+
);
|
|
772
|
+
for (const symbol of inferred.types) {
|
|
734
773
|
for (const target of chains.chainOf(symbol)) {
|
|
735
774
|
reach(target.id);
|
|
736
775
|
}
|
|
737
776
|
}
|
|
738
|
-
|
|
739
|
-
|
|
777
|
+
for (const symbol of inferred.members) {
|
|
778
|
+
for (const target of chains.chainOf(symbol)) {
|
|
779
|
+
const member = byId.get(target.id);
|
|
780
|
+
if (member?.visibility === "public" || member?.visibility === "protected") {
|
|
781
|
+
reach(target.id);
|
|
782
|
+
}
|
|
783
|
+
}
|
|
784
|
+
}
|
|
785
|
+
project.symbolsAt(named.map((node) => project.handle(node))).forEach((symbol, index) => {
|
|
786
|
+
if (symbol === UNANSWERED) {
|
|
787
|
+
for (const id of held.byName.get(named[index]?.getText() ?? "") ?? []) {
|
|
788
|
+
guessed.add(id);
|
|
789
|
+
reach(id);
|
|
790
|
+
}
|
|
791
|
+
} else if (symbol !== undefined) {
|
|
792
|
+
for (const target of chains.chainOf(symbol)) {
|
|
793
|
+
reach(target.id);
|
|
794
|
+
}
|
|
795
|
+
}
|
|
796
|
+
});
|
|
797
|
+
}
|
|
798
|
+
} while (globalsReached());
|
|
740
799
|
return { ids: [...reached], guessed: [...guessed] };
|
|
741
800
|
}
|
|
742
801
|
|
|
@@ -783,30 +842,139 @@ function declarationNodes(
|
|
|
783
842
|
const INFERRED_DEPTH = 4;
|
|
784
843
|
|
|
785
844
|
/**
|
|
786
|
-
* The
|
|
787
|
-
*
|
|
788
|
-
*
|
|
789
|
-
|
|
790
|
-
|
|
845
|
+
* The values one function body returns, each through the type assertions around it: the
|
|
846
|
+
* value a caller receives carries the operand's type whatever type the assertion or the
|
|
847
|
+
* signature names. A nested function's returns are its own.
|
|
848
|
+
*/
|
|
849
|
+
function returnedValues(body: Node): readonly Expression[] {
|
|
850
|
+
const found: Expression[] = [];
|
|
851
|
+
const add = (value: Expression): void => {
|
|
852
|
+
for (let at: Expression | undefined = value; at !== undefined;) {
|
|
853
|
+
found.push(at);
|
|
854
|
+
at =
|
|
855
|
+
isAssertionExpression(at) || isSatisfiesExpression(at) || isParenthesizedExpression(at)
|
|
856
|
+
? at.expression
|
|
857
|
+
: undefined;
|
|
858
|
+
}
|
|
859
|
+
};
|
|
860
|
+
if (!isFunctionLikeDeclaration(body.parent) || body.parent.body !== body) {
|
|
861
|
+
return found;
|
|
862
|
+
}
|
|
863
|
+
if (isArrowFunction(body.parent) && body.kind !== SyntaxKind.Block) {
|
|
864
|
+
add(body as Expression);
|
|
865
|
+
return found;
|
|
866
|
+
}
|
|
867
|
+
const visit = (node: Node): void => {
|
|
868
|
+
if (isFunctionLikeDeclaration(node)) {
|
|
869
|
+
return;
|
|
870
|
+
}
|
|
871
|
+
if (isReturnStatement(node) && node.expression !== undefined) {
|
|
872
|
+
add(node.expression);
|
|
873
|
+
}
|
|
874
|
+
node.forEachChild(visit);
|
|
875
|
+
};
|
|
876
|
+
body.forEachChild(visit);
|
|
877
|
+
return found;
|
|
878
|
+
}
|
|
879
|
+
|
|
880
|
+
/** The object literal one returned value is, written there or as the initializer of a variable `body` declares. */
|
|
881
|
+
function returnedLiteral(value: Expression, body: Node): ObjectLiteralExpression | undefined {
|
|
882
|
+
if (isObjectLiteralExpression(value)) {
|
|
883
|
+
return value;
|
|
884
|
+
}
|
|
885
|
+
if (!isIdentifier(value)) {
|
|
886
|
+
return undefined;
|
|
887
|
+
}
|
|
888
|
+
let found: ObjectLiteralExpression | undefined;
|
|
889
|
+
const visit = (node: Node): void => {
|
|
890
|
+
if (
|
|
891
|
+
isVariableDeclaration(node) &&
|
|
892
|
+
isIdentifier(node.name) &&
|
|
893
|
+
node.name.text === value.text &&
|
|
894
|
+
node.initializer !== undefined
|
|
895
|
+
) {
|
|
896
|
+
let at: Expression = node.initializer;
|
|
897
|
+
while (
|
|
898
|
+
isAssertionExpression(at) ||
|
|
899
|
+
isSatisfiesExpression(at) ||
|
|
900
|
+
isParenthesizedExpression(at)
|
|
901
|
+
) {
|
|
902
|
+
at = at.expression;
|
|
903
|
+
}
|
|
904
|
+
found = isObjectLiteralExpression(at) ? at : found;
|
|
905
|
+
}
|
|
906
|
+
if (!isFunctionLikeDeclaration(node)) {
|
|
907
|
+
node.forEachChild(visit);
|
|
908
|
+
}
|
|
909
|
+
};
|
|
910
|
+
body.forEachChild(visit);
|
|
911
|
+
return found;
|
|
912
|
+
}
|
|
913
|
+
|
|
914
|
+
/**
|
|
915
|
+
* The values one function body returns, and those each method of an object literal it
|
|
916
|
+
* returns returns, to a fixed depth: a caller holding the literal calls its methods, and
|
|
917
|
+
* receives what they return.
|
|
918
|
+
*/
|
|
919
|
+
function returnedThroughLiterals(body: Node, depth = 0): readonly Expression[] {
|
|
920
|
+
const values = returnedValues(body);
|
|
921
|
+
if (depth >= INFERRED_DEPTH) {
|
|
922
|
+
return values;
|
|
923
|
+
}
|
|
924
|
+
const found = [...values];
|
|
925
|
+
for (const value of values) {
|
|
926
|
+
for (const property of returnedLiteral(value, body)?.properties ?? []) {
|
|
927
|
+
const fn = isMethodDeclaration(property)
|
|
928
|
+
? property
|
|
929
|
+
: isPropertyAssignment(property) && isFunctionLikeDeclaration(property.initializer)
|
|
930
|
+
? property.initializer
|
|
931
|
+
: undefined;
|
|
932
|
+
if (fn?.body !== undefined) {
|
|
933
|
+
found.push(...returnedThroughLiterals(fn.body, depth + 1));
|
|
934
|
+
}
|
|
935
|
+
}
|
|
936
|
+
}
|
|
937
|
+
return found;
|
|
938
|
+
}
|
|
939
|
+
|
|
940
|
+
/**
|
|
941
|
+
* The declarations of the types the compiler infers where a declaration writes none, and
|
|
942
|
+
* of the values a function returns: the type of a variable or a property with no
|
|
943
|
+
* annotation, the result of a function or a method with no result annotation, and the type
|
|
944
|
+
* of each value its body returns, each with the types its type arguments and its union or
|
|
945
|
+
* intersection constituents name, to a fixed depth; and as `members` the properties of each
|
|
946
|
+
* returned value's type. A question the checker leaves unanswered names nothing.
|
|
791
947
|
*/
|
|
792
948
|
function inferredTypeSymbols<Brand>(
|
|
793
949
|
project: ProjectView<Brand>,
|
|
794
950
|
declarations: readonly Node[],
|
|
795
|
-
): readonly TSSymbol[] {
|
|
951
|
+
): { readonly types: readonly TSSymbol[]; readonly members: readonly TSSymbol[] } {
|
|
796
952
|
const { queries } = project;
|
|
797
|
-
const valued = declarations.flatMap((node) =>
|
|
953
|
+
const valued: Node[] = declarations.flatMap((node) =>
|
|
798
954
|
(isVariableDeclaration(node) || isPropertyDeclaration(node)) &&
|
|
799
955
|
node.type === undefined &&
|
|
800
956
|
!isFunctionLikeDeclaration(node.initializer ?? node)
|
|
801
957
|
? [node.name]
|
|
802
958
|
: [],
|
|
803
959
|
);
|
|
960
|
+
const returned = declarations.flatMap((node) => {
|
|
961
|
+
const signature = isFunctionLikeDeclaration(node)
|
|
962
|
+
? node
|
|
963
|
+
: isVariableDeclaration(node) &&
|
|
964
|
+
node.initializer !== undefined &&
|
|
965
|
+
isFunctionLikeDeclaration(node.initializer)
|
|
966
|
+
? node.initializer
|
|
967
|
+
: undefined;
|
|
968
|
+
return signature?.body === undefined ? [] : returnedThroughLiterals(signature.body);
|
|
969
|
+
});
|
|
804
970
|
const types: Type[] = [];
|
|
805
|
-
|
|
971
|
+
const returnedTypes: Type[] = [];
|
|
972
|
+
queries.typesAt([...valued, ...returned]).forEach((type, at) => {
|
|
806
973
|
if (type !== UNANSWERED && type !== undefined) {
|
|
807
|
-
types.push(type);
|
|
974
|
+
(at < valued.length ? types : returnedTypes).push(type);
|
|
808
975
|
}
|
|
809
976
|
});
|
|
977
|
+
types.push(...returnedTypes);
|
|
810
978
|
for (const node of declarations) {
|
|
811
979
|
const signature = isFunctionLikeDeclaration(node)
|
|
812
980
|
? node
|
|
@@ -849,7 +1017,19 @@ function inferredTypeSymbols<Brand>(
|
|
|
849
1017
|
types.forEach((type) => {
|
|
850
1018
|
visit(type, 0);
|
|
851
1019
|
});
|
|
852
|
-
|
|
1020
|
+
// A returned value carries each property of its type, one an anonymous type declares or
|
|
1021
|
+
// an intersection merges included, which no type name reaches.
|
|
1022
|
+
const members = new Map<number, TSSymbol>();
|
|
1023
|
+
for (const type of returnedTypes) {
|
|
1024
|
+
const parts = queries.constituents(type);
|
|
1025
|
+
for (const one of [type, ...(parts === UNANSWERED ? [] : parts)]) {
|
|
1026
|
+
const properties = queries.propertiesOf(one);
|
|
1027
|
+
for (const property of properties === UNANSWERED ? [] : properties) {
|
|
1028
|
+
members.set(property.id, property);
|
|
1029
|
+
}
|
|
1030
|
+
}
|
|
1031
|
+
}
|
|
1032
|
+
return { types: [...found.values()], members: [...members.values()] };
|
|
853
1033
|
}
|
|
854
1034
|
|
|
855
1035
|
/**
|