Skip to content

Commit 82f50c7

Browse files
committed
fix(typescript): parse 'import type * from ...'
1 parent f0ef6e1 commit 82f50c7

File tree

3 files changed

+125
-3
lines changed

3 files changed

+125
-3
lines changed

docs/CHANGELOG.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -85,6 +85,8 @@ Semantic Versioning.
8585
parsed correctly.
8686
* `import("modulename")` in a type is now allowed and no longer falsely
8787
reports a diagnostic.
88+
* `import type * from 'othermodule';` no longer crashes quick-lint-js with an
89+
assertion failure.
8890
* Generic call signatures are now parsed correctly when using a semicolon-free
8991
coding style.
9092
* Interface index signatures and computed property names in interfaces are now

src/quick-lint-js/fe/parse-statement.cpp

Lines changed: 18 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1182,6 +1182,7 @@ void Parser::parse_and_visit_export(Parse_Visitor_Base &v,
11821182

11831183
// export * from "module";
11841184
// export * as name from "module";
1185+
export_star:
11851186
case Token_Type::star:
11861187
// Do not set is_current_typescript_namespace_non_empty_. See
11871188
// NOTE[ambiguous-ambient-statement-in-namespace].
@@ -1412,8 +1413,9 @@ void Parser::parse_and_visit_export(Parse_Visitor_Base &v,
14121413
// Do not set is_current_typescript_namespace_non_empty_.
14131414
Source_Code_Span type_keyword = this->peek().span();
14141415
this->skip();
1415-
if (this->peek().type == Token_Type::left_curly) {
1416-
// export type {A, B as C};
1416+
switch (this->peek().type) {
1417+
// export type {A, B as C};
1418+
case Token_Type::left_curly:
14171419
// Do not set is_current_typescript_namespace_non_empty_. See
14181420
// NOTE[ambiguous-ambient-statement-in-namespace].
14191421
typescript_type_only_keyword = type_keyword;
@@ -1424,10 +1426,23 @@ void Parser::parse_and_visit_export(Parse_Visitor_Base &v,
14241426
});
14251427
}
14261428
goto named_export_list;
1427-
} else {
1429+
1430+
// export type * from 'othermod';
1431+
case Token_Type::star:
1432+
typescript_type_only_keyword = type_keyword;
1433+
if (!this->options_.typescript) {
1434+
this->diag_reporter_->report(
1435+
Diag_TypeScript_Type_Export_Not_Allowed_In_JavaScript{
1436+
.type_keyword = type_keyword,
1437+
});
1438+
}
1439+
goto export_star;
1440+
1441+
default:
14281442
// export type A = B;
14291443
// Do not set is_current_typescript_namespace_non_empty_.
14301444
this->parse_and_visit_typescript_type_alias(v, type_keyword);
1445+
break;
14311446
}
14321447
break;
14331448
}

test/test-parse-typescript-module.cpp

Lines changed: 105 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@
2121
#include <vector>
2222

2323
using ::testing::ElementsAreArray;
24+
using ::testing::IsEmpty;
2425

2526
namespace quick_lint_js {
2627
namespace {
@@ -334,6 +335,22 @@ TEST_F(Test_Parse_TypeScript_Module,
334335
"visit_end_of_module",
335336
}));
336337
}
338+
339+
{
340+
Spy_Visitor p = test_parse_and_visit_statement(
341+
u8"export type {T} from 'othermod';"_sv, //
342+
u8" ^^^^ Diag_TypeScript_Type_Export_Not_Allowed_In_JavaScript"_diag, //
343+
javascript_options);
344+
EXPECT_THAT(p.visits, IsEmpty());
345+
}
346+
347+
{
348+
Spy_Visitor p = test_parse_and_visit_statement(
349+
u8"export type * from 'othermod';"_sv, //
350+
u8" ^^^^ Diag_TypeScript_Type_Export_Not_Allowed_In_JavaScript"_diag, //
351+
javascript_options);
352+
EXPECT_THAT(p.visits, IsEmpty());
353+
}
337354
}
338355

339356
TEST_F(Test_Parse_TypeScript_Module, inline_type_export) {
@@ -409,6 +426,94 @@ TEST_F(Test_Parse_TypeScript_Module, mixed_inline_type_and_type_only_export) {
409426
}
410427
}
411428

429+
TEST_F(Test_Parse_TypeScript_Module, export_type_from) {
430+
{
431+
Spy_Visitor p = test_parse_and_visit_statement(
432+
u8"export type * from 'other';"_sv, no_diags, typescript_options);
433+
EXPECT_THAT(p.visits, IsEmpty());
434+
}
435+
436+
{
437+
Spy_Visitor p = test_parse_and_visit_statement(
438+
u8"export type * as mother from 'other';"_sv, no_diags,
439+
typescript_options);
440+
EXPECT_THAT(p.visits, IsEmpty());
441+
}
442+
443+
{
444+
Spy_Visitor p = test_parse_and_visit_statement(
445+
u8"export type * as 'mother' from 'other';"_sv, no_diags,
446+
typescript_options);
447+
EXPECT_THAT(p.visits, IsEmpty());
448+
}
449+
450+
{
451+
Spy_Visitor p = test_parse_and_visit_statement(
452+
u8"export type {} from 'other';"_sv, no_diags, typescript_options);
453+
EXPECT_THAT(p.visits, IsEmpty());
454+
}
455+
456+
{
457+
Spy_Visitor p = test_parse_and_visit_statement(
458+
u8"export type {util1, util2, util3} from 'other';"_sv, no_diags,
459+
typescript_options);
460+
EXPECT_THAT(p.visits, IsEmpty());
461+
}
462+
463+
{
464+
Spy_Visitor p = test_parse_and_visit_statement(
465+
u8"export type {readFileSync as readFile} from 'fs';"_sv, no_diags,
466+
typescript_options);
467+
EXPECT_THAT(p.visits, IsEmpty());
468+
}
469+
470+
{
471+
Spy_Visitor p = test_parse_and_visit_statement(
472+
u8"export type {promises as default} from 'fs';"_sv, no_diags,
473+
typescript_options);
474+
EXPECT_THAT(p.visits, IsEmpty());
475+
}
476+
477+
for (String8 keyword : keywords) {
478+
Padded_String code(
479+
concat(u8"export type {"_sv, keyword, u8"} from 'other';"_sv));
480+
SCOPED_TRACE(code);
481+
Spy_Visitor p = test_parse_and_visit_statement(code.string_view(), no_diags,
482+
typescript_options);
483+
EXPECT_THAT(p.visits, IsEmpty());
484+
}
485+
486+
{
487+
// Keywords are legal, even if Unicode-escaped.
488+
Spy_Visitor p = test_parse_and_visit_statement(
489+
u8"export type {\\u{76}ar} from 'fs';"_sv, no_diags,
490+
typescript_options);
491+
EXPECT_THAT(p.visits, IsEmpty());
492+
}
493+
494+
{
495+
// Keywords are legal, even if Unicode-escaped.
496+
Spy_Visitor p = test_parse_and_visit_statement(
497+
u8"export type {\\u{76}ar as \\u{69}f} from 'fs';"_sv, no_diags,
498+
typescript_options);
499+
EXPECT_THAT(p.visits, IsEmpty());
500+
}
501+
502+
{
503+
Spy_Visitor p = test_parse_and_visit_statement(
504+
u8"export type {'name'} from 'other';"_sv, no_diags,
505+
typescript_options);
506+
EXPECT_THAT(p.visits, IsEmpty());
507+
}
508+
509+
{
510+
Spy_Visitor p = test_parse_and_visit_statement(
511+
u8"export type {'name' as 'othername'} from 'other';"_sv, no_diags,
512+
typescript_options);
513+
EXPECT_THAT(p.visits, IsEmpty());
514+
}
515+
}
516+
412517
TEST_F(Test_Parse_TypeScript_Module, import_require) {
413518
{
414519
Spy_Visitor p = test_parse_and_visit_module(

0 commit comments

Comments
 (0)