diff --git a/javascript/change-notes/2021-04-15-typescript-template-literal-type-crash.md b/javascript/change-notes/2021-04-15-typescript-template-literal-type-crash.md new file mode 100644 index 000000000000..e08dbd54dd69 --- /dev/null +++ b/javascript/change-notes/2021-04-15-typescript-template-literal-type-crash.md @@ -0,0 +1,3 @@ +lgtm,codescanning +* Fixed a bug that could cause extraction to fail when extracting a TypeScript + code base containing a template literal type without substitutions. diff --git a/javascript/extractor/src/com/semmle/ts/extractor/TypeScriptASTConverter.java b/javascript/extractor/src/com/semmle/ts/extractor/TypeScriptASTConverter.java index 36d4f8886187..f8ad99915c90 100644 --- a/javascript/extractor/src/com/semmle/ts/extractor/TypeScriptASTConverter.java +++ b/javascript/extractor/src/com/semmle/ts/extractor/TypeScriptASTConverter.java @@ -5,7 +5,6 @@ import java.util.List; import java.util.regex.Matcher; import java.util.regex.Pattern; -import java.util.stream.Collectors; import com.google.gson.JsonArray; import com.google.gson.JsonElement; @@ -1001,10 +1000,10 @@ private Node convertConditionalExpression(JsonObject node, SourceLocation loc) t private Node convertConditionalType(JsonObject node, SourceLocation loc) throws ParseError { return new ConditionalTypeExpr( loc, - convertChild(node, "checkType"), - convertChild(node, "extendsType"), - convertChild(node, "trueType"), - convertChild(node, "falseType")); + convertChildAsType(node, "checkType"), + convertChildAsType(node, "extendsType"), + convertChildAsType(node, "trueType"), + convertChildAsType(node, "falseType")); } private SourceLocation getSourceRange(Position from, Position to) { @@ -1613,6 +1612,10 @@ private Node convertLiteralType(JsonObject node, SourceLocation loc) throws Pars literal = new Literal(loc, arg.getTokenType(), "-" + arg.getValue()); } } + if (literal instanceof TemplateLiteral) { + // A LiteralType containing a NoSubstitutionTemplateLiteral must produce a TemplateLiteralTypeExpr + return new TemplateLiteralTypeExpr(literal.getLoc(), new ArrayList<>(), ((TemplateLiteral)literal).getQuasis()); + } return literal; } @@ -1842,7 +1845,7 @@ private Node convertOmittedExpression() { } private Node convertOptionalType(JsonObject node, SourceLocation loc) throws ParseError { - return new OptionalTypeExpr(loc, convertChild(node, "type")); + return new OptionalTypeExpr(loc, convertChildAsType(node, "type")); } private ITypeExpression asType(Node node) { diff --git a/javascript/extractor/tests/ts/output/trap/importNonStrings.ts.trap b/javascript/extractor/tests/ts/output/trap/importNonStrings.ts.trap index c5fc95ddcb86..af044be98c89 100644 --- a/javascript/extractor/tests/ts/output/trap/importNonStrings.ts.trap +++ b/javascript/extractor/tests/ts/output/trap/importNonStrings.ts.trap @@ -9,34 +9,249 @@ hasLocation(#10000,#10002) #20000=@"global_scope" scopes(#20000,0) #20001=@"script;{#10000},1,1" -toplevels(#20001,0) -#20002=@"loc,{#10000},1,1,1,1" -locations_default(#20002,#10000,1,1,1,1) -hasLocation(#20001,#20002) -#20003=* -js_parse_errors(#20003,#20001,"Error: Unsupported syntax in import","type Y = import(`Foo`); -") -#20004=@"loc,{#10000},2,10,2,10" -locations_default(#20004,#10000,2,10,2,10) -hasLocation(#20003,#20004) -#20005=* -lines(#20005,#20001,"type X = import(3);"," +#20002=* +lines(#20002,#20001,"type X = import(3);"," ") -#20006=@"loc,{#10000},1,1,1,19" -locations_default(#20006,#10000,1,1,1,19) -hasLocation(#20005,#20006) -#20007=* -lines(#20007,#20001,"type Y = import(`Foo`);"," +#20003=@"loc,{#10000},1,1,1,19" +locations_default(#20003,#10000,1,1,1,19) +hasLocation(#20002,#20003) +#20004=* +lines(#20004,#20001,"type Y = import(`Foo`);"," ") -#20008=@"loc,{#10000},2,1,2,23" -locations_default(#20008,#10000,2,1,2,23) -hasLocation(#20007,#20008) -#20009=* -lines(#20009,#20001,"type Z = import(Y);"," +#20005=@"loc,{#10000},2,1,2,23" +locations_default(#20005,#10000,2,1,2,23) +hasLocation(#20004,#20005) +#20006=* +lines(#20006,#20001,"type Z = import(Y);"," ") -#20010=@"loc,{#10000},3,1,3,19" -locations_default(#20010,#10000,3,1,3,19) -hasLocation(#20009,#20010) -numlines(#20001,3,0,0) -numlines(#10000,3,0,0) +#20007=@"loc,{#10000},3,1,3,19" +locations_default(#20007,#10000,3,1,3,19) +hasLocation(#20006,#20007) +numlines(#20001,3,3,0) +#20008=* +tokeninfo(#20008,7,#20001,0,"type") +#20009=@"loc,{#10000},1,1,1,4" +locations_default(#20009,#10000,1,1,1,4) +hasLocation(#20008,#20009) +#20010=* +tokeninfo(#20010,6,#20001,1,"X") +#20011=@"loc,{#10000},1,6,1,6" +locations_default(#20011,#10000,1,6,1,6) +hasLocation(#20010,#20011) +#20012=* +tokeninfo(#20012,8,#20001,2,"=") +#20013=@"loc,{#10000},1,8,1,8" +locations_default(#20013,#10000,1,8,1,8) +hasLocation(#20012,#20013) +#20014=* +tokeninfo(#20014,7,#20001,3,"import") +#20015=@"loc,{#10000},1,10,1,15" +locations_default(#20015,#10000,1,10,1,15) +hasLocation(#20014,#20015) +#20016=* +tokeninfo(#20016,8,#20001,4,"(") +#20017=@"loc,{#10000},1,16,1,16" +locations_default(#20017,#10000,1,16,1,16) +hasLocation(#20016,#20017) +#20018=* +tokeninfo(#20018,3,#20001,5,"3") +#20019=@"loc,{#10000},1,17,1,17" +locations_default(#20019,#10000,1,17,1,17) +hasLocation(#20018,#20019) +#20020=* +tokeninfo(#20020,8,#20001,6,")") +#20021=@"loc,{#10000},1,18,1,18" +locations_default(#20021,#10000,1,18,1,18) +hasLocation(#20020,#20021) +#20022=* +tokeninfo(#20022,8,#20001,7,";") +#20023=@"loc,{#10000},1,19,1,19" +locations_default(#20023,#10000,1,19,1,19) +hasLocation(#20022,#20023) +#20024=* +tokeninfo(#20024,7,#20001,8,"type") +#20025=@"loc,{#10000},2,1,2,4" +locations_default(#20025,#10000,2,1,2,4) +hasLocation(#20024,#20025) +#20026=* +tokeninfo(#20026,6,#20001,9,"Y") +#20027=@"loc,{#10000},2,6,2,6" +locations_default(#20027,#10000,2,6,2,6) +hasLocation(#20026,#20027) +#20028=* +tokeninfo(#20028,8,#20001,10,"=") +#20029=@"loc,{#10000},2,8,2,8" +locations_default(#20029,#10000,2,8,2,8) +hasLocation(#20028,#20029) +#20030=* +tokeninfo(#20030,7,#20001,11,"import") +#20031=@"loc,{#10000},2,10,2,15" +locations_default(#20031,#10000,2,10,2,15) +hasLocation(#20030,#20031) +#20032=* +tokeninfo(#20032,8,#20001,12,"(") +#20033=@"loc,{#10000},2,16,2,16" +locations_default(#20033,#10000,2,16,2,16) +hasLocation(#20032,#20033) +#20034=* +tokeninfo(#20034,4,#20001,13,"`Foo`") +#20035=@"loc,{#10000},2,17,2,21" +locations_default(#20035,#10000,2,17,2,21) +hasLocation(#20034,#20035) +#20036=* +tokeninfo(#20036,8,#20001,14,")") +#20037=@"loc,{#10000},2,22,2,22" +locations_default(#20037,#10000,2,22,2,22) +hasLocation(#20036,#20037) +#20038=* +tokeninfo(#20038,8,#20001,15,";") +#20039=@"loc,{#10000},2,23,2,23" +locations_default(#20039,#10000,2,23,2,23) +hasLocation(#20038,#20039) +#20040=* +tokeninfo(#20040,7,#20001,16,"type") +#20041=@"loc,{#10000},3,1,3,4" +locations_default(#20041,#10000,3,1,3,4) +hasLocation(#20040,#20041) +#20042=* +tokeninfo(#20042,6,#20001,17,"Z") +#20043=@"loc,{#10000},3,6,3,6" +locations_default(#20043,#10000,3,6,3,6) +hasLocation(#20042,#20043) +#20044=* +tokeninfo(#20044,8,#20001,18,"=") +#20045=@"loc,{#10000},3,8,3,8" +locations_default(#20045,#10000,3,8,3,8) +hasLocation(#20044,#20045) +#20046=* +tokeninfo(#20046,7,#20001,19,"import") +#20047=@"loc,{#10000},3,10,3,15" +locations_default(#20047,#10000,3,10,3,15) +hasLocation(#20046,#20047) +#20048=* +tokeninfo(#20048,8,#20001,20,"(") +#20049=@"loc,{#10000},3,16,3,16" +locations_default(#20049,#10000,3,16,3,16) +hasLocation(#20048,#20049) +#20050=* +tokeninfo(#20050,6,#20001,21,"Y") +#20051=@"loc,{#10000},3,17,3,17" +locations_default(#20051,#10000,3,17,3,17) +hasLocation(#20050,#20051) +#20052=* +tokeninfo(#20052,8,#20001,22,")") +#20053=@"loc,{#10000},3,18,3,18" +locations_default(#20053,#10000,3,18,3,18) +hasLocation(#20052,#20053) +#20054=* +tokeninfo(#20054,8,#20001,23,";") +#20055=@"loc,{#10000},3,19,3,19" +locations_default(#20055,#10000,3,19,3,19) +hasLocation(#20054,#20055) +#20056=* +tokeninfo(#20056,0,#20001,24,"") +#20057=@"loc,{#10000},4,1,4,0" +locations_default(#20057,#10000,4,1,4,0) +hasLocation(#20056,#20057) +toplevels(#20001,0) +#20058=@"loc,{#10000},1,1,4,0" +locations_default(#20058,#10000,1,1,4,0) +hasLocation(#20001,#20058) +#20059=@"local_type_name;{X};{#20000}" +local_type_names(#20059,"X",#20000) +#20060=@"local_type_name;{Y};{#20000}" +local_type_names(#20060,"Y",#20000) +#20061=@"local_type_name;{Z};{#20000}" +local_type_names(#20061,"Z",#20000) +#20062=* +stmts(#20062,35,#20001,0,"type X = import(3);") +hasLocation(#20062,#20003) +stmt_containers(#20062,#20001) +#20063=* +typeexprs(#20063,1,#20062,0,"X") +hasLocation(#20063,#20011) +enclosing_stmt(#20063,#20062) +expr_containers(#20063,#20001) +literals("X","X",#20063) +typedecl(#20063,#20059) +#20064=* +typeexprs(#20064,30,#20062,1,"import(3)") +#20065=@"loc,{#10000},1,10,1,18" +locations_default(#20065,#10000,1,10,1,18) +hasLocation(#20064,#20065) +enclosing_stmt(#20064,#20062) +expr_containers(#20064,#20001) +#20066=* +typeexprs(#20066,4,#20064,0,"3") +hasLocation(#20066,#20019) +enclosing_stmt(#20066,#20062) +expr_containers(#20066,#20001) +literals("3","3",#20066) +#20067=* +stmts(#20067,35,#20001,1,"type Y ... `Foo`);") +hasLocation(#20067,#20005) +stmt_containers(#20067,#20001) +#20068=* +typeexprs(#20068,1,#20067,0,"Y") +hasLocation(#20068,#20027) +enclosing_stmt(#20068,#20067) +expr_containers(#20068,#20001) +literals("Y","Y",#20068) +typedecl(#20068,#20060) +#20069=* +typeexprs(#20069,30,#20067,1,"import(`Foo`)") +#20070=@"loc,{#10000},2,10,2,22" +locations_default(#20070,#10000,2,10,2,22) +hasLocation(#20069,#20070) +enclosing_stmt(#20069,#20067) +expr_containers(#20069,#20001) +#20071=* +typeexprs(#20071,37,#20069,0,"`Foo`") +hasLocation(#20071,#20035) +enclosing_stmt(#20071,#20067) +expr_containers(#20071,#20001) +#20072=* +typeexprs(#20072,3,#20071,0,"`Foo`") +hasLocation(#20072,#20035) +enclosing_stmt(#20072,#20067) +expr_containers(#20072,#20001) +literals("Foo","Foo",#20072) +#20073=* +stmts(#20073,35,#20001,2,"type Z = import(Y);") +hasLocation(#20073,#20007) +stmt_containers(#20073,#20001) +#20074=* +typeexprs(#20074,1,#20073,0,"Z") +hasLocation(#20074,#20043) +enclosing_stmt(#20074,#20073) +expr_containers(#20074,#20001) +literals("Z","Z",#20074) +typedecl(#20074,#20061) +#20075=* +typeexprs(#20075,30,#20073,1,"import(Y)") +#20076=@"loc,{#10000},3,10,3,18" +locations_default(#20076,#10000,3,10,3,18) +hasLocation(#20075,#20076) +enclosing_stmt(#20075,#20073) +expr_containers(#20075,#20001) +#20077=* +typeexprs(#20077,0,#20075,0,"Y") +hasLocation(#20077,#20051) +enclosing_stmt(#20077,#20073) +expr_containers(#20077,#20001) +literals("Y","Y",#20077) +typebind(#20077,#20060) +#20078=* +entry_cfg_node(#20078,#20001) +#20079=@"loc,{#10000},1,1,1,0" +locations_default(#20079,#10000,1,1,1,0) +hasLocation(#20078,#20079) +#20080=* +exit_cfg_node(#20080,#20001) +hasLocation(#20080,#20057) +successor(#20073,#20080) +successor(#20067,#20073) +successor(#20062,#20067) +successor(#20078,#20062) +numlines(#10000,3,3,0) filetype(#10000,"typescript") diff --git a/javascript/ql/test/library-tests/TypeScript/RegressionTests/TemplateTypes/TemplateTypes.expected b/javascript/ql/test/library-tests/TypeScript/RegressionTests/TemplateTypes/TemplateTypes.expected new file mode 100644 index 000000000000..e713b3e48049 --- /dev/null +++ b/javascript/ql/test/library-tests/TypeScript/RegressionTests/TemplateTypes/TemplateTypes.expected @@ -0,0 +1,6 @@ +| tst.ts:2:11:2:21 | `foo ${T1}` | +| tst.ts:4:45:4:49 | `foo` | +| tst.ts:4:53:4:57 | `bar` | +| tst.ts:5:46:5:50 | `foo` | +| tst.ts:5:54:5:63 | `bar ${K}` | +| tst.ts:7:15:7:19 | `foo` | diff --git a/javascript/ql/test/library-tests/TypeScript/RegressionTests/TemplateTypes/TemplateTypes.ql b/javascript/ql/test/library-tests/TypeScript/RegressionTests/TemplateTypes/TemplateTypes.ql new file mode 100644 index 000000000000..f08f5a201dda --- /dev/null +++ b/javascript/ql/test/library-tests/TypeScript/RegressionTests/TemplateTypes/TemplateTypes.ql @@ -0,0 +1,3 @@ +import javascript + +query TemplateLiteralTypeExpr literalType() { any() } diff --git a/javascript/ql/test/library-tests/TypeScript/RegressionTests/TemplateTypes/tsconfig.json b/javascript/ql/test/library-tests/TypeScript/RegressionTests/TemplateTypes/tsconfig.json new file mode 100644 index 000000000000..82194fc7ab06 --- /dev/null +++ b/javascript/ql/test/library-tests/TypeScript/RegressionTests/TemplateTypes/tsconfig.json @@ -0,0 +1,3 @@ +{ + "include": ["."] +} diff --git a/javascript/ql/test/library-tests/TypeScript/RegressionTests/TemplateTypes/tst.ts b/javascript/ql/test/library-tests/TypeScript/RegressionTests/TemplateTypes/tst.ts new file mode 100644 index 000000000000..21718ea3b340 --- /dev/null +++ b/javascript/ql/test/library-tests/TypeScript/RegressionTests/TemplateTypes/tst.ts @@ -0,0 +1,7 @@ +type T1 = 'foo' | 'bar'; +type T2 = `foo ${T1}`; + +type FooToBar = K extends `foo` ? `bar` : K; +type FooToBar2 = K extends `foo` ? `bar ${K}` : K; + +type Tuple = [`foo`?];