Skip to content

Commit ac7f1d2

Browse files
committed
Improve error for 'class C {'
If a class body is incomplete, quick-lint-js fails with an unexpected token error. Improve the error by special-casing end-of-file when parsing class members.
1 parent 233f6f5 commit ac7f1d2

File tree

5 files changed

+85
-0
lines changed

5 files changed

+85
-0
lines changed

docs/CHANGELOG.md

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,13 @@ based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
66
quick-lint-js' version numbers are arbitrary. quick-lint-js does *not* adhere to
77
Semantic Versioning.
88

9+
## Unreleased
10+
11+
### Added
12+
13+
* An incomplete `class` now reports [E0199][] instead of failing with the
14+
catch-all [E0054][]. (Thanks to [Dave Churchill][] for reporting.)
15+
916
## 2.2.0 (2022-02-17)
1017

1118
[Downloads](https://c.quick-lint-js.com/releases/2.2.0/)
@@ -401,6 +408,7 @@ Beta release.
401408
[Amir]: https://github.com/ahmafi
402409
[Christian Mund]: https://github.com/kkkrist
403410
[Daniel La Rocque]: https://github.com/dlarocque
411+
[Dave Churchill]: https://www.cs.mun.ca/~dchurchill/
404412
[David Vasileff]: https://github.com/dav000
405413
[Erlliam Mejia]: https://github.com/erlliam
406414
[Himanshu]: https://github.com/singalhimanshu
@@ -463,6 +471,7 @@ Beta release.
463471
[E0195]: https://quick-lint-js.com/errors/E0195/
464472
[E0196]: https://quick-lint-js.com/errors/E0196/
465473
[E0197]: https://quick-lint-js.com/errors/E0197/
474+
[E0199]: https://quick-lint-js.com/errors/E0199/
466475
[E0203]: https://quick-lint-js.com/errors/E0203/
467476
[E0205]: https://quick-lint-js.com/errors/E0205/
468477
[E0207]: https://quick-lint-js.com/errors/E0207/

docs/errors/E0199.md

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
# E0199: unclosed class; expected '}' by end of file
2+
3+
```config-for-examples
4+
{
5+
"globals": {
6+
"doWork": true,
7+
"work": true
8+
}
9+
}
10+
```
11+
12+
Every `{` introducing a class block must have a matching `}` ending a class
13+
block. It is a syntax error to omit the `}`:
14+
15+
class Banana {
16+
peel() {
17+
throw new Error("Bananas can't peel themselves!");
18+
}
19+
20+
To fix this error, write a matching `}`:
21+
22+
class Banana {
23+
peel() {
24+
throw new Error("Bananas can't peel themselves!");
25+
}
26+
}

src/quick-lint-js/error.h

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1223,6 +1223,11 @@
12231223
{ source_code_span comment_open; }, \
12241224
ERROR(QLJS_TRANSLATABLE("unclosed block comment"), comment_open)) \
12251225
\
1226+
QLJS_ERROR_TYPE( \
1227+
error_unclosed_class_block, "E0199", { source_code_span block_open; }, \
1228+
ERROR(QLJS_TRANSLATABLE("unclosed class; expected '}' by end of file"), \
1229+
block_open)) \
1230+
\
12261231
QLJS_ERROR_TYPE( \
12271232
error_unclosed_code_block, "E0134", { source_code_span block_open; }, \
12281233
ERROR(QLJS_TRANSLATABLE( \

src/quick-lint-js/parse-statement-inl.h

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1198,10 +1198,17 @@ template <QLJS_PARSE_VISITOR Visitor>
11981198
void parser::parse_and_visit_class_body(Visitor &v) {
11991199
class_guard g(this, std::exchange(this->in_class_, true));
12001200

1201+
source_code_span left_curly_span = this->peek().span();
12011202
this->skip();
12021203

12031204
while (this->peek().type != token_type::right_curly) {
12041205
this->parse_and_visit_class_member(v);
1206+
if (this->peek().type == token_type::end_of_file) {
1207+
this->error_reporter_->report(error_unclosed_class_block{
1208+
.block_open = left_curly_span,
1209+
});
1210+
return;
1211+
}
12051212
}
12061213

12071214
QLJS_PARSER_UNIMPLEMENTED_IF_NOT_TOKEN(token_type::right_curly);
@@ -1521,6 +1528,11 @@ void parser::parse_and_visit_class_member(Visitor &v) {
15211528
this->skip();
15221529
break;
15231530

1531+
// class C { // Invalid.
1532+
case token_type::end_of_file:
1533+
// An error is reported by parse_and_visit_class_body.
1534+
break;
1535+
15241536
default:
15251537
QLJS_PARSER_UNIMPLEMENTED();
15261538
break;

test/test-parse-class.cpp

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -129,6 +129,39 @@ TEST(test_parse, class_statement_requires_a_body) {
129129
}
130130
}
131131

132+
TEST(test_parse, unclosed_class_statement) {
133+
{
134+
spy_visitor v;
135+
padded_string code(u8"class C { "_sv);
136+
parser p(&code, &v);
137+
EXPECT_TRUE(p.parse_and_visit_statement(v));
138+
EXPECT_THAT(v.visits, ElementsAre("visit_variable_declaration", // C
139+
"visit_enter_class_scope", //
140+
"visit_exit_class_scope"));
141+
EXPECT_THAT(v.errors, ElementsAre(ERROR_TYPE_OFFSETS(
142+
&code, error_unclosed_class_block, //
143+
block_open, strlen(u8"class C "), u8"{")));
144+
}
145+
146+
{
147+
spy_visitor v;
148+
padded_string code(u8"class C { method() {} "_sv);
149+
parser p(&code, &v);
150+
EXPECT_TRUE(p.parse_and_visit_statement(v));
151+
EXPECT_THAT(v.visits,
152+
ElementsAre("visit_variable_declaration", // C
153+
"visit_enter_class_scope", // C
154+
"visit_property_declaration", // method
155+
"visit_enter_function_scope", // method
156+
"visit_enter_function_scope_body", // method
157+
"visit_exit_function_scope", // method
158+
"visit_exit_class_scope")); // C
159+
EXPECT_THAT(v.errors, ElementsAre(ERROR_TYPE_OFFSETS(
160+
&code, error_unclosed_class_block, //
161+
block_open, strlen(u8"class C "), u8"{")));
162+
}
163+
}
164+
132165
TEST(test_parse, class_statement_with_odd_heritage) {
133166
{
134167
// TODO(strager): Should this report errors?

0 commit comments

Comments
 (0)