diff --git a/docs/CHANGELOG.md b/docs/CHANGELOG.md index 52e421fdb5..734e810183 100644 --- a/docs/CHANGELOG.md +++ b/docs/CHANGELOG.md @@ -27,6 +27,7 @@ Semantic Versioning. (Implemented by [Samir Hamud][].) * Type predicates are now supported in function types (e.g. `(param) => param is Type`). + * `declare` fields are now parsed inside classes. ### Fixed diff --git a/po/messages.pot b/po/messages.pot index f3bc95b207..ce9832d989 100644 --- a/po/messages.pot +++ b/po/messages.pot @@ -1501,6 +1501,22 @@ msgstr "" msgid "'declare class' cannot contain static block" msgstr "" +#: src/quick-lint-js/diag/diagnostic-metadata-generated.cpp +msgid "TypeScript 'declare' fields are now allowed in JavaScript" +msgstr "" + +#: src/quick-lint-js/diag/diagnostic-metadata-generated.cpp +msgid "private identifiers are not allowed for 'declare' fields; use 'private' instead" +msgstr "" + +#: src/quick-lint-js/diag/diagnostic-metadata-generated.cpp +msgid "assignment assertion is not allowed on fields be marked 'declare'" +msgstr "" + +#: src/quick-lint-js/diag/diagnostic-metadata-generated.cpp +msgid "methods cannot be marked 'declare'" +msgstr "" + #: src/quick-lint-js/diag/diagnostic-metadata-generated.cpp msgid "TypeScript's 'interface' feature is not allowed in JavaScript code" msgstr "" @@ -1905,6 +1921,10 @@ msgstr "" msgid "'accessor' is not allowed for TypeScript interface fields" msgstr "" +#: src/quick-lint-js/diag/diagnostic-metadata-generated.cpp +msgid "'declare' is not allowed for TypeScript interface fields" +msgstr "" + #: src/quick-lint-js/diag/diagnostic-metadata-generated.cpp msgid "TypeScript interface fields cannot be initalized" msgstr "" diff --git a/src/quick-lint-js/diag/diagnostic-metadata-generated.cpp b/src/quick-lint-js/diag/diagnostic-metadata-generated.cpp index 22ae1de440..3626242f96 100644 --- a/src/quick-lint-js/diag/diagnostic-metadata-generated.cpp +++ b/src/quick-lint-js/diag/diagnostic-metadata-generated.cpp @@ -4467,6 +4467,70 @@ const QLJS_CONSTINIT Diagnostic_Info all_diagnostic_infos[] = { }, }, + // Diag_TypeScript_Declare_Field_Not_Allowed_In_JavaScript + { + .code = 415, + .severity = Diagnostic_Severity::error, + .message_formats = { + QLJS_TRANSLATABLE("TypeScript 'declare' fields are now allowed in JavaScript"), + }, + .message_args = { + { + Diagnostic_Message_Arg_Info(offsetof(Diag_TypeScript_Declare_Field_Not_Allowed_In_JavaScript, declare_keyword), Diagnostic_Arg_Type::source_code_span), + }, + }, + }, + + // Diag_TypeScript_Declare_Field_Cannot_Use_Private_Identifier + { + .code = 416, + .severity = Diagnostic_Severity::error, + .message_formats = { + QLJS_TRANSLATABLE("private identifiers are not allowed for 'declare' fields; use 'private' instead"), + QLJS_TRANSLATABLE("'declare' here"), + }, + .message_args = { + { + Diagnostic_Message_Arg_Info(offsetof(Diag_TypeScript_Declare_Field_Cannot_Use_Private_Identifier, private_identifier_hash), Diagnostic_Arg_Type::source_code_span), + }, + { + Diagnostic_Message_Arg_Info(offsetof(Diag_TypeScript_Declare_Field_Cannot_Use_Private_Identifier, declare_keyword), Diagnostic_Arg_Type::source_code_span), + }, + }, + }, + + // Diag_TypeScript_Declare_Field_Cannot_Be_Assignment_Asserted + { + .code = 418, + .severity = Diagnostic_Severity::error, + .message_formats = { + QLJS_TRANSLATABLE("assignment assertion is not allowed on fields be marked 'declare'"), + QLJS_TRANSLATABLE("'declare' here"), + }, + .message_args = { + { + Diagnostic_Message_Arg_Info(offsetof(Diag_TypeScript_Declare_Field_Cannot_Be_Assignment_Asserted, bang), Diagnostic_Arg_Type::source_code_span), + }, + { + Diagnostic_Message_Arg_Info(offsetof(Diag_TypeScript_Declare_Field_Cannot_Be_Assignment_Asserted, declare_keyword), Diagnostic_Arg_Type::source_code_span), + }, + }, + }, + + // Diag_TypeScript_Declare_Method + { + .code = 417, + .severity = Diagnostic_Severity::error, + .message_formats = { + QLJS_TRANSLATABLE("methods cannot be marked 'declare'"), + }, + .message_args = { + { + Diagnostic_Message_Arg_Info(offsetof(Diag_TypeScript_Declare_Method, declare_keyword), Diagnostic_Arg_Type::source_code_span), + }, + }, + }, + // Diag_TypeScript_Interfaces_Not_Allowed_In_JavaScript { .code = 213, @@ -5776,6 +5840,20 @@ const QLJS_CONSTINIT Diagnostic_Info all_diagnostic_infos[] = { }, }, + // Diag_Interface_Field_Cannot_Be_Declare + { + .code = 419, + .severity = Diagnostic_Severity::error, + .message_formats = { + QLJS_TRANSLATABLE("'declare' is not allowed for TypeScript interface fields"), + }, + .message_args = { + { + Diagnostic_Message_Arg_Info(offsetof(Diag_Interface_Field_Cannot_Be_Declare, declare_keyword), Diagnostic_Arg_Type::source_code_span), + }, + }, + }, + // Diag_Interface_Fields_Cannot_Have_Initializers { .code = 221, diff --git a/src/quick-lint-js/diag/diagnostic-metadata-generated.h b/src/quick-lint-js/diag/diagnostic-metadata-generated.h index 05c750e062..2c0a394269 100644 --- a/src/quick-lint-js/diag/diagnostic-metadata-generated.h +++ b/src/quick-lint-js/diag/diagnostic-metadata-generated.h @@ -308,6 +308,10 @@ namespace quick_lint_js { QLJS_DIAG_TYPE_NAME(Diag_TypeScript_Inline_Type_Import_Not_Allowed_In_Type_Only_Import) \ QLJS_DIAG_TYPE_NAME(Diag_TypeScript_Interfaces_Cannot_Contain_Static_Blocks) \ QLJS_DIAG_TYPE_NAME(Diag_TypeScript_Declare_Class_Cannot_Contain_Static_Block_Statement) \ + QLJS_DIAG_TYPE_NAME(Diag_TypeScript_Declare_Field_Not_Allowed_In_JavaScript) \ + QLJS_DIAG_TYPE_NAME(Diag_TypeScript_Declare_Field_Cannot_Use_Private_Identifier) \ + QLJS_DIAG_TYPE_NAME(Diag_TypeScript_Declare_Field_Cannot_Be_Assignment_Asserted) \ + QLJS_DIAG_TYPE_NAME(Diag_TypeScript_Declare_Method) \ QLJS_DIAG_TYPE_NAME(Diag_TypeScript_Interfaces_Not_Allowed_In_JavaScript) \ QLJS_DIAG_TYPE_NAME(Diag_TypeScript_Missing_Name_And_Colon_In_Named_Tuple_Type) \ QLJS_DIAG_TYPE_NAME(Diag_TypeScript_Missing_Name_In_Named_Tuple_Type) \ @@ -396,6 +400,7 @@ namespace quick_lint_js { QLJS_DIAG_TYPE_NAME(Diag_Function_Call_Before_Declaration_In_Block_Scope) \ QLJS_DIAG_TYPE_NAME(Diag_Import_Cannot_Have_Declare_Keyword) \ QLJS_DIAG_TYPE_NAME(Diag_Interface_Field_Cannot_Be_Accessor) \ + QLJS_DIAG_TYPE_NAME(Diag_Interface_Field_Cannot_Be_Declare) \ QLJS_DIAG_TYPE_NAME(Diag_Interface_Fields_Cannot_Have_Initializers) \ QLJS_DIAG_TYPE_NAME(Diag_Interface_Methods_Cannot_Be_Async) \ QLJS_DIAG_TYPE_NAME(Diag_Interface_Methods_Cannot_Be_Generators) \ @@ -432,7 +437,7 @@ namespace quick_lint_js { /* END */ // clang-format on -inline constexpr int Diag_Type_Count = 418; +inline constexpr int Diag_Type_Count = 423; extern const Diagnostic_Info all_diagnostic_infos[Diag_Type_Count]; } diff --git a/src/quick-lint-js/diag/diagnostic-types-2.h b/src/quick-lint-js/diag/diagnostic-types-2.h index 7f18c9a521..ebc55bb913 100644 --- a/src/quick-lint-js/diag/diagnostic-types-2.h +++ b/src/quick-lint-js/diag/diagnostic-types-2.h @@ -2301,6 +2301,41 @@ struct Diag_TypeScript_Declare_Class_Cannot_Contain_Static_Block_Statement { Source_Code_Span static_token; }; +struct Diag_TypeScript_Declare_Field_Not_Allowed_In_JavaScript { + [[qljs::diag("E0415", Diagnostic_Severity::error)]] // + [[qljs::message("TypeScript 'declare' fields are now allowed in JavaScript", + ARG(declare_keyword))]] // + Source_Code_Span declare_keyword; +}; + +struct Diag_TypeScript_Declare_Field_Cannot_Use_Private_Identifier { + [[qljs::diag("E0416", Diagnostic_Severity::error)]] // + [ + [qljs::message("private identifiers are not allowed for 'declare' " + "fields; use 'private' instead", + ARG(private_identifier_hash))]] // + [[qljs::message("'declare' here", ARG(declare_keyword))]] // + Source_Code_Span private_identifier_hash; + Source_Code_Span declare_keyword; +}; + +struct Diag_TypeScript_Declare_Field_Cannot_Be_Assignment_Asserted { + [[qljs::diag("E0418", Diagnostic_Severity::error)]] // + [[qljs::message( + "assignment assertion is not allowed on fields be marked 'declare'", + ARG(bang))]] // + [[qljs::message("'declare' here", ARG(declare_keyword))]] // + Source_Code_Span bang; + Source_Code_Span declare_keyword; +}; + +struct Diag_TypeScript_Declare_Method { + [[qljs::diag("E0417", Diagnostic_Severity::error)]] // + [[qljs::message("methods cannot be marked 'declare'", + ARG(declare_keyword))]] // + Source_Code_Span declare_keyword; +}; + struct Diag_TypeScript_Interfaces_Not_Allowed_In_JavaScript { [[qljs::diag("E0213", Diagnostic_Severity::error)]] // [[qljs::message( @@ -2976,6 +3011,13 @@ struct Diag_Interface_Field_Cannot_Be_Accessor { Source_Code_Span accessor_keyword; }; +struct Diag_Interface_Field_Cannot_Be_Declare { + [[qljs::diag("E0419", Diagnostic_Severity::error)]] // + [[qljs::message("'declare' is not allowed for TypeScript interface fields", + ARG(declare_keyword))]] // + Source_Code_Span declare_keyword; +}; + struct Diag_Interface_Fields_Cannot_Have_Initializers { [[qljs::diag("E0221", Diagnostic_Severity::error)]] // [[qljs::message("TypeScript interface fields cannot be initalized", diff --git a/src/quick-lint-js/fe/identifier.h b/src/quick-lint-js/fe/identifier.h index 1a0e752a8b..816e353047 100644 --- a/src/quick-lint-js/fe/identifier.h +++ b/src/quick-lint-js/fe/identifier.h @@ -47,6 +47,10 @@ class Identifier { return String8_View(this->normalized_begin_, this->normalized_size_); } + bool is_private_identifier() const { + return this->normalized_begin_[0] == u8'#'; + } + private: const Char8* span_begin_; const Char8* normalized_begin_; diff --git a/src/quick-lint-js/fe/parse-class.cpp b/src/quick-lint-js/fe/parse-class.cpp index 5c3734863b..a7cd75bf25 100644 --- a/src/quick-lint-js/fe/parse-class.cpp +++ b/src/quick-lint-js/fe/parse-class.cpp @@ -282,8 +282,8 @@ void Parser::parse_and_visit_class_or_interface_member( // first modifier or the beginning of the member's name. const Char8 *current_member_begin; - // *, !, ?, accessor, async, function, get, private, protected, public, - // readonly, set, static, @ (decorator) + // *, !, ?, accessor, async, declare, function, get, private, protected, + // public, readonly, set, static, @ (decorator) struct Modifier { Source_Code_Span span; Token_Type type; @@ -345,9 +345,11 @@ void Parser::parse_and_visit_class_or_interface_member( // private f() {} // public static field = 42; // readonly field: number; + // declare field; case Token_Type::kw_abstract: case Token_Type::kw_accessor: case Token_Type::kw_async: + case Token_Type::kw_declare: case Token_Type::kw_function: case Token_Type::kw_private: case Token_Type::kw_protected: @@ -374,7 +376,7 @@ void Parser::parse_and_visit_class_or_interface_member( case Token_Type::identifier: case Token_Type::private_identifier: case Token_Type::star: - check_modifiers_for_field_without_type_annotation(); + check_modifiers_for_field_without_type_annotation(last_ident); v.visit_property_declaration(last_ident); return true; default: @@ -941,14 +943,14 @@ void Parser::parse_and_visit_class_or_interface_member( case Token_Type::end_of_file: case Token_Type::right_curly: case Token_Type::semicolon: - check_modifiers_for_field_without_type_annotation(); + check_modifiers_for_field_without_type_annotation(property_name); v.visit_property_declaration(property_name); this->parse_field_terminator(); break; // field = initialValue; case Token_Type::equal: - check_modifiers_for_field_without_type_annotation(); + check_modifiers_for_field_without_type_annotation(property_name); this->parse_field_initializer(); v.visit_property_declaration(property_name); this->parse_field_terminator(); @@ -962,7 +964,7 @@ void Parser::parse_and_visit_class_or_interface_member( // field // ASI // method() {} // } - check_modifiers_for_field_without_type_annotation(); + check_modifiers_for_field_without_type_annotation(property_name); v.visit_property_declaration(property_name); } else { if (u8"const"_sv == property_name_span.string_view()) { @@ -976,7 +978,7 @@ void Parser::parse_and_visit_class_or_interface_member( // class C { // field? method() {} // Invalid. // } - check_modifiers_for_field_without_type_annotation(); + check_modifiers_for_field_without_type_annotation(property_name); v.visit_property_declaration(property_name); this->parse_field_terminator(); } else { @@ -999,7 +1001,7 @@ void Parser::parse_and_visit_class_or_interface_member( // field // ASI // [expr]() {} // } - check_modifiers_for_field_without_type_annotation(); + check_modifiers_for_field_without_type_annotation(property_name); v.visit_property_declaration(property_name); } else { QLJS_PARSER_UNIMPLEMENTED_WITH_PARSER(p); @@ -1007,7 +1009,7 @@ void Parser::parse_and_visit_class_or_interface_member( break; case Token_Type::colon: - this->check_modifiers_for_field_with_type_annotation(); + this->check_modifiers_for_field_with_type_annotation(property_name); if (this->find_modifier(Token_Type::bang)) { // If we have a bang modifier, we already reported // Diag_TypeScript_Assignment_Asserted_Fields_Not_Allowed_In_JavaScript. @@ -1108,8 +1110,9 @@ void Parser::parse_and_visit_class_or_interface_member( QLJS_UNREACHABLE(); } - void check_modifiers_for_field_without_type_annotation() { - this->check_modifiers_for_field(); + void check_modifiers_for_field_without_type_annotation( + const std::optional &field_name) { + this->check_modifiers_for_field(field_name); if (!this->is_interface && p->options_.typescript) { if (const Modifier *bang = this->find_modifier(Token_Type::bang)) { @@ -1121,11 +1124,13 @@ void Parser::parse_and_visit_class_or_interface_member( } } - void check_modifiers_for_field_with_type_annotation() { - this->check_modifiers_for_field(); + void check_modifiers_for_field_with_type_annotation( + const std::optional &field_name) { + this->check_modifiers_for_field(field_name); } - void check_modifiers_for_field() { + void check_modifiers_for_field( + const std::optional &field_name) { error_if_invalid_access_specifier(); error_if_getter_setter_field(); error_if_access_specifier_not_first_in_field(); @@ -1134,6 +1139,10 @@ void Parser::parse_and_visit_class_or_interface_member( error_if_abstract_or_static_after_accessor(); 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_accessor_in_interface(); error_if_static_in_interface(); error_if_static_abstract(); @@ -1145,6 +1154,7 @@ void Parser::parse_and_visit_class_or_interface_member( void check_modifiers_for_method() { error_if_accessor_method(); + error_if_declare_method(); error_if_readonly_method(); error_if_async_or_generator_without_method_body(); error_if_invalid_access_specifier(); @@ -1305,6 +1315,15 @@ void Parser::parse_and_visit_class_or_interface_member( } } + void error_if_declare_method() { + if (const Modifier *declare_modifier = + this->find_modifier(Token_Type::kw_declare)) { + p->diag_reporter_->report(Diag_TypeScript_Declare_Method{ + .declare_keyword = declare_modifier->span, + }); + } + } + void error_if_readonly_method() { if (const Modifier *readonly_modifier = find_modifier(Token_Type::kw_readonly)) { @@ -1326,6 +1345,59 @@ void Parser::parse_and_visit_class_or_interface_member( } } + void error_if_declare_in_not_typescript() { + if (!p->options_.typescript) { + if (const Modifier *declare_modifier = + this->find_modifier(Token_Type::kw_declare)) { + p->diag_reporter_->report( + Diag_TypeScript_Declare_Field_Not_Allowed_In_JavaScript{ + .declare_keyword = declare_modifier->span, + }); + } + } + } + + void error_if_declare_in_interface() { + if (this->is_interface) { + if (const Modifier *declare_modifier = + this->find_modifier(Token_Type::kw_declare)) { + p->diag_reporter_->report(Diag_Interface_Field_Cannot_Be_Declare{ + .declare_keyword = declare_modifier->span, + }); + } + } + } + + void error_if_declare_of_private_identifier( + const std::optional &field_name) { + if (field_name.has_value() && field_name->is_private_identifier()) { + if (const Modifier *declare_modifier = + this->find_modifier(Token_Type::kw_declare)) { + const Char8 *field_name_begin = field_name->span().begin(); + p->diag_reporter_->report( + Diag_TypeScript_Declare_Field_Cannot_Use_Private_Identifier{ + .private_identifier_hash = + Source_Code_Span(field_name_begin, field_name_begin + 1), + .declare_keyword = declare_modifier->span, + }); + } + } + } + + void error_if_declare_field_with_assignment_assertion() { + if (const Modifier *declare_modifier = + this->find_modifier(Token_Type::kw_declare)) { + if (const Modifier *assignment_assertion_modifier = + this->find_modifier(Token_Type::bang)) { + p->diag_reporter_->report( + Diag_TypeScript_Declare_Field_Cannot_Be_Assignment_Asserted{ + .bang = assignment_assertion_modifier->span, + .declare_keyword = declare_modifier->span, + }); + } + } + } + void error_if_invalid_access_specifier() { if (const Modifier *access_specifier = find_access_specifier()) { if (is_interface) { diff --git a/src/quick-lint-js/i18n/translation-table-generated.cpp b/src/quick-lint-js/i18n/translation-table-generated.cpp index 1e7dbb1e98..2f5770e6ce 100644 --- a/src/quick-lint-js/i18n/translation-table-generated.cpp +++ b/src/quick-lint-js/i18n/translation-table-generated.cpp @@ -58,6 +58,7 @@ const Translation_Table translation_data = { {0, 0, 0, 0, 0, 27}, // {0, 0, 0, 0, 0, 15}, // {0, 0, 0, 0, 0, 57}, // + {0, 0, 0, 0, 0, 57}, // {0, 0, 0, 0, 0, 61}, // {71, 48, 0, 44, 0, 25}, // {18, 13, 53, 16, 51, 16}, // @@ -114,7 +115,8 @@ const Translation_Table translation_data = { {0, 0, 0, 0, 0, 65}, // {0, 0, 0, 0, 0, 56}, // {0, 0, 0, 0, 0, 59}, // - {0, 0, 0, 78, 0, 54}, // + {0, 0, 0, 0, 0, 54}, // + {0, 0, 0, 78, 0, 58}, // {0, 0, 0, 0, 0, 53}, // {0, 0, 0, 45, 0, 61}, // {0, 0, 0, 78, 0, 62}, // @@ -168,7 +170,8 @@ const Translation_Table translation_data = { {18, 53, 70, 52, 76, 53}, // {19, 30, 21, 19, 19, 19}, // {15, 13, 19, 17, 15, 14}, // - {80, 64, 90, 59, 56, 59}, // + {0, 0, 0, 0, 0, 59}, // + {80, 64, 90, 59, 56, 66}, // {40, 33, 46, 45, 40, 36}, // {32, 45, 38, 38, 33, 29}, // {48, 49, 0, 60, 0, 52}, // @@ -310,7 +313,8 @@ const Translation_Table translation_data = { {61, 50, 75, 49, 64, 50}, // {50, 22, 0, 63, 0, 51}, // {0, 0, 0, 0, 0, 45}, // - {0, 0, 0, 46, 0, 19}, // + {0, 0, 0, 0, 0, 19}, // + {0, 0, 0, 46, 0, 35}, // {50, 47, 66, 33, 53, 27}, // {0, 0, 0, 0, 0, 46}, // {0, 0, 0, 0, 0, 56}, // @@ -423,7 +427,8 @@ const Translation_Table translation_data = { {0, 0, 0, 0, 0, 48}, // {0, 0, 0, 0, 0, 44}, // {0, 0, 0, 0, 0, 44}, // - {32, 13, 50, 41, 38, 29}, // + {0, 0, 0, 0, 0, 29}, // + {32, 13, 50, 41, 38, 80}, // {0, 0, 0, 0, 0, 54}, // {0, 0, 0, 0, 0, 34}, // {0, 0, 0, 0, 0, 30}, // @@ -1838,6 +1843,7 @@ const Translation_Table translation_data = { u8"'declare {1}' cannot have initializer\0" u8"'declare {1}' started here\0" u8"'declare' here\0" + u8"'declare' is not allowed for TypeScript interface fields\0" u8"'declare' or 'export' is required for {1} in .d.ts files\0" u8"'declare' should not be written inside a 'declare namespace'\0" u8"'declare' specified here\0" @@ -1896,6 +1902,7 @@ const Translation_Table translation_data = { u8"TypeScript 'declare class' is not allowed in JavaScript\0" u8"TypeScript 'declare function' is not allowed in JavaScript\0" u8"TypeScript 'declare {1}' is not allowed in JavaScript\0" + u8"TypeScript 'declare' fields are now allowed in JavaScript\0" u8"TypeScript 'implements' is not allowed in JavaScript\0" u8"TypeScript 'satisfies' operator is not allowed in JavaScript\0" u8"TypeScript type assertions are not allowed in JSX mode\0" @@ -1950,6 +1957,7 @@ const Translation_Table translation_data = { u8"array started here\0" u8"arrow is here\0" u8"assigning to 'async' in a for-of loop requires parentheses\0" + u8"assignment assertion is not allowed on fields be marked 'declare'\0" u8"assignment to const global variable\0" u8"assignment to const variable\0" u8"assignment to const variable before its declaration\0" @@ -2092,6 +2100,7 @@ const Translation_Table translation_data = { u8"let statement cannot declare variables named 'let'\0" u8"lower case letters compared with toUpperCase\0" u8"method starts here\0" + u8"methods cannot be marked 'declare'\0" u8"methods cannot be readonly\0" u8"methods should not use the 'function' keyword\0" u8"misleading use of ',' operator in conditional statement\0" @@ -2205,6 +2214,7 @@ const Translation_Table translation_data = { u8"parameter properties cannot be destructured\0" u8"parentheses are required around 'infer {1}'\0" u8"prior spread element is here\0" + u8"private identifiers are not allowed for 'declare' fields; use 'private' instead\0" u8"private properties are not allowed in object literals\0" u8"property declared 'abstract' here\0" u8"property declared static here\0" diff --git a/src/quick-lint-js/i18n/translation-table-generated.h b/src/quick-lint-js/i18n/translation-table-generated.h index 236364e676..4879acc060 100644 --- a/src/quick-lint-js/i18n/translation-table-generated.h +++ b/src/quick-lint-js/i18n/translation-table-generated.h @@ -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 = 511; -constexpr std::size_t translation_table_string_table_size = 79343; +constexpr std::uint16_t translation_table_mapping_table_size = 516; +constexpr std::size_t translation_table_string_table_size = 79639; constexpr std::size_t translation_table_locale_table_size = 35; QLJS_CONSTEVAL std::uint16_t translation_table_const_look_up( @@ -71,6 +71,7 @@ QLJS_CONSTEVAL std::uint16_t translation_table_const_look_up( "'declare {1}' cannot have initializer"sv, "'declare {1}' started here"sv, "'declare' here"sv, + "'declare' is not allowed for TypeScript interface fields"sv, "'declare' or 'export' is required for {1} in .d.ts files"sv, "'declare' should not be written inside a 'declare namespace'"sv, "'declare' specified here"sv, @@ -129,6 +130,7 @@ QLJS_CONSTEVAL std::uint16_t translation_table_const_look_up( "TypeScript 'declare class' is not allowed in JavaScript"sv, "TypeScript 'declare function' is not allowed in JavaScript"sv, "TypeScript 'declare {1}' is not allowed in JavaScript"sv, + "TypeScript 'declare' fields are now allowed in JavaScript"sv, "TypeScript 'implements' is not allowed in JavaScript"sv, "TypeScript 'satisfies' operator is not allowed in JavaScript"sv, "TypeScript type assertions are not allowed in JSX mode"sv, @@ -183,6 +185,7 @@ QLJS_CONSTEVAL std::uint16_t translation_table_const_look_up( "array started here"sv, "arrow is here"sv, "assigning to 'async' in a for-of loop requires parentheses"sv, + "assignment assertion is not allowed on fields be marked 'declare'"sv, "assignment to const global variable"sv, "assignment to const variable"sv, "assignment to const variable before its declaration"sv, @@ -325,6 +328,7 @@ QLJS_CONSTEVAL std::uint16_t translation_table_const_look_up( "let statement cannot declare variables named 'let'"sv, "lower case letters compared with toUpperCase"sv, "method starts here"sv, + "methods cannot be marked 'declare'"sv, "methods cannot be readonly"sv, "methods should not use the 'function' keyword"sv, "misleading use of ',' operator in conditional statement"sv, @@ -438,6 +442,7 @@ QLJS_CONSTEVAL std::uint16_t translation_table_const_look_up( "parameter properties cannot be destructured"sv, "parentheses are required around 'infer {1}'"sv, "prior spread element is here"sv, + "private identifiers are not allowed for 'declare' fields; use 'private' instead"sv, "private properties are not allowed in object literals"sv, "property declared 'abstract' here"sv, "property declared static here"sv, diff --git a/src/quick-lint-js/i18n/translation-table-test-generated.h b/src/quick-lint-js/i18n/translation-table-test-generated.h index f27564c8c6..28f8c17f23 100644 --- a/src/quick-lint-js/i18n/translation-table-test-generated.h +++ b/src/quick-lint-js/i18n/translation-table-test-generated.h @@ -27,7 +27,7 @@ struct Translated_String { }; // clang-format off -inline const Translated_String test_translation_table[510] = { +inline const Translated_String test_translation_table[515] = { { "\"global-groups\" entries must be strings"_translatable, u8"\"global-groups\" entries must be strings", @@ -523,6 +523,17 @@ inline const Translated_String test_translation_table[510] = { u8"'declare' here", }, }, + { + "'declare' is not allowed for TypeScript interface fields"_translatable, + u8"'declare' is not allowed for TypeScript interface fields", + { + u8"'declare' is not allowed for TypeScript interface fields", + u8"'declare' is not allowed for TypeScript interface fields", + u8"'declare' is not allowed for TypeScript interface fields", + u8"'declare' is not allowed for TypeScript interface fields", + u8"'declare' is not allowed for TypeScript interface fields", + }, + }, { "'declare' or 'export' is required for {1} in .d.ts files"_translatable, u8"'declare' or 'export' is required for {1} in .d.ts files", @@ -1161,6 +1172,17 @@ inline const Translated_String test_translation_table[510] = { u8"TypeScript 'declare {1}' is not allowed in JavaScript", }, }, + { + "TypeScript 'declare' fields are now allowed in JavaScript"_translatable, + u8"TypeScript 'declare' fields are now allowed in JavaScript", + { + u8"TypeScript 'declare' fields are now allowed in JavaScript", + u8"TypeScript 'declare' fields are now allowed in JavaScript", + u8"TypeScript 'declare' fields are now allowed in JavaScript", + u8"TypeScript 'declare' fields are now allowed in JavaScript", + u8"TypeScript 'declare' fields are now allowed in JavaScript", + }, + }, { "TypeScript 'implements' is not allowed in JavaScript"_translatable, u8"TypeScript 'implements' is not allowed in JavaScript", @@ -1755,6 +1777,17 @@ inline const Translated_String test_translation_table[510] = { u8"tilldelning av 'async' i for-of loop kr\u00e4ver paranteser", }, }, + { + "assignment assertion is not allowed on fields be marked 'declare'"_translatable, + u8"assignment assertion is not allowed on fields be marked 'declare'", + { + u8"assignment assertion is not allowed on fields be marked 'declare'", + u8"assignment assertion is not allowed on fields be marked 'declare'", + u8"assignment assertion is not allowed on fields be marked 'declare'", + u8"assignment assertion is not allowed on fields be marked 'declare'", + u8"assignment assertion is not allowed on fields be marked 'declare'", + }, + }, { "assignment to const global variable"_translatable, u8"assignment to const global variable", @@ -3317,6 +3350,17 @@ inline const Translated_String test_translation_table[510] = { u8"method starts here", }, }, + { + "methods cannot be marked 'declare'"_translatable, + u8"methods cannot be marked 'declare'", + { + u8"methods cannot be marked 'declare'", + u8"methods cannot be marked 'declare'", + u8"methods cannot be marked 'declare'", + u8"methods cannot be marked 'declare'", + u8"methods cannot be marked 'declare'", + }, + }, { "methods cannot be readonly"_translatable, u8"methods cannot be readonly", @@ -4560,6 +4604,17 @@ inline const Translated_String test_translation_table[510] = { u8"prior spread element is here", }, }, + { + "private identifiers are not allowed for 'declare' fields; use 'private' instead"_translatable, + u8"private identifiers are not allowed for 'declare' fields; use 'private' instead", + { + u8"private identifiers are not allowed for 'declare' fields; use 'private' instead", + u8"private identifiers are not allowed for 'declare' fields; use 'private' instead", + u8"private identifiers are not allowed for 'declare' fields; use 'private' instead", + u8"private identifiers are not allowed for 'declare' fields; use 'private' instead", + u8"private identifiers are not allowed for 'declare' fields; use 'private' instead", + }, + }, { "private properties are not allowed in object literals"_translatable, u8"private properties are not allowed in object literals", diff --git a/test/test-parse-typescript-class.cpp b/test/test-parse-typescript-class.cpp index f44cb9f731..7ec7051661 100644 --- a/test/test-parse-typescript-class.cpp +++ b/test/test-parse-typescript-class.cpp @@ -544,6 +544,87 @@ TEST_F(Test_Parse_TypeScript_Class, readonly_static_field_is_disallowed) { } } +TEST_F(Test_Parse_TypeScript_Class, + declare_fields_are_disallowed_in_javascript) { + { + Spy_Visitor p = test_parse_and_visit_statement( + u8"class C { declare field; }"_sv, // + u8" ^^^^^^^ Diag_TypeScript_Declare_Field_Not_Allowed_In_JavaScript"_diag); + EXPECT_THAT(p.property_declarations, ElementsAreArray({u8"field"_sv})); + } +} + +TEST_F(Test_Parse_TypeScript_Class, declare_fields_are_allowed_in_typescript) { + { + Spy_Visitor p = test_parse_and_visit_statement( + u8"class C { declare field; }"_sv, no_diags, typescript_options); + EXPECT_THAT(p.visits, ElementsAreArray({ + "visit_enter_class_scope", // C + "visit_enter_class_scope_body", // + "visit_property_declaration", // field + "visit_exit_class_scope", // C + "visit_variable_declaration", // C + })); + EXPECT_THAT(p.property_declarations, ElementsAreArray({u8"field"_sv})); + } + + { + Spy_Visitor p = test_parse_and_visit_statement( + u8"class C { static declare field; }"_sv, no_diags, typescript_options); + EXPECT_THAT(p.property_declarations, ElementsAreArray({u8"field"_sv})); + } + + { + Spy_Visitor p = test_parse_and_visit_statement( + u8"class C { declare static field; }"_sv, no_diags, typescript_options); + EXPECT_THAT(p.property_declarations, ElementsAreArray({u8"field"_sv})); + } +} + +TEST_F(Test_Parse_TypeScript_Class, declare_fields_cannot_have_private_name) { + test_parse_and_visit_statement( + u8"class C { declare #field; }"_sv, // + u8" ^ Diag_TypeScript_Declare_Field_Cannot_Use_Private_Identifier.private_identifier_hash\n"_diag + u8" ^^^^^^^ .declare_keyword"_diag, + typescript_options); +} + +TEST_F(Test_Parse_TypeScript_Class, newline_after_declare_is_asi) { + { + Spy_Visitor p = test_parse_and_visit_statement( + u8"class C { declare\n myField; }"_sv, no_diags, typescript_options); + EXPECT_THAT(p.property_declarations, + ElementsAreArray({u8"declare"_sv, u8"myField"_sv})); + } +} + +TEST_F(Test_Parse_TypeScript_Class, declare_methods_are_invalid) { + { + Spy_Visitor p = test_parse_and_visit_statement( + u8"class C { declare method() {} }"_sv, // + u8" ^^^^^^^ Diag_TypeScript_Declare_Method"_diag); + EXPECT_THAT(p.visits, ElementsAreArray({ + "visit_enter_class_scope", // C + "visit_enter_class_scope_body", // + "visit_enter_function_scope", // method + "visit_enter_function_scope_body", // method + "visit_exit_function_scope", // method + "visit_property_declaration", // method + "visit_exit_class_scope", // C + "visit_variable_declaration", // C + })); + } +} + +TEST_F(Test_Parse_TypeScript_Class, + declare_field_cannot_be_assignment_asserted) { + test_parse_and_visit_statement( + u8"class C { declare myField!: number; }"_sv, // + u8" ^ Diag_TypeScript_Declare_Field_Cannot_Be_Assignment_Asserted.bang\n"_diag + u8" ^^^^^^^ .declare_keyword"_diag, + typescript_options); +} + TEST_F(Test_Parse_TypeScript_Class, generic_classes_are_disallowed_in_javascript) { { diff --git a/test/test-parse-typescript-interface.cpp b/test/test-parse-typescript-interface.cpp index a977765484..81d845ac96 100644 --- a/test/test-parse-typescript-interface.cpp +++ b/test/test-parse-typescript-interface.cpp @@ -1065,6 +1065,13 @@ TEST_F(Test_Parse_TypeScript_Interface, accessor_fields_are_not_allowed) { typescript_options); } +TEST_F(Test_Parse_TypeScript_Interface, declare_fields_are_not_allowed) { + test_parse_and_visit_module( + u8"interface I { declare field; }"_sv, // + u8" ^^^^^^^ Diag_Interface_Field_Cannot_Be_Declare"_diag, + typescript_options); +} + TEST_F(Test_Parse_TypeScript_Interface, async_methods_are_not_allowed) { for (String8 method_name : Dirty_Set{u8"method"} | keywords) { SCOPED_TRACE(out_string8(method_name));