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