diff --git a/src/quick-lint-js/error.h b/src/quick-lint-js/error.h index 918e1c7e93..1a65355e2c 100644 --- a/src/quick-lint-js/error.h +++ b/src/quick-lint-js/error.h @@ -1139,6 +1139,12 @@ { source_code_span continue_statement; }, \ .error(QLJS_TRANSLATABLE("continue can only be used inside of a loop"), \ continue_statement)) \ + \ + QLJS_ERROR_TYPE( \ + error_redundant_semicolon_after_else, "E202", \ + { source_code_span semicolon; }, \ + .warning(QLJS_TRANSLATABLE("redundant semicolon after else"), \ + semicolon)) \ /* END */ namespace quick_lint_js { diff --git a/src/quick-lint-js/parse.h b/src/quick-lint-js/parse.h index cdb2d3d6d5..595e0a9846 100644 --- a/src/quick-lint-js/parse.h +++ b/src/quick-lint-js/parse.h @@ -2516,6 +2516,13 @@ class parser { if (this->peek().type == token_type::kw_else) { this->skip(); + if (this->peek().type == token_type::semicolon) { + source_code_span semicolon = this->peek().span(); + this->error_reporter_->report(error_redundant_semicolon_after_else{ + .semicolon = source_code_span( + semicolon.begin(), semicolon.end()), + }); + } parse_and_visit_body(); } } diff --git a/test/test-parse-statement.cpp b/test/test-parse-statement.cpp index 2277edd9ad..5db2d927a3 100644 --- a/test/test-parse-statement.cpp +++ b/test/test-parse-statement.cpp @@ -578,6 +578,22 @@ TEST(test_parse, else_without_if) { } } +TEST(test_parse, else_redundant_semicolon) { + { + spy_visitor v; + padded_string code(u8"if (cond) { body; } else; { body; }"_sv); + parser p(&code, &v); + EXPECT_TRUE(p.parse_and_visit_statement(v)); + EXPECT_THAT(v.visits, ElementsAre("visit_variable_use", // cond + "visit_enter_block_scope", // (if) + "visit_variable_use", // body + "visit_exit_block_scope")); // (else) + EXPECT_THAT(v.errors, ElementsAre(ERROR_TYPE_FIELD( + error_redundant_semicolon_after_else, semicolon, + offsets_matcher(&code, strlen(u8"if (cond) { body; } else"), u8";")))); + } +} + TEST(test_parse, block_statement) { { spy_visitor v = parse_and_visit_statement(u8"{ }"_sv);