Skip to content

Commit 8764498

Browse files
committed
feat(typescript): support import types
1 parent f92491f commit 8764498

10 files changed

+135
-17
lines changed

docs/CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ Semantic Versioning.
1818
* TypeScript support (still experimental):
1919
* Invalid recursive type definitions such as `type T = T;` now report
2020
[E0384][] ("cannot use type directly in its own definition").
21+
* quick-lint-js now recognizes `import` types.
2122
* quick-lint-js now recognizes `.d.ts` files:
2223
* CLI: `--language=experimental-typescript-definition`
2324
* LSP server: `typescriptdefinition`; `typescript` will detect from the URI,

po/messages.pot

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1293,6 +1293,10 @@ msgstr ""
12931293
msgid "'export' keyword here"
12941294
msgstr ""
12951295

1296+
#: src/quick-lint-js/diag/diagnostic-metadata-generated.cpp
1297+
msgid "missing exported name in import type"
1298+
msgstr ""
1299+
12961300
#: src/quick-lint-js/diag/diagnostic-metadata-generated.cpp
12971301
msgid "'extends' must be before 'implements'"
12981302
msgstr ""

src/quick-lint-js/diag/diagnostic-metadata-generated.cpp

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3884,6 +3884,20 @@ const QLJS_CONSTINIT Diagnostic_Info all_diagnostic_infos[] = {
38843884
},
38853885
},
38863886

3887+
// Diag_TypeScript_Import_Type_Missing_Export_Name
3888+
{
3889+
.code = 391,
3890+
.severity = Diagnostic_Severity::error,
3891+
.message_formats = {
3892+
QLJS_TRANSLATABLE("missing exported name in import type"),
3893+
},
3894+
.message_args = {
3895+
{
3896+
Diagnostic_Message_Arg_Info(offsetof(Diag_TypeScript_Import_Type_Missing_Export_Name, expected_export_name), Diagnostic_Arg_Type::source_code_span),
3897+
},
3898+
},
3899+
},
3900+
38873901
// Diag_TypeScript_Implements_Must_Be_After_Extends
38883902
{
38893903
.code = 246,

src/quick-lint-js/diag/diagnostic-metadata-generated.h

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -272,6 +272,7 @@ namespace quick_lint_js {
272272
QLJS_DIAG_TYPE_NAME(Diag_TypeScript_Enum_Member_Name_Cannot_Be_Number) \
273273
QLJS_DIAG_TYPE_NAME(Diag_TypeScript_Enum_Value_Must_Be_Constant) \
274274
QLJS_DIAG_TYPE_NAME(Diag_TypeScript_Export_Equal_Not_Allowed_In_JavaScript) \
275+
QLJS_DIAG_TYPE_NAME(Diag_TypeScript_Import_Type_Missing_Export_Name) \
275276
QLJS_DIAG_TYPE_NAME(Diag_TypeScript_Implements_Must_Be_After_Extends) \
276277
QLJS_DIAG_TYPE_NAME(Diag_TypeScript_Import_Alias_Not_Allowed_In_JavaScript) \
277278
QLJS_DIAG_TYPE_NAME(Diag_TypeScript_Index_Signature_Cannot_Be_Method) \
@@ -408,7 +409,7 @@ namespace quick_lint_js {
408409
/* END */
409410
// clang-format on
410411

411-
inline constexpr int Diag_Type_Count = 394;
412+
inline constexpr int Diag_Type_Count = 395;
412413

413414
extern const Diagnostic_Info all_diagnostic_infos[Diag_Type_Count];
414415
}

src/quick-lint-js/diag/diagnostic-types-2.h

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1991,6 +1991,14 @@ struct Diag_TypeScript_Export_Equal_Not_Allowed_In_JavaScript {
19911991
Source_Code_Span export_keyword;
19921992
};
19931993

1994+
struct Diag_TypeScript_Import_Type_Missing_Export_Name {
1995+
[[qljs::diag("E0391", Diagnostic_Severity::error)]] //
1996+
[[qljs::message("missing exported name in import type",
1997+
ARG(expected_export_name))]] //
1998+
Source_Code_Span expected_export_name;
1999+
Source_Code_Span import_keyword;
2000+
};
2001+
19942002
struct Diag_TypeScript_Implements_Must_Be_After_Extends {
19952003
[[qljs::diag("E0246", Diagnostic_Severity::error)]] //
19962004
[[qljs::message("'extends' must be before 'implements'",

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

Lines changed: 53 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,27 @@ void Parser::parse_and_visit_typescript_type_expression(
6464
this->skip();
6565
}
6666

67+
auto maybe_parse_dots_after_generic_arguments = [this]() -> void {
68+
while (this->peek().type == Token_Type::dot) {
69+
Source_Code_Span dot_span = this->peek().span();
70+
this->skip();
71+
switch (this->peek().type) {
72+
QLJS_CASE_KEYWORD:
73+
case Token_Type::identifier:
74+
this->diag_reporter_->report(
75+
Diag_Dot_Not_Allowed_After_Generic_Arguments_In_Type{
76+
.dot = dot_span,
77+
.property_name = this->peek().span(),
78+
});
79+
this->skip();
80+
break;
81+
default:
82+
QLJS_PARSER_UNIMPLEMENTED();
83+
break;
84+
}
85+
}
86+
};
87+
6788
again:
6889
std::optional<Source_Code_Span> readonly_keyword;
6990
if (this->peek().type == Token_Type::kw_readonly) {
@@ -439,31 +460,51 @@ void Parser::parse_and_visit_typescript_type_expression(
439460
if (this->peek().type == Token_Type::less) {
440461
this->parse_and_visit_typescript_generic_arguments(v);
441462
}
463+
maybe_parse_dots_after_generic_arguments();
464+
break;
465+
466+
// keyof Type
467+
case Token_Type::kw_keyof:
468+
this->skip();
469+
this->parse_and_visit_typescript_type_expression(v, parse_options);
470+
break;
471+
472+
// import("module").Name
473+
case Token_Type::kw_import: {
474+
Source_Code_Span import_keyword_span = this->peek().span();
475+
this->skip();
476+
QLJS_PARSER_UNIMPLEMENTED_IF_NOT_TOKEN(Token_Type::left_paren);
477+
this->skip();
478+
QLJS_PARSER_UNIMPLEMENTED_IF_NOT_TOKEN(Token_Type::string);
479+
this->skip();
480+
QLJS_PARSER_UNIMPLEMENTED_IF_NOT_TOKEN(Token_Type::right_paren);
481+
const Char8 *right_paren_end = this->peek().end;
482+
this->skip();
483+
if (this->peek().type != Token_Type::dot) {
484+
this->diag_reporter_->report(
485+
Diag_TypeScript_Import_Type_Missing_Export_Name{
486+
.expected_export_name = Source_Code_Span::unit(right_paren_end),
487+
.import_keyword = import_keyword_span,
488+
});
489+
}
442490
while (this->peek().type == Token_Type::dot) {
443-
Source_Code_Span dot_span = this->peek().span();
444491
this->skip();
445492
switch (this->peek().type) {
446493
QLJS_CASE_KEYWORD:
447494
case Token_Type::identifier:
448-
this->diag_reporter_->report(
449-
Diag_Dot_Not_Allowed_After_Generic_Arguments_In_Type{
450-
.dot = dot_span,
451-
.property_name = this->peek().span(),
452-
});
453495
this->skip();
454496
break;
455497
default:
456498
QLJS_PARSER_UNIMPLEMENTED();
457499
break;
458500
}
459501
}
502+
if (this->peek().type == Token_Type::less) {
503+
this->parse_and_visit_typescript_generic_arguments(v);
504+
}
505+
maybe_parse_dots_after_generic_arguments();
460506
break;
461-
462-
// keyof Type
463-
case Token_Type::kw_keyof:
464-
this->skip();
465-
this->parse_and_visit_typescript_type_expression(v, parse_options);
466-
break;
507+
}
467508

468509
case Token_Type::comma:
469510
case Token_Type::end_of_file:

src/quick-lint-js/i18n/translation-table-generated.cpp

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -324,7 +324,8 @@ const Translation_Table translation_data = {
324324
{33, 10, 42, 37, 31, 35}, //
325325
{38, 14, 46, 41, 38, 39}, //
326326
{35, 13, 45, 40, 37, 38}, //
327-
{36, 34, 39, 35, 40, 35}, //
327+
{0, 0, 0, 0, 0, 35}, //
328+
{36, 34, 39, 35, 40, 37}, //
328329
{0, 0, 0, 0, 0, 39}, //
329330
{33, 7, 40, 40, 33, 58}, //
330331
{24, 11, 33, 22, 23, 24}, //
@@ -2073,6 +2074,7 @@ const Translation_Table translation_data = {
20732074
u8"missing condition for switch statement\0"
20742075
u8"missing condition for while statement\0"
20752076
u8"missing end of array; expected ']'\0"
2077+
u8"missing exported name in import type\0"
20762078
u8"missing expression between parentheses\0"
20772079
u8"missing expression in placeholder within template literal\0"
20782080
u8"missing for loop header\0"

src/quick-lint-js/i18n/translation-table-generated.h

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -18,8 +18,8 @@ namespace quick_lint_js {
1818
using namespace std::literals::string_view_literals;
1919

2020
constexpr std::uint32_t translation_table_locale_count = 5;
21-
constexpr std::uint16_t translation_table_mapping_table_size = 473;
22-
constexpr std::size_t translation_table_string_table_size = 77783;
21+
constexpr std::uint16_t translation_table_mapping_table_size = 474;
22+
constexpr std::size_t translation_table_string_table_size = 77820;
2323
constexpr std::size_t translation_table_locale_table_size = 35;
2424

2525
QLJS_CONSTEVAL std::uint16_t translation_table_const_look_up(
@@ -339,6 +339,7 @@ QLJS_CONSTEVAL std::uint16_t translation_table_const_look_up(
339339
"missing condition for switch statement"sv,
340340
"missing condition for while statement"sv,
341341
"missing end of array; expected ']'"sv,
342+
"missing exported name in import type"sv,
342343
"missing expression between parentheses"sv,
343344
"missing expression in placeholder within template literal"sv,
344345
"missing for loop header"sv,

src/quick-lint-js/i18n/translation-table-test-generated.h

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@ struct Translated_String {
2727
};
2828

2929
// clang-format off
30-
inline const Translated_String test_translation_table[472] = {
30+
inline const Translated_String test_translation_table[473] = {
3131
{
3232
"\"global-groups\" entries must be strings"_translatable,
3333
u8"\"global-groups\" entries must be strings",
@@ -3471,6 +3471,17 @@ inline const Translated_String test_translation_table[472] = {
34713471
u8"saknar slut av lista; f\u00f6rv\u00e4ntades ']'",
34723472
},
34733473
},
3474+
{
3475+
"missing exported name in import type"_translatable,
3476+
u8"missing exported name in import type",
3477+
{
3478+
u8"missing exported name in import type",
3479+
u8"missing exported name in import type",
3480+
u8"missing exported name in import type",
3481+
u8"missing exported name in import type",
3482+
u8"missing exported name in import type",
3483+
},
3484+
},
34743485
{
34753486
"missing expression between parentheses"_translatable,
34763487
u8"missing expression between parentheses",

test/test-parse-typescript-type.cpp

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2027,6 +2027,41 @@ TEST_F(Test_Parse_TypeScript_Type, typeof_allows_array_and_indexed) {
20272027
}
20282028
}
20292029

2030+
TEST_F(Test_Parse_TypeScript_Type, imported_type) {
2031+
{
2032+
Spy_Visitor p = test_parse_and_visit_typescript_type_expression(
2033+
u8"import('mymod').MyClass"_sv, no_diags, typescript_options);
2034+
EXPECT_THAT(p.visits, IsEmpty());
2035+
}
2036+
2037+
{
2038+
Spy_Visitor p = test_parse_and_visit_typescript_type_expression(
2039+
u8"import('mymod').mynamespace.MyClass"_sv, no_diags,
2040+
typescript_options);
2041+
EXPECT_THAT(p.visits, IsEmpty());
2042+
}
2043+
2044+
{
2045+
Spy_Visitor p = test_parse_and_visit_typescript_type_expression(
2046+
u8"import('mymod').MyClass<T>"_sv, no_diags, typescript_options);
2047+
EXPECT_THAT(p.visits, ElementsAreArray({
2048+
"visit_variable_type_use", // T
2049+
}));
2050+
}
2051+
2052+
test_parse_and_visit_typescript_type_expression(
2053+
u8"import('mymod').MyClass<T>.prop"_sv, //
2054+
u8" ^^^^ Diag_Dot_Not_Allowed_After_Generic_Arguments_In_Type.property_name\n"_diag
2055+
u8" ^ .dot"_diag,
2056+
typescript_options);
2057+
2058+
test_parse_and_visit_typescript_type_expression(
2059+
u8"import('mymod')"_sv, //
2060+
u8" ` Diag_TypeScript_Import_Type_Missing_Export_Name.expected_export_name\n"_diag
2061+
u8"^^^^^^ .import_keyword"_diag,
2062+
typescript_options);
2063+
}
2064+
20302065
TEST_F(Test_Parse_TypeScript_Type, keyof) {
20312066
{
20322067
Test_Parser p(u8"keyof Type"_sv, typescript_options);

0 commit comments

Comments
 (0)