Skip to content

Commit

Permalink
feat(typescript): parse abstract constructor types
Browse files Browse the repository at this point in the history
  • Loading branch information
strager committed Dec 27, 2023
1 parent 19d96a9 commit af790ca
Show file tree
Hide file tree
Showing 10 changed files with 157 additions and 6 deletions.
2 changes: 2 additions & 0 deletions docs/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,8 @@ Semantic Versioning.
* Assigning to a class now reports [E0003][] ("cannot assign to class").
* Definite assignment assertions (`!` after a variable name in `let` or `var`)
is now supported.
* Abstract constructor types (such as `abstract new () => C`) are now
supported.

### Fixed

Expand Down
4 changes: 4 additions & 0 deletions po/messages.pot
Original file line number Diff line number Diff line change
Expand Up @@ -1205,6 +1205,10 @@ msgstr ""
msgid "missing name or parentheses for function"
msgstr ""

#: src/quick-lint-js/diag/diagnostic-metadata-generated.cpp
msgid "missing 'new' in constructor type"
msgstr ""

#: src/quick-lint-js/diag/diagnostic-metadata-generated.cpp
msgid "missing operand for operator"
msgstr ""
Expand Down
14 changes: 14 additions & 0 deletions src/quick-lint-js/diag/diagnostic-metadata-generated.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -3022,6 +3022,20 @@ const QLJS_CONSTINIT Diagnostic_Info all_diagnostic_infos[] = {
},
},

// Diag_Missing_New_In_Abstract_Constructor_Type
{
.code = 447,
.severity = Diagnostic_Severity::error,
.message_formats = {
QLJS_TRANSLATABLE("missing 'new' in constructor type"),
},
.message_args = {
{
Diagnostic_Message_Arg_Info(offsetof(Diag_Missing_New_In_Abstract_Constructor_Type, expected_new), Diagnostic_Arg_Type::source_code_span),
},
},
},

// Diag_Missing_Operand_For_Operator
{
.code = 26,
Expand Down
3 changes: 2 additions & 1 deletion src/quick-lint-js/diag/diagnostic-metadata-generated.h
Original file line number Diff line number Diff line change
Expand Up @@ -212,6 +212,7 @@ namespace quick_lint_js {
QLJS_DIAG_TYPE_NAME(Diag_Missing_Name_Of_Exported_Class) \
QLJS_DIAG_TYPE_NAME(Diag_Missing_Name_Of_Exported_Function) \
QLJS_DIAG_TYPE_NAME(Diag_Missing_Name_Or_Parentheses_For_Function) \
QLJS_DIAG_TYPE_NAME(Diag_Missing_New_In_Abstract_Constructor_Type) \
QLJS_DIAG_TYPE_NAME(Diag_Missing_Operand_For_Operator) \
QLJS_DIAG_TYPE_NAME(Diag_Missing_Parameter_Name) \
QLJS_DIAG_TYPE_NAME(Diag_Missing_Separator_Between_Object_Type_Entries) \
Expand Down Expand Up @@ -468,7 +469,7 @@ namespace quick_lint_js {
/* END */
// clang-format on

inline constexpr int Diag_Type_Count = 454;
inline constexpr int Diag_Type_Count = 455;

extern const Diagnostic_Info all_diagnostic_infos[Diag_Type_Count];
}
Expand Down
6 changes: 6 additions & 0 deletions src/quick-lint-js/diag/diagnostic-types-2.h
Original file line number Diff line number Diff line change
Expand Up @@ -1553,6 +1553,12 @@ struct Diag_Missing_Name_Or_Parentheses_For_Function {
Source_Code_Span function;
};

struct Diag_Missing_New_In_Abstract_Constructor_Type {
[[qljs::diag("E0447", Diagnostic_Severity::error)]] //
[[qljs::message("missing 'new' in constructor type", ARG(expected_new))]] //
Source_Code_Span expected_new;
};

struct Diag_Missing_Operand_For_Operator {
[[qljs::diag("E0026", Diagnostic_Severity::error)]] //
[[qljs::message("missing operand for operator", ARG(where))]] //
Expand Down
40 changes: 39 additions & 1 deletion src/quick-lint-js/fe/parse-type.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -201,7 +201,6 @@ void Parser::parse_and_visit_typescript_type_expression_no_scope(
// ns.Type<T>
// param is Type
type_variable_or_namespace_or_type_predicate:
case Token_Type::kw_abstract:
case Token_Type::kw_accessor:
case Token_Type::kw_as:
case Token_Type::kw_assert:
Expand Down Expand Up @@ -495,6 +494,45 @@ void Parser::parse_and_visit_typescript_type_expression_no_scope(
this->parse_and_visit_typescript_arrow_type_expression(v);
break;

// abstract
// abstract new (param, param) => ReturnType
case Token_Type::kw_abstract: {
Lexer_Transaction transaction = this->lexer_.begin_transaction();
this->skip();
switch (this->peek().type) {
// abstract new (param, param) => ReturnType
case Token_Type::kw_new:
this->lexer_.commit_transaction(std::move(transaction));
this->skip();
this->parse_and_visit_typescript_arrow_type_expression(v);
break;

// type T = abstract /*ASI*/ (param, param) => body;
// abstract (param, param) => ReturnType // Invalid.
case Token_Type::left_paren:
if (this->peek().has_leading_newline) {
// ASI.
this->lexer_.roll_back_transaction(std::move(transaction));
goto type_variable_or_namespace_or_type_predicate;
}
// Missing 'new' keyword.
this->lexer_.commit_transaction(std::move(transaction));
this->diag_reporter_->report(
Diag_Missing_New_In_Abstract_Constructor_Type{
.expected_new = Source_Code_Span::unit(this->peek().begin),
});
this->parse_and_visit_typescript_arrow_type_expression(v);
break;

// type T = abstract;
// [abstract]
default:
this->lexer_.roll_back_transaction(std::move(transaction));
goto type_variable_or_namespace_or_type_predicate;
}
break;
}

// <T>(param, param) => ReturnType
case Token_Type::less:
this->parse_and_visit_typescript_arrow_type_expression(v);
Expand Down
4 changes: 3 additions & 1 deletion src/quick-lint-js/i18n/translation-table-generated.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -388,7 +388,8 @@ const Translation_Table translation_data = {
{0, 0, 0, 0, 0, 27}, //
{0, 0, 0, 0, 0, 74}, //
{0, 40, 0, 28, 0, 38}, //
{29, 22, 33, 28, 26, 26}, //
{0, 0, 0, 0, 0, 26}, //
{29, 22, 33, 28, 26, 34}, //
{0, 0, 0, 55, 0, 51}, //
{48, 27, 63, 27, 0, 24}, //
{40, 4, 58, 41, 50, 42}, //
Expand Down Expand Up @@ -2252,6 +2253,7 @@ const Translation_Table translation_data = {
u8"missing 'break;' or '// fallthrough' comment between statement and 'case'\0"
u8"missing 'export' keyword for function\0"
u8"missing 'if' after 'else'\0"
u8"missing 'new' in constructor type\0"
u8"missing 'while (condition)' for do-while statement\0"
u8"missing TypeScript type\0"
u8"missing arrow operator for arrow function\0"
Expand Down
5 changes: 3 additions & 2 deletions src/quick-lint-js/i18n/translation-table-generated.h
Original file line number Diff line number Diff line change
Expand Up @@ -18,8 +18,8 @@ namespace quick_lint_js {
using namespace std::literals::string_view_literals;

constexpr std::uint32_t translation_table_locale_count = 5;
constexpr std::uint16_t translation_table_mapping_table_size = 598;
constexpr std::size_t translation_table_string_table_size = 82115;
constexpr std::uint16_t translation_table_mapping_table_size = 599;
constexpr std::size_t translation_table_string_table_size = 82149;
constexpr std::size_t translation_table_locale_table_size = 35;

QLJS_CONSTEVAL std::uint16_t translation_table_const_look_up(
Expand Down Expand Up @@ -403,6 +403,7 @@ QLJS_CONSTEVAL std::uint16_t translation_table_const_look_up(
"missing 'break;' or '// fallthrough' comment between statement and 'case'"sv,
"missing 'export' keyword for function"sv,
"missing 'if' after 'else'"sv,
"missing 'new' in constructor type"sv,
"missing 'while (condition)' for do-while statement"sv,
"missing TypeScript type"sv,
"missing arrow operator for arrow function"sv,
Expand Down
13 changes: 12 additions & 1 deletion src/quick-lint-js/i18n/translation-table-test-generated.h
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ struct Translated_String {
};

// clang-format off
inline const Translated_String test_translation_table[597] = {
inline const Translated_String test_translation_table[598] = {
{
"\"global-groups\" entries must be strings"_translatable,
u8"\"global-groups\" entries must be strings",
Expand Down Expand Up @@ -4175,6 +4175,17 @@ inline const Translated_String test_translation_table[597] = {
u8"missing 'if' after 'else'",
},
},
{
"missing 'new' in constructor type"_translatable,
u8"missing 'new' in constructor type",
{
u8"missing 'new' in constructor type",
u8"missing 'new' in constructor type",
u8"missing 'new' in constructor type",
u8"missing 'new' in constructor type",
u8"missing 'new' in constructor type",
},
},
{
"missing 'while (condition)' for do-while statement"_translatable,
u8"missing 'while (condition)' for do-while statement",
Expand Down
72 changes: 72 additions & 0 deletions test/test-parse-typescript-type.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1141,6 +1141,78 @@ TEST_F(Test_Parse_TypeScript_Type, constructor_function) {
}
}

TEST_F(Test_Parse_TypeScript_Type, abstract_constructor_function) {
{
Spy_Visitor p = test_parse_and_visit_typescript_type_expression(
u8"abstract new () => ReturnType"_sv, no_diags, typescript_options);
EXPECT_THAT(p.visits, ElementsAreArray({
"visit_enter_function_scope", //
"visit_enter_type_scope", // =>
"visit_variable_type_use", // ReturnType
"visit_exit_type_scope", //
"visit_exit_function_scope",
}));
EXPECT_THAT(p.variable_uses, ElementsAreArray({u8"ReturnType"}));
}
}

TEST_F(Test_Parse_TypeScript_Type,
abstract_constructor_function_requires_new_keyword) {
{
Spy_Visitor p = test_parse_and_visit_typescript_type_expression(
u8"abstract () => ReturnType"_sv, //
u8" ` Diag_Missing_New_In_Abstract_Constructor_Type.expected_new"_diag,
typescript_options);
EXPECT_THAT(p.visits, ElementsAreArray({
"visit_enter_function_scope", //
"visit_enter_type_scope", // =>
"visit_variable_type_use", // ReturnType
"visit_exit_type_scope", //
"visit_exit_function_scope",
}));
EXPECT_THAT(p.variable_uses, ElementsAreArray({u8"ReturnType"}));
}
}

TEST_F(
Test_Parse_TypeScript_Type,
newline_between_abstract_and_new_in_constructor_function_does_not_trigger_asi) {
{
Spy_Visitor p = test_parse_and_visit_typescript_type_expression(
u8"abstract\nnew () => ReturnType"_sv, no_diags, typescript_options);
EXPECT_THAT(p.visits, ElementsAreArray({
"visit_enter_function_scope", //
"visit_enter_type_scope", // =>
"visit_variable_type_use", // ReturnType
"visit_exit_type_scope", //
"visit_exit_function_scope",
}));
}
}

TEST_F(
Test_Parse_TypeScript_Type,
newline_after_abstract_without_new_in_constructor_function_triggers_asi) {
{
Spy_Visitor p =
test_parse_and_visit_module(u8"type T = abstract\n() => ReturnType;"_sv,
no_diags, typescript_options);
EXPECT_THAT(p.visits, ElementsAreArray({
"visit_variable_declaration", // T
"visit_enter_type_scope", //
"visit_variable_type_use", // abstract
"visit_exit_type_scope", //
"visit_enter_function_scope", //
"visit_enter_function_scope_body", // =>
"visit_variable_use", // ReturnType
"visit_exit_function_scope", //
"visit_end_of_module", //
}));
EXPECT_THAT(p.variable_uses,
ElementsAreArray({u8"abstract"_sv, u8"ReturnType"_sv}));
}
}

TEST_F(Test_Parse_TypeScript_Type, array) {
{
Spy_Visitor p = test_parse_and_visit_typescript_type_expression(
Expand Down

0 comments on commit af790ca

Please sign in to comment.