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.
Files changed (41) hide show
  1. package/README.md +7 -1
  2. package/dist/api/compiler/ast.d.ts +6 -0
  3. package/dist/api/compiler/ast.js +19 -3
  4. package/dist/api/compiler/checker-functions.js +5 -1
  5. package/dist/api/compiler/checker-grammar.d.ts +39 -4
  6. package/dist/api/compiler/checker-grammar.js +239 -75
  7. package/dist/api/compiler/checker-heritage.js +20 -14
  8. package/dist/api/compiler/checker-modules.js +4 -2
  9. package/dist/api/compiler/checker-narrowing.d.ts +7 -0
  10. package/dist/api/compiler/checker-narrowing.js +31 -4
  11. package/dist/api/compiler/checker-source-file.js +16 -1
  12. package/dist/api/compiler/checker-statements.js +11 -2
  13. package/dist/api/compiler/checker-type-factory.js +17 -7
  14. package/dist/api/compiler/checker-type-nodes.d.ts +1 -1
  15. package/dist/api/compiler/checker-type-nodes.js +5 -5
  16. package/dist/api/compiler/checker-unused.js +15 -10
  17. package/dist/api/compiler/checker.d.ts +4 -0
  18. package/dist/api/compiler/checker.js +6 -0
  19. package/dist/api/compiler/file-loader.d.ts +5 -0
  20. package/dist/api/compiler/file-loader.js +7 -0
  21. package/dist/api/compiler/program-checker.d.ts +18 -0
  22. package/dist/api/compiler/program-checker.js +27 -0
  23. package/package.json +14 -9
  24. package/src/compiler/ast.ts +20 -3
  25. package/src/compiler/checker-functions.ts +5 -0
  26. package/src/compiler/checker-grammar.ts +247 -88
  27. package/src/compiler/checker-heritage.ts +31 -17
  28. package/src/compiler/checker-modules.ts +4 -2
  29. package/src/compiler/checker-narrowing.ts +30 -4
  30. package/src/compiler/checker-source-file.ts +16 -1
  31. package/src/compiler/checker-statements.ts +10 -2
  32. package/src/compiler/checker-type-factory.ts +14 -7
  33. package/src/compiler/checker-type-nodes.ts +5 -5
  34. package/src/compiler/checker-unused.ts +13 -8
  35. package/src/compiler/checker.ts +6 -0
  36. package/src/compiler/file-loader.ts +8 -0
  37. package/src/compiler/parallel-check.ts +13 -1
  38. package/src/compiler/parse-thread.ts +24 -2
  39. package/src/compiler/program-checker.ts +28 -0
  40. package/src/compiler/run-records.ts +12 -5
  41. 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
- const instanceNames = new Map<string, MemberKind>();
969
- const staticNames = new Map<string, MemberKind>();
970
- const privateNames = new Map<string, number>();
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) checkPropertyOrAccessor(c, node, instanceNames, symbol, MemberKind.Property, false);
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
- const names = memberIsStatic ? staticNames : instanceNames;
992
- const hasAccessorModifier = hasSyntacticModifier(member, ModifierFlags.Accessor);
993
- if (
994
- (member.kind === SyntaxKind.PropertyDeclaration && !hasAccessorModifier) ||
995
- member.kind === SyntaxKind.PropertySignature
996
- )
997
- checkPropertyOrAccessor(c, node, names, symbol, MemberKind.Property, memberIsStatic);
998
- else if (
999
- member.kind === SyntaxKind.GetAccessor ||
1000
- member.kind === SyntaxKind.SetAccessor ||
1001
- (member.kind === SyntaxKind.PropertyDeclaration && hasAccessorModifier)
1002
- )
1003
- checkPropertyOrAccessor(c, node, names, symbol, MemberKind.Accessor, memberIsStatic);
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
- const seen = new Set<Symbol>();
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.has(target)) break;
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
- function reportFlowControlError(c: Checker, node: Node): void {
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 isFunctionOrModuleBlock(node: Node): boolean {
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(c, e) & ElementFlags.Variadic) === 0) continue;
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
- for (const s of getStatements(node)!.nodes) checkSourceElement(c, s);
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 readonly seen = new Set<Type>();
1776
+ private index: Set<Type> | undefined;
1771
1777
  includes = 0;
1772
1778
 
1773
1779
  has(t: Type): boolean {
1774
- return this.seen.has(t);
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 (!this.seen.has(t)) {
1779
- this.seen.add(t);
1780
- this.values.push(t);
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
- const t = getIntersectionTypeEx(c, constituents.slice(), flags, undefined);
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(c: Checker, node: Node): ElementFlags {
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(c: Checker, node: Node): TupleElementInfo {
904
+ function getTupleElementInfo(node: Node): TupleElementInfo {
905
905
  return new TupleElementInfo(
906
- getTupleElementFlags(c, node),
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(c, e));
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(c, e) & ElementFlags.Variadic) !== 0) return true;
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
- const variableParents = new Set<Node>();
425
- const importClauses = new Map<Node, Node[]>();
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
- for (const declaration of variableParents) {
460
- if (declaration.kind === SyntaxKind.VariableDeclarationList)
461
- reportUnusedVariables(c, declaration, (declaration as VariableDeclarationList).declarations.nodes);
462
- else reportUnusedVariableDeclarations(c, getParameters(declaration)!.nodes);
463
- }
464
- for (const [declaration, unuseds] of importClauses) reportUnusedImports(c, declaration as ImportClause, unuseds);
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 {
@@ -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
- signals.redundancy = (library - unionChars) / (library + own);
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 Partial<RunRecord>;
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 Partial<RecordShape>;
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 Partial<RecordChoice>;
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 Partial<RecordRejection>;
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 Partial<RecordSample>;
352
+ const s = value as Unchecked<RecordSample>;
346
353
  return (
347
354
  isNumber(s.run) &&
348
355
  isNumber(s.at) &&