diff --git a/javascript/extractor/src/com/semmle/js/parser/TypeScriptASTConverter.java b/javascript/extractor/src/com/semmle/js/parser/TypeScriptASTConverter.java index 16069da810e..86a08a03fea 100644 --- a/javascript/extractor/src/com/semmle/js/parser/TypeScriptASTConverter.java +++ b/javascript/extractor/src/com/semmle/js/parser/TypeScriptASTConverter.java @@ -345,7 +345,7 @@ public class TypeScriptASTConverter { case "ArrowFunction": return convertArrowFunction(node, loc); case "AsExpression": - return convertAsExpression(node, loc); + return convertTypeAssertionExpression(node, loc); case "AwaitExpression": return convertAwaitExpression(node, loc); case "BigIntKeyword": @@ -797,11 +797,6 @@ public class TypeScriptASTConverter { convertChildAsType(node, "type")); } - private Node convertAsExpression(JsonObject node, SourceLocation loc) throws ParseError { - return new TypeAssertion( - loc, convertChild(node, "expression"), convertChildAsType(node, "type"), true); - } - private Node convertAwaitExpression(JsonObject node, SourceLocation loc) throws ParseError { return new AwaitExpression(loc, convertChild(node, "expression")); } @@ -2115,8 +2110,12 @@ public class TypeScriptASTConverter { private Node convertTypeAssertionExpression(JsonObject node, SourceLocation loc) throws ParseError { - return new TypeAssertion( - loc, convertChild(node, "expression"), convertChildAsType(node, "type"), false); + ITypeExpression type = convertChildAsType(node, "type"); + // `T as const` is extracted as a cast to the keyword type `const`. + if (type instanceof Identifier && ((Identifier) type).getName().equals("const")) { + type = new KeywordTypeExpr(type.getLoc(), "const"); + } + return new TypeAssertion(loc, convertChild(node, "expression"), type, false); } private Node convertTypeLiteral(JsonObject obj, SourceLocation loc) throws ParseError { diff --git a/javascript/ql/src/semmle/javascript/TypeScript.qll b/javascript/ql/src/semmle/javascript/TypeScript.qll index ec1f1ac7724..c5335be4396 100644 --- a/javascript/ql/src/semmle/javascript/TypeScript.qll +++ b/javascript/ql/src/semmle/javascript/TypeScript.qll @@ -585,6 +585,9 @@ class TypeExpr extends ExprOrType, @typeexpr { /** Holds if this is the `bigint` type. */ predicate isBigInt() { none() } + /** Holds if this is the `const` keyword, occurding in a type assertion such as `x as const`. */ + predicate isConstKeyword() { none() } + /** Gets this type expression, with any surrounding parentheses removed. */ override TypeExpr stripParens() { result = this } @@ -638,6 +641,8 @@ private class KeywordTypeExpr extends @keywordtypeexpr, TypeExpr { override predicate isUnknownKeyword() { getName() = "unknown" } override predicate isBigInt() { getName() = "bigint" } + + override predicate isConstKeyword() { getName() = "const" } } /** diff --git a/javascript/ql/test/library-tests/TypeScript/ConstKeyword/ConstKeyword.expected b/javascript/ql/test/library-tests/TypeScript/ConstKeyword/ConstKeyword.expected new file mode 100644 index 00000000000..ba419d4befd --- /dev/null +++ b/javascript/ql/test/library-tests/TypeScript/ConstKeyword/ConstKeyword.expected @@ -0,0 +1,6 @@ +test_ConstKeyword +| tst.ts:1:19:1:23 | const | +| tst.ts:2:10:2:14 | const | +test_ConstTypeAssertion +| tst.ts:1:9:1:23 | [1, 2] as const | +| tst.ts:2:9:2:21 | [1, 2] | diff --git a/javascript/ql/test/library-tests/TypeScript/ConstKeyword/ConstKeyword.ql b/javascript/ql/test/library-tests/TypeScript/ConstKeyword/ConstKeyword.ql new file mode 100644 index 00000000000..70ae3f18eab --- /dev/null +++ b/javascript/ql/test/library-tests/TypeScript/ConstKeyword/ConstKeyword.ql @@ -0,0 +1,9 @@ +import javascript + +query predicate test_ConstKeyword(TypeExpr t) { + t.isConstKeyword() +} + +query predicate test_ConstTypeAssertion(TypeAssertion t) { + t.getTypeAnnotation().isConstKeyword() +} diff --git a/javascript/ql/test/library-tests/TypeScript/ConstKeyword/tst.ts b/javascript/ql/test/library-tests/TypeScript/ConstKeyword/tst.ts new file mode 100644 index 00000000000..839595fef36 --- /dev/null +++ b/javascript/ql/test/library-tests/TypeScript/ConstKeyword/tst.ts @@ -0,0 +1,2 @@ +var x = [1, 2] as const; +var x = [1, 2]; diff --git a/javascript/ql/test/library-tests/TypeScript/Types/GetExprType.expected b/javascript/ql/test/library-tests/TypeScript/Types/GetExprType.expected index 1e3dd58b195..1cac7a026c9 100644 --- a/javascript/ql/test/library-tests/TypeScript/Types/GetExprType.expected +++ b/javascript/ql/test/library-tests/TypeScript/Types/GetExprType.expected @@ -80,6 +80,16 @@ | 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 | +| tst.ts:42:5:42:21 | constArrayLiteral | readonly [1, 2] | +| tst.ts:42:25:42:30 | [1, 2] | readonly [1, 2] | +| tst.ts:42:25:42:39 | [1, 2] as const | readonly [1, 2] | +| tst.ts:42:26:42:26 | 1 | 1 | +| tst.ts:42:29:42:29 | 2 | 2 | +| tst.ts:43:5:43:22 | constObjectLiteral | { readonly foo: "foo"; } | +| tst.ts:43:26:43:39 | { foo: "foo" } | { readonly foo: "foo"; } | +| tst.ts:43:26:43:48 | { foo: ... s const | { readonly foo: "foo"; } | +| tst.ts:43:28:43:30 | foo | "foo" | +| tst.ts:43:33:43:37 | "foo" | "foo" | | 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 cc363c882b8..449b6b25cbc 100644 --- a/javascript/ql/test/library-tests/TypeScript/Types/GetTypeExprType.expected +++ b/javascript/ql/test/library-tests/TypeScript/Types/GetTypeExprType.expected @@ -68,6 +68,8 @@ | tst.ts:39:60:39:65 | number | number | | tst.ts:39:60:39:67 | number[] | number[] | | tst.ts:40:18:40:24 | unknown | unknown | +| tst.ts:42:35:42:39 | const | any | +| tst.ts:43:44:43:48 | const | any | | 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 index 29c0c7dfbf4..e0d06c863e7 100644 --- a/javascript/ql/test/library-tests/TypeScript/Types/TupleTypes.expected +++ b/javascript/ql/test/library-tests/TypeScript/Types/TupleTypes.expected @@ -8,3 +8,9 @@ | 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 | +| tst.ts:42:5:42:21 | constArrayLiteral | readonly [1, 2] | 0 | 1 | 2 | no-rest | +| tst.ts:42:5:42:21 | constArrayLiteral | readonly [1, 2] | 1 | 2 | 2 | no-rest | +| tst.ts:42:25:42:30 | [1, 2] | readonly [1, 2] | 0 | 1 | 2 | no-rest | +| tst.ts:42:25:42:30 | [1, 2] | readonly [1, 2] | 1 | 2 | 2 | no-rest | +| tst.ts:42:25:42:39 | [1, 2] as const | readonly [1, 2] | 0 | 1 | 2 | no-rest | +| tst.ts:42:25:42:39 | [1, 2] as const | readonly [1, 2] | 1 | 2 | 2 | no-rest | diff --git a/javascript/ql/test/library-tests/TypeScript/Types/tst.ts b/javascript/ql/test/library-tests/TypeScript/Types/tst.ts index d1228fa38d0..7e426a30faa 100644 --- a/javascript/ql/test/library-tests/TypeScript/Types/tst.ts +++ b/javascript/ql/test/library-tests/TypeScript/Types/tst.ts @@ -37,4 +37,7 @@ 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 +let unknownType: unknown; + +let constArrayLiteral = [1, 2] as const; +let constObjectLiteral = { foo: "foo" } as const;