diff --git a/javascript/ql/src/semmle/javascript/TypeScript.qll b/javascript/ql/src/semmle/javascript/TypeScript.qll
index 0a89346d279e..24e453bcf253 100644
--- a/javascript/ql/src/semmle/javascript/TypeScript.qll
+++ b/javascript/ql/src/semmle/javascript/TypeScript.qll
@@ -686,6 +686,9 @@ class TypeExpr extends ExprOrType, @typeexpr {
/** Holds if this is the `object` type. */
predicate isObjectKeyword() { none() }
+ /** Holds if this is the `unknown` type. */
+ predicate isUnknownKeyword() { none() }
+
/** Gets this type expression, with any surrounding parentheses removed. */
override TypeExpr stripParens() {
result = this
@@ -725,6 +728,7 @@ private class KeywordTypeExpr extends @keywordtypeexpr, TypeExpr {
override predicate isSymbol() { getName() = "symbol" }
override predicate isUniqueSymbol() { getName() = "unique symbol" }
override predicate isObjectKeyword() { getName() = "object" }
+ override predicate isUnknownKeyword() { getName() = "unknown" }
}
/**
@@ -1073,6 +1077,25 @@ class IsTypeExpr extends @istypeexpr, TypeExpr {
TypeExpr getPredicateType() { result = this.getChildTypeExpr(1) }
}
+/**
+ * An optional type element in a tuple type, such as `number?` in `[string, number?]`.
+ */
+class OptionalTypeExpr extends @optionaltypeexpr, TypeExpr {
+ /** Gets the type `T` in `T?` */
+ TypeExpr getElementType() { result = getChildTypeExpr(0) }
+}
+
+/**
+ * A rest element in a tuple type, such as `...string[]` in `[number, ...string[]]`.
+ */
+class RestTypeExpr extends @resttypeexpr, TypeExpr {
+ /** Gets the type `T[]` in `...T[]`, such as `string[]` in `[number, ...string[]]`. */
+ TypeExpr getArrayType() { result = getChildTypeExpr(0) }
+
+ /** Gets the type `T` in `...T[]`, such as `string` in `[number, ...string[]]`. */
+ TypeExpr getElementType() { result = getArrayType().(ArrayTypeExpr).getElementType() }
+}
+
/**
* A possibly qualified name that refers to a variable from inside a type.
*
@@ -2143,7 +2166,7 @@ class TupleType extends ArrayType, @tupletype {
}
/**
- * Gets the number of elements in this tuple type.
+ * Gets the number of elements in this tuple type, including optional elements and the rest element.
*/
int getNumElementType() {
result = count(int i | exists(getElementType(i)))
@@ -2158,6 +2181,31 @@ class TupleType extends ArrayType, @tupletype {
PlainArrayType getUnderlyingArrayType() {
result.getArrayElementType() = getArrayElementType()
}
+
+ /**
+ * Gets the number of required tuple elements, that is, excluding optional and rest elements.
+ *
+ * For example, the minimum length of `[number, string?, ...number[]]` is 1.
+ */
+ int getMinimumLength() {
+ tuple_type_min_length(this, result)
+ }
+
+ /**
+ * Holds if this tuple type ends with a rest element, such as `[number, ...string[]]`.
+ */
+ predicate hasRestElement() {
+ tuple_type_rest(this)
+ }
+
+ /**
+ * Gets the type of the rest element, if there is one.
+ *
+ * For example, the rest element of `[number, ...string[]]` is `string`.
+ */
+ Type getRestElementType() {
+ hasRestElement() and result = getElementType(getNumElementType() - 1)
+ }
}
/**
@@ -2165,6 +2213,11 @@ class TupleType extends ArrayType, @tupletype {
*/
class AnyType extends Type, @anytype {}
+/**
+ * The predefined `unknown` type.
+ */
+class UnknownType extends Type, @unknowntype {}
+
/**
* The predefined `string` type.
*/
diff --git a/javascript/ql/src/semmlecode.javascript.dbscheme b/javascript/ql/src/semmlecode.javascript.dbscheme
index f88740175fc1..6486c78671c4 100644
--- a/javascript/ql/src/semmlecode.javascript.dbscheme
+++ b/javascript/ql/src/semmlecode.javascript.dbscheme
@@ -556,7 +556,10 @@ case @typeexpr.kind of
| 29 = @infertypeexpr
| 30 = @importtypeaccess
| 31 = @importnamespaceaccess
-| 32 = @importvartypeaccess;
+| 32 = @importvartypeaccess
+| 33 = @optionaltypeexpr
+| 34 = @resttypeexpr
+;
@typeref = @typeaccess | @typedecl;
@typeidentifier = @typedecl | @localtypeaccess | @typelabel | @localvartypeaccess | @localnamespaceaccess;
@@ -607,6 +610,7 @@ case @type.kind of
| 20 = @thistype
| 21 = @numberliteraltype
| 22 = @stringliteraltype
+| 23 = @unknowntype
;
@booleanliteraltype = @truetype | @falsetype;
@@ -734,6 +738,15 @@ self_types(
int selfType: @typereference ref
);
+tuple_type_min_length(
+ unique int typ: @type ref,
+ int minLength: int ref
+);
+
+tuple_type_rest(
+ unique int typ: @type ref
+);
+
// comments
comments (unique int id: @comment,
int kind: int ref,
diff --git a/javascript/ql/src/semmlecode.javascript.dbscheme.stats b/javascript/ql/src/semmlecode.javascript.dbscheme.stats
index 2b97a5c7b13c..9ec01562f78b 100644
--- a/javascript/ql/src/semmlecode.javascript.dbscheme.stats
+++ b/javascript/ql/src/semmlecode.javascript.dbscheme.stats
@@ -806,6 +806,14 @@
100
+@optionaltypeexpr
+100
+
+
+@resttypeexpr
+100
+
+
@generictypeexpr
5220
@@ -962,6 +970,10 @@
30638
+@unknowntype
+100
+
+
@uniquesymboltype
100
@@ -15385,6 +15397,100 @@
+tuple_type_min_length
+241
+
+
+typ
+241
+
+
+minLength
+10
+
+
+
+
+typ
+minLength
+
+
+12
+
+
+1
+2
+241
+
+
+
+
+
+
+minLength
+typ
+
+
+12
+
+
+2
+3
+3
+
+
+3
+4
+1
+
+
+4
+5
+1
+
+
+7
+8
+1
+
+
+20
+21
+1
+
+
+42
+43
+1
+
+
+66
+67
+1
+
+
+93
+94
+1
+
+
+
+
+
+
+
+
+tuple_type_rest
+100
+
+
+typ
+100
+
+
+
+
+
comments
id
104947
diff --git a/javascript/ql/test/library-tests/TypeScript/TypeAnnotations/ArrayTypeExpr.expected b/javascript/ql/test/library-tests/TypeScript/TypeAnnotations/ArrayTypeExpr.expected
index a721f2a48149..d6fc26c35200 100644
--- a/javascript/ql/test/library-tests/TypeScript/TypeAnnotations/ArrayTypeExpr.expected
+++ b/javascript/ql/test/library-tests/TypeScript/TypeAnnotations/ArrayTypeExpr.expected
@@ -6,3 +6,5 @@
| tst.ts:40:17:40:28 | string[][][] | tst.ts:40:17:40:26 | string[][] |
| tst.ts:80:49:80:54 | Leaf[] | tst.ts:80:49:80:52 | Leaf |
| tst.ts:81:27:81:34 | string[] | tst.ts:81:27:81:32 | string |
+| tst.ts:135:39:135:46 | string[] | tst.ts:135:39:135:44 | string |
+| tst.ts:136:60:136:67 | number[] | tst.ts:136:60:136:65 | number |
diff --git a/javascript/ql/test/library-tests/TypeScript/TypeAnnotations/OptionalTypeExpr.expected b/javascript/ql/test/library-tests/TypeScript/TypeAnnotations/OptionalTypeExpr.expected
new file mode 100644
index 000000000000..f1fbc75409f6
--- /dev/null
+++ b/javascript/ql/test/library-tests/TypeScript/TypeAnnotations/OptionalTypeExpr.expected
@@ -0,0 +1,2 @@
+| tst.ts:133:48:133:54 | number? | tst.ts:133:48:133:53 | number |
+| tst.ts:136:48:136:54 | string? | tst.ts:136:48:136:53 | string |
diff --git a/javascript/ql/test/library-tests/TypeScript/TypeAnnotations/OptionalTypeExpr.ql b/javascript/ql/test/library-tests/TypeScript/TypeAnnotations/OptionalTypeExpr.ql
new file mode 100644
index 000000000000..641fb7b8729e
--- /dev/null
+++ b/javascript/ql/test/library-tests/TypeScript/TypeAnnotations/OptionalTypeExpr.ql
@@ -0,0 +1,4 @@
+import javascript
+
+from OptionalTypeExpr type
+select type, type.getElementType()
diff --git a/javascript/ql/test/library-tests/TypeScript/TypeAnnotations/RestTypeExpr.expected b/javascript/ql/test/library-tests/TypeScript/TypeAnnotations/RestTypeExpr.expected
new file mode 100644
index 000000000000..1d6e759e0d2d
--- /dev/null
+++ b/javascript/ql/test/library-tests/TypeScript/TypeAnnotations/RestTypeExpr.expected
@@ -0,0 +1,2 @@
+| tst.ts:135:36:135:46 | ...string[] | tst.ts:135:39:135:46 | string[] | tst.ts:135:39:135:44 | string |
+| tst.ts:136:57:136:67 | ...number[] | tst.ts:136:60:136:67 | number[] | tst.ts:136:60:136:65 | number |
diff --git a/javascript/ql/test/library-tests/TypeScript/TypeAnnotations/RestTypeExpr.ql b/javascript/ql/test/library-tests/TypeScript/TypeAnnotations/RestTypeExpr.ql
new file mode 100644
index 000000000000..c77bed8c6881
--- /dev/null
+++ b/javascript/ql/test/library-tests/TypeScript/TypeAnnotations/RestTypeExpr.ql
@@ -0,0 +1,4 @@
+import javascript
+
+from RestTypeExpr rest
+select rest, rest.getArrayType(), rest.getElementType()
diff --git a/javascript/ql/test/library-tests/TypeScript/TypeAnnotations/TupleTypeExpr.expected b/javascript/ql/test/library-tests/TypeScript/TypeAnnotations/TupleTypeExpr.expected
index 12cee1a06d0b..6c4d47bea6bd 100644
--- a/javascript/ql/test/library-tests/TypeScript/TypeAnnotations/TupleTypeExpr.expected
+++ b/javascript/ql/test/library-tests/TypeScript/TypeAnnotations/TupleTypeExpr.expected
@@ -3,3 +3,11 @@
| tst.ts:48:16:48:40 | [number ... oolean] | 2 | 3 | tst.ts:48:33:48:39 | boolean |
| tst.ts:95:31:95:35 | [S,T] | 0 | 2 | tst.ts:95:32:95:32 | S |
| tst.ts:95:31:95:35 | [S,T] | 1 | 2 | tst.ts:95:34:95:34 | T |
+| tst.ts:133:31:133:55 | [number ... umber?] | 0 | 3 | tst.ts:133:32:133:37 | number |
+| tst.ts:133:31:133:55 | [number ... umber?] | 1 | 3 | tst.ts:133:40:133:45 | string |
+| tst.ts:133:31:133:55 | [number ... umber?] | 2 | 3 | tst.ts:133:48:133:54 | number? |
+| tst.ts:135:27:135:47 | [number ... ring[]] | 0 | 2 | tst.ts:135:28:135:33 | number |
+| tst.ts:135:27:135:47 | [number ... ring[]] | 1 | 2 | tst.ts:135:36:135:46 | ...string[] |
+| tst.ts:136:39:136:68 | [number ... mber[]] | 0 | 3 | tst.ts:136:40:136:45 | number |
+| tst.ts:136:39:136:68 | [number ... mber[]] | 1 | 3 | tst.ts:136:48:136:54 | string? |
+| tst.ts:136:39:136:68 | [number ... mber[]] | 2 | 3 | tst.ts:136:57:136:67 | ...number[] |
diff --git a/javascript/ql/test/library-tests/TypeScript/TypeAnnotations/UnknownTypeExpr.expected b/javascript/ql/test/library-tests/TypeScript/TypeAnnotations/UnknownTypeExpr.expected
new file mode 100644
index 000000000000..5f5df1a3e361
--- /dev/null
+++ b/javascript/ql/test/library-tests/TypeScript/TypeAnnotations/UnknownTypeExpr.expected
@@ -0,0 +1 @@
+| tst.ts:137:18:137:24 | unknown |
diff --git a/javascript/ql/test/library-tests/TypeScript/TypeAnnotations/UnknownTypeExpr.ql b/javascript/ql/test/library-tests/TypeScript/TypeAnnotations/UnknownTypeExpr.ql
new file mode 100644
index 000000000000..5472547df6b5
--- /dev/null
+++ b/javascript/ql/test/library-tests/TypeScript/TypeAnnotations/UnknownTypeExpr.ql
@@ -0,0 +1,5 @@
+import javascript
+
+from TypeExpr type
+where type.isUnknownKeyword()
+select type
diff --git a/javascript/ql/test/library-tests/TypeScript/TypeAnnotations/VariableTypes.expected b/javascript/ql/test/library-tests/TypeScript/TypeAnnotations/VariableTypes.expected
index 965b210f2d02..7f6035afaac3 100644
--- a/javascript/ql/test/library-tests/TypeScript/TypeAnnotations/VariableTypes.expected
+++ b/javascript/ql/test/library-tests/TypeScript/TypeAnnotations/VariableTypes.expected
@@ -69,3 +69,8 @@
| tst.ts:126:5:126:18 | importedTypeof | importedTypeof | tst.ts:126:21:126:42 | typeof ... value") |
| tst.ts:127:5:127:27 | importe ... dTypeof | importedQualifiedTypeof | tst.ts:127:30:127:53 | typeof ... lue").x |
| tst.ts:128:5:128:35 | importe ... tespace | importedQualifiedTypeWhitespace | tst.ts:131:4:131:6 | bar |
+| tst.ts:133:5:133:28 | tupleWi ... Element | tupleWithOptionalElement | tst.ts:133:31:133:55 | [number ... umber?] |
+| tst.ts:134:5:134:14 | emptyTuple | emptyTuple | tst.ts:134:17:134:18 | [] |
+| tst.ts:135:5:135:24 | tupleWithRestElement | tupleWithRestElement | tst.ts:135:27:135:47 | [number ... ring[]] |
+| tst.ts:136:5:136:36 | tupleWi ... lements | tupleWithOptionalAndRestElements | tst.ts:136:39:136:68 | [number ... mber[]] |
+| tst.ts:137:5:137:15 | unknownType | unknownType | tst.ts:137:18:137:24 | unknown |
diff --git a/javascript/ql/test/library-tests/TypeScript/TypeAnnotations/tst.ts b/javascript/ql/test/library-tests/TypeScript/TypeAnnotations/tst.ts
index 355bbcd08f7b..dbf0eb564e85 100644
--- a/javascript/ql/test/library-tests/TypeScript/TypeAnnotations/tst.ts
+++ b/javascript/ql/test/library-tests/TypeScript/TypeAnnotations/tst.ts
@@ -129,3 +129,9 @@ var importedQualifiedTypeWhitespace: import(
'awkard-namespace'
)
.bar;
+
+let tupleWithOptionalElement: [number, string, number?];
+let emptyTuple: [];
+let tupleWithRestElement: [number, ...string[]];
+let tupleWithOptionalAndRestElements: [number, string?, ...number[]];
+let unknownType: unknown;
diff --git a/javascript/ql/test/library-tests/TypeScript/Types/GetExprType.expected b/javascript/ql/test/library-tests/TypeScript/Types/GetExprType.expected
index 1f34ae5d82f3..1e3dd58b1959 100644
--- a/javascript/ql/test/library-tests/TypeScript/Types/GetExprType.expected
+++ b/javascript/ql/test/library-tests/TypeScript/Types/GetExprType.expected
@@ -75,6 +75,11 @@
| tst.ts:33:5:33:16 | intersection | string & { x: string; } |
| tst.ts:33:29:33:29 | x | string |
| tst.ts:34:5:34:9 | tuple | [number, string] |
+| tst.ts:36:5:36:28 | tupleWi ... Element | [number, string, number?] |
+| tst.ts:37:5:37:14 | emptyTuple | [] |
+| tst.ts:38:5:38:24 | tupleWithRestElement | [number, ...string[]] |
+| tst.ts:39:5:39:36 | tupleWi ... lements | [number, string?, ...number[]] |
+| tst.ts:40:5:40:15 | unknownType | unknown |
| type_alias.ts:3:5:3:5 | b | boolean |
| type_definition_objects.ts:1:13:1:17 | dummy | typeof dummy.ts |
| type_definition_objects.ts:1:24:1:32 | "./dummy" | any |
diff --git a/javascript/ql/test/library-tests/TypeScript/Types/GetTypeExprType.expected b/javascript/ql/test/library-tests/TypeScript/Types/GetTypeExprType.expected
index 121b2eff5312..87db20c57967 100644
--- a/javascript/ql/test/library-tests/TypeScript/Types/GetTypeExprType.expected
+++ b/javascript/ql/test/library-tests/TypeScript/Types/GetTypeExprType.expected
@@ -49,6 +49,25 @@
| tst.ts:34:12:34:27 | [number, string] | [number, string] |
| tst.ts:34:13:34:18 | number | number |
| tst.ts:34:21:34:26 | string | string |
+| tst.ts:36:31:36:55 | [number ... umber?] | [number, string, number?] |
+| tst.ts:36:32:36:37 | number | number |
+| tst.ts:36:40:36:45 | string | string |
+| tst.ts:36:48:36:53 | number | number |
+| tst.ts:36:48:36:54 | number? | number |
+| tst.ts:37:17:37:18 | [] | [] |
+| tst.ts:38:27:38:47 | [number ... ring[]] | [number, ...string[]] |
+| tst.ts:38:28:38:33 | number | number |
+| tst.ts:38:36:38:46 | ...string[] | string[] |
+| tst.ts:38:39:38:44 | string | string |
+| tst.ts:38:39:38:46 | string[] | string[] |
+| tst.ts:39:39:39:68 | [number ... mber[]] | [number, string?, ...number[]] |
+| tst.ts:39:40:39:45 | number | number |
+| tst.ts:39:48:39:53 | string | string |
+| tst.ts:39:48:39:54 | string? | string |
+| tst.ts:39:57:39:67 | ...number[] | number[] |
+| tst.ts:39:60:39:65 | number | number |
+| tst.ts:39:60:39:67 | number[] | number[] |
+| tst.ts:40:18:40:24 | unknown | unknown |
| type_alias.ts:1:6:1:6 | B | boolean |
| type_alias.ts:1:10:1:16 | boolean | boolean |
| type_alias.ts:3:8:3:8 | B | boolean |
diff --git a/javascript/ql/test/library-tests/TypeScript/Types/TupleTypes.expected b/javascript/ql/test/library-tests/TypeScript/Types/TupleTypes.expected
new file mode 100644
index 000000000000..29c0c7dfbf42
--- /dev/null
+++ b/javascript/ql/test/library-tests/TypeScript/Types/TupleTypes.expected
@@ -0,0 +1,10 @@
+| tst.ts:34:5:34:9 | tuple | [number, string] | 0 | number | 2 | no-rest |
+| tst.ts:34:5:34:9 | tuple | [number, string] | 1 | string | 2 | no-rest |
+| tst.ts:36:5:36:28 | tupleWi ... Element | [number, string, number?] | 0 | number | 2 | no-rest |
+| tst.ts:36:5:36:28 | tupleWi ... Element | [number, string, number?] | 1 | string | 2 | no-rest |
+| tst.ts:36:5:36:28 | tupleWi ... Element | [number, string, number?] | 2 | number | 2 | no-rest |
+| tst.ts:38:5:38:24 | tupleWithRestElement | [number, ...string[]] | 0 | number | 1 | string |
+| tst.ts:38:5:38:24 | tupleWithRestElement | [number, ...string[]] | 1 | string | 1 | string |
+| tst.ts:39:5:39:36 | tupleWi ... lements | [number, string?, ...number[]] | 0 | number | 1 | number |
+| tst.ts:39:5:39:36 | tupleWi ... lements | [number, string?, ...number[]] | 1 | string | 1 | number |
+| tst.ts:39:5:39:36 | tupleWi ... lements | [number, string?, ...number[]] | 2 | number | 1 | number |
diff --git a/javascript/ql/test/library-tests/TypeScript/Types/TupleTypes.ql b/javascript/ql/test/library-tests/TypeScript/Types/TupleTypes.ql
new file mode 100644
index 000000000000..afb8b2a28081
--- /dev/null
+++ b/javascript/ql/test/library-tests/TypeScript/Types/TupleTypes.ql
@@ -0,0 +1,12 @@
+import javascript
+
+string getRest(TupleType tuple) {
+ if tuple.hasRestElement() then
+ result = tuple.getRestElementType().toString()
+ else
+ result = "no-rest"
+}
+
+from Expr e, TupleType tuple, int n
+where e.getType() = tuple
+select e, tuple, n, tuple.getElementType(n), tuple.getMinimumLength(), getRest(tuple)
diff --git a/javascript/ql/test/library-tests/TypeScript/Types/UnknownType.expected b/javascript/ql/test/library-tests/TypeScript/Types/UnknownType.expected
new file mode 100644
index 000000000000..dfc3f2808edb
--- /dev/null
+++ b/javascript/ql/test/library-tests/TypeScript/Types/UnknownType.expected
@@ -0,0 +1 @@
+| tst.ts:40:5:40:15 | unknownType | unknown |
diff --git a/javascript/ql/test/library-tests/TypeScript/Types/UnknownType.ql b/javascript/ql/test/library-tests/TypeScript/Types/UnknownType.ql
new file mode 100644
index 000000000000..3ee95b362914
--- /dev/null
+++ b/javascript/ql/test/library-tests/TypeScript/Types/UnknownType.ql
@@ -0,0 +1,5 @@
+import javascript
+
+from Expr e
+where e.getType() instanceof UnknownType
+select e, e.getType()
diff --git a/javascript/ql/test/library-tests/TypeScript/Types/tst.ts b/javascript/ql/test/library-tests/TypeScript/Types/tst.ts
index d348ce8b3b85..d1228fa38d0c 100644
--- a/javascript/ql/test/library-tests/TypeScript/Types/tst.ts
+++ b/javascript/ql/test/library-tests/TypeScript/Types/tst.ts
@@ -32,3 +32,9 @@ const uniqueSymbolType: unique symbol = null;
let objectType: object;
let intersection: string & {x: string};
let tuple: [number, string];
+
+let tupleWithOptionalElement: [number, string, number?];
+let emptyTuple: [];
+let tupleWithRestElement: [number, ...string[]];
+let tupleWithOptionalAndRestElements: [number, string?, ...number[]];
+let unknownType: unknown;
\ No newline at end of file