Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Commit b6b8213

Browse files
committed
TS: Handle rest parameters in call signatures
1 parent f2c3d73 commit b6b8213

7 files changed

Lines changed: 209 additions & 12 deletions

File tree

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

Lines changed: 41 additions & 3 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.
@@ -902,7 +916,7 @@ export class TypeTable {
902916
/**
903917
* Returns a unique string for the given call/constructor signature.
904918
*/
905-
private getSignatureString(kind: ts.SignatureKind, signature: ts.Signature): string {
919+
private getSignatureString(kind: ts.SignatureKind, signature: AugmentedSignature): string {
906920
let parameters = signature.getParameters();
907921
let numberOfTypeParameters = signature.typeParameters == null
908922
? 0
@@ -915,11 +929,26 @@ export class TypeTable {
915929
break;
916930
}
917931
}
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+
}
918947
let returnTypeId = this.getId(signature.getReturnType(), false);
919948
if (returnTypeId == null) {
920949
return null;
921950
}
922-
let tag = `${kind};${numberOfTypeParameters};${requiredParameters};${returnTypeId}`;
951+
let tag = `${kind};${numberOfTypeParameters};${requiredParameters};${restParameterTag};${returnTypeId}`;
923952
for (let typeParameter of signature.typeParameters || []) {
924953
tag += ";" + typeParameter.symbol.name;
925954
let constraint = typeParameter.getConstraint();
@@ -930,11 +959,20 @@ export class TypeTable {
930959
tag += ";" + constraintId;
931960
}
932961
}
933-
for (let parameter of parameters) {
962+
for (let paramIndex = 0; paramIndex < parameters.length; ++paramIndex) {
963+
let parameter = parameters[paramIndex];
934964
let parameterType = this.typeChecker.getTypeOfSymbolAtLocation(parameter, this.arbitraryAstNode);
935965
if (parameterType == null) {
936966
return null;
937967
}
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+
}
938976
let parameterTypeId = this.getId(parameterType, false);
939977
if (parameterTypeId == null) {
940978
return null;

javascript/extractor/src/com/semmle/ts/extractor/TypeExtractor.java

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -201,13 +201,18 @@ private void extractSymbolNameMapping(String relationName, JsonObject mappings)
201201

202202
private void extractSignature(int index) {
203203
// Format is:
204-
// kind;numTypeParams;requiredParams;returnType(;paramName;paramType)*
204+
// kind;numTypeParams;requiredParams;restParamType;returnType(;paramName;paramType)*
205205
String[] parts = split(table.getSignatureString(index));
206206
Label label = trapWriter.globalID("signature;" + index);
207207
int kind = Integer.parseInt(parts[0]);
208208
int numberOfTypeParameters = Integer.parseInt(parts[1]);
209209
int requiredParameters = Integer.parseInt(parts[2]);
210-
Label returnType = trapWriter.globalID("type;" + parts[3]);
210+
String restParamTypeTag = parts[3];
211+
if (!restParamTypeTag.isEmpty()) {
212+
trapWriter.addTuple(
213+
"signature_rest_parameter", label, trapWriter.globalID("type;" + restParamTypeTag));
214+
}
215+
Label returnType = trapWriter.globalID("type;" + parts[4]);
211216
trapWriter.addTuple(
212217
"signature_types",
213218
label,
@@ -216,9 +221,9 @@ private void extractSignature(int index) {
216221
numberOfTypeParameters,
217222
requiredParameters);
218223
trapWriter.addTuple("signature_contains_type", returnType, label, -1);
219-
int numberOfParameters = (parts.length - 4) / 2; // includes type parameters
224+
int numberOfParameters = (parts.length - 5) / 2; // includes type parameters
220225
for (int i = 0; i < numberOfParameters; ++i) {
221-
int partIndex = 4 + (2 * i);
226+
int partIndex = 5 + (2 * i);
222227
String paramName = parts[partIndex];
223228
String paramTypeId = parts[partIndex + 1];
224229
if (paramTypeId.length() > 0) { // Unconstrained type parameters have an empty type ID.

javascript/ql/src/semmle/javascript/TypeScript.qll

Lines changed: 33 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -2555,17 +2555,19 @@ class CallSignatureType extends @signature_type {
25552555
predicate hasTypeParameters() { getNumTypeParameter() > 0 }
25562556

25572557
/**
2558-
* Gets the type of the `n`th parameter of this signature.
2558+
* Gets the type of the `n`th parameter declared in this signature.
2559+
*
2560+
* If the `n`th parameter is a rest parameter `...T[]`, gets type `T`.
25592561
*/
25602562
Type getParameter(int n) { n >= 0 and result = getChild(n + getNumTypeParameter()) }
25612563

25622564
/**
2563-
* Gets the type of a parameter of this signature.
2565+
* Gets the type of a parameter of this signature, including the rest parameter, if any.
25642566
*/
25652567
Type getAParameter() { result = getParameter(_) }
25662568

25672569
/**
2568-
* Gets the number of parameters.
2570+
* Gets the number of parameters, including the rest parameter, if any.
25692571
*/
25702572
int getNumParameter() { result = count(int i | exists(getParameter(i))) }
25712573

@@ -2577,7 +2579,7 @@ class CallSignatureType extends @signature_type {
25772579

25782580
/**
25792581
* Gets the number of optional parameters, that is,
2580-
* parameters that are marked as optional with the `?` suffix.
2582+
* parameters that are marked as optional with the `?` suffix or is a rest parameter.
25812583
*/
25822584
int getNumOptionalParameter() { result = getNumParameter() - getNumRequiredParameter() }
25832585

@@ -2591,7 +2593,9 @@ class CallSignatureType extends @signature_type {
25912593
}
25922594

25932595
/**
2594-
* Holds if the `n`th parameter is declared optional with the `?` suffix.
2596+
* Holds if the `n`th parameter is declared optional with the `?` suffix or is the rest parameter.
2597+
*
2598+
* Note that rest parameters are not considered optional in this sense.
25952599
*/
25962600
predicate isOptionalParameter(int n) {
25972601
exists(getParameter(n)) and
@@ -2610,6 +2614,30 @@ class CallSignatureType extends @signature_type {
26102614
* Gets the name of a parameter of this signature.
26112615
*/
26122616
string getAParameterName() { result = getParameterName(_) }
2617+
2618+
/**
2619+
* Holds if this signature declares a rest parameter, such as `(x: number, ...y: string[])`.
2620+
*/
2621+
predicate hasRestParameter() { signature_rest_parameter(this, _) } // TODO
2622+
2623+
/**
2624+
* Gets the type of the rest parameter, if any.
2625+
*
2626+
* For example, for the signature `(...y: string[])`, this gets the type `string`.
2627+
*/
2628+
Type getRestParameterType() {
2629+
hasRestParameter() and
2630+
result = getParameter(getNumParameter() - 1)
2631+
}
2632+
2633+
/**
2634+
* Gets the type of the rest parameter as an array, if it exists.
2635+
*
2636+
* For example, for the signature `(...y: string[])`, this gets the type `string[]`.
2637+
*/
2638+
PlainArrayType getRestParameterArrayType() {
2639+
signature_rest_parameter(this, result)
2640+
}
26132641
}
26142642

26152643
/**

javascript/ql/src/semmlecode.javascript.dbscheme

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -721,6 +721,11 @@ signature_types (
721721
int required_params: int ref
722722
);
723723

724+
signature_rest_parameter(
725+
unique int sig: @signature_type ref,
726+
int rest_param_arra_type: @type ref
727+
);
728+
724729
case @signature_type.kind of
725730
0 = @function_signature_type
726731
| 1 = @constructor_signature_type

javascript/ql/test/library-tests/TypeScript/CallSignatureTypes/test.expected

Lines changed: 84 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,14 +47,39 @@ test_ExprSignature
4747
| tst.ts:45:15:45:15 | x | string |
4848
| tst.ts:46:3:46:25 | constru ... umber); | any |
4949
| tst.ts:46:15:46:15 | x | number |
50+
| tst.ts:50:3:50:36 | method( ... ing[]); | (x: number, ...y: string[]): any |
51+
| tst.ts:50:10:50:10 | x | number |
52+
| tst.ts:50:24:50:24 | y | string[] |
53+
| tst.ts:51:4:51:4 | x | number |
54+
| tst.ts:51:18:51:18 | y | string[] |
55+
| tst.ts:52:7:52:7 | x | number |
56+
| tst.ts:52:21:52:21 | y | string[] |
57+
| tst.ts:54:3:54:34 | method2 ... ing[]); | (x: number, y: string[]): any |
58+
| tst.ts:54:11:54:11 | x | number |
59+
| tst.ts:54:22:54:22 | y | string[] |
60+
| tst.ts:55:3:55:32 | method3 ... tring); | (x: number, y: string): any |
61+
| tst.ts:55:11:55:11 | x | number |
62+
| tst.ts:55:22:55:22 | y | string |
63+
| tst.ts:59:3:59:25 | method( ... ing[]); | (...y: string[]): any |
64+
| tst.ts:59:13:59:13 | y | string[] |
65+
| tst.ts:60:7:60:7 | y | string[] |
66+
| tst.ts:61:10:61:10 | y | string[] |
67+
| tst.ts:63:3:63:23 | method2 ... ing[]); | (y: string[]): any |
68+
| tst.ts:63:11:63:11 | y | string[] |
69+
| tst.ts:64:3:64:21 | method3(y: string); | (y: string): any |
70+
| tst.ts:64:11:64:11 | y | string |
5071
test_TypeReferenceSig
5172
| Callable | function | 0 | (x: number): string |
5273
| Newable | constructor | 0 | new (x: number): any |
74+
| OnlyRestParams | constructor | 0 | new (...y: string[]): any |
75+
| OnlyRestParams | function | 0 | (...y: string[]): any |
5376
| OverloadedCallable | function | 0 | (x: number): number |
5477
| OverloadedCallable | function | 1 | (x: string): string |
5578
| OverloadedCallable | function | 2 | (x: any): any |
5679
| OverloadedNewable | constructor | 0 | new (x: number): OverloadedNewable |
5780
| OverloadedNewable | constructor | 1 | new (x: any): any |
81+
| WithRestParams | constructor | 0 | new (x: number, ...y: string[]): any |
82+
| WithRestParams | function | 0 | (x: number, ...y: string[]): any |
5883
test_FunctionCallSig
5984
| tst.ts:2:3:2:22 | (x: number): string; | (x: number): string |
6085
| tst.ts:6:3:6:22 | (x: number): number; | (x: number): number |
@@ -72,3 +97,62 @@ test_FunctionCallSig
7297
| tst.ts:40:1:42:1 | functio ... oo");\\n} | (g: Generic<string>): string |
7398
| tst.ts:45:3:45:25 | constru ... tring); | new (x: string): C |
7499
| tst.ts:46:3:46:25 | constru ... umber); | new (x: number): C |
100+
| tst.ts:50:3:50:36 | method( ... ing[]); | (x: number, ...y: string[]): any |
101+
| tst.ts:51:3:51:30 | (x: num ... ing[]); | (x: number, ...y: string[]): any |
102+
| tst.ts:52:3:52:33 | new(x: ... ing[]); | new (x: number, ...y: string[]): any |
103+
| tst.ts:54:3:54:34 | method2 ... ing[]); | (x: number, y: string[]): any |
104+
| tst.ts:55:3:55:32 | method3 ... tring); | (x: number, y: string): any |
105+
| tst.ts:59:3:59:25 | method( ... ing[]); | (...y: string[]): any |
106+
| tst.ts:60:3:60:19 | (...y: string[]); | (...y: string[]): any |
107+
| tst.ts:61:3:61:22 | new(...y: string[]); | new (...y: string[]): any |
108+
| tst.ts:63:3:63:23 | method2 ... ing[]); | (y: string[]): any |
109+
| tst.ts:64:3:64:21 | method3(y: string); | (y: string): any |
110+
test_getRestParameterType
111+
| (...items: (string \| ConcatArray<string>)[]): string[] | string \| ConcatArray<string> |
112+
| (...items: ConcatArray<string>[]): string[] | ConcatArray<string> |
113+
| (...items: string[]): number | string |
114+
| (...strings: string[]): string | string |
115+
| (...y: string[]): any | string |
116+
| (start: number, deleteCount: number, ...items: string[]): string[] | string |
117+
| (substring: string, ...args: any[]): string | any |
118+
| (x: number, ...y: string[]): any | string |
119+
| new (...y: string[]): any | string |
120+
| new (x: number, ...y: string[]): any | string |
121+
test_getRestParameterArray
122+
| (...items: (string \| ConcatArray<string>)[]): string[] | (string \| ConcatArray<string>)[] |
123+
| (...items: ConcatArray<string>[]): string[] | ConcatArray<string>[] |
124+
| (...items: string[]): number | string[] |
125+
| (...strings: string[]): string | string[] |
126+
| (...y: string[]): any | string[] |
127+
| (start: number, deleteCount: number, ...items: string[]): string[] | string[] |
128+
| (substring: string, ...args: any[]): string | any[] |
129+
| (x: number, ...y: string[]): any | string[] |
130+
| new (...y: string[]): any | string[] |
131+
| new (x: number, ...y: string[]): any | string[] |
132+
test_RestSig_getParameter
133+
| (...items: (string \| ConcatArray<string>)[]): string[] | 0 | items | string \| ConcatArray<string> |
134+
| (...items: ConcatArray<string>[]): string[] | 0 | items | ConcatArray<string> |
135+
| (...items: string[]): number | 0 | items | string |
136+
| (...strings: string[]): string | 0 | strings | string |
137+
| (...y: string[]): any | 0 | y | string |
138+
| (start: number, deleteCount: number, ...items: string[]): string[] | 0 | start | number |
139+
| (start: number, deleteCount: number, ...items: string[]): string[] | 1 | deleteCount | number |
140+
| (start: number, deleteCount: number, ...items: string[]): string[] | 2 | items | string |
141+
| (substring: string, ...args: any[]): string | 0 | substring | string |
142+
| (substring: string, ...args: any[]): string | 1 | args | any |
143+
| (x: number, ...y: string[]): any | 0 | x | number |
144+
| (x: number, ...y: string[]): any | 1 | y | string |
145+
| new (...y: string[]): any | 0 | y | string |
146+
| new (x: number, ...y: string[]): any | 0 | x | number |
147+
| new (x: number, ...y: string[]): any | 1 | y | string |
148+
test_RestSig_numRequiredParams
149+
| (...items: (string \| ConcatArray<string>)[]): string[] | 0 |
150+
| (...items: ConcatArray<string>[]): string[] | 0 |
151+
| (...items: string[]): number | 0 |
152+
| (...strings: string[]): string | 0 |
153+
| (...y: string[]): any | 0 |
154+
| (start: number, deleteCount: number, ...items: string[]): string[] | 2 |
155+
| (substring: string, ...args: any[]): string | 1 |
156+
| (x: number, ...y: string[]): any | 1 |
157+
| new (...y: string[]): any | 0 |
158+
| new (x: number, ...y: string[]): any | 1 |

javascript/ql/test/library-tests/TypeScript/CallSignatureTypes/test.ql

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,3 +20,22 @@ query predicate test_TypeReferenceSig(TypeReference type, SignatureKind kind, in
2020
query predicate test_FunctionCallSig(Function f, CallSignatureType sig) {
2121
sig = f.getCallSignature()
2222
}
23+
24+
query Type test_getRestParameterType(CallSignatureType sig) {
25+
result = sig.getRestParameterType()
26+
}
27+
28+
query Type test_getRestParameterArray(CallSignatureType sig) {
29+
result = sig.getRestParameterArrayType()
30+
}
31+
32+
query predicate test_RestSig_getParameter(CallSignatureType sig, int n, string name, Type type) {
33+
sig.hasRestParameter() and
34+
name = sig.getParameterName(n) and
35+
type = sig.getParameter(n)
36+
}
37+
38+
query int test_RestSig_numRequiredParams(CallSignatureType sig) {
39+
sig.hasRestParameter() and
40+
result = sig.getNumRequiredParameter()
41+
}

javascript/ql/test/library-tests/TypeScript/CallSignatureTypes/tst.ts

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,3 +45,21 @@ declare class C {
4545
constructor(x: string);
4646
constructor(x: number);
4747
}
48+
49+
interface WithRestParams {
50+
method(x: number, ...y: string[]);
51+
(x: number, ...y: string[]);
52+
new(x: number, ...y: string[]);
53+
54+
method2(x: number, y: string[]);
55+
method3(x: number, y: string);
56+
}
57+
58+
interface OnlyRestParams {
59+
method(...y: string[]);
60+
(...y: string[]);
61+
new(...y: string[]);
62+
63+
method2(y: string[]);
64+
method3(y: string);
65+
}

0 commit comments

Comments
 (0)