Skip to content

Commit 5d34806

Browse files
authored
Merge pull request #2379 from asger-semmle/typescript-fixes
TS: A bunch of TypeScript fixes
2 parents 846600e + ec8ced7 commit 5d34806

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

58 files changed

+3116
-80
lines changed

javascript/extractor/lib/typescript/package.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,8 @@
88
"build": "tsc --project tsconfig.json",
99
"check": "tsc --noEmit --project . && tslint --project .",
1010
"lint": "tslint --project .",
11-
"lint-fix": "tslint --project . --fix"
11+
"lint-fix": "tslint --project . --fix",
12+
"watch": "tsc -p . -w --sourceMap"
1213
},
1314
"devDependencies": {
1415
"@types/node": "12.7.11",

javascript/extractor/lib/typescript/src/ast_extractor.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -198,7 +198,9 @@ export function augmentAst(ast: AugmentedSourceFile, code: string, project: Proj
198198
: null;
199199
let type = contextualType || typeChecker.getTypeAtLocation(node);
200200
if (type != null) {
201-
let id = typeTable.buildType(type);
201+
let parent = node.parent;
202+
let unfoldAlias = ts.isTypeAliasDeclaration(parent) && node === parent.type;
203+
let id = typeTable.buildType(type, unfoldAlias);
202204
if (id != null) {
203205
node.$type = id;
204206
}

javascript/extractor/lib/typescript/src/main.ts

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -253,7 +253,7 @@ function handleOpenProjectCommand(command: OpenProjectCommand) {
253253
fileExists: (path: string) => fs.existsSync(path),
254254
readFile: ts.sys.readFile,
255255
};
256-
let config = ts.parseJsonConfigFileContent(tsConfig, parseConfigHost, basePath);
256+
let config = ts.parseJsonConfigFileContent(tsConfig.config, parseConfigHost, basePath);
257257
let project = new Project(tsConfigFilename, config, state.typeTable);
258258
project.load();
259259

@@ -272,7 +272,9 @@ function handleOpenProjectCommand(command: OpenProjectCommand) {
272272
});
273273

274274
for (let typeRoot of typeRoots || []) {
275-
traverseTypeRoot(typeRoot, "");
275+
if (fs.existsSync(typeRoot) && fs.statSync(typeRoot).isDirectory()) {
276+
traverseTypeRoot(typeRoot, "");
277+
}
276278
}
277279

278280
for (let sourceFile of program.getSourceFiles()) {

javascript/extractor/lib/typescript/src/type_table.ts

Lines changed: 92 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -92,6 +92,20 @@ function isTypeofCandidateSymbol(symbol: ts.Symbol) {
9292

9393
const signatureKinds = [ts.SignatureKind.Call, ts.SignatureKind.Construct];
9494

95+
/**
96+
* Bitmask of flags set on a signature, but not exposed in the public API.
97+
*/
98+
const enum InternalSignatureFlags {
99+
HasRestParameter = 1
100+
}
101+
102+
/**
103+
* Signature interface with some internal properties exposed.
104+
*/
105+
interface AugmentedSignature extends ts.Signature {
106+
flags?: InternalSignatureFlags;
107+
}
108+
95109
/**
96110
* Encodes property lookup tuples `(baseType, name, property)` as three
97111
* staggered arrays.
@@ -102,6 +116,16 @@ interface PropertyLookupTable {
102116
propertyTypes: number[];
103117
}
104118

119+
/**
120+
* Encodes `(aliasType, underlyingType)` tuples as two staggered arrays.
121+
*
122+
* Such a tuple denotes that `aliasType` is an alias for `underlyingType`.
123+
*/
124+
interface TypeAliasTable {
125+
aliasTypes: number[];
126+
underlyingTypes: number[];
127+
}
128+
105129
/**
106130
* Encodes type signature tuples `(baseType, kind, index, signature)` as four
107131
* staggered arrays.
@@ -287,6 +311,11 @@ export class TypeTable {
287311
propertyTypes: [],
288312
};
289313

314+
private typeAliases: TypeAliasTable = {
315+
aliasTypes: [],
316+
underlyingTypes: [],
317+
};
318+
290319
private signatureMappings: SignatureTable = {
291320
baseTypes: [],
292321
kinds: [],
@@ -304,7 +333,7 @@ export class TypeTable {
304333
propertyTypes: [],
305334
};
306335

307-
private buildTypeWorklist: [ts.Type, number][] = [];
336+
private buildTypeWorklist: [ts.Type, number, boolean][] = [];
308337

309338
private expansiveTypes: Map<number, boolean> = new Map();
310339

@@ -372,9 +401,9 @@ export class TypeTable {
372401
/**
373402
* Gets the canonical ID for the given type, generating a fresh ID if necessary.
374403
*/
375-
public buildType(type: ts.Type): number | null {
404+
public buildType(type: ts.Type, unfoldAlias: boolean): number | null {
376405
this.isInShallowTypeContext = false;
377-
let id = this.getId(type);
406+
let id = this.getId(type, unfoldAlias);
378407
this.iterateBuildTypeWorklist();
379408
if (id == null) return null;
380409
return id;
@@ -385,7 +414,7 @@ export class TypeTable {
385414
*
386415
* Returns `null` if we do not support extraction of this type.
387416
*/
388-
public getId(type: ts.Type): number | null {
417+
public getId(type: ts.Type, unfoldAlias: boolean): number | null {
389418
if (this.typeRecursionDepth > 100) {
390419
// Ignore infinitely nested anonymous types, such as `{x: {x: {x: ... }}}`.
391420
// Such a type can't be written directly with TypeScript syntax (as it would need to be named),
@@ -397,19 +426,19 @@ export class TypeTable {
397426
type = this.typeChecker.getBaseTypeOfLiteralType(type);
398427
}
399428
++this.typeRecursionDepth;
400-
let content = this.getTypeString(type);
429+
let content = this.getTypeString(type, unfoldAlias);
401430
--this.typeRecursionDepth;
402431
if (content == null) return null; // Type not supported.
403432
let id = this.typeIds.get(content);
404433
if (id == null) {
405-
let stringValue = this.stringifyType(type);
434+
let stringValue = this.stringifyType(type, unfoldAlias);
406435
if (stringValue == null) {
407436
return null; // Type not supported.
408437
}
409438
id = this.typeIds.size;
410439
this.typeIds.set(content, id);
411440
this.typeToStringValues.push(stringValue);
412-
this.buildTypeWorklist.push([type, id]);
441+
this.buildTypeWorklist.push([type, id, unfoldAlias]);
413442
this.typeExtractionState.push(
414443
this.isInShallowTypeContext ? TypeExtractionState.PendingShallow : TypeExtractionState.PendingFull);
415444
// If the type is the self-type for a named type (not a generic instantiation of it),
@@ -426,20 +455,20 @@ export class TypeTable {
426455
this.typeExtractionState[id] = TypeExtractionState.PendingFull;
427456
} else if (state === TypeExtractionState.DoneShallow) {
428457
this.typeExtractionState[id] = TypeExtractionState.PendingFull;
429-
this.buildTypeWorklist.push([type, id]);
458+
this.buildTypeWorklist.push([type, id, unfoldAlias]);
430459
}
431460
}
432461
return id;
433462
}
434463

435-
private stringifyType(type: ts.Type): string {
464+
private stringifyType(type: ts.Type, unfoldAlias: boolean): string {
465+
let formatFlags = unfoldAlias
466+
? ts.TypeFormatFlags.InTypeAlias
467+
: ts.TypeFormatFlags.UseAliasDefinedOutsideCurrentScope;
436468
let toStringValue: string;
437469
// Some types can't be stringified. Just discard the type if we can't stringify it.
438470
try {
439-
toStringValue = this.typeChecker.typeToString(
440-
type,
441-
undefined,
442-
ts.TypeFormatFlags.UseAliasDefinedOutsideCurrentScope);
471+
toStringValue = this.typeChecker.typeToString(type, undefined, formatFlags);
443472
} catch (e) {
444473
console.warn("Recovered from a compiler crash while stringifying a type. Discarding the type.");
445474
console.warn(e.stack);
@@ -477,9 +506,9 @@ export class TypeTable {
477506
/**
478507
* Gets a string representing the kind and contents of the given type.
479508
*/
480-
private getTypeString(type: AugmentedType): string | null {
509+
private getTypeString(type: AugmentedType, unfoldAlias: boolean): string | null {
481510
// Reference to a type alias.
482-
if (type.aliasSymbol != null) {
511+
if (!unfoldAlias && type.aliasSymbol != null) {
483512
let tag = "reference;" + this.getSymbolId(type.aliasSymbol);
484513
return type.aliasTypeArguments == null
485514
? tag
@@ -499,7 +528,7 @@ export class TypeTable {
499528
if (flags & ts.TypeFlags.TypeVariable) {
500529
let enclosingType = getEnclosingTypeOfThisType(type);
501530
if (enclosingType != null) {
502-
return "this;" + this.getId(enclosingType);
531+
return "this;" + this.getId(enclosingType, false);
503532
} else if (symbol.parent == null) {
504533
// The type variable is bound on a call signature. Only extract it by name.
505534
return "lextypevar;" + symbol.name;
@@ -730,7 +759,7 @@ export class TypeTable {
730759
private makeTypeStringVector(tag: string, types: ReadonlyArray<ts.Type>, length = types.length): string | null {
731760
let hash = tag;
732761
for (let i = 0; i < length; ++i) {
733-
let id = this.getId(types[i]);
762+
let id = this.getId(types[i], false);
734763
if (id == null) return null;
735764
hash += ";" + id;
736765
}
@@ -748,7 +777,7 @@ export class TypeTable {
748777
for (let property of type.getProperties()) {
749778
let propertyType = this.typeChecker.getTypeOfSymbolAtLocation(property, this.arbitraryAstNode);
750779
if (propertyType == null) return null;
751-
let propertyTypeId = this.getId(propertyType);
780+
let propertyTypeId = this.getId(propertyType, false);
752781
if (propertyTypeId == null) return null;
753782
hash += ";p" + this.getSymbolId(property) + ';' + propertyTypeId;
754783
}
@@ -761,13 +790,13 @@ export class TypeTable {
761790
}
762791
let indexType = type.getStringIndexType();
763792
if (indexType != null) {
764-
let indexTypeId = this.getId(indexType);
793+
let indexTypeId = this.getId(indexType, false);
765794
if (indexTypeId == null) return null;
766795
hash += ";s" + indexTypeId;
767796
}
768797
indexType = type.getNumberIndexType();
769798
if (indexType != null) {
770-
let indexTypeId = this.getId(indexType);
799+
let indexTypeId = this.getId(indexType, false);
771800
if (indexTypeId == null) return null;
772801
hash += ";i" + indexTypeId;
773802
}
@@ -789,6 +818,7 @@ export class TypeTable {
789818
typeStrings: Array.from(this.typeIds.keys()),
790819
typeToStringValues: this.typeToStringValues,
791820
propertyLookups: this.propertyLookups,
821+
typeAliases: this.typeAliases,
792822
symbolStrings: Array.from(this.symbolIds.keys()),
793823
moduleMappings: this.moduleMappings,
794824
globalMappings: this.globalMappings,
@@ -812,10 +842,17 @@ export class TypeTable {
812842
let worklist = this.buildTypeWorklist;
813843
let typeExtractionState = this.typeExtractionState;
814844
while (worklist.length > 0) {
815-
let [type, id] = worklist.pop();
845+
let [type, id, unfoldAlias] = worklist.pop();
816846
let isShallowContext = typeExtractionState[id] === TypeExtractionState.PendingShallow;
817847
if (isShallowContext && !isTypeAlwaysSafeToExpand(type)) {
818848
typeExtractionState[id] = TypeExtractionState.DoneShallow;
849+
} else if (type.aliasSymbol != null && !unfoldAlias) {
850+
typeExtractionState[id] = TypeExtractionState.DoneFull;
851+
let underlyingTypeId = this.getId(type, true);
852+
if (underlyingTypeId != null) {
853+
this.typeAliases.aliasTypes.push(id);
854+
this.typeAliases.underlyingTypes.push(underlyingTypeId);
855+
}
819856
} else {
820857
typeExtractionState[id] = TypeExtractionState.DoneFull;
821858
this.isInShallowTypeContext = isShallowContext || this.isExpansiveTypeReference(type);
@@ -847,7 +884,7 @@ export class TypeTable {
847884
for (let symbol of props) {
848885
let propertyType = this.typeChecker.getTypeOfSymbolAtLocation(symbol, this.arbitraryAstNode);
849886
if (propertyType == null) continue;
850-
let propertyTypeId = this.getId(propertyType);
887+
let propertyTypeId = this.getId(propertyType, false);
851888
if (propertyTypeId == null) continue;
852889
this.propertyLookups.baseTypes.push(id);
853890
this.propertyLookups.names.push(symbol.name);
@@ -879,7 +916,7 @@ export class TypeTable {
879916
/**
880917
* Returns a unique string for the given call/constructor signature.
881918
*/
882-
private getSignatureString(kind: ts.SignatureKind, signature: ts.Signature): string {
919+
private getSignatureString(kind: ts.SignatureKind, signature: AugmentedSignature): string {
883920
let parameters = signature.getParameters();
884921
let numberOfTypeParameters = signature.typeParameters == null
885922
? 0
@@ -892,27 +929,51 @@ export class TypeTable {
892929
break;
893930
}
894931
}
895-
let returnTypeId = this.getId(signature.getReturnType());
932+
let hasRestParam = (signature.flags & InternalSignatureFlags.HasRestParameter) !== 0;
933+
let restParameterTag = '';
934+
if (hasRestParam) {
935+
if (requiredParameters === parameters.length) {
936+
// Do not count the rest parameter as a required parameter
937+
requiredParameters = parameters.length - 1;
938+
}
939+
if (parameters.length === 0) return null;
940+
let restParameter = parameters[parameters.length - 1];
941+
let restParameterType = this.typeChecker.getTypeOfSymbolAtLocation(restParameter, this.arbitraryAstNode);
942+
if (restParameterType == null) return null;
943+
let restParameterTypeId = this.getId(restParameterType, false);
944+
if (restParameterTypeId == null) return null;
945+
restParameterTag = '' + restParameterTypeId;
946+
}
947+
let returnTypeId = this.getId(signature.getReturnType(), false);
896948
if (returnTypeId == null) {
897949
return null;
898950
}
899-
let tag = `${kind};${numberOfTypeParameters};${requiredParameters};${returnTypeId}`;
951+
let tag = `${kind};${numberOfTypeParameters};${requiredParameters};${restParameterTag};${returnTypeId}`;
900952
for (let typeParameter of signature.typeParameters || []) {
901953
tag += ";" + typeParameter.symbol.name;
902954
let constraint = typeParameter.getConstraint();
903955
let constraintId: number;
904-
if (constraint == null || (constraintId = this.getId(constraint)) == null) {
956+
if (constraint == null || (constraintId = this.getId(constraint, false)) == null) {
905957
tag += ";";
906958
} else {
907959
tag += ";" + constraintId;
908960
}
909961
}
910-
for (let parameter of parameters) {
962+
for (let paramIndex = 0; paramIndex < parameters.length; ++paramIndex) {
963+
let parameter = parameters[paramIndex];
911964
let parameterType = this.typeChecker.getTypeOfSymbolAtLocation(parameter, this.arbitraryAstNode);
912965
if (parameterType == null) {
913966
return null;
914967
}
915-
let parameterTypeId = this.getId(parameterType);
968+
let isRestParameter = hasRestParam && (paramIndex === parameters.length - 1);
969+
if (isRestParameter) {
970+
// The type of the rest parameter is the array type, but we wish to extract the non-array type.
971+
if (!isTypeReference(parameterType)) return null;
972+
let typeArguments = parameterType.typeArguments;
973+
if (typeArguments == null || typeArguments.length === 0) return null;
974+
parameterType = typeArguments[0];
975+
}
976+
let parameterTypeId = this.getId(parameterType, false);
916977
if (parameterTypeId == null) {
917978
return null;
918979
}
@@ -946,7 +1007,7 @@ export class TypeTable {
9461007

9471008
private extractIndexer(baseType: number, indexType: ts.Type, table: IndexerTable) {
9481009
if (indexType == null) return;
949-
let indexTypeId = this.getId(indexType);
1010+
let indexTypeId = this.getId(indexType, false);
9501011
if (indexTypeId == null) return;
9511012
table.baseTypes.push(baseType);
9521013
table.propertyTypes.push(indexTypeId);
@@ -1017,7 +1078,7 @@ export class TypeTable {
10171078
let selfType = this.getSelfType(type);
10181079
if (selfType != null) {
10191080
this.checkExpansiveness(selfType);
1020-
let id = this.getId(selfType);
1081+
let id = this.getId(selfType, false);
10211082
return this.expansiveTypes.get(id);
10221083
}
10231084
return false;
@@ -1086,7 +1147,7 @@ export class TypeTable {
10861147
search(type, 0);
10871148

10881149
function search(type: ts.TypeReference, expansionDepth: number): number | null {
1089-
let id = typeTable.getId(type);
1150+
let id = typeTable.getId(type, false);
10901151
if (id == null) return null;
10911152

10921153
let index = indexTable.get(id);

javascript/extractor/src/com/semmle/js/ast/AFunction.java

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
import com.semmle.ts.ast.DecoratorList;
44
import com.semmle.ts.ast.ITypeExpression;
55
import com.semmle.ts.ast.TypeParameter;
6+
import com.semmle.util.data.IntList;
67
import java.util.ArrayList;
78
import java.util.List;
89

@@ -18,6 +19,9 @@ public class AFunction<B> {
1819
private final List<ITypeExpression> parameterTypes;
1920
private final ITypeExpression thisParameterType;
2021
private final List<DecoratorList> parameterDecorators;
22+
private final IntList optionalParameterIndices;
23+
24+
public static final IntList noOptionalParams = IntList.create(0, 0);
2125

2226
public AFunction(
2327
Identifier id,
@@ -29,7 +33,8 @@ public AFunction(
2933
List<ITypeExpression> parameterTypes,
3034
List<DecoratorList> parameterDecorators,
3135
ITypeExpression returnType,
32-
ITypeExpression thisParameterType) {
36+
ITypeExpression thisParameterType,
37+
IntList optionalParameterIndices) {
3338
this.id = id;
3439
this.params = new ArrayList<IPattern>(params.size());
3540
this.defaults = new ArrayList<Expression>(params.size());
@@ -42,6 +47,7 @@ public AFunction(
4247
this.returnType = returnType;
4348
this.thisParameterType = thisParameterType;
4449
this.parameterDecorators = parameterDecorators;
50+
this.optionalParameterIndices = optionalParameterIndices;
4551

4652
IPattern rest = null;
4753
for (Expression param : params) {
@@ -143,4 +149,8 @@ public ITypeExpression getThisParameterType() {
143149
public List<DecoratorList> getParameterDecorators() {
144150
return parameterDecorators;
145151
}
152+
153+
public IntList getOptionalParmaeterIndices() {
154+
return optionalParameterIndices;
155+
}
146156
}

0 commit comments

Comments
 (0)