diff --git a/docs/CHANGELOG.md b/docs/CHANGELOG.md index df78615f44..edd93b8219 100644 --- a/docs/CHANGELOG.md +++ b/docs/CHANGELOG.md @@ -13,6 +13,7 @@ Semantic Versioning. * VS Code: You can now make quick-lint-js messages fun and insulting with the `quick-lint-js.snarky` setting (disabled by default). (Implemented by [vegerot][].) +* TypeScript: Decorators on abstract classes are now parsed. ([#1194][]) ### Fixed @@ -1415,6 +1416,7 @@ Beta release. [#1168]: https://github.com/quick-lint/quick-lint-js/pull/1168 [#1171]: https://github.com/quick-lint/quick-lint-js/issues/1171 [#1180]: https://github.com/quick-lint/quick-lint-js/issues/1180 +[#1194]: https://github.com/quick-lint/quick-lint-js/issues/1194 [E0001]: https://quick-lint-js.com/errors/E0001/ [E0003]: https://quick-lint-js.com/errors/E0003/ diff --git a/src/quick-lint-js/fe/parse-class.cpp b/src/quick-lint-js/fe/parse-class.cpp index b8251db7ea..18e676dd82 100644 --- a/src/quick-lint-js/fe/parse-class.cpp +++ b/src/quick-lint-js/fe/parse-class.cpp @@ -25,6 +25,20 @@ namespace quick_lint_js { void Parser::parse_and_visit_class(Parse_Visitor_Base &v, Parse_Class_Options options) { + if (this->peek().type == Token_Type::kw_abstract) { + if (options.abstract_keyword_span.has_value()) { + // abstract abstract class??? + QLJS_PARSER_UNIMPLEMENTED(); + } + options.abstract_keyword_span = this->peek().span(); + this->skip(); + if (this->peek().has_leading_newline) { + this->diag_reporter_->report( + Diag_Newline_Not_Allowed_After_Abstract_Keyword{ + .abstract_keyword = *options.abstract_keyword_span, + }); + } + } QLJS_ASSERT(this->peek().type == Token_Type::kw_class); Source_Code_Span class_keyword_span = this->peek().span(); diff --git a/src/quick-lint-js/fe/parse-statement.cpp b/src/quick-lint-js/fe/parse-statement.cpp index df3c959349..740a82f04b 100644 --- a/src/quick-lint-js/fe/parse-statement.cpp +++ b/src/quick-lint-js/fe/parse-statement.cpp @@ -2999,6 +2999,7 @@ void Parser::parse_and_visit_decorator_statement(Parse_Visitor_Base &v) { this->parse_and_visit_one_or_more_decorators(decorator_visits.visitor()); switch (this->peek().type) { + case Token_Type::kw_abstract: case Token_Type::kw_class: this->parse_and_visit_class( v, Parse_Class_Options{ diff --git a/test/test-parse-decorator.cpp b/test/test-parse-decorator.cpp index 543cfc4b4c..48050329aa 100644 --- a/test/test-parse-decorator.cpp +++ b/test/test-parse-decorator.cpp @@ -456,6 +456,17 @@ TEST_F(Test_Parse_Decorator, typescript_options); } +TEST_F(Test_Parse_Decorator, decorator_on_typescript_abstract_class) { + test_parse_and_visit_module( + u8"@decorator abstract class C { abstract m(); }"_sv, no_diags, + typescript_options); + + test_parse_and_visit_module( + u8"@decorator abstract\nclass C { abstract m(); }"_sv, // + u8" ^^^^^^^^ Diag_Newline_Not_Allowed_After_Abstract_Keyword.abstract_keyword"_diag, + typescript_options); +} + TEST_F(Test_Parse_Decorator, typescript_parameter_decorator) { { Spy_Visitor p = test_parse_and_visit_statement(