forked from github/codeql
-
Notifications
You must be signed in to change notification settings - Fork 2
/
type_table.ts
1330 lines (1231 loc) · 47.2 KB
/
type_table.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
import * as ts from "./typescript";
import { VirtualSourceRoot } from "./virtual_source_root";
interface AugmentedSymbol extends ts.Symbol {
parent?: AugmentedSymbol;
/** Cache of our own symbol ID. */
$id?: number;
}
interface AugmentedType extends ts.Type {
/**
* An internal property for predefined types, such as "true", "false", and "object".
*/
intrinsicName?: string;
}
function isTypeReference(type: ts.Type): type is ts.TypeReference {
return (type.flags & ts.TypeFlags.Object) !== 0 &&
((type as ts.ObjectType).objectFlags & ts.ObjectFlags.Reference) !== 0;
}
function isTypeVariable(type: ts.Type): type is ts.TypeVariable {
return (type.flags & ts.TypeFlags.TypeVariable) !== 0;
}
/**
* Returns `true` if the properties of the given type can safely be extracted
* without restricting expansion depth.
*
* This predicate is very approximate, and considers all unions, intersections,
* named types, and mapped types as potentially unsafe.
*/
function isTypeAlwaysSafeToExpand(type: ts.Type): boolean {
let flags = type.flags;
if (flags & ts.TypeFlags.UnionOrIntersection) {
return false;
}
if (flags & ts.TypeFlags.Object) {
let objectType = type as ts.ObjectType;
let objectFlags = objectType.objectFlags;
if (objectFlags & (ts.ObjectFlags.Reference | ts.ObjectFlags.Mapped)) {
return false;
}
}
return true;
}
/**
* If `type` is a `this` type, returns the enclosing type.
* Otherwise returns `null`.
*/
function getEnclosingTypeOfThisType(type: ts.TypeVariable): ts.TypeReference {
// A 'this' type is an implicit type parameter to a class or interface.
// The type parameter itself doesn't have any good indicator of being a 'this' type,
// but we can get it like this:
// - the upper bound of the 'this' type parameter is always the enclosing type
// - the enclosing type knows its own 'this' type.
let bound = type.getConstraint();
if (bound == null) return null;
let target = (bound as ts.TypeReference).target; // undefined if not a TypeReference
if (target == null) return null;
return (target.thisType === type) ? target : null;
}
const typeDefinitionSymbols = ts.SymbolFlags.Class | ts.SymbolFlags.Interface |
ts.SymbolFlags.TypeAlias | ts.SymbolFlags.EnumMember | ts.SymbolFlags.Enum;
/** Returns true if the given symbol refers to a type definition. */
function isTypeDefinitionSymbol(symbol: ts.Symbol) {
return (symbol.flags & typeDefinitionSymbols) !== 0;
}
/** Gets the nearest enclosing block statement, function body, module body, or top-level. */
function getEnclosingBlock(node: ts.Node) {
while (true) {
if (node == null) return null;
if (ts.isSourceFile(node) || ts.isFunctionLike(node) || ts.isBlock(node) || ts.isModuleBlock(node)) return node;
node = node.parent;
}
}
const typeofSymbols = ts.SymbolFlags.Class | ts.SymbolFlags.Namespace |
ts.SymbolFlags.Module | ts.SymbolFlags.Enum | ts.SymbolFlags.EnumMember;
/**
* Returns true if the given symbol refers to a value that we consider
* a valid target for a `typeof` type.
*/
function isTypeofCandidateSymbol(symbol: ts.Symbol) {
return (symbol.flags & typeofSymbols) !== 0;
}
const signatureKinds = [ts.SignatureKind.Call, ts.SignatureKind.Construct];
/**
* Bitmask of flags set on a signature, but not exposed in the public API.
*/
const enum InternalSignatureFlags {
HasRestParameter = 1
}
/**
* Signature interface with some internal properties exposed.
*/
interface AugmentedSignature extends ts.Signature {
flags?: InternalSignatureFlags;
}
/**
* Encodes property lookup tuples `(baseType, name, property)` as three
* staggered arrays.
*/
interface PropertyLookupTable {
baseTypes: number[];
names: string[];
propertyTypes: number[];
}
/**
* Encodes `(aliasType, underlyingType)` tuples as two staggered arrays.
*
* Such a tuple denotes that `aliasType` is an alias for `underlyingType`.
*/
interface TypeAliasTable {
aliasTypes: number[];
underlyingTypes: number[];
}
/**
* Encodes type signature tuples `(baseType, kind, index, signature)` as four
* staggered arrays.
*/
interface SignatureTable {
baseTypes: number[];
kinds: ts.SignatureKind[];
indices: number[];
signatures: number[];
}
/**
* Enodes `(baseType, propertyType)` tuples as two staggered arrays.
*
* The index key type is not stored in the table - there are separate tables
* for number and string index signatures.
*
* For example, the `(Foo, T)` tuple would be extracted from this sample:
* ```
* interface Foo {
* [x: string]: T;
* }
* ```
*/
interface IndexerTable {
baseTypes: number[];
propertyTypes: number[];
}
/**
* Encodes `(symbol, name)` pairs as two staggered arrays.
*
* In general, a table may associate multiple names with a given symbol.
*/
interface SymbolNameTable {
symbols: number[];
names: string[];
}
/**
* Encodes `(symbol, baseTypeSymbol)` pairs as two staggered arrays.
*
* Such a pair associates the canonical name of a type with the canonical name
* of one of its base types.
*/
interface BaseTypeTable {
symbols: number[];
baseTypeSymbols: number[];
}
/**
* Encodes `(symbol, selfType)` pairs as two staggered arrays.
*
* Such a pair associates the canonical name of a type with the self-type of
* that type definition. (e.g `Array` with `Array<T>`).
*/
interface SelfTypeTable {
symbols: number[];
selfTypes: number[];
}
/**
* Denotes whether a type is currently in the worklist ("pending") and whether
* it was discovered in shallow or full context.
*
* Types can be discovered in one of two different contexts:
* - Full context:
* Any type that is the type of an AST node, or is reachable through members of
* such a type, without going through an expansive type.
* - Shallow context:
* Any type that is reachable through the members of an expansive type,
* without following any type references after that.
*
* For example:
* ```
* interface Expansive<T> {
* expand: Expansive<{x: T}>;
* foo: { bar: T };
* }
* let instance: Expansive<number>;
* ```
* The type `Expansive<number>` is discovered in full context, but as it is expansive,
* its members are only discovered in shallow context.
*
* This means `Expansive<{x: number}>` becomes a stub type, a type that has an entity in
* the database, but appears to have no members.
*
* The type `{ bar: number }` is also discovered in shallow context, but because it is
* an "inline type" (not a reference) its members are extracted anyway (in shallow context),
* and will thus appear to have the `bar` property of type `number`.
*/
const enum TypeExtractionState {
/**
* The type is in the worklist and was discovered in shallow context.
*/
PendingShallow,
/**
* The type has been extracted as a shallow type.
*
* It may later transition to `PendingFull` if it is found that full extraction is warranted.
*/
DoneShallow,
/**
* The type is in the worklist and is pending full extraction.
*/
PendingFull,
/**
* The type has been fully extracted.
*/
DoneFull,
}
/**
* Generates canonical IDs and serialized representations of types.
*/
export class TypeTable {
/**
* Maps type strings to type IDs. The types must be inserted in order,
* so the `n`th type has ID `n`.
*
* A type string is a `;`-separated string consisting of:
* - a tag string such as `union` or `reference`,
* - optionally a symbol ID or kind-specific data (depends on the tag),
* - IDs of child types.
*
* Type strings serve a dual purpose:
* - Canonicalizing types. Two type objects with the same type string are considered identical.
* - Extracting types. The Java-part of the extractor parses type strings to extract data about the type.
*/
private typeIds: Map<string, number> = new Map();
private typeToStringValues: string[] = [];
private typeChecker: ts.TypeChecker = null;
/**
* Needed for TypeChecker.getTypeOfSymbolAtLocation when we don't care about the location.
* There is no way to get the type of a symbol without providing a location, though.
*/
private arbitraryAstNode: ts.Node = null;
/**
* Maps symbol strings to to symbol IDs. The symbols must be inserted in order,
* so the `n`th symbol has ID `n`.
*
* A symbol string is a `;`-separated string consisting of:
* - a tag string, `root`, `member`, or `other`,
* - an empty string or a `file:pos` string to distinguish this from symbols with other lexical roots,
* - the ID of the parent symbol, or an empty string if this is a root symbol,
* - the unqualified name of the symbol.
*
* Symbol strings serve the same dual purpose as type strings (see `typeIds`).
*/
private symbolIds: Map<string, number> = new Map();
/**
* Maps file names to IDs unique for that file name.
*
* Used to generate short `file:pos` strings in symbol strings.
*/
private fileIds: Map<String, number> = new Map();
/**
* Maps signature strings to signature IDs. The signatures must be inserted in order,
* so the `n`th signature has ID `n`.
*
* A signature string is a `;`-separated string consisting of:
* - a `ts.SignatureKind` value (i.e. the value 0 or 1)
* - number of type parameters
* - number of required parameters
* - ID of the return type
* - interleaved names and bounds (type IDs) of type parameters
* - interleaved names and type IDs of parameters
*/
private signatureIds: Map<String, number> = new Map();
private signatureToStringValues: string[] = [];
private propertyLookups: PropertyLookupTable = {
baseTypes: [],
names: [],
propertyTypes: [],
};
private typeAliases: TypeAliasTable = {
aliasTypes: [],
underlyingTypes: [],
};
private signatureMappings: SignatureTable = {
baseTypes: [],
kinds: [],
indices: [],
signatures: []
};
private numberIndexTypes: IndexerTable = {
baseTypes: [],
propertyTypes: [],
};
private stringIndexTypes: IndexerTable = {
baseTypes: [],
propertyTypes: [],
};
private buildTypeWorklist: [ts.Type, number, boolean][] = [];
private expansiveTypes: Map<number, boolean> = new Map();
private moduleMappings: SymbolNameTable = {
symbols: [],
names: [],
};
private globalMappings: SymbolNameTable = {
symbols: [],
names: [],
};
private baseTypes: BaseTypeTable = {
symbols: [],
baseTypeSymbols: [],
};
private selfTypes: SelfTypeTable = {
symbols: [],
selfTypes: [],
};
/**
* When true, newly discovered types should be extracted as "shallow" types in order
* to prevent expansive types from unfolding into infinitely many types.
*
* @see TypeExtractionState
*/
private isInShallowTypeContext = false;
/**
* Maps a type ID to the extraction state of that type.
*/
private typeExtractionState: TypeExtractionState[] = [];
/**
* Number of types we are currently in the process of flattening to a type string.
*/
private typeRecursionDepth = 0;
/**
* If set to true, all types are considered expansive.
*/
public restrictedExpansion = false;
private virtualSourceRoot: VirtualSourceRoot;
/**
* Called when a new compiler instance has started.
*/
public setProgram(program: ts.Program, virtualSourceRoot: VirtualSourceRoot) {
this.typeChecker = program.getTypeChecker();
this.arbitraryAstNode = program.getSourceFiles()[0];
this.virtualSourceRoot = virtualSourceRoot;
}
/**
* Called when the compiler instance should be relased from memory.
*
* This can happen because we are done with a project, or because the
* compiler instance needs to be rebooted.
*/
public releaseProgram() {
this.typeChecker = null;
this.arbitraryAstNode = null;
}
/**
* Gets the canonical ID for the given type, generating a fresh ID if necessary.
*/
public buildType(type: ts.Type, unfoldAlias: boolean): number | null {
this.isInShallowTypeContext = false;
let id = this.getId(type, unfoldAlias);
this.iterateBuildTypeWorklist();
if (id == null) return null;
return id;
}
/**
* Gets the canonical ID for the given type, generating a fresh ID if necessary.
*
* Returns `null` if we do not support extraction of this type.
*/
public getId(type: ts.Type, unfoldAlias: boolean): number | null {
if (this.typeRecursionDepth > 100) {
// Ignore infinitely nested anonymous types, such as `{x: {x: {x: ... }}}`.
// Such a type can't be written directly with TypeScript syntax (as it would need to be named),
// but it can occur rarely as a result of type inference.
return null;
}
// Replace very long string literal types with `string`.
if ((type.flags & ts.TypeFlags.StringLiteral) && ((type as ts.LiteralType).value as string).length > 30) {
type = this.typeChecker.getBaseTypeOfLiteralType(type);
}
++this.typeRecursionDepth;
let content = this.getTypeString(type, unfoldAlias);
--this.typeRecursionDepth;
if (content == null) return null; // Type not supported.
let id = this.typeIds.get(content);
if (id == null) {
let stringValue = this.stringifyType(type, unfoldAlias);
if (stringValue == null) {
return null; // Type not supported.
}
id = this.typeIds.size;
this.typeIds.set(content, id);
this.typeToStringValues.push(stringValue);
this.buildTypeWorklist.push([type, id, unfoldAlias]);
this.typeExtractionState.push(
this.isInShallowTypeContext ? TypeExtractionState.PendingShallow : TypeExtractionState.PendingFull);
// If the type is the self-type for a named type (not a generic instantiation of it),
// emit the self-type binding for that type.
if (content.startsWith("reference;") && !(isTypeReference(type) && type.target !== type)) {
this.selfTypes.symbols.push(this.getSymbolId(type.aliasSymbol || type.symbol));
this.selfTypes.selfTypes.push(id);
}
} else if (!this.isInShallowTypeContext) {
// If the type was previously marked as shallow, promote it to full,
// and put it back in the worklist if necessary.
let state = this.typeExtractionState[id];
if (state === TypeExtractionState.PendingShallow) {
this.typeExtractionState[id] = TypeExtractionState.PendingFull;
} else if (state === TypeExtractionState.DoneShallow) {
this.typeExtractionState[id] = TypeExtractionState.PendingFull;
this.buildTypeWorklist.push([type, id, unfoldAlias]);
}
}
return id;
}
private stringifyType(type: ts.Type, unfoldAlias: boolean): string {
let formatFlags = unfoldAlias
? ts.TypeFormatFlags.InTypeAlias
: ts.TypeFormatFlags.UseAliasDefinedOutsideCurrentScope;
let toStringValue: string;
// Some types can't be stringified. Just discard the type if we can't stringify it.
try {
toStringValue = this.typeChecker.typeToString(type, undefined, formatFlags);
} catch (e) {
console.warn("Recovered from a compiler crash while stringifying a type. Discarding the type.");
console.warn(e.stack);
return null;
}
if (toStringValue.length > 50) {
return toStringValue.substring(0, 47) + "...";
} else {
return toStringValue;
}
}
private stringifySignature(signature: ts.Signature, kind: ts.SignatureKind) {
let toStringValue: string;
// Some types can't be stringified. Just discard the type if we can't stringify it.
try {
toStringValue =
this.typeChecker.signatureToString(
signature,
signature.declaration,
ts.TypeFormatFlags.UseAliasDefinedOutsideCurrentScope,
kind);
} catch (e) {
console.warn("Recovered from a compiler crash while stringifying a signature. Discarding the signature.");
console.warn(e.stack);
return null;
}
if (toStringValue.length > 70) {
return toStringValue.substring(0, 69) + "...";
} else {
return toStringValue;
}
}
/**
* Gets a string representing the kind and contents of the given type.
*/
private getTypeString(type: AugmentedType, unfoldAlias: boolean): string | null {
// Reference to a type alias.
if (!unfoldAlias && type.aliasSymbol != null) {
let tag = "reference;" + this.getSymbolId(type.aliasSymbol);
return type.aliasTypeArguments == null
? tag
: this.makeTypeStringVector(tag, type.aliasTypeArguments);
}
let flags = type.flags;
let objectFlags = (flags & ts.TypeFlags.Object) && (type as ts.ObjectType).objectFlags;
let symbol: AugmentedSymbol = type.symbol;
// Type that contains a reference to something.
if (symbol != null) {
// Possibly parameterized type.
if (isTypeReference(type)) {
let tag = "reference;" + this.getSymbolId(symbol);
return this.makeTypeStringVectorFromTypeReferenceArguments(tag, type);
}
// Reference to a type variable.
if (flags & ts.TypeFlags.TypeVariable) {
let enclosingType = getEnclosingTypeOfThisType(type);
if (enclosingType != null) {
return "this;" + this.getId(enclosingType, false);
} else if (symbol.parent == null) {
// The type variable is bound on a call signature. Only extract it by name.
return "lextypevar;" + symbol.name;
} else {
return "typevar;" + this.getSymbolId(symbol);
}
}
// Recognize types of form `typeof X` where `X` is a class, namespace, module, or enum.
// The TypeScript API has no explicit tag for `typeof` types. They can be recognized
// as anonymous object types that have a symbol (i.e. a "named anonymous type").
if ((objectFlags & ts.ObjectFlags.Anonymous) && isTypeofCandidateSymbol(symbol)) {
return "typeof;" + this.getSymbolId(symbol);
}
// Reference to a named type.
// Must occur after the `typeof` case to avoid matching `typeof C` as the type `C`.
if (isTypeDefinitionSymbol(symbol)) {
return "reference;" + this.getSymbolId(type.symbol);
}
}
if (flags === ts.TypeFlags.Any) {
return "any";
}
if (flags === ts.TypeFlags.String) {
return "string";
}
if (flags === ts.TypeFlags.Number) {
return "number";
}
if (flags === ts.TypeFlags.Void) {
return "void";
}
if (flags === ts.TypeFlags.Never) {
return "never";
}
if (flags === ts.TypeFlags.BigInt) {
return "bigint";
}
if (flags & ts.TypeFlags.Null) {
return "null";
}
if (flags & ts.TypeFlags.Undefined) {
return "undefined";
}
if (flags === ts.TypeFlags.ESSymbol) {
return "plainsymbol";
}
if (flags & ts.TypeFlags.Unknown) {
return "unknown";
}
if (flags === ts.TypeFlags.UniqueESSymbol) {
return "uniquesymbol;" + this.getSymbolId((type as ts.UniqueESSymbolType).symbol);
}
if (flags === ts.TypeFlags.NonPrimitive && type.intrinsicName === "object") {
return "objectkeyword";
}
// Note that TypeScript represents the `boolean` type as `true|false`.
if (flags === ts.TypeFlags.BooleanLiteral) {
// There is no public API to distinguish true and false.
// We rely on the internal property `intrinsicName`, which
// should be either "true" or "false" here.
return type.intrinsicName;
}
if (flags & ts.TypeFlags.NumberLiteral) {
return "numlit;" + (type as ts.LiteralType).value;
}
if (flags & ts.TypeFlags.StringLiteral) {
return "strlit;" + (type as ts.LiteralType).value;
}
if (flags & ts.TypeFlags.BigIntLiteral) {
let literalType = type as ts.LiteralType;
let value = literalType.value as ts.PseudoBigInt;
return "bigintlit;" + (value.negative ? "-" : "") + value.base10Value;
}
if (flags & ts.TypeFlags.Union) {
let unionType = type as ts.UnionType;
if (unionType.types.length === 0) {
// We ignore malformed types like unions and intersections without any operands.
// These trigger an assertion failure in `typeToString` - presumably because they
// cannot be written using TypeScript syntax - so we ignore them entirely.
return null;
}
return this.makeTypeStringVector("union", unionType.types);
}
if (flags & ts.TypeFlags.Intersection) {
let intersectionType = type as ts.IntersectionType;
if (intersectionType.types.length === 0) {
return null; // Ignore malformed type.
}
return this.makeTypeStringVector("intersection", intersectionType.types);
}
if (isTypeReference(type) && (type.target.objectFlags & ts.ObjectFlags.Tuple)) {
// Encode the minimum length and presence of rest element in the first two parts of the type string.
// Handle the absence of `minLength` and `hasRestElement` to be compatible with pre-3.0 compiler versions.
let tupleReference = type as ts.TupleTypeReference;
let tupleType = tupleReference.target;
let minLength = tupleType.minLength != null
? tupleType.minLength
: this.typeChecker.getTypeArguments(tupleReference).length;
let hasRestElement = tupleType.hasRestElement ? 't' : 'f';
let restIndex = -1;
for (let i = 0; i < tupleType.elementFlags.length; i++) {
if (tupleType.elementFlags[i] & ts.ElementFlags.Rest) {
restIndex = i;
break;
}
}
let prefix = `tuple;${minLength};${restIndex}`;
return this.makeTypeStringVectorFromTypeReferenceArguments(prefix, type);
}
if (objectFlags & ts.ObjectFlags.Anonymous) {
return this.makeStructuralTypeVector("object;", type as ts.ObjectType);
}
return null;
}
/**
* Gets the canonical ID for the given symbol.
*
* Note that this may be called with symbols from different compiler instantiations,
* and it should return the same ID for symbols that logically refer to the same thing.
*/
public getSymbolId(symbol: AugmentedSymbol): number {
if (symbol.flags & ts.SymbolFlags.Alias) {
symbol = this.typeChecker.getAliasedSymbol(symbol);
}
// We cache the symbol ID to avoid rebuilding long symbol strings.
let id = symbol.$id;
if (id != null) return id;
let content = this.getSymbolString(symbol);
id = this.symbolIds.get(content);
if (id != null) {
// The ID was determined in a previous compiler instantiation.
return symbol.$id = id;
}
if (id == null) {
id = this.symbolIds.size;
this.symbolIds.set(content, id);
symbol.$id = id;
// Associate names with global symbols.
if (this.isGlobalSymbol(symbol)) {
this.addGlobalMapping(id, symbol.name);
}
// Associate type names with their base type names.
this.extractSymbolBaseTypes(symbol, id);
}
return id;
}
/** Returns true if the given symbol represents a name in the global scope. */
private isGlobalSymbol(symbol: AugmentedSymbol): boolean {
let parent = symbol.parent;
if (parent != null) {
if (parent.escapedName === ts.InternalSymbolName.Global) {
return true; // Symbol declared in a global augmentation block.
}
return false; // Symbol is not a root.
}
if (symbol.declarations == null || symbol.declarations.length === 0) return false;
let declaration = symbol.declarations[0];
let block = getEnclosingBlock(declaration);
if (ts.isSourceFile(block) && !this.isModuleSourceFile(block)) {
return true; // Symbol is declared at the top-level of a non-module file.
}
return false;
}
/** Returns true if the given source file defines a module. */
private isModuleSourceFile(file: ts.SourceFile) {
// This is not directly exposed, but a reliable indicator seems to be whether
// the file has a symbol.
return this.typeChecker.getSymbolAtLocation(file) != null;
}
/**
* Gets a unique string for the given symbol.
*/
private getSymbolString(symbol: AugmentedSymbol): string {
let parent = symbol.parent;
if (parent == null || parent.escapedName === ts.InternalSymbolName.Global) {
return "root;" + this.getSymbolDeclarationString(symbol) + ";;" + this.rewriteSymbolName(symbol);
} else if (parent.exports != null && parent.exports.get(symbol.escapedName) === symbol) {
return "member;;" + this.getSymbolId(parent) + ";" + this.rewriteSymbolName(symbol);
} else {
return "other;" + this.getSymbolDeclarationString(symbol) + ";" + this.getSymbolId(parent) + ";" + this.rewriteSymbolName(symbol);
}
}
private rewriteSymbolName(symbol: AugmentedSymbol) {
let { virtualSourceRoot, sourceRoot } = this.virtualSourceRoot;
let { name } = symbol;
if (virtualSourceRoot == null || sourceRoot == null) return name;
return name.replace(virtualSourceRoot, sourceRoot);
}
/**
* Gets a string that distinguishes the given symbol from symbols with different
* lexical roots, or an empty string if the symbol is not a lexical root.
*/
private getSymbolDeclarationString(symbol: AugmentedSymbol): string {
if (symbol.declarations == null || symbol.declarations.length === 0) {
return "";
}
let decl = symbol.declarations[0];
if (ts.isSourceFile(decl)) return "";
return this.getFileId(decl.getSourceFile().fileName) + ":" + decl.pos;
}
/**
* Gets a number unique for the given filename.
*/
private getFileId(fileName: string): number {
let id = this.fileIds.get(fileName);
if (id == null) {
id = this.fileIds.size;
this.fileIds.set(fileName, id);
}
return id;
}
/**
* Like `makeTypeStringVector` using the type arguments in the given type reference.
*/
private makeTypeStringVectorFromTypeReferenceArguments(tag: string, type: ts.TypeReference) {
// There can be an extra type argument at the end, denoting an explicit 'this' type argument.
// We discard the extra argument in our model.
let target = type.target;
let typeArguments = this.typeChecker.getTypeArguments(type);
if (typeArguments == null) return tag;
if (target.typeParameters != null) {
return this.makeTypeStringVector(tag, typeArguments, target.typeParameters.length);
} else {
return this.makeTypeStringVector(tag, typeArguments);
}
}
/**
* Returns the given string with the IDs of the given types appended,
* each separated by `;`.
*/
private makeTypeStringVector(tag: string, types: ReadonlyArray<ts.Type>, length = types.length): string | null {
let hash = tag;
for (let i = 0; i < length; ++i) {
let id = this.getId(types[i], false);
if (id == null) return null;
hash += ";" + id;
}
return hash;
}
/** Returns the type of `symbol` or `null` if it could not be computed. */
private tryGetTypeOfSymbol(symbol: ts.Symbol) {
try {
return this.typeChecker.getTypeOfSymbolAtLocation(symbol, this.arbitraryAstNode)
} catch (e) {
console.warn(`Could not compute type of '${this.typeChecker.symbolToString(symbol)}'`);
return null;
}
}
/**
* Returns a type string consisting of all the members of the given type.
*
* This must only be called for anonymous object types, as the type string for this
* type could otherwise depend on itself recursively.
*/
private makeStructuralTypeVector(tag: string, type: ts.ObjectType): string | null {
let hash = tag;
for (let property of type.getProperties()) {
let propertyType = this.tryGetTypeOfSymbol(property);
if (propertyType == null) return null;
let propertyTypeId = this.getId(propertyType, false);
if (propertyTypeId == null) return null;
hash += ";p" + this.getSymbolId(property) + ';' + propertyTypeId;
}
for (let kind of signatureKinds) {
for (let signature of this.typeChecker.getSignaturesOfType(type, kind)) {
let id = this.getSignatureId(kind, signature);
if (id == null) return null;
hash += ";c" + id;
}
}
let indexType = type.getStringIndexType();
if (indexType != null) {
let indexTypeId = this.getId(indexType, false);
if (indexTypeId == null) return null;
hash += ";s" + indexTypeId;
}
indexType = type.getNumberIndexType();
if (indexType != null) {
let indexTypeId = this.getId(indexType, false);
if (indexTypeId == null) return null;
hash += ";i" + indexTypeId;
}
return hash;
}
public addModuleMapping(symbolId: number, moduleName: string) {
this.moduleMappings.symbols.push(symbolId);
this.moduleMappings.names.push(moduleName);
}
public addGlobalMapping(symbolId: number, globalName: string) {
this.globalMappings.symbols.push(symbolId);
this.globalMappings.names.push(globalName);
}
public getTypeTableJson(): object {
return {
typeStrings: Array.from(this.typeIds.keys()),
typeToStringValues: this.typeToStringValues,
propertyLookups: this.propertyLookups,
typeAliases: this.typeAliases,
symbolStrings: Array.from(this.symbolIds.keys()),
moduleMappings: this.moduleMappings,
globalMappings: this.globalMappings,
signatureStrings: Array.from(this.signatureIds.keys()),
signatureMappings: this.signatureMappings,
signatureToStringValues: this.signatureToStringValues,
numberIndexTypes: this.numberIndexTypes,
stringIndexTypes: this.stringIndexTypes,
baseTypes: this.baseTypes,
selfTypes: this.selfTypes,
};
}
/**
* Extracts the deep property and signature graph of recently discovered types.
*
* Types are added to the worklist when they are first assigned an ID,
* which happen transparently during property extraction and expansiveness checks.
*/
private iterateBuildTypeWorklist() {
let worklist = this.buildTypeWorklist;
let typeExtractionState = this.typeExtractionState;
while (worklist.length > 0) {
let [type, id, unfoldAlias] = worklist.pop();
let isShallowContext = typeExtractionState[id] === TypeExtractionState.PendingShallow;
if (isShallowContext && !isTypeAlwaysSafeToExpand(type)) {
typeExtractionState[id] = TypeExtractionState.DoneShallow;
} else if (type.aliasSymbol != null && !unfoldAlias) {
typeExtractionState[id] = TypeExtractionState.DoneFull;
let underlyingTypeId = this.getId(type, true);
if (underlyingTypeId != null) {
this.typeAliases.aliasTypes.push(id);
this.typeAliases.underlyingTypes.push(underlyingTypeId);
}
} else {
typeExtractionState[id] = TypeExtractionState.DoneFull;
this.isInShallowTypeContext = isShallowContext || this.isExpansiveTypeReference(type);
this.extractProperties(type, id);
this.extractSignatures(type, id);
this.extractIndexers(type, id);
}
}
this.isInShallowTypeContext = false;
}
/**
* Returns the properties to extract for the given type or `null` if nothing should be extracted.
*
* For performance reasons we only extract properties needed to recognize promise types at the QL
* level.
*/
private getPropertiesToExtract(type: ts.Type) {
if (this.getSelfType(type) === type) {
let thenSymbol = this.typeChecker.getPropertyOfType(type, "then");
if (thenSymbol != null) {
return [thenSymbol];
}
}
return null;
}
private extractProperties(type: ts.Type, id: number) {
let props = this.getPropertiesToExtract(type);
if (props == null) return;
for (let symbol of props) {
let propertyType = this.tryGetTypeOfSymbol(symbol);
if (propertyType == null) continue;
let propertyTypeId = this.getId(propertyType, false);
if (propertyTypeId == null) continue;
this.propertyLookups.baseTypes.push(id);
this.propertyLookups.names.push(symbol.name);
this.propertyLookups.propertyTypes.push(propertyTypeId);
}
}
/**
* Returns a unique ID for the given call/construct signature.
*/
public getSignatureId(kind: ts.SignatureKind, signature: ts.Signature): number {
let content = this.getSignatureString(kind, signature);
if (content == null) {
return null;
}
let id = this.signatureIds.get(content);
if (id == null) {
let stringValue = this.stringifySignature(signature, kind);
if (stringValue == null) {
return null; // Not supported.
}
id = this.signatureIds.size;
this.signatureIds.set(content, id);
this.signatureToStringValues.push(stringValue);
}
return id;
}
/**
* Returns a unique string for the given call/constructor signature.
*/
private getSignatureString(kind: ts.SignatureKind, signature: AugmentedSignature): string {
let modifiers = signature.getDeclaration()?.modifiers;
let isAbstract = modifiers && modifiers.filter(modifier => modifier.kind == ts.SyntaxKind.AbstractKeyword).length > 0
let parameters = signature.getParameters();
let numberOfTypeParameters = signature.typeParameters == null
? 0
: signature.typeParameters.length;
// Count the number of required parameters.
let requiredParameters = parameters.length;
for (let i = 0; i < parameters.length; ++i) {
if (parameters[i].flags & ts.SymbolFlags.Optional) {
requiredParameters = i;
break;
}
}
let hasRestParam = (signature.flags & InternalSignatureFlags.HasRestParameter) !== 0;
let restParameterTag = '';
if (hasRestParam) {
if (requiredParameters === parameters.length) {
// Do not count the rest parameter as a required parameter
requiredParameters = parameters.length - 1;
}
if (parameters.length === 0) return null;
let restParameter = parameters[parameters.length - 1];
let restParameterType = this.tryGetTypeOfSymbol(restParameter);
if (restParameterType == null) return null;
let restParameterTypeId = this.getId(restParameterType, false);
if (restParameterTypeId == null) return null;
restParameterTag = '' + restParameterTypeId;
}
let returnTypeId = this.getId(signature.getReturnType(), false);
if (returnTypeId == null) {
return null;
}
let tag = `${kind};${isAbstract ? "t" : "f"};${numberOfTypeParameters};${requiredParameters};${restParameterTag};${returnTypeId}`;
for (let typeParameter of signature.typeParameters || []) {
tag += ";" + typeParameter.symbol.name;
let constraint = typeParameter.getConstraint();
let constraintId: number;
if (constraint == null || (constraintId = this.getId(constraint, false)) == null) {
tag += ";";
} else {
tag += ";" + constraintId;
}
}
for (let paramIndex = 0; paramIndex < parameters.length; ++paramIndex) {
let parameter = parameters[paramIndex];
let parameterType = this.tryGetTypeOfSymbol(parameter);
if (parameterType == null) {
return null;
}