Skip to content

Commit

Permalink
feat(typescript): support 'override' properties in classes
Browse files Browse the repository at this point in the history
  • Loading branch information
strager committed Dec 29, 2023
1 parent 253983c commit b7f9349
Show file tree
Hide file tree
Showing 11 changed files with 202 additions and 5 deletions.
1 change: 1 addition & 0 deletions docs/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@ 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.
* `override` is now supported in classes.
* Abstract constructor types (such as `abstract new () => C`) are now
supported.
* `export class { m(); }` in a .d.ts file no longer falsely reports [E0172][]
Expand Down
4 changes: 4 additions & 0 deletions po/messages.pot
Original file line number Diff line number Diff line change
Expand Up @@ -1433,6 +1433,10 @@ msgstr ""
msgid "this required parameter appears after the optional parameter"
msgstr ""

#: src/quick-lint-js/diag/diagnostic-metadata-generated.cpp
msgid "override properties are not allowed in interfaces"
msgstr ""

#: src/quick-lint-js/diag/diagnostic-metadata-generated.cpp
msgid "integer cannot be represented and will be rounded to '{1}'"
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 @@ -3741,6 +3741,20 @@ const QLJS_CONSTINIT Diagnostic_Info all_diagnostic_infos[] = {
},
},

// Diag_Override_Property_Not_Allowed_In_Interface
{
.code = 449,
.severity = Diagnostic_Severity::error,
.message_formats = {
QLJS_TRANSLATABLE("override properties are not allowed in interfaces"),
},
.message_args = {
{
Diagnostic_Message_Arg_Info(offsetof(Diag_Override_Property_Not_Allowed_In_Interface, override_keyword), Diagnostic_Arg_Type::source_code_span),
},
},
},

// Diag_Integer_Literal_Will_Lose_Precision
{
.code = 212,
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 @@ -260,6 +260,7 @@ namespace quick_lint_js {
QLJS_DIAG_TYPE_NAME(Diag_Optional_Arrow_Parameter_With_Type_Annotation_Requires_Parentheses) \
QLJS_DIAG_TYPE_NAME(Diag_Optional_Parameter_Cannot_Have_Initializer) \
QLJS_DIAG_TYPE_NAME(Diag_Optional_Parameter_Cannot_Be_Followed_By_Required_Parameter) \
QLJS_DIAG_TYPE_NAME(Diag_Override_Property_Not_Allowed_In_Interface) \
QLJS_DIAG_TYPE_NAME(Diag_Integer_Literal_Will_Lose_Precision) \
QLJS_DIAG_TYPE_NAME(Diag_Parameter_Decorator_In_Abstract_Method) \
QLJS_DIAG_TYPE_NAME(Diag_Parameter_Decorator_In_Declare_Class) \
Expand Down Expand Up @@ -469,7 +470,7 @@ namespace quick_lint_js {
/* END */
// clang-format on

inline constexpr int Diag_Type_Count = 455;
inline constexpr int Diag_Type_Count = 456;

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

struct Diag_Override_Property_Not_Allowed_In_Interface {
[[qljs::diag("E0449", Diagnostic_Severity::error)]] //
[[qljs::message("override properties are not allowed in interfaces",
ARG(override_keyword))]] //
Source_Code_Span override_keyword;
};

struct Diag_Integer_Literal_Will_Lose_Precision {
[[qljs::diag("E0212", Diagnostic_Severity::warning)]] //
[[qljs::message("integer cannot be represented and will be rounded to '{1}'",
Expand Down
59 changes: 58 additions & 1 deletion src/quick-lint-js/fe/parse-class.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -359,6 +359,7 @@ void Parser::parse_and_visit_class_or_interface_member(
case Token_Type::kw_async:
case Token_Type::kw_declare:
case Token_Type::kw_function:
case Token_Type::kw_override:
case Token_Type::kw_private:
case Token_Type::kw_protected:
case Token_Type::kw_public:
Expand Down Expand Up @@ -1178,12 +1179,14 @@ void Parser::parse_and_visit_class_or_interface_member(
error_if_decorator_not_first();
error_if_abstract_with_decorator();
error_if_abstract_or_static_after_accessor();
error_if_override_in_wrong_order();
error_if_conflicting_modifiers();
error_if_readonly_in_not_typescript();
error_if_declare_in_not_typescript();
error_if_declare_in_interface();
error_if_declare_of_private_identifier(field_name);
error_if_declare_field_with_assignment_assertion();
error_if_override_in_interface();
error_if_accessor_in_interface();
error_if_static_in_interface();
error_if_static_abstract();
Expand All @@ -1204,6 +1207,8 @@ void Parser::parse_and_visit_class_or_interface_member(
error_if_access_specifier_not_first_in_method();
error_if_decorator_not_first();
error_if_abstract_with_decorator();
error_if_override_in_wrong_order();
error_if_override_in_interface();
error_if_static_in_interface();
error_if_static_abstract();
error_if_optional_in_not_typescript();
Expand Down Expand Up @@ -1554,6 +1559,7 @@ void Parser::parse_and_visit_class_or_interface_member(
check_if_access_specifier_precedes_given_modifiers(
Span<const Token_Type>({
Token_Type::kw_accessor,
Token_Type::kw_override,
Token_Type::kw_readonly,
Token_Type::kw_static,
}));
Expand All @@ -1564,8 +1570,9 @@ void Parser::parse_and_visit_class_or_interface_member(
if (p->options_.typescript && !modifiers.empty()) {
check_if_access_specifier_precedes_given_modifiers(
Span<const Token_Type>({
Token_Type::kw_static,
Token_Type::kw_async,
Token_Type::kw_override,
Token_Type::kw_static,
}));
}
}
Expand Down Expand Up @@ -1640,6 +1647,44 @@ void Parser::parse_and_visit_class_or_interface_member(
});
}
}

if (const Modifier *override_modifier =
this->find_modifier(Token_Type::kw_override)) {
if (override_modifier > accessor_modifier) {
p->diag_reporter_->report(
Diag_Class_Modifier_Must_Preceed_Other_Modifier{
.expected_first_modifier = override_modifier->span,
.expected_second_modifier = accessor_modifier->span,
});
}
}
}
}

void error_if_override_in_wrong_order() {
if (const Modifier *override_modifier =
this->find_modifier(Token_Type::kw_override)) {
if (const Modifier *static_modifier =
this->find_modifier(Token_Type::kw_static)) {
if (static_modifier > override_modifier) {
p->diag_reporter_->report(
Diag_Class_Modifier_Must_Preceed_Other_Modifier{
.expected_first_modifier = static_modifier->span,
.expected_second_modifier = override_modifier->span,
});
}
}

if (const Modifier *abstract_modifier =
this->find_modifier(Token_Type::kw_abstract)) {
if (abstract_modifier > override_modifier) {
p->diag_reporter_->report(
Diag_Class_Modifier_Must_Preceed_Other_Modifier{
.expected_first_modifier = abstract_modifier->span,
.expected_second_modifier = override_modifier->span,
});
}
}
}
}

Expand Down Expand Up @@ -1686,6 +1731,18 @@ void Parser::parse_and_visit_class_or_interface_member(
}
}

void error_if_override_in_interface() {
if (is_interface) {
if (const Modifier *override_modifier =
find_modifier(Token_Type::kw_override)) {
p->diag_reporter_->report(
Diag_Override_Property_Not_Allowed_In_Interface{
.override_keyword = override_modifier->span,
});
}
}
}

void error_if_static_in_interface() {
if (is_interface) {
if (const Modifier *static_modifier =
Expand Down
2 changes: 2 additions & 0 deletions src/quick-lint-js/i18n/translation-table-generated.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -490,6 +490,7 @@ const Translation_Table translation_data = {
{0, 0, 0, 68, 0, 64}, //
{0, 0, 0, 0, 0, 40}, //
{0, 0, 0, 0, 0, 34}, //
{0, 0, 0, 0, 0, 50}, //
{0, 0, 0, 0, 0, 10}, //
{0, 0, 0, 0, 0, 51}, //
{0, 0, 0, 0, 0, 56}, //
Expand Down Expand Up @@ -2354,6 +2355,7 @@ const Translation_Table translation_data = {
u8"overload signature must match modifiers on this overload method\0"
u8"overloaded function '{0}' declared here\0"
u8"overloaded method is marked '{0}'\0"
u8"override properties are not allowed in interfaces\0"
u8"parameter\0"
u8"parameter decorator must be before other modifiers\0"
u8"parameter decorators are not allowed in 'declare class'\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 = 599;
constexpr std::size_t translation_table_string_table_size = 82152;
constexpr std::uint16_t translation_table_mapping_table_size = 600;
constexpr std::size_t translation_table_string_table_size = 82202;
constexpr std::size_t translation_table_locale_table_size = 35;

QLJS_CONSTEVAL std::uint16_t translation_table_const_look_up(
Expand Down Expand Up @@ -504,6 +504,7 @@ QLJS_CONSTEVAL std::uint16_t translation_table_const_look_up(
"overload signature must match modifiers on this overload method"sv,
"overloaded function '{0}' declared here"sv,
"overloaded method is marked '{0}'"sv,
"override properties are not allowed in interfaces"sv,
"parameter"sv,
"parameter decorator must be before other modifiers"sv,
"parameter decorators are not allowed in 'declare class'"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[598] = {
inline const Translated_String test_translation_table[599] = {
{
"\"global-groups\" entries must be strings"_translatable,
u8"\"global-groups\" entries must be strings",
Expand Down Expand Up @@ -5286,6 +5286,17 @@ inline const Translated_String test_translation_table[598] = {
u8"overloaded method is marked '{0}'",
},
},
{
"override properties are not allowed in interfaces"_translatable,
u8"override properties are not allowed in interfaces",
{
u8"override properties are not allowed in interfaces",
u8"override properties are not allowed in interfaces",
u8"override properties are not allowed in interfaces",
u8"override properties are not allowed in interfaces",
u8"override properties are not allowed in interfaces",
},
},
{
"parameter"_translatable,
u8"parameter",
Expand Down
92 changes: 92 additions & 0 deletions test/test-parse-typescript-class.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1407,6 +1407,98 @@ TEST_F(Test_Parse_TypeScript_Class,
}
}

TEST_F(Test_Parse_TypeScript_Class, override_property) {
{
Spy_Visitor p = test_parse_and_visit_module(
u8"class C extends B { override method() {} }"_sv, no_diags,
typescript_options);
EXPECT_THAT(p.visits, ElementsAreArray({
"visit_enter_class_scope", // C
"visit_variable_use", // B
"visit_enter_class_scope_body", // {
"visit_enter_function_scope", // method
"visit_enter_function_scope_body", // {
"visit_exit_function_scope", // }
"visit_property_declaration", // method
"visit_exit_class_scope", // }
"visit_variable_declaration", // C
"visit_end_of_module",
}));
}

{
Spy_Visitor p = test_parse_and_visit_module(
u8"class C extends B { override field; }"_sv, no_diags,
typescript_options);
EXPECT_THAT(p.visits, ElementsAreArray({
"visit_enter_class_scope", // C
"visit_variable_use", // B
"visit_enter_class_scope_body", // {
"visit_property_declaration", // field
"visit_exit_class_scope", // }
"visit_variable_declaration", // C
"visit_end_of_module",
}));
}

{
Spy_Visitor p = test_parse_and_visit_module(
u8"class C extends B { override accessor prop; }"_sv, no_diags,
typescript_options);
EXPECT_THAT(p.visits, ElementsAreArray({
"visit_enter_class_scope", // C
"visit_variable_use", // B
"visit_enter_class_scope_body", // {
"visit_property_declaration", // prop
"visit_exit_class_scope", // }
"visit_variable_declaration", // C
"visit_end_of_module",
}));
}

test_parse_and_visit_module(
u8"class C extends B { static override method() {} }"_sv, no_diags,
typescript_options);
}

TEST_F(Test_Parse_TypeScript_Class, override_keyword_order) {
test_parse_and_visit_module(
u8"class C extends B { override static method() {} }"_sv, //
u8" ^^^^^^ Diag_Class_Modifier_Must_Preceed_Other_Modifier.expected_first_modifier\n"_diag //
u8" ^^^^^^^^ .expected_second_modifier"_diag,
typescript_options);
test_parse_and_visit_module(
u8"class C extends B { accessor override prop; }"_sv, //
u8" ^^^^^^^^ Diag_Class_Modifier_Must_Preceed_Other_Modifier.expected_first_modifier\n"_diag //
u8" ^^^^^^^^ .expected_second_modifier"_diag,
typescript_options);
test_parse_and_visit_module(
u8"class C extends B { override public method() {} }"_sv, //
u8" ^^^^^^ Diag_Access_Specifier_Must_Precede_Other_Modifiers.second_modifier\n"_diag //
u8" ^^^^^^^^ .first_modifier"_diag,
typescript_options);
test_parse_and_visit_module(
u8"class C extends B { override public field; }"_sv, //
u8" ^^^^^^ Diag_Access_Specifier_Must_Precede_Other_Modifiers.second_modifier\n"_diag //
u8" ^^^^^^^^ .first_modifier"_diag,
typescript_options);
test_parse_and_visit_module(
u8"abstract class C extends B { override abstract field; }"_sv, //
u8" ^^^^^^^^ Diag_Class_Modifier_Must_Preceed_Other_Modifier.expected_first_modifier\n"_diag //
u8" ^^^^^^^^ .expected_second_modifier"_diag,
typescript_options);
}

TEST_F(Test_Parse_TypeScript_Class,
override_method_prohibits_newline_after_override_keyword) {
{
Spy_Visitor p = test_parse_and_visit_statement(
u8"class C { override\n m() { } }"_sv, no_diags, typescript_options);
EXPECT_THAT(p.property_declarations,
ElementsAreArray({u8"override", u8"m"}));
}
}

TEST_F(Test_Parse_TypeScript_Class, accessor_field) {
{
Spy_Visitor p = test_parse_and_visit_statement(
Expand Down
7 changes: 7 additions & 0 deletions test/test-parse-typescript-interface.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1610,6 +1610,13 @@ TEST_F(Test_Parse_TypeScript_Interface,
}));
}
}

TEST_F(Test_Parse_TypeScript_Interface, override_is_not_allowed) {
test_parse_and_visit_statement(
u8"interface I { override method(); }"_sv,
u8" ^^^^^^^^ Diag_Override_Property_Not_Allowed_In_Interface"_diag,
typescript_options);
}
}
}

Expand Down

0 comments on commit b7f9349

Please sign in to comment.