From 8462f95585c27dd112f153a088972b7d6f8690b8 Mon Sep 17 00:00:00 2001 From: Valekh Bayramov Date: Fri, 22 Sep 2023 21:18:01 +0300 Subject: [PATCH] fix: new diag for async/generator on getter/setter --- docs/errors/E0713.md | 36 +++++++++++ docs/errors/E0714.md | 64 +++++++++++++++++++ po/messages.pot | 8 +++ .../diag/diagnostic-metadata-generated.cpp | 36 +++++++++++ .../diag/diagnostic-metadata-generated.h | 4 +- src/quick-lint-js/diag/diagnostic-types-2.h | 20 ++++++ src/quick-lint-js/fe/parse-class.cpp | 43 +++++++++++++ .../i18n/translation-table-generated.cpp | 8 ++- .../i18n/translation-table-generated.h | 6 +- .../i18n/translation-table-test-generated.h | 24 ++++++- test/test-parse-class.cpp | 42 ++++++++++++ 11 files changed, 285 insertions(+), 6 deletions(-) create mode 100644 docs/errors/E0713.md create mode 100644 docs/errors/E0714.md diff --git a/docs/errors/E0713.md b/docs/errors/E0713.md new file mode 100644 index 0000000000..772191ead4 --- /dev/null +++ b/docs/errors/E0713.md @@ -0,0 +1,36 @@ +# E0713: '*' keyword is not allowed on getters or setters + +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 with 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..4240e7f7ac --- /dev/null +++ b/docs/errors/E0714.md @@ -0,0 +1,64 @@ +# 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() { + // Perform asynchronous operations here + return await asyncFuctnion(); + } + + async setValueAsync(newValue) { + // Perform asynchronous operations here + await asyncFuctnion(); + this._value = newValue; + } +} +``` \ No newline at end of file diff --git a/po/messages.pot b/po/messages.pot index 8b4a6c74f4..92f350436c 100644 --- a/po/messages.pot +++ b/po/messages.pot @@ -1929,6 +1929,14 @@ msgstr "" msgid "missing expression in placeholder within template literal" msgstr "" +#: src/quick-lint-js/diag/diagnostic-metadata-generated.cpp +msgid "'*' keyword is not allowed on getters or setters" +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 eeea146b1c..d22485c893 100644 --- a/src/quick-lint-js/diag/diagnostic-metadata-generated.cpp +++ b/src/quick-lint-js/diag/diagnostic-metadata-generated.cpp @@ -5961,6 +5961,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("'*' keyword is not allowed on getters or setters"), + QLJS_TRANSLATABLE("'{0}' here"), + }, + .message_args = { + { + Diagnostic_Message_Arg_Info(offsetof(Diag_Class_Generator_On_Getter_Or_Setter, generator_keyword), 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 c200fcb064..744345a644 100644 --- a/src/quick-lint-js/diag/diagnostic-metadata-generated.h +++ b/src/quick-lint-js/diag/diagnostic-metadata-generated.h @@ -412,10 +412,12 @@ namespace quick_lint_js { QLJS_DIAG_TYPE_NAME(Diag_Variable_Assigned_To_Self_Is_Noop) \ QLJS_DIAG_TYPE_NAME(Diag_Xor_Used_As_Exponentiation) \ QLJS_DIAG_TYPE_NAME(Diag_Expected_Expression_In_Template_Literal) \ + 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 = 401; +inline constexpr int Diag_Type_Count = 403; 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 4c164e674a..436f1fdaf3 100644 --- a/src/quick-lint-js/diag/diagnostic-types-2.h +++ b/src/quick-lint-js/diag/diagnostic-types-2.h @@ -3065,6 +3065,26 @@ struct Diag_Expected_Expression_In_Template_Literal { ARG(placeholder))]] // Source_Code_Span placeholder; }; + +struct Diag_Class_Generator_On_Getter_Or_Setter { + [[qljs::diag("E0713", Diagnostic_Severity::error)]] // + [[qljs::message("'*' keyword is not allowed on getters or setters", + ARG(generator_keyword))]] // + [[qljs::message("'{0}' here", ARG(getter_setter_keyword))]] // + Source_Code_Span method_start; + Source_Code_Span generator_keyword; + 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 method_start; + 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 2c8c280213..af4505d5ca 100644 --- a/src/quick-lint-js/fe/parse-class.cpp +++ b/src/quick-lint-js/fe/parse-class.cpp @@ -981,6 +981,8 @@ void Parser::parse_and_visit_class_or_interface_member( void check_modifiers_for_method() { error_if_accessor_method(); + error_if_generator_method(); + error_if_async_method(); error_if_readonly_method(); error_if_async_or_generator_without_method_body(); error_if_invalid_access_specifier(); @@ -1131,6 +1133,47 @@ void Parser::parse_and_visit_class_or_interface_member( } } + void error_if_generator_method() { + if (const Modifier *star_modifier = find_modifier(Token_Type::star)) { + Source_Code_Span method_start = p->peek().span(); + if (const Modifier *get_modifier = find_modifier(Token_Type::kw_get)) { + p->diag_reporter_->report(Diag_Class_Generator_On_Getter_Or_Setter{ + .method_start = method_start, + .generator_keyword = 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{ + .method_start = method_start, + .generator_keyword = star_modifier->span, + .getter_setter_keyword = set_modifier->span, + }); + } + } + } + + void error_if_async_method() { + if (const Modifier *async_modifier = + find_modifier(Token_Type::kw_async)) { + Source_Code_Span method_start = p->peek().span(); + if (const Modifier *get_modifier = find_modifier(Token_Type::kw_get)) { + p->diag_reporter_->report(Diag_Class_Async_On_Getter_Or_Setter{ + .method_start = method_start, + .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{ + .method_start = method_start, + .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 ee93179ee5..fe2d079e22 100644 --- a/src/quick-lint-js/i18n/translation-table-generated.cpp +++ b/src/quick-lint-js/i18n/translation-table-generated.cpp @@ -18,7 +18,8 @@ const Translation_Table translation_data = { {74, 87, 79, 56, 0, 59}, // {71, 80, 60, 58, 0, 52}, // {0, 0, 0, 0, 0, 28}, // - {31, 56, 0, 32, 0, 63}, // + {0, 0, 0, 0, 0, 63}, // + {31, 56, 0, 32, 0, 49}, // {0, 0, 0, 0, 0, 67}, // {0, 0, 0, 70, 0, 26}, // {79, 25, 30, 63, 49593, 66}, // @@ -40,7 +41,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}, // @@ -1773,6 +1775,7 @@ const Translation_Table translation_data = { u8"\"globals\" descriptor must be a boolean or an object\0" u8"\"globals\" must be an object\0" u8"'!' here treated as the TypeScript non-null assertion operator\0" + u8"'*' keyword is not allowed on getters or setters\0" u8"'**' operator cannot be used after unary '{1}' without parentheses\0" u8"',' should be ';' instead\0" u8"'.' is not allowed after generic arguments; write [\"{1}\"] instead\0" @@ -1795,6 +1798,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" diff --git a/src/quick-lint-js/i18n/translation-table-generated.h b/src/quick-lint-js/i18n/translation-table-generated.h index 19bfaef4b1..2976fb546a 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 = 484; -constexpr std::size_t translation_table_string_table_size = 78112; +constexpr std::uint16_t translation_table_mapping_table_size = 486; +constexpr std::size_t translation_table_string_table_size = 78214; constexpr std::size_t translation_table_locale_table_size = 35; QLJS_CONSTEVAL std::uint16_t translation_table_const_look_up( @@ -33,6 +33,7 @@ QLJS_CONSTEVAL std::uint16_t translation_table_const_look_up( "\"globals\" descriptor must be a boolean or an object"sv, "\"globals\" must be an object"sv, "'!' here treated as the TypeScript non-null assertion operator"sv, + "'*' keyword is not allowed on getters or setters"sv, "'**' operator cannot be used after unary '{1}' without parentheses"sv, "',' should be ';' instead"sv, "'.' is not allowed after generic arguments; write [\"{1}\"] instead"sv, @@ -55,6 +56,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, 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 fad782d3ac..78b981b6f9 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[483] = { +inline const Translated_String test_translation_table[485] = { { "\"global-groups\" entries must be strings"_translatable, u8"\"global-groups\" entries must be strings", @@ -105,6 +105,17 @@ inline const Translated_String test_translation_table[483] = { u8"'!' here treated as the TypeScript non-null assertion operator", }, }, + { + "'*' keyword is not allowed on getters or setters"_translatable, + u8"'*' keyword is not allowed on getters or setters", + { + u8"'*' keyword is not allowed on getters or setters", + u8"'*' keyword is not allowed on getters or setters", + u8"'*' keyword is not allowed on getters or setters", + u8"'*' keyword is not allowed on getters or setters", + u8"'*' keyword is not allowed on getters or setters", + }, + }, { "'**' operator cannot be used after unary '{1}' without parentheses"_translatable, u8"'**' operator cannot be used after unary '{1}' without parentheses", @@ -347,6 +358,17 @@ inline const Translated_String test_translation_table[483] = { 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", diff --git a/test/test-parse-class.cpp b/test/test-parse-class.cpp index a0d6e97cfa..debaaf8548 100644 --- a/test/test-parse-class.cpp +++ b/test/test-parse-class.cpp @@ -833,6 +833,48 @@ 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.method_start\n"_diag + u8" ^^^^^ .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.method_start\n"_diag + u8" ^^^^^ .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.method_start\n"_diag + u8" ^^^ .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.method_start\n"_diag + u8" ^^^ .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.method_start\n"_diag + u8" ^ .generator_keyword\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.method_start\n"_diag + u8" ^ .generator_keyword\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(