Skip to content

Commit

Permalink
feat(fe): add new diagnostic E0721
Browse files Browse the repository at this point in the history
This diagnostic reports an error when `.` or `?.` directly follows `++`
or `--`. Error message recommends adding parentheses around the postfix
expression. This closes issue quick-lint#1208.

quick-lint#1208
  • Loading branch information
msharipov committed Mar 14, 2024
1 parent 313e1cf commit 73049dd
Show file tree
Hide file tree
Showing 10 changed files with 56 additions and 27 deletions.
18 changes: 18 additions & 0 deletions docs/errors/E0721.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
# E0721: invalid syntax; missing parentheses around postfix expression

In JavaScript, putting a `.` or a `?.` directly following a postfix increment
or decrement is a syntax error:

```javascript
let x = 3;
console.log(x++.toString()); // Invalid syntax
console.log(x--?.constructor); // Invalid syntax
```
Add parentheses around the postfix expression to resolve this:
```javascript
let x = 3;
console.log((x++).toString());
console.log((x--)?.constructor);
```
2 changes: 1 addition & 1 deletion po/messages.pot
Original file line number Diff line number Diff line change
Expand Up @@ -2418,7 +2418,7 @@ msgid "namespace alias cannot use 'import type'"
msgstr ""

#: src/quick-lint-js/diag/diagnostic-metadata-generated.cpp
msgid "invalid syntax; missing parentheses around {0}"
msgid "invalid syntax; missing parentheses around '{0}'"
msgstr ""

#: test/test-diagnostic-formatter.cpp
Expand Down
6 changes: 3 additions & 3 deletions src/quick-lint-js/diag/diagnostic-metadata-generated.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -6930,16 +6930,16 @@ const QLJS_CONSTINIT Diagnostic_Info all_diagnostic_infos[] = {
},
},

// Diag_To_String_After_Increment
// Diag_Invalid_Operator_Directly_After_Postfix
{
.code = 721,
.severity = Diagnostic_Severity::error,
.message_formats = {
QLJS_TRANSLATABLE("invalid syntax; missing parentheses around {0}"),
QLJS_TRANSLATABLE("invalid syntax; missing parentheses around '{0}'"),
},
.message_args = {
{
Diagnostic_Message_Arg_Info(offsetof(Diag_To_String_After_Increment, increment), Diagnostic_Arg_Type::source_code_span),
Diagnostic_Message_Arg_Info(offsetof(Diag_Invalid_Operator_Directly_After_Postfix, postfix_expression), Diagnostic_Arg_Type::source_code_span),
},
},
},
Expand Down
2 changes: 1 addition & 1 deletion src/quick-lint-js/diag/diagnostic-metadata-generated.h
Original file line number Diff line number Diff line change
Expand Up @@ -473,7 +473,7 @@ namespace quick_lint_js {
QLJS_DIAG_TYPE_NAME(Diag_Multiple_Export_Defaults) \
QLJS_DIAG_TYPE_NAME(Diag_Unintuitive_Bitshift_Precedence) \
QLJS_DIAG_TYPE_NAME(Diag_TypeScript_Namespace_Alias_Cannot_Use_Import_Type) \
QLJS_DIAG_TYPE_NAME(Diag_To_String_After_Increment) \
QLJS_DIAG_TYPE_NAME(Diag_Invalid_Operator_Directly_After_Postfix) \
/* END */
// clang-format on

Expand Down
7 changes: 2 additions & 5 deletions src/quick-lint-js/diag/diagnostic-types-2.h
Original file line number Diff line number Diff line change
Expand Up @@ -3531,9 +3531,6 @@ struct Diag_Variable_Assigned_To_Self_Is_Noop {
Source_Code_Span assignment_statement;
};




struct Diag_Xor_Used_As_Exponentiation {
[[qljs::diag("E0710", Diagnostic_Severity::warning)]] //
[[qljs::message("'^' is the XOR operator; to exponentiate, use '**' instead",
Expand Down Expand Up @@ -3603,9 +3600,9 @@ struct Diag_TypeScript_Namespace_Alias_Cannot_Use_Import_Type {
Source_Code_Span type_keyword;
};

struct Diag_To_String_After_Postfix {
struct Diag_Invalid_Operator_Directly_After_Postfix {
[[qljs::diag("E0721", Diagnostic_Severity::error)]] //
[[qljs::message("invalid syntax; missing parentheses around {0}",
[[qljs::message("invalid syntax; missing parentheses around '{0}'",
ARG(postfix_expression))]] //
Source_Code_Span postfix_expression;
};
Expand Down
8 changes: 8 additions & 0 deletions src/quick-lint-js/fe/parse-expression.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1896,6 +1896,14 @@ Expression* Parser::parse_expression_remainder(Parse_Visitor_Base& v,
binary_builder.replace_last(
this->make_expression<Expression::RW_Unary_Suffix>(
binary_builder.last_expression(), operator_span));
Token_Type next_type = this->peek().type;
if (next_type == Token_Type::dot ||
next_type == Token_Type::question_dot) {
this->diag_reporter_->report(
Diag_Invalid_Operator_Directly_After_Postfix{
.postfix_expression =
binary_builder.last_expression()->span()});
}
}
goto next;

Expand Down
4 changes: 2 additions & 2 deletions src/quick-lint-js/i18n/translation-table-generated.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -364,7 +364,7 @@ const Translation_Table translation_data = {
{31, 39, 32, 33, 28, 27}, //
{34, 15, 43, 40, 37, 33}, //
{0, 0, 0, 0, 0, 39}, //
{39, 51, 50, 40, 38, 47}, //
{39, 51, 50, 40, 38, 49}, //
{57, 72, 67, 55, 0, 41}, //
{0, 0, 0, 58, 0, 50}, //
{0, 0, 0, 22, 0, 19}, //
Expand Down Expand Up @@ -2236,7 +2236,7 @@ const Translation_Table translation_data = {
u8"invalid function parameter\0"
u8"invalid hex escape sequence: {0}\0"
u8"invalid lone literal in object literal\0"
u8"invalid syntax; missing parentheses around {0}\0"
u8"invalid syntax; missing parentheses around '{0}'\0"
u8"keywords cannot contain escape sequences\0"
u8"label named 'await' not allowed in async function\0"
u8"labelled statement\0"
Expand Down
4 changes: 2 additions & 2 deletions src/quick-lint-js/i18n/translation-table-generated.h
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ 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 = 607;
constexpr std::size_t translation_table_string_table_size = 82488;
constexpr std::size_t translation_table_string_table_size = 82490;
constexpr std::size_t translation_table_locale_table_size = 35;

QLJS_CONSTEVAL std::uint16_t translation_table_const_look_up(
Expand Down Expand Up @@ -378,7 +378,7 @@ QLJS_CONSTEVAL std::uint16_t translation_table_const_look_up(
"invalid function parameter"sv,
"invalid hex escape sequence: {0}"sv,
"invalid lone literal in object literal"sv,
"invalid syntax; missing parentheses around {0}"sv,
"invalid syntax; missing parentheses around '{0}'"sv,
"keywords cannot contain escape sequences"sv,
"label named 'await' not allowed in async function"sv,
"labelled statement"sv,
Expand Down
16 changes: 8 additions & 8 deletions src/quick-lint-js/i18n/translation-table-test-generated.h
Original file line number Diff line number Diff line change
Expand Up @@ -3901,14 +3901,14 @@ inline const Translated_String test_translation_table[606] = {
},
},
{
"invalid syntax; missing parentheses around {0}"_translatable,
u8"invalid syntax; missing parentheses around {0}",
{
u8"invalid syntax; missing parentheses around {0}",
u8"invalid syntax; missing parentheses around {0}",
u8"invalid syntax; missing parentheses around {0}",
u8"invalid syntax; missing parentheses around {0}",
u8"invalid syntax; missing parentheses around {0}",
"invalid syntax; missing parentheses around '{0}'"_translatable,
u8"invalid syntax; missing parentheses around '{0}'",
{
u8"invalid syntax; missing parentheses around '{0}'",
u8"invalid syntax; missing parentheses around '{0}'",
u8"invalid syntax; missing parentheses around '{0}'",
u8"invalid syntax; missing parentheses around '{0}'",
u8"invalid syntax; missing parentheses around '{0}'",
},
},
{
Expand Down
16 changes: 11 additions & 5 deletions test/test-parse-warning.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -507,13 +507,19 @@ TEST_F(Test_Parse_Warning, early_exit_does_not_trigger_fallthrough_warning) {
}
}

TEST_F(Test_Parse_Warning, to_string_called_directly_after_postfix) {
TEST_F(Test_Parse_Warning, invalid_operator_directly_after_postfix) {
test_parse_and_visit_expression(
u8"x++.toString()"_sv, //
u8"^^^ Diag_To_String_After_Postfix"_diag);
u8"x++.toString()"_sv, //
u8"^^^ Diag_Invalid_Operator_Directly_After_Postfix"_diag);
test_parse_and_visit_expression(u8"(x++).toString()"_sv, no_diags);
test_parse_and_visit_expression(
u8"(x++).toString()"_sv,
no_diags);
u8"x++.constructor"_sv, //
u8"^^^ Diag_Invalid_Operator_Directly_After_Postfix"_diag);
test_parse_and_visit_expression(u8"(x++).constructor"_sv, no_diags);
test_parse_and_visit_expression(
u8"x--?.constructor"_sv, //
u8"^^^ Diag_Invalid_Operator_Directly_After_Postfix"_diag);
test_parse_and_visit_expression(u8"(x--)?.constructor"_sv, no_diags);
}
}
}
Expand Down

0 comments on commit 73049dd

Please sign in to comment.