tsc-ts 0.1.1 → 0.1.3
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 +7 -1
- package/dist/api/compiler/ast.d.ts +6 -0
- package/dist/api/compiler/ast.js +19 -3
- package/dist/api/compiler/checker-functions.js +5 -1
- package/dist/api/compiler/checker-grammar.d.ts +39 -4
- package/dist/api/compiler/checker-grammar.js +239 -75
- package/dist/api/compiler/checker-heritage.js +20 -14
- package/dist/api/compiler/checker-modules.js +4 -2
- package/dist/api/compiler/checker-narrowing.d.ts +7 -0
- package/dist/api/compiler/checker-narrowing.js +31 -4
- package/dist/api/compiler/checker-source-file.js +16 -1
- package/dist/api/compiler/checker-statements.js +11 -2
- package/dist/api/compiler/checker-type-factory.js +17 -7
- package/dist/api/compiler/checker-type-nodes.d.ts +1 -1
- package/dist/api/compiler/checker-type-nodes.js +5 -5
- package/dist/api/compiler/checker-unused.js +15 -10
- package/dist/api/compiler/checker.d.ts +4 -0
- package/dist/api/compiler/checker.js +6 -0
- package/dist/api/compiler/file-loader.d.ts +5 -0
- package/dist/api/compiler/file-loader.js +7 -0
- package/dist/api/compiler/program-checker.d.ts +18 -0
- package/dist/api/compiler/program-checker.js +27 -0
- package/package.json +14 -9
- package/src/compiler/ast.ts +20 -3
- package/src/compiler/checker-functions.ts +5 -0
- package/src/compiler/checker-grammar.ts +247 -88
- package/src/compiler/checker-heritage.ts +31 -17
- package/src/compiler/checker-modules.ts +4 -2
- package/src/compiler/checker-narrowing.ts +30 -4
- package/src/compiler/checker-source-file.ts +16 -1
- package/src/compiler/checker-statements.ts +10 -2
- package/src/compiler/checker-type-factory.ts +14 -7
- package/src/compiler/checker-type-nodes.ts +5 -5
- package/src/compiler/checker-unused.ts +13 -8
- package/src/compiler/checker.ts +6 -0
- package/src/compiler/file-loader.ts +8 -0
- package/src/compiler/parallel-check.ts +13 -1
- package/src/compiler/parse-thread.ts +24 -2
- package/src/compiler/program-checker.ts +28 -0
- package/src/compiler/run-records.ts +12 -5
- package/src/compiler/tsc-compile.ts +10 -1
|
@@ -965,16 +965,25 @@ const enum MemberKind {
|
|
|
965
965
|
* (with `checkPrivateNames`) private names shared by static and instance members.
|
|
966
966
|
*/
|
|
967
967
|
export function checkObjectTypeForDuplicateDeclarations(c: Checker, node: Node, checkPrivateNames: boolean): void {
|
|
968
|
-
|
|
969
|
-
|
|
970
|
-
|
|
968
|
+
// Only a symbol with several declarations can be a duplicate, so the maps are created for the first one.
|
|
969
|
+
let instanceNames: Map<string, MemberKind> | undefined;
|
|
970
|
+
let staticNames: Map<string, MemberKind> | undefined;
|
|
971
|
+
let privateNames: Map<string, number> | undefined;
|
|
971
972
|
const nodeInAmbientContext = (node.flags & NodeFlags.Ambient) !== 0;
|
|
972
973
|
for (const member of getMembers(node)!.nodes) {
|
|
973
974
|
if (member.kind === SyntaxKind.Constructor) {
|
|
974
975
|
for (const param of getParameters(member)!.nodes)
|
|
975
976
|
if (isParameterPropertyDeclaration(param, member) && !isBindingPattern(getName(param))) {
|
|
976
977
|
const symbol = getSymbolOfDeclaration(c, param);
|
|
977
|
-
if (symbol !== undefined
|
|
978
|
+
if (symbol !== undefined && declarationCount(symbol) > 1)
|
|
979
|
+
checkPropertyOrAccessor(
|
|
980
|
+
c,
|
|
981
|
+
node,
|
|
982
|
+
(instanceNames ??= new Map<string, MemberKind>()),
|
|
983
|
+
symbol,
|
|
984
|
+
MemberKind.Property,
|
|
985
|
+
false,
|
|
986
|
+
);
|
|
978
987
|
}
|
|
979
988
|
continue;
|
|
980
989
|
}
|
|
@@ -988,21 +997,26 @@ export function checkObjectTypeForDuplicateDeclarations(c: Checker, node: Node,
|
|
|
988
997
|
symbol.name,
|
|
989
998
|
symbolToString(c, getSymbolOfDeclaration(c, node)!),
|
|
990
999
|
);
|
|
991
|
-
|
|
992
|
-
|
|
993
|
-
|
|
994
|
-
|
|
995
|
-
member
|
|
996
|
-
|
|
997
|
-
|
|
998
|
-
|
|
999
|
-
|
|
1000
|
-
|
|
1001
|
-
|
|
1002
|
-
|
|
1003
|
-
|
|
1000
|
+
if (declarationCount(symbol) > 1) {
|
|
1001
|
+
const names = memberIsStatic
|
|
1002
|
+
? (staticNames ??= new Map<string, MemberKind>())
|
|
1003
|
+
: (instanceNames ??= new Map<string, MemberKind>());
|
|
1004
|
+
const hasAccessorModifier = hasSyntacticModifier(member, ModifierFlags.Accessor);
|
|
1005
|
+
if (
|
|
1006
|
+
(member.kind === SyntaxKind.PropertyDeclaration && !hasAccessorModifier) ||
|
|
1007
|
+
member.kind === SyntaxKind.PropertySignature
|
|
1008
|
+
)
|
|
1009
|
+
checkPropertyOrAccessor(c, node, names, symbol, MemberKind.Property, memberIsStatic);
|
|
1010
|
+
else if (
|
|
1011
|
+
member.kind === SyntaxKind.GetAccessor ||
|
|
1012
|
+
member.kind === SyntaxKind.SetAccessor ||
|
|
1013
|
+
(member.kind === SyntaxKind.PropertyDeclaration && hasAccessorModifier)
|
|
1014
|
+
)
|
|
1015
|
+
checkPropertyOrAccessor(c, node, names, symbol, MemberKind.Accessor, memberIsStatic);
|
|
1016
|
+
}
|
|
1004
1017
|
const name = getName(member);
|
|
1005
1018
|
if (checkPrivateNames && name !== undefined && name.kind === SyntaxKind.PrivateIdentifier) {
|
|
1019
|
+
privateNames ??= new Map<string, number>();
|
|
1006
1020
|
let flags = privateNames.get(symbol.name) ?? 0;
|
|
1007
1021
|
if (flags === 3) continue;
|
|
1008
1022
|
flags |= isStatic(member) ? 2 : 1;
|
|
@@ -2167,14 +2167,16 @@ function getSymbolFlagsEx(
|
|
|
2167
2167
|
excludeTypeOnlyMeanings: boolean,
|
|
2168
2168
|
excludeLocalMeanings: boolean,
|
|
2169
2169
|
): SymbolFlags {
|
|
2170
|
-
|
|
2170
|
+
// Only a chain of aliases needs the set, so most calls never create it.
|
|
2171
|
+
let seen: Set<Symbol> | undefined;
|
|
2171
2172
|
let flags = excludeLocalMeanings ? SymbolFlags.None : symbol.flags;
|
|
2172
2173
|
while ((symbol.flags & SymbolFlags.Alias) !== 0) {
|
|
2173
2174
|
if (excludeTypeOnlyMeanings && getTypeOnlyAliasDeclaration(c, symbol) !== undefined) break;
|
|
2174
2175
|
const target = getExportSymbolOfValueSymbolIfExported(c, resolveAlias(c, symbol));
|
|
2175
2176
|
if (target === c.unknownSymbol) return SymbolFlags.All;
|
|
2176
2177
|
if ((target.flags & SymbolFlags.Alias) !== 0) {
|
|
2177
|
-
if (target === symbol || seen
|
|
2178
|
+
if (target === symbol || seen?.has(target)) break;
|
|
2179
|
+
seen ??= new Set<Symbol>();
|
|
2178
2180
|
seen.add(symbol);
|
|
2179
2181
|
seen.add(target);
|
|
2180
2182
|
}
|
|
@@ -369,7 +369,7 @@ export function getFlowTypeOfReferenceAt(
|
|
|
369
369
|
flowContainer: Node | undefined,
|
|
370
370
|
flowNode: FlowNode | undefined,
|
|
371
371
|
): Type {
|
|
372
|
-
if (c.flowAnalysisDisabled) return c.errorType;
|
|
372
|
+
if (c.flowAnalysisDisabled && isInFlowAnalysisStop(c, reference)) return c.errorType;
|
|
373
373
|
if (flowNode === undefined) return declaredType;
|
|
374
374
|
let f: FlowState;
|
|
375
375
|
const stateDepth = c.flowStateDepth;
|
|
@@ -396,7 +396,7 @@ export function getFlowTypeOfReferenceAt(
|
|
|
396
396
|
f.steps >= minFlowWalkSteps &&
|
|
397
397
|
!f.incomplete &&
|
|
398
398
|
!f.positional &&
|
|
399
|
-
!c.flowAnalysisDisabled &&
|
|
399
|
+
!(c.flowAnalysisDisabled && isInFlowAnalysisStop(c, reference)) &&
|
|
400
400
|
c.resolutionCycleCount === cycles
|
|
401
401
|
) {
|
|
402
402
|
const walk = f.walk;
|
|
@@ -438,6 +438,7 @@ function complete(f: FlowState, t: Type): Type {
|
|
|
438
438
|
export function getTypeAtFlowNode(c: Checker, f: FlowState, start: FlowNode): Type {
|
|
439
439
|
if (f.depth === 2000) {
|
|
440
440
|
c.flowAnalysisDisabled = true;
|
|
441
|
+
c.flowAnalysisDisabledBody = getContainingFlowBody(f.reference);
|
|
441
442
|
reportFlowControlError(c, f.reference);
|
|
442
443
|
return complete(f, c.errorType);
|
|
443
444
|
}
|
|
@@ -3148,9 +3149,33 @@ function getContextFreeTypeOfExpression(c: Checker, node: Node): Type {
|
|
|
3148
3149
|
return t;
|
|
3149
3150
|
}
|
|
3150
3151
|
|
|
3151
|
-
|
|
3152
|
+
/** The body whose walk reached the flow limit, or undefined when no body is stopped. */
|
|
3153
|
+
function getContainingFlowBody(node: Node): Node | undefined {
|
|
3152
3154
|
let block: Node | undefined = node;
|
|
3153
3155
|
while (block !== undefined && !isFunctionOrModuleBlock(block)) block = block.parent;
|
|
3156
|
+
return block;
|
|
3157
|
+
}
|
|
3158
|
+
|
|
3159
|
+
/** Whether `node` is in the body whose walk reached the flow limit, or in a body it contains. */
|
|
3160
|
+
function isInFlowAnalysisStop(c: Checker, node: Node): boolean {
|
|
3161
|
+
const stop = c.flowAnalysisDisabledBody;
|
|
3162
|
+
if (stop === undefined) return false;
|
|
3163
|
+
for (let n: Node | undefined = node; n !== undefined; n = n.parent) if (n === stop) return true;
|
|
3164
|
+
return false;
|
|
3165
|
+
}
|
|
3166
|
+
|
|
3167
|
+
/**
|
|
3168
|
+
* Ends the narrowing stopped in `body`, whose checks have finished: what follows the body starts over.
|
|
3169
|
+
* A walk that stopped in another body keeps its body's narrowing stopped.
|
|
3170
|
+
*/
|
|
3171
|
+
export function endFlowAnalysisStop(c: Checker, body: Node): void {
|
|
3172
|
+
if (c.flowAnalysisDisabledBody !== body) return;
|
|
3173
|
+
c.flowAnalysisDisabled = false;
|
|
3174
|
+
c.flowAnalysisDisabledBody = undefined;
|
|
3175
|
+
}
|
|
3176
|
+
|
|
3177
|
+
function reportFlowControlError(c: Checker, node: Node): void {
|
|
3178
|
+
const block = getContainingFlowBody(node);
|
|
3154
3179
|
if (block === undefined) return;
|
|
3155
3180
|
const file = getSourceFileOfNode(node);
|
|
3156
3181
|
const span = getRangeOfTokenAtPosition(file, getStatements(block)!.pos);
|
|
@@ -3165,7 +3190,8 @@ function reportFlowControlError(c: Checker, node: Node): void {
|
|
|
3165
3190
|
);
|
|
3166
3191
|
}
|
|
3167
3192
|
|
|
3168
|
-
function
|
|
3193
|
+
/** Whether `node` is the body of a function or a module, where flow analysis starts over (flowAnalysisDisabled). */
|
|
3194
|
+
export function isFunctionOrModuleBlock(node: Node): boolean {
|
|
3169
3195
|
return (
|
|
3170
3196
|
node.kind === SyntaxKind.SourceFile ||
|
|
3171
3197
|
node.kind === SyntaxKind.ModuleBlock ||
|
|
@@ -132,6 +132,11 @@ import { checkContextualDeprecations, checkDeprecatedTypeReference } from './che
|
|
|
132
132
|
* identifiers, which need the whole file checked first.
|
|
133
133
|
*/
|
|
134
134
|
export function checkSourceFile(c: Checker, file: SourceFile, checkUnused = false): void {
|
|
135
|
+
// A source file is a module body: narrowing stopped by a body too large for control flow analysis
|
|
136
|
+
// (flowAnalysisDisabled, checker-narrowing.ts) starts over in the next check. A checker checks many files
|
|
137
|
+
// (parallel-check.ts), so its diagnostics of a file must not depend on the files it checked before it.
|
|
138
|
+
c.flowAnalysisDisabled = false;
|
|
139
|
+
c.flowAnalysisDisabledBody = undefined;
|
|
135
140
|
const links = c.nodeLinks(file);
|
|
136
141
|
if ((links.flags & NodeCheckFlags.TypeChecked) === 0) {
|
|
137
142
|
links.flags |= NodeCheckFlags.TypeChecked;
|
|
@@ -141,6 +146,9 @@ export function checkSourceFile(c: Checker, file: SourceFile, checkUnused = fals
|
|
|
141
146
|
links.flags |= NodeCheckFlags.UnusedChecked;
|
|
142
147
|
checkUnusedIdentifiers(c, file);
|
|
143
148
|
}
|
|
149
|
+
// Every body of the file has finished its checks; a stop recorded for one of them ends with the file.
|
|
150
|
+
c.flowAnalysisDisabled = false;
|
|
151
|
+
c.flowAnalysisDisabledBody = undefined;
|
|
144
152
|
}
|
|
145
153
|
|
|
146
154
|
function checkSourceFileWorker(c: Checker, file: SourceFile): void {
|
|
@@ -176,6 +184,10 @@ function checkSourceFileWorker(c: Checker, file: SourceFile): void {
|
|
|
176
184
|
*/
|
|
177
185
|
export function checkSourceFileRange(c: Checker, file: SourceFile, start: number, end: number): boolean {
|
|
178
186
|
const statements = getStatements(file)!.nodes;
|
|
187
|
+
// A range of a file's statements is part of its module body: narrowing stopped by a body too large for
|
|
188
|
+
// control flow analysis (flowAnalysisDisabled, checker-narrowing.ts) starts over in the next range.
|
|
189
|
+
c.flowAnalysisDisabled = false;
|
|
190
|
+
c.flowAnalysisDisabledBody = undefined;
|
|
179
191
|
c.fileBeingChecked = file;
|
|
180
192
|
for (let i = start; i < end; i++) checkSourceElement(c, statements[i]!);
|
|
181
193
|
checkDeferredNodes(c, file);
|
|
@@ -191,6 +203,9 @@ export function checkSourceFileRange(c: Checker, file: SourceFile, start: number
|
|
|
191
203
|
produceDeferredDiagnostics(c);
|
|
192
204
|
// Deferred nodes of a later range are queued again (checkNodeDeferred).
|
|
193
205
|
c.nodeLinks(file).deferredNodes = undefined;
|
|
206
|
+
// The range's bodies have finished their checks; a stop recorded for one of them ends with the range.
|
|
207
|
+
c.flowAnalysisDisabled = false;
|
|
208
|
+
c.flowAnalysisDisabledBody = undefined;
|
|
194
209
|
return c.diagnostics.forFile(file.fileName).length > 0;
|
|
195
210
|
}
|
|
196
211
|
|
|
@@ -639,7 +654,7 @@ function checkTypeMemberAccessor(c: Checker, node: Node): void {
|
|
|
639
654
|
/** Variadic tuple elements must be array-like. */
|
|
640
655
|
function checkTupleRestElements(c: Checker, node: TupleTypeNode): void {
|
|
641
656
|
for (const e of node.elements.nodes) {
|
|
642
|
-
if ((getTupleElementFlags(
|
|
657
|
+
if ((getTupleElementFlags(e) & ElementFlags.Variadic) === 0) continue;
|
|
643
658
|
if (!isArrayLikeType(c, getTypeFromTypeNode(c, getType(e)!))) {
|
|
644
659
|
c.error(e, Diagnostics.A_rest_element_type_must_be_an_array_type);
|
|
645
660
|
return;
|
|
@@ -42,7 +42,7 @@ import {
|
|
|
42
42
|
isTypeEqualityComparableTo,
|
|
43
43
|
} from './checker-operators.js';
|
|
44
44
|
import { checkTypeComparableTo, isTypeAssignableTo } from './relations.js';
|
|
45
|
-
import { isReachableFlowNode } from './checker-narrowing.js';
|
|
45
|
+
import { endFlowAnalysisStop, isFunctionOrModuleBlock, isReachableFlowNode } from './checker-narrowing.js';
|
|
46
46
|
import { getIndexType, isTypeAssignableToKindEx as isTypeAssignableToKind } from './checker-type-operators.js';
|
|
47
47
|
import { getExtractStringType } from './checker-symbol-types.js';
|
|
48
48
|
import { checkSourceElement } from './checker-source-file.js';
|
|
@@ -57,7 +57,15 @@ import { typeToString } from './type-printer.js';
|
|
|
57
57
|
import { TypeFacts, getNonNullableType, hasTypeFacts } from './checker-flow.js';
|
|
58
58
|
|
|
59
59
|
export function checkBlock(c: Checker, node: Node): void {
|
|
60
|
-
|
|
60
|
+
const statements = getStatements(node)!.nodes;
|
|
61
|
+
if (!isFunctionOrModuleBlock(node)) {
|
|
62
|
+
for (const s of statements) checkSourceElement(c, s);
|
|
63
|
+
return;
|
|
64
|
+
}
|
|
65
|
+
// A body too large for control flow analysis stops narrowing in the rest of that body only
|
|
66
|
+
// (flowAnalysisDisabled, checker-narrowing.ts); what follows the body starts over.
|
|
67
|
+
for (const s of statements) checkSourceElement(c, s);
|
|
68
|
+
endFlowAnalysisStop(c, node);
|
|
61
69
|
}
|
|
62
70
|
|
|
63
71
|
export function checkExpressionStatement(c: Checker, node: Node): void {
|
|
@@ -1765,20 +1765,26 @@ export function getIntersectionType(c: Checker, types: Type[]): Type {
|
|
|
1765
1765
|
return getIntersectionTypeEx(c, types, IntersectionFlags.None, undefined);
|
|
1766
1766
|
}
|
|
1767
1767
|
|
|
1768
|
+
/**
|
|
1769
|
+
* The distinct types of an intersection, in the order they were added. Most intersections have a few
|
|
1770
|
+
* types, which a scan of `values` finds faster than a `Set` costs to build, so the set is created only
|
|
1771
|
+
* once the list grows past `INDEX_AFTER`.
|
|
1772
|
+
*/
|
|
1768
1773
|
class OrderedTypeSet {
|
|
1774
|
+
private static readonly INDEX_AFTER = 8;
|
|
1769
1775
|
readonly values: Type[] = [];
|
|
1770
|
-
private
|
|
1776
|
+
private index: Set<Type> | undefined;
|
|
1771
1777
|
includes = 0;
|
|
1772
1778
|
|
|
1773
1779
|
has(t: Type): boolean {
|
|
1774
|
-
return this.
|
|
1780
|
+
return this.index !== undefined ? this.index.has(t) : this.values.includes(t);
|
|
1775
1781
|
}
|
|
1776
1782
|
|
|
1777
1783
|
add(t: Type): void {
|
|
1778
|
-
if (
|
|
1779
|
-
|
|
1780
|
-
|
|
1781
|
-
|
|
1784
|
+
if (this.has(t)) return;
|
|
1785
|
+
this.values.push(t);
|
|
1786
|
+
if (this.index !== undefined) this.index.add(t);
|
|
1787
|
+
else if (this.values.length > OrderedTypeSet.INDEX_AFTER) this.index = new Set(this.values);
|
|
1782
1788
|
}
|
|
1783
1789
|
}
|
|
1784
1790
|
|
|
@@ -2153,7 +2159,8 @@ function addCrossProductIntersections(
|
|
|
2153
2159
|
intersections: Type[],
|
|
2154
2160
|
): void {
|
|
2155
2161
|
if (index === types.length) {
|
|
2156
|
-
|
|
2162
|
+
// getIntersectionTypeEx copies the types it keeps, so the buffer is reused for the next combination.
|
|
2163
|
+
const t = getIntersectionTypeEx(c, constituents, flags, undefined);
|
|
2157
2164
|
if ((t.flags & TypeFlags.Never) === 0) intersections.push(t);
|
|
2158
2165
|
return;
|
|
2159
2166
|
}
|
|
@@ -882,7 +882,7 @@ function getTypeFromTypeQueryNode(c: Checker, node: Node): Type {
|
|
|
882
882
|
return type;
|
|
883
883
|
}
|
|
884
884
|
|
|
885
|
-
export function getTupleElementFlags(
|
|
885
|
+
export function getTupleElementFlags(node: Node): ElementFlags {
|
|
886
886
|
switch (node.kind) {
|
|
887
887
|
case SyntaxKind.OptionalType:
|
|
888
888
|
return ElementFlags.Optional;
|
|
@@ -901,9 +901,9 @@ export function getTupleElementFlags(c: Checker, node: Node): ElementFlags {
|
|
|
901
901
|
return ElementFlags.Required;
|
|
902
902
|
}
|
|
903
903
|
|
|
904
|
-
function getTupleElementInfo(
|
|
904
|
+
function getTupleElementInfo(node: Node): TupleElementInfo {
|
|
905
905
|
return new TupleElementInfo(
|
|
906
|
-
getTupleElementFlags(
|
|
906
|
+
getTupleElementFlags(node),
|
|
907
907
|
node.kind === SyntaxKind.NamedTupleMember || node.kind === SyntaxKind.Parameter ? node : undefined,
|
|
908
908
|
);
|
|
909
909
|
}
|
|
@@ -934,7 +934,7 @@ function getArrayOrTupleTargetType(c: Checker, node: Node): Type {
|
|
|
934
934
|
parent.kind === SyntaxKind.TypeOperator && (parent as TypeOperatorNode).operator === SyntaxKind.ReadonlyKeyword;
|
|
935
935
|
if (getArrayElementTypeNode(node) !== undefined) return readonly ? c.globalReadonlyArrayType : c.globalArrayType;
|
|
936
936
|
const elementInfos: TupleElementInfo[] = [];
|
|
937
|
-
for (const e of (node as TupleTypeNode).elements.nodes) elementInfos.push(getTupleElementInfo(
|
|
937
|
+
for (const e of (node as TupleTypeNode).elements.nodes) elementInfos.push(getTupleElementInfo(e));
|
|
938
938
|
return getTupleTargetType(c, elementInfos, readonly);
|
|
939
939
|
}
|
|
940
940
|
|
|
@@ -969,7 +969,7 @@ function getTypeFromArrayOrTupleTypeNode(c: Checker, node: Node): Type {
|
|
|
969
969
|
}
|
|
970
970
|
|
|
971
971
|
function hasVariadicTupleElement(c: Checker, node: TupleTypeNode): boolean {
|
|
972
|
-
for (const e of node.elements.nodes) if ((getTupleElementFlags(
|
|
972
|
+
for (const e of node.elements.nodes) if ((getTupleElementFlags(e) & ElementFlags.Variadic) !== 0) return true;
|
|
973
973
|
return false;
|
|
974
974
|
}
|
|
975
975
|
|
|
@@ -421,8 +421,9 @@ function checkUnusedClassMembers(c: Checker, node: Node): void {
|
|
|
421
421
|
function checkUnusedLocalsAndParameters(c: Checker, node: Node): void {
|
|
422
422
|
const locals = getLocals(node);
|
|
423
423
|
if (locals === undefined) return;
|
|
424
|
-
|
|
425
|
-
|
|
424
|
+
// Created on the first unused declaration: most scopes have none.
|
|
425
|
+
let variableParents: Set<Node> | undefined;
|
|
426
|
+
let importClauses: Map<Node, Node[]> | undefined;
|
|
426
427
|
for (const local of locals.values()) {
|
|
427
428
|
const referenceKinds = c.symbolReferenceKinds.get(getSymbolId(local));
|
|
428
429
|
if ((local.flags & SymbolFlags.TypeParameter) !== 0) {
|
|
@@ -438,6 +439,7 @@ function checkUnusedLocalsAndParameters(c: Checker, node: Node): void {
|
|
|
438
439
|
case SyntaxKind.VariableDeclaration:
|
|
439
440
|
case SyntaxKind.Parameter:
|
|
440
441
|
case SyntaxKind.BindingElement:
|
|
442
|
+
variableParents ??= new Set<Node>();
|
|
441
443
|
variableParents.add(getRootDeclaration(declaration).parent!);
|
|
442
444
|
break;
|
|
443
445
|
case SyntaxKind.ImportClause:
|
|
@@ -445,6 +447,7 @@ function checkUnusedLocalsAndParameters(c: Checker, node: Node): void {
|
|
|
445
447
|
case SyntaxKind.NamespaceImport:
|
|
446
448
|
if (!isIdentifierThatStartsWithUnderscore(getName(declaration))) {
|
|
447
449
|
const importClause = importClauseFromImported(declaration);
|
|
450
|
+
importClauses ??= new Map<Node, Node[]>();
|
|
448
451
|
const unuseds = importClauses.get(importClause);
|
|
449
452
|
if (unuseds === undefined) importClauses.set(importClause, [declaration]);
|
|
450
453
|
else unuseds.push(declaration);
|
|
@@ -456,12 +459,14 @@ function checkUnusedLocalsAndParameters(c: Checker, node: Node): void {
|
|
|
456
459
|
}
|
|
457
460
|
}
|
|
458
461
|
}
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
462
|
+
if (variableParents !== undefined)
|
|
463
|
+
for (const declaration of variableParents) {
|
|
464
|
+
if (declaration.kind === SyntaxKind.VariableDeclarationList)
|
|
465
|
+
reportUnusedVariables(c, declaration, (declaration as VariableDeclarationList).declarations.nodes);
|
|
466
|
+
else reportUnusedVariableDeclarations(c, getParameters(declaration)!.nodes);
|
|
467
|
+
}
|
|
468
|
+
if (importClauses !== undefined)
|
|
469
|
+
for (const [declaration, unuseds] of importClauses) reportUnusedImports(c, declaration as ImportClause, unuseds);
|
|
465
470
|
}
|
|
466
471
|
|
|
467
472
|
function reportUnusedLocal(c: Checker, node: Node, name: string): void {
|
package/src/compiler/checker.ts
CHANGED
|
@@ -411,6 +411,8 @@ export class Checker implements TypeChecking {
|
|
|
411
411
|
// Counters for type and signature ids.
|
|
412
412
|
typeCount = 0;
|
|
413
413
|
signatureCount = 0;
|
|
414
|
+
/** Transient symbols the checker created (statistics), as tsgo's `Checker.SymbolCount`. */
|
|
415
|
+
symbolCount = 0;
|
|
414
416
|
/** Positions of members by name in the tables and lists that lazily instantiated members come from. */
|
|
415
417
|
readonly memberPositionsInLists = new Map<Symbol[], Map<string, number>>();
|
|
416
418
|
/** The property names of the last instantiation of each such list whose table is built on demand. */
|
|
@@ -753,6 +755,8 @@ export class Checker implements TypeChecking {
|
|
|
753
755
|
|
|
754
756
|
// Control flow analysis
|
|
755
757
|
flowAnalysisDisabled = false;
|
|
758
|
+
/** The body whose walk reached the flow limit (flowAnalysisDisabled), until its checks finish. */
|
|
759
|
+
flowAnalysisDisabledBody: Node | undefined = undefined;
|
|
756
760
|
inlineLevel = 0;
|
|
757
761
|
/**
|
|
758
762
|
* Shared flow nodes cached by the walks in progress, with their types: the first
|
|
@@ -1173,11 +1177,13 @@ export class Checker implements TypeChecking {
|
|
|
1173
1177
|
}
|
|
1174
1178
|
|
|
1175
1179
|
newSymbol(flags: SymbolFlags, name: string): Symbol {
|
|
1180
|
+
this.symbolCount++;
|
|
1176
1181
|
const symbol = new Symbol(flags | SymbolFlags.Transient, name);
|
|
1177
1182
|
return symbol;
|
|
1178
1183
|
}
|
|
1179
1184
|
|
|
1180
1185
|
newSymbolWithCheckFlags(flags: SymbolFlags, name: string, checkFlags: CheckFlags): Symbol {
|
|
1186
|
+
this.symbolCount++;
|
|
1181
1187
|
const symbol = new Symbol(flags | SymbolFlags.Transient, name);
|
|
1182
1188
|
symbol.checkFlags = checkFlags;
|
|
1183
1189
|
return symbol;
|
|
@@ -131,6 +131,12 @@ export class CompilerHost extends ModuleResolutionHost {
|
|
|
131
131
|
return this.fs.getAccessibleEntries(path).directories;
|
|
132
132
|
}
|
|
133
133
|
|
|
134
|
+
/**
|
|
135
|
+
* The metadata of the file `FileLoader` is parsing, which it computes from the file's package scope
|
|
136
|
+
* before it parses it; a host that walks the grammar of the parsed file reads it (parse-thread.ts).
|
|
137
|
+
*/
|
|
138
|
+
loadingSourceFileMetadata: SourceFileMetadata | undefined = undefined;
|
|
139
|
+
|
|
134
140
|
/** Parses a file, or returns `undefined` if it can't be read. */
|
|
135
141
|
getSourceFile(
|
|
136
142
|
fileName: string,
|
|
@@ -1222,6 +1228,8 @@ export class FileLoader {
|
|
|
1222
1228
|
? libFileMetadata
|
|
1223
1229
|
: (loaded?.metadata ?? this.loadSourceFileMetadata(task.normalizedFilePath));
|
|
1224
1230
|
const options = this.mapper.getCompilerOptionsForFile(task.path, task.normalizedFilePath);
|
|
1231
|
+
// For the host that walks the grammar of the file as it parses it (parse-thread.ts).
|
|
1232
|
+
this.host.loadingSourceFileMetadata = task.metadata;
|
|
1225
1233
|
const file = this.host.getSourceFile(
|
|
1226
1234
|
task.normalizedFilePath,
|
|
1227
1235
|
task.path,
|
|
@@ -199,6 +199,11 @@ export class CheckerResult {
|
|
|
199
199
|
readonly units: number[] = [],
|
|
200
200
|
/** Those of `units` after which its checker had diagnostics in their file (`checkSourceFileRange`). */
|
|
201
201
|
readonly reportingUnits: number[] = [],
|
|
202
|
+
/**
|
|
203
|
+
* The types, instantiations and transient symbols its checker created, which `--diagnostics` adds to
|
|
204
|
+
* the main thread's (`CheckedProgram.addCheckerCounts`); empty for a result without a checker of its own.
|
|
205
|
+
*/
|
|
206
|
+
readonly counts: number[] = [],
|
|
202
207
|
) {}
|
|
203
208
|
}
|
|
204
209
|
|
|
@@ -243,6 +248,7 @@ export type PackedResult = {
|
|
|
243
248
|
readonly reserved: number[];
|
|
244
249
|
readonly units: Int32Array;
|
|
245
250
|
readonly reportingUnits: Int32Array;
|
|
251
|
+
readonly counts: number[];
|
|
246
252
|
};
|
|
247
253
|
|
|
248
254
|
class ResultPacker {
|
|
@@ -346,6 +352,7 @@ export function packResult(result: CheckerResult, program: Program): PackedResul
|
|
|
346
352
|
reserved: result.reserved,
|
|
347
353
|
units: Int32Array.from(result.units),
|
|
348
354
|
reportingUnits: Int32Array.from(result.reportingUnits),
|
|
355
|
+
counts: result.counts,
|
|
349
356
|
};
|
|
350
357
|
}
|
|
351
358
|
|
|
@@ -475,6 +482,7 @@ export function unpackResult(
|
|
|
475
482
|
emits,
|
|
476
483
|
Array.from(packed.units),
|
|
477
484
|
Array.from(packed.reportingUnits),
|
|
485
|
+
packed.counts,
|
|
478
486
|
);
|
|
479
487
|
}
|
|
480
488
|
|
|
@@ -1928,6 +1936,7 @@ export function checkFromQueue(
|
|
|
1928
1936
|
emits,
|
|
1929
1937
|
unitsDone,
|
|
1930
1938
|
reportingUnits,
|
|
1939
|
+
onMainThread ? [] : [checked.checker.typeCount, checked.checker.totalInstantiations, checked.checker.symbolCount],
|
|
1931
1940
|
);
|
|
1932
1941
|
}
|
|
1933
1942
|
|
|
@@ -2152,7 +2161,8 @@ function checkSignals(
|
|
|
2152
2161
|
}
|
|
2153
2162
|
signals.library = library / ranges;
|
|
2154
2163
|
signals.own = own / ranges;
|
|
2155
|
-
|
|
2164
|
+
// Every range can be empty (all of a program's files in the shared queue), so `library + own` can be 0.
|
|
2165
|
+
signals.redundancy = library + own === 0 ? 0 : (library - unionChars) / (library + own);
|
|
2156
2166
|
signals.declarationShare = declarationFiles / files.length;
|
|
2157
2167
|
signals.nodeModulesShare = nodeModulesFiles / files.length;
|
|
2158
2168
|
if (config !== undefined) config.signals = signals;
|
|
@@ -2326,6 +2336,7 @@ export function startParallelCheck(
|
|
|
2326
2336
|
done(performance.now() - start);
|
|
2327
2337
|
};
|
|
2328
2338
|
const record = (result: CheckerResult): void => {
|
|
2339
|
+
checked.addCheckerCounts(result.counts);
|
|
2329
2340
|
if (declarationsTo !== undefined) {
|
|
2330
2341
|
for (let k = 0; k < result.declarations.length; k++) declarationsTo[result.files[k]!] = result.declarations[k]!;
|
|
2331
2342
|
if (trace)
|
|
@@ -2569,6 +2580,7 @@ class Revival {
|
|
|
2569
2580
|
copy.emits.map((emit) => this.emit(emit)),
|
|
2570
2581
|
copy.units,
|
|
2571
2582
|
copy.reportingUnits,
|
|
2583
|
+
copy.counts,
|
|
2572
2584
|
);
|
|
2573
2585
|
}
|
|
2574
2586
|
|
|
@@ -6,10 +6,11 @@
|
|
|
6
6
|
import { publish } from '@scriptc/threads';
|
|
7
7
|
import { type ExternalModuleIndicatorOptions, ParseOptions, ScriptKind, type SourceFile } from './ast.js';
|
|
8
8
|
import { bindSourceFile } from './binder.js';
|
|
9
|
+
import { checkGrammarSourceFileInParseThread } from './checker-grammar.js';
|
|
9
10
|
import { useThreadTextCaches } from './diagnostics.js';
|
|
10
11
|
import { CompilerHost, FileLoader } from './file-loader.js';
|
|
11
12
|
import { computeHash } from './incremental.js';
|
|
12
|
-
import { isIncremental } from './options.js';
|
|
13
|
+
import { type CompilerOptions, isIncremental } from './options.js';
|
|
13
14
|
import type { FileSystem } from './file-system.js';
|
|
14
15
|
import {
|
|
15
16
|
fileSize,
|
|
@@ -29,6 +30,18 @@ class PreparingHost extends CompilerHost {
|
|
|
29
30
|
prepared: PreparedFile[] = [];
|
|
30
31
|
/** Whether the files' build info versions are computed (an incremental program). */
|
|
31
32
|
computesVersions = false;
|
|
33
|
+
/**
|
|
34
|
+
* Whether the files the loader parses here run their grammar checks before they are published. The
|
|
35
|
+
* walk is the checker's, run off the checking threads (checker-grammar.ts).
|
|
36
|
+
*/
|
|
37
|
+
grammarChecks = true;
|
|
38
|
+
/** The program's options, which the grammar checks of a file are given. */
|
|
39
|
+
readonly options: CompilerOptions;
|
|
40
|
+
|
|
41
|
+
constructor(fs: FileSystem, currentDirectory: string, defaultLibraryPath: string, options: CompilerOptions) {
|
|
42
|
+
super(fs, currentDirectory, defaultLibraryPath);
|
|
43
|
+
this.options = options;
|
|
44
|
+
}
|
|
32
45
|
readMs = 0;
|
|
33
46
|
parseMs = 0;
|
|
34
47
|
bindMs = 0;
|
|
@@ -49,6 +62,12 @@ class PreparingHost extends CompilerHost {
|
|
|
49
62
|
);
|
|
50
63
|
const t2 = timeline ? performance.now() : 0;
|
|
51
64
|
bindSourceFile(file);
|
|
65
|
+
// The checker does not check the declaration files `skipLibCheck` names, so it does not walk their
|
|
66
|
+
// grammar either. The walk reads the metadata the loader computed for the file, which is the one the
|
|
67
|
+
// checking thread reads it with, so it looks up no package.json the load has not looked up.
|
|
68
|
+
const metadata = this.loadingSourceFileMetadata;
|
|
69
|
+
if (this.grammarChecks && metadata !== undefined && !(this.options.skipLibCheck === true && file.isDeclarationFile))
|
|
70
|
+
checkGrammarSourceFileInParseThread(file, this.options, metadata);
|
|
52
71
|
// Published now, while its nodes are in cache: the chunk's publish (`ParseThread.load`) then skips
|
|
53
72
|
// them, where walking the whole chunk's files at once reads each node from memory again.
|
|
54
73
|
publish(file);
|
|
@@ -85,11 +104,14 @@ export class ParseThread {
|
|
|
85
104
|
private sentPackageJsons = 0;
|
|
86
105
|
|
|
87
106
|
constructor(config: ParsedCommandLine, fs: FileSystem, currentDirectory: string, defaultLibraryPath: string) {
|
|
88
|
-
this.host = new PreparingHost(fs, currentDirectory, defaultLibraryPath);
|
|
107
|
+
this.host = new PreparingHost(fs, currentDirectory, defaultLibraryPath, config.options);
|
|
89
108
|
// The versions of an incremental program's files are computed here rather than one after another
|
|
90
109
|
// on the main thread (programToSnapshot, incremental.ts).
|
|
91
110
|
this.host.computesVersions = isIncremental(config.options);
|
|
92
111
|
this.loader = new FileLoader(this.host, config);
|
|
112
|
+
// A program that does not check has no grammar diagnostics to report. An incremental program checks
|
|
113
|
+
// the files a change affects and parses all of them, so the walk stays with the checker there.
|
|
114
|
+
this.host.grammarChecks = config.options.noCheck !== true && !isIncremental(config.options);
|
|
93
115
|
// The files this thread loads are published with their chunk: text caches it computes later (of
|
|
94
116
|
// declaration files, which the parser leaves out) go to copies.
|
|
95
117
|
useThreadTextCaches();
|
|
@@ -248,6 +248,28 @@ export class CheckedProgram {
|
|
|
248
248
|
this.reportingChecker = undefined;
|
|
249
249
|
}
|
|
250
250
|
|
|
251
|
+
/** Adds the counts a checker thread sent (`CheckerResult.counts`): types, instantiations, symbols. */
|
|
252
|
+
addCheckerCounts(counts: readonly number[]): void {
|
|
253
|
+
if (counts.length < 3) return;
|
|
254
|
+
this.threadTypeCount += counts[0]!;
|
|
255
|
+
this.threadInstantiations += counts[1]!;
|
|
256
|
+
this.threadSymbolCount += counts[2]!;
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
/**
|
|
260
|
+
* The types, instantiations and transient symbols of every checker of the program: this thread's and
|
|
261
|
+
* the checker threads', as tsgo's `Program.TypeCount`, `InstantiationCount` and `SymbolCount` add them up.
|
|
262
|
+
* Reading them doesn't create this thread's checker.
|
|
263
|
+
*/
|
|
264
|
+
checkerCounts(): { types: number; instantiations: number; symbols: number } {
|
|
265
|
+
const own = this.reportingChecker;
|
|
266
|
+
return {
|
|
267
|
+
types: (own === undefined ? 0 : own.typeCount) + this.threadTypeCount,
|
|
268
|
+
instantiations: (own === undefined ? 0 : own.totalInstantiations) + this.threadInstantiations,
|
|
269
|
+
symbols: (own === undefined ? 0 : own.symbolCount) + this.threadSymbolCount,
|
|
270
|
+
};
|
|
271
|
+
}
|
|
272
|
+
|
|
251
273
|
getBindDiagnostics(file: SourceFile | undefined): Diagnostic[] {
|
|
252
274
|
return this.collect(file, (f) => f.bindDiagnostics);
|
|
253
275
|
}
|
|
@@ -272,6 +294,12 @@ export class CheckedProgram {
|
|
|
272
294
|
*/
|
|
273
295
|
referenceThreads = 1;
|
|
274
296
|
readonly parallelGlobalDiagnostics: Diagnostic[] = [];
|
|
297
|
+
/** The types checker threads created (`CheckerResult.counts`), which `--diagnostics` adds to `checker`'s. */
|
|
298
|
+
threadTypeCount = 0;
|
|
299
|
+
/** The instantiations checker threads made. */
|
|
300
|
+
threadInstantiations = 0;
|
|
301
|
+
/** The transient symbols checker threads created. */
|
|
302
|
+
threadSymbolCount = 0;
|
|
275
303
|
/**
|
|
276
304
|
* Whether the semantic diagnostics of a file include the global diagnostics its checker found while
|
|
277
305
|
* checking it, as an incremental program caches them (IncrementalProgram). Such a program doesn't report
|
|
@@ -282,10 +282,17 @@ export function readRecord(file: string, now: number = Math.floor(Date.now() / 1
|
|
|
282
282
|
return dropOldSamples(record, now);
|
|
283
283
|
}
|
|
284
284
|
|
|
285
|
+
/**
|
|
286
|
+
* A parsed value whose fields are read as `unknown` until they are checked. A native build checks a
|
|
287
|
+
* cast to a record type such as `Partial<RunRecord>` when it converts the value, and throws on a field
|
|
288
|
+
* of the wrong type, so a malformed record file would end the run instead of being ignored.
|
|
289
|
+
*/
|
|
290
|
+
type Unchecked<T> = { [K in keyof T]?: unknown };
|
|
291
|
+
|
|
285
292
|
/** `parsed` as a record, or `undefined` where it is not one this build writes. */
|
|
286
293
|
function recordOf(parsed: unknown): RunRecord | undefined {
|
|
287
294
|
if (typeof parsed !== 'object' || parsed === null) return undefined;
|
|
288
|
-
const r = parsed as
|
|
295
|
+
const r = parsed as Unchecked<RunRecord>;
|
|
289
296
|
if (r.format !== recordFormat || typeof r.runs !== 'number' || !isShape(r.shape) || !isChoice(r.chosen))
|
|
290
297
|
return undefined;
|
|
291
298
|
const samples = Array.isArray(r.samples) ? r.samples.filter(isSample) : [];
|
|
@@ -304,7 +311,7 @@ function recordOf(parsed: unknown): RunRecord | undefined {
|
|
|
304
311
|
|
|
305
312
|
function isShape(value: unknown): value is RecordShape {
|
|
306
313
|
if (typeof value !== 'object' || value === null) return false;
|
|
307
|
-
const s = value as
|
|
314
|
+
const s = value as Unchecked<RecordShape>;
|
|
308
315
|
return (
|
|
309
316
|
isNumber(s.files) &&
|
|
310
317
|
isNumber(s.chars) &&
|
|
@@ -318,7 +325,7 @@ function isShape(value: unknown): value is RecordShape {
|
|
|
318
325
|
|
|
319
326
|
function isChoice(value: unknown): value is RecordChoice {
|
|
320
327
|
if (typeof value !== 'object' || value === null) return false;
|
|
321
|
-
const c = value as
|
|
328
|
+
const c = value as Unchecked<RecordChoice>;
|
|
322
329
|
return (
|
|
323
330
|
isNumber(c.checkers) &&
|
|
324
331
|
typeof c.share === 'boolean' &&
|
|
@@ -331,7 +338,7 @@ function isChoice(value: unknown): value is RecordChoice {
|
|
|
331
338
|
|
|
332
339
|
function isRejection(value: unknown): value is RecordRejection {
|
|
333
340
|
if (typeof value !== 'object' || value === null) return false;
|
|
334
|
-
const r = value as
|
|
341
|
+
const r = value as Unchecked<RecordRejection>;
|
|
335
342
|
return (
|
|
336
343
|
isNumber(r.checkers) &&
|
|
337
344
|
typeof r.share === 'boolean' &&
|
|
@@ -342,7 +349,7 @@ function isRejection(value: unknown): value is RecordRejection {
|
|
|
342
349
|
|
|
343
350
|
function isSample(value: unknown): value is RecordSample {
|
|
344
351
|
if (typeof value !== 'object' || value === null) return false;
|
|
345
|
-
const s = value as
|
|
352
|
+
const s = value as Unchecked<RecordSample>;
|
|
346
353
|
return (
|
|
347
354
|
isNumber(s.run) &&
|
|
348
355
|
isNumber(s.at) &&
|