diff --git a/docs/errors/E0713.md b/docs/errors/E0713.md new file mode 100644 index 0000000000..32c9e3e6d3 --- /dev/null +++ b/docs/errors/E0713.md @@ -0,0 +1,36 @@ +# E0713: getters and setters cannot be generators + +Use of the '*' character, defining generator functions, is not allowed on getters or setters. +Getters and setters are synchronous operations and do not support the generator functionality. + +```javascript +class C { + constructor() { + this._value = 0; + } + + get *value() { + return this._value; + } + set *value(newValue) { + this._value = newValue; + } +} +``` + +To fix this error define a getter or setter, using regular function syntax. + +```javascript +class C { + constructor() { + this._value = 0; + } + + get value() { + return this._value; + } + set value(newValue) { + this._value = newValue; + } +} +``` \ No newline at end of file diff --git a/docs/errors/E0714.md b/docs/errors/E0714.md new file mode 100644 index 0000000000..1d3e3f9a72 --- /dev/null +++ b/docs/errors/E0714.md @@ -0,0 +1,61 @@ +# E0714: 'async' keyword is not allowed on getters or setters + +Use of 'async' keyword, defining asynchronous functions, is not allowed on getters or setters. +Getters and setters are synchronous operations and do not support the asynchronous functionality. + + +```javascript +class C { + constructor() { + this._value = 0; + } + + async get value() { + return this._value; + } + + async set value(newValue) { + this._value = newValue; + } +} +``` + +To fix this error simply remove 'async' keyword from getters and setters, +so they can function properly as synchronous operations. + + +```javascript +class C { + constructor() { + this._value = 0; + } + + get value() { + return this._value; + } + + set value(newValue) { + this._value = newValue; + } +} +``` + +However, if you require asynchronous behavior within getters or setters, +you can achieve this by implementing separate asynchronous methods. + + +```javascript +class C { + constructor() { + this._value = 0; + } + + async getValueAsync() { + return await fetch(this._value); + } + + async setValueAsync(newValue) { + this._value = await fetch(newValue); + } +} +``` \ No newline at end of file diff --git a/po/messages.pot b/po/messages.pot index ce9832d989..a3c37737ed 100644 --- a/po/messages.pot +++ b/po/messages.pot @@ -2057,6 +2057,14 @@ msgstr "" msgid "missing ',' between array elements" msgstr "" +#: src/quick-lint-js/diag/diagnostic-metadata-generated.cpp +msgid "getters and setters cannot be generators" +msgstr "" + +#: src/quick-lint-js/diag/diagnostic-metadata-generated.cpp +msgid "'async' keyword is not allowed on getters or setters" +msgstr "" + #: test/test-diagnostic-formatter.cpp #: test/test-vim-qflist-json-diag-reporter.cpp msgid "something happened" diff --git a/src/quick-lint-js/diag/diagnostic-metadata-generated.cpp b/src/quick-lint-js/diag/diagnostic-metadata-generated.cpp index 3626242f96..ce89b6a7d3 100644 --- a/src/quick-lint-js/diag/diagnostic-metadata-generated.cpp +++ b/src/quick-lint-js/diag/diagnostic-metadata-generated.cpp @@ -6325,6 +6325,42 @@ const QLJS_CONSTINIT Diagnostic_Info all_diagnostic_infos[] = { }, }, }, + + // Diag_Class_Generator_On_Getter_Or_Setter + { + .code = 713, + .severity = Diagnostic_Severity::error, + .message_formats = { + QLJS_TRANSLATABLE("getters and setters cannot be generators"), + QLJS_TRANSLATABLE("'{0}' here"), + }, + .message_args = { + { + Diagnostic_Message_Arg_Info(offsetof(Diag_Class_Generator_On_Getter_Or_Setter, star_token), Diagnostic_Arg_Type::source_code_span), + }, + { + Diagnostic_Message_Arg_Info(offsetof(Diag_Class_Generator_On_Getter_Or_Setter, getter_setter_keyword), Diagnostic_Arg_Type::source_code_span), + }, + }, + }, + + // Diag_Class_Async_On_Getter_Or_Setter + { + .code = 714, + .severity = Diagnostic_Severity::error, + .message_formats = { + QLJS_TRANSLATABLE("'async' keyword is not allowed on getters or setters"), + QLJS_TRANSLATABLE("'{0}' here"), + }, + .message_args = { + { + Diagnostic_Message_Arg_Info(offsetof(Diag_Class_Async_On_Getter_Or_Setter, async_keyword), Diagnostic_Arg_Type::source_code_span), + }, + { + Diagnostic_Message_Arg_Info(offsetof(Diag_Class_Async_On_Getter_Or_Setter, getter_setter_keyword), Diagnostic_Arg_Type::source_code_span), + }, + }, + }, }; } diff --git a/src/quick-lint-js/diag/diagnostic-metadata-generated.h b/src/quick-lint-js/diag/diagnostic-metadata-generated.h index 2c0a394269..78240fd26b 100644 --- a/src/quick-lint-js/diag/diagnostic-metadata-generated.h +++ b/src/quick-lint-js/diag/diagnostic-metadata-generated.h @@ -434,10 +434,12 @@ namespace quick_lint_js { QLJS_DIAG_TYPE_NAME(Diag_Xor_Used_As_Exponentiation) \ QLJS_DIAG_TYPE_NAME(Diag_Expected_Expression_In_Template_Literal) \ QLJS_DIAG_TYPE_NAME(Diag_Missing_Comma_Between_Array_Elements) \ + QLJS_DIAG_TYPE_NAME(Diag_Class_Generator_On_Getter_Or_Setter) \ + QLJS_DIAG_TYPE_NAME(Diag_Class_Async_On_Getter_Or_Setter) \ /* END */ // clang-format on -inline constexpr int Diag_Type_Count = 423; +inline constexpr int Diag_Type_Count = 425; 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 ebc55bb913..3fec098609 100644 --- a/src/quick-lint-js/diag/diagnostic-types-2.h +++ b/src/quick-lint-js/diag/diagnostic-types-2.h @@ -3261,6 +3261,24 @@ struct Diag_Missing_Comma_Between_Array_Elements { ARG(expected_comma))]] // Source_Code_Span expected_comma; }; + +struct Diag_Class_Generator_On_Getter_Or_Setter { + [[qljs::diag("E0713", Diagnostic_Severity::error)]] // + [[qljs::message("getters and setters cannot be generators", + ARG(star_token))]] // + [[qljs::message("'{0}' here", ARG(getter_setter_keyword))]] // + Source_Code_Span star_token; + Source_Code_Span getter_setter_keyword; +}; + +struct Diag_Class_Async_On_Getter_Or_Setter { + [[qljs::diag("E0714", Diagnostic_Severity::error)]] // + [[qljs::message("'async' keyword is not allowed on getters or setters", + ARG(async_keyword))]] // + [[qljs::message("'{0}' here", ARG(getter_setter_keyword))]] // + Source_Code_Span async_keyword; + Source_Code_Span getter_setter_keyword; +}; } QLJS_WARNING_POP diff --git a/src/quick-lint-js/fe/parse-class.cpp b/src/quick-lint-js/fe/parse-class.cpp index a7cd75bf25..559746aec7 100644 --- a/src/quick-lint-js/fe/parse-class.cpp +++ b/src/quick-lint-js/fe/parse-class.cpp @@ -1155,6 +1155,8 @@ void Parser::parse_and_visit_class_or_interface_member( void check_modifiers_for_method() { error_if_accessor_method(); error_if_declare_method(); + error_if_generator_getter_or_setter(); + error_if_async_getter_or_setter(); error_if_readonly_method(); error_if_async_or_generator_without_method_body(); error_if_invalid_access_specifier(); @@ -1324,6 +1326,41 @@ void Parser::parse_and_visit_class_or_interface_member( } } + void error_if_generator_getter_or_setter() { + if (const Modifier *star_modifier = find_modifier(Token_Type::star)) { + if (const Modifier *get_modifier = find_modifier(Token_Type::kw_get)) { + p->diag_reporter_->report(Diag_Class_Generator_On_Getter_Or_Setter{ + .star_token = star_modifier->span, + .getter_setter_keyword = get_modifier->span, + }); + } else if (const Modifier *set_modifier = + find_modifier(Token_Type::kw_set)) { + p->diag_reporter_->report(Diag_Class_Generator_On_Getter_Or_Setter{ + .star_token = star_modifier->span, + .getter_setter_keyword = set_modifier->span, + }); + } + } + } + + void error_if_async_getter_or_setter() { + if (const Modifier *async_modifier = + find_modifier(Token_Type::kw_async)) { + if (const Modifier *get_modifier = find_modifier(Token_Type::kw_get)) { + p->diag_reporter_->report(Diag_Class_Async_On_Getter_Or_Setter{ + .async_keyword = async_modifier->span, + .getter_setter_keyword = get_modifier->span, + }); + } else if (const Modifier *set_modifier = + find_modifier(Token_Type::kw_set)) { + p->diag_reporter_->report(Diag_Class_Async_On_Getter_Or_Setter{ + .async_keyword = async_modifier->span, + .getter_setter_keyword = set_modifier->span, + }); + } + } + } + void error_if_readonly_method() { if (const Modifier *readonly_modifier = find_modifier(Token_Type::kw_readonly)) { diff --git a/src/quick-lint-js/i18n/translation-table-generated.cpp b/src/quick-lint-js/i18n/translation-table-generated.cpp index 2f5770e6ce..0216c2f0b2 100644 --- a/src/quick-lint-js/i18n/translation-table-generated.cpp +++ b/src/quick-lint-js/i18n/translation-table-generated.cpp @@ -40,7 +40,8 @@ const Translation_Table translation_data = { {0, 0, 0, 100, 0, 89}, // {0, 0, 0, 0, 0, 24}, // {50, 77, 41, 27, 0, 60}, // - {70, 31, 69, 53, 0, 60}, // + {0, 0, 0, 0, 0, 60}, // + {70, 31, 69, 53, 0, 53}, // {93, 15, 80, 68, 26, 69}, // {0, 0, 0, 0, 0, 43}, // {0, 0, 0, 0, 0, 44}, // @@ -282,6 +283,7 @@ const Translation_Table translation_data = { {84, 28, 66, 75, 25, 54}, // {0, 0, 0, 70, 0, 52}, // {0, 0, 0, 0, 0, 45}, // + {0, 0, 0, 0, 0, 41}, // {70, 27, 0, 57, 0, 52}, // {0, 0, 0, 5, 0, 5}, // {5, 11, 66, 52, 54, 42}, // @@ -1827,6 +1829,7 @@ const Translation_Table translation_data = { u8"'as const' located here\0" u8"'async export' is not allowed; write 'export async' instead\0" u8"'async static' is not allowed; write 'static async' instead\0" + u8"'async' keyword is not allowed on getters or setters\0" u8"'await' cannot be followed by an arrow function; use 'async' instead\0" u8"'await' is only allowed in async functions\0" u8"'declare class' cannot contain static block\0" @@ -2068,6 +2071,7 @@ const Translation_Table translation_data = { u8"generator function '*' belongs after keyword function\0" u8"generator function '*' belongs before function name\0" u8"generic arrow function needs ',' here in TSX\0" + u8"getters and setters cannot be generators\0" u8"getters and setters cannot have overload signatures\0" u8"here\0" u8"here is the assignment assertion operator\0" diff --git a/src/quick-lint-js/i18n/translation-table-generated.h b/src/quick-lint-js/i18n/translation-table-generated.h index 4879acc060..7c6c1d8612 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 = 516; -constexpr std::size_t translation_table_string_table_size = 79639; +constexpr std::uint16_t translation_table_mapping_table_size = 518; +constexpr std::size_t translation_table_string_table_size = 79733; constexpr std::size_t translation_table_locale_table_size = 35; QLJS_CONSTEVAL std::uint16_t translation_table_const_look_up( @@ -55,6 +55,7 @@ QLJS_CONSTEVAL std::uint16_t translation_table_const_look_up( "'as const' located here"sv, "'async export' is not allowed; write 'export async' instead"sv, "'async static' is not allowed; write 'static async' instead"sv, + "'async' keyword is not allowed on getters or setters"sv, "'await' cannot be followed by an arrow function; use 'async' instead"sv, "'await' is only allowed in async functions"sv, "'declare class' cannot contain static block"sv, @@ -296,6 +297,7 @@ QLJS_CONSTEVAL std::uint16_t translation_table_const_look_up( "generator function '*' belongs after keyword function"sv, "generator function '*' belongs before function name"sv, "generic arrow function needs ',' here in TSX"sv, + "getters and setters cannot be generators"sv, "getters and setters cannot have overload signatures"sv, "here"sv, "here is the assignment assertion operator"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 28f8c17f23..f8c4c73f5a 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[515] = { +inline const Translated_String test_translation_table[517] = { { "\"global-groups\" entries must be strings"_translatable, u8"\"global-groups\" entries must be strings", @@ -347,6 +347,17 @@ inline const Translated_String test_translation_table[515] = { u8"'async static' is not allowed; write 'static async' instead", }, }, + { + "'async' keyword is not allowed on getters or setters"_translatable, + u8"'async' keyword is not allowed on getters or setters", + { + u8"'async' keyword is not allowed on getters or setters", + u8"'async' keyword is not allowed on getters or setters", + u8"'async' keyword is not allowed on getters or setters", + u8"'async' keyword is not allowed on getters or setters", + u8"'async' keyword is not allowed on getters or setters", + }, + }, { "'await' cannot be followed by an arrow function; use 'async' instead"_translatable, u8"'await' cannot be followed by an arrow function; use 'async' instead", @@ -2998,6 +3009,17 @@ inline const Translated_String test_translation_table[515] = { u8"generic arrow function needs ',' here in TSX", }, }, + { + "getters and setters cannot be generators"_translatable, + u8"getters and setters cannot be generators", + { + u8"getters and setters cannot be generators", + u8"getters and setters cannot be generators", + u8"getters and setters cannot be generators", + u8"getters and setters cannot be generators", + u8"getters and setters cannot be generators", + }, + }, { "getters and setters cannot have overload signatures"_translatable, u8"getters and setters cannot have overload signatures", diff --git a/test/test-parse-class.cpp b/test/test-parse-class.cpp index ebc28d0c38..3e86be02c0 100644 --- a/test/test-parse-class.cpp +++ b/test/test-parse-class.cpp @@ -836,6 +836,42 @@ TEST_F(Test_Parse_Class, accessor_cannot_be_method) { javascript_options); } +TEST_F(Test_Parse_Class, async_keyword_on_getters_and_setters) { + test_parse_and_visit_statement( + u8"class C { get async myMethod() { } }"_sv, // + u8" ^^^^^ Diag_Class_Async_On_Getter_Or_Setter.async_keyword\n"_diag + u8" ^^^ .getter_setter_keyword"_diag, + javascript_options); + test_parse_and_visit_statement( + u8"class C { set async myMethod() { } }"_sv, // + u8" ^^^^^ Diag_Class_Async_On_Getter_Or_Setter.async_keyword\n"_diag + u8" ^^^ .getter_setter_keyword"_diag, + javascript_options); + test_parse_and_visit_statement( + u8"class C { async get myMethod() { } }"_sv, // + u8" ^^^ Diag_Class_Async_On_Getter_Or_Setter.getter_setter_keyword\n"_diag + u8" ^^^^^ .async_keyword"_diag, + javascript_options); + test_parse_and_visit_statement( + u8"class C { async set myMethod() { } }"_sv, // + u8" ^^^ Diag_Class_Async_On_Getter_Or_Setter.getter_setter_keyword\n"_diag + u8" ^^^^^ .async_keyword"_diag, + javascript_options); +} + +TEST_F(Test_Parse_Class, getters_and_setters_cannot_be_generator) { + test_parse_and_visit_statement( + u8"class C { get *myMethod() { } }"_sv, // + u8" ^ Diag_Class_Generator_On_Getter_Or_Setter.star_token\n"_diag + u8" ^^^ .getter_setter_keyword"_diag, + javascript_options); + test_parse_and_visit_statement( + u8"class C { set *myMethod() { } }"_sv, // + u8" ^ Diag_Class_Generator_On_Getter_Or_Setter.star_token\n"_diag + u8" ^^^ .getter_setter_keyword"_diag, + javascript_options); +} + TEST_F(Test_Parse_Class, class_methods_should_not_use_function_keyword) { { Spy_Visitor p = test_parse_and_visit_statement(