Skip to content

Commit 9947942

Browse files
authored
improve error message for unopened block comment
1 parent 467d61e commit 9947942

File tree

4 files changed

+154
-0
lines changed

4 files changed

+154
-0
lines changed

src/lex.cpp

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -450,13 +450,38 @@ void lexer::parse_current_token() {
450450
if (this->input_[2] == '=') {
451451
this->last_token_.type = token_type::star_star_equal;
452452
this->input_ += 3;
453+
} else if (this->input_[2] == '/') {
454+
bool parsed_ok = this->test_for_regexp(&this->input_[2]);
455+
if (!parsed_ok) {
456+
// We saw '**/'. Emit a '*' token now. Later, we will interpret the
457+
// following '*/' as a comment.
458+
this->last_token_.type = token_type::star;
459+
this->input_ += 1;
460+
} else {
461+
this->last_token_.type = token_type::star_star;
462+
this->input_ += 2;
463+
}
453464
} else {
454465
this->last_token_.type = token_type::star_star;
455466
this->input_ += 2;
456467
}
457468
} else if (this->input_[1] == '=') {
458469
this->last_token_.type = token_type::star_equal;
459470
this->input_ += 2;
471+
} else if (this->input_[1] == '/') {
472+
const char8* starpos = &this->input_[0];
473+
bool parsed_ok = this->test_for_regexp(&this->input_[1]);
474+
475+
if (!parsed_ok) {
476+
this->error_reporter_->report(error_unopened_block_comment{
477+
source_code_span(starpos, &this->input_[2])});
478+
this->input_ += 2;
479+
this->skip_whitespace();
480+
goto retry;
481+
} else {
482+
this->last_token_.type = token_type::star;
483+
this->input_ += 1;
484+
}
460485
} else {
461486
this->last_token_.type = token_type::star;
462487
this->input_ += 1;
@@ -644,6 +669,19 @@ void lexer::parse_current_token() {
644669
}
645670
}
646671

672+
bool lexer::test_for_regexp(const char8* regexp_begin) {
673+
lexer_transaction transaction = this->begin_transaction();
674+
675+
this->last_token_.type = token_type::slash;
676+
this->input_ = regexp_begin;
677+
this->last_token_.begin = this->input_;
678+
this->reparse_as_regexp();
679+
680+
bool parsed_ok = !this->transaction_has_lex_errors(transaction);
681+
this->roll_back_transaction(std::move(transaction));
682+
return parsed_ok;
683+
}
684+
647685
const char8* lexer::parse_string_literal() noexcept {
648686
char8 opening_quote = this->input_[0];
649687

src/quick-lint-js/error.h

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1006,6 +1006,11 @@
10061006
{ source_code_span comment_open; }, \
10071007
.error(QLJS_TRANSLATABLE("unclosed block comment"), comment_open)) \
10081008
\
1009+
QLJS_ERROR_TYPE( \
1010+
error_unopened_block_comment, "E210", \
1011+
{ source_code_span comment_close; }, \
1012+
.error(QLJS_TRANSLATABLE("unopened block comment"), comment_close)) \
1013+
\
10091014
QLJS_ERROR_TYPE( \
10101015
error_unclosed_code_block, "E134", { source_code_span block_open; }, \
10111016
.error(QLJS_TRANSLATABLE( \

src/quick-lint-js/lex.h

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -108,6 +108,10 @@ class lexer {
108108
// Postcondition: this->peek().type == token_type::regexp.
109109
void reparse_as_regexp();
110110

111+
// Returns true if a valid regexp literal is found
112+
// Precondition: *regexp_begin == '/'
113+
bool test_for_regexp(const char8* regexp_begin);
114+
111115
// Save lexer state.
112116
//
113117
// After calling begin_transaction, you must call either commit_transaction or

test/test-lex.cpp

Lines changed: 107 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -113,6 +113,113 @@ TEST_F(test_lex, lex_block_comments) {
113113
}
114114
}
115115

116+
TEST_F(test_lex, lex_unopened_block_comment) {
117+
{
118+
error_collector v;
119+
padded_string input(u8"hello */"_sv);
120+
lexer l(&input, &v); // identifier
121+
EXPECT_EQ(l.peek().type, token_type::identifier);
122+
l.skip(); // end of file
123+
EXPECT_EQ(l.peek().type, token_type::end_of_file);
124+
EXPECT_THAT(v.errors,
125+
ElementsAre(ERROR_TYPE_FIELD(
126+
error_unopened_block_comment, comment_close,
127+
offsets_matcher(&input, strlen(u8"hello "), u8"*/"))));
128+
}
129+
{
130+
error_collector v;
131+
padded_string input(u8"*-----*/"_sv);
132+
lexer l(&input, &v);
133+
134+
while (l.peek().type != token_type::end_of_file) {
135+
l.skip();
136+
}
137+
EXPECT_EQ(l.peek().type, token_type::end_of_file);
138+
139+
EXPECT_THAT(v.errors,
140+
ElementsAre(ERROR_TYPE_FIELD(
141+
error_unopened_block_comment, comment_close,
142+
offsets_matcher(&input, strlen(u8"*-----"), u8"*/"))));
143+
}
144+
{
145+
error_collector v;
146+
padded_string input(u8"*******/"_sv);
147+
lexer l(&input, &v);
148+
EXPECT_EQ(l.peek().type, token_type::star_star);
149+
l.skip();
150+
EXPECT_EQ(l.peek().type, token_type::star_star);
151+
l.skip();
152+
EXPECT_EQ(l.peek().type, token_type::star_star);
153+
l.skip();
154+
EXPECT_EQ(l.peek().type, token_type::end_of_file);
155+
156+
EXPECT_THAT(v.errors,
157+
ElementsAre(ERROR_TYPE_FIELD(
158+
error_unopened_block_comment, comment_close,
159+
offsets_matcher(&input, strlen(u8"******"), u8"*/"))));
160+
}
161+
{
162+
error_collector v;
163+
padded_string input(u8"*/"_sv);
164+
lexer l(&input, &v);
165+
EXPECT_EQ(l.peek().type, token_type::end_of_file);
166+
167+
EXPECT_THAT(v.errors, ElementsAre(ERROR_TYPE_FIELD(
168+
error_unopened_block_comment, comment_close,
169+
offsets_matcher(&input, 0, u8"*/"))));
170+
}
171+
{
172+
error_collector v;
173+
padded_string input(u8"**/"_sv);
174+
lexer l(&input, &v);
175+
EXPECT_EQ(l.peek().type, token_type::star);
176+
l.skip();
177+
EXPECT_EQ(l.peek().type, token_type::end_of_file);
178+
EXPECT_THAT(v.errors, ElementsAre(ERROR_TYPE_FIELD(
179+
error_unopened_block_comment, comment_close,
180+
offsets_matcher(&input, 1, u8"*/"))));
181+
}
182+
}
183+
184+
TEST_F(test_lex, lex_regexp_literal_starting_with_star_slash) {
185+
{
186+
// '/*' is not an end of block comment because it precedes a regexp literal
187+
error_collector v;
188+
padded_string input(u8"*/ hello/"_sv);
189+
lexer l(&input, &v);
190+
EXPECT_EQ(l.peek().type, token_type::star);
191+
l.skip();
192+
EXPECT_EQ(l.peek().type, token_type::slash);
193+
l.reparse_as_regexp();
194+
EXPECT_EQ(l.peek().type, token_type::regexp);
195+
EXPECT_EQ(l.peek().begin, &input[1]);
196+
EXPECT_EQ(l.peek().end, &input[input.size()]);
197+
l.skip();
198+
EXPECT_EQ(l.peek().type, token_type::end_of_file);
199+
EXPECT_THAT(v.errors, IsEmpty());
200+
}
201+
}
202+
203+
TEST_F(test_lex, lex_regexp_literal_starting_with_star_star_slash) {
204+
{
205+
padded_string input(u8"3 **/ banana/"_sv);
206+
error_collector v;
207+
lexer l(&input, &v);
208+
EXPECT_EQ(l.peek().type, token_type::number);
209+
l.skip();
210+
EXPECT_EQ(l.peek().type, token_type::star_star);
211+
l.skip();
212+
EXPECT_EQ(l.peek().type, token_type::slash);
213+
l.reparse_as_regexp();
214+
EXPECT_EQ(l.peek().type, token_type::regexp);
215+
EXPECT_EQ(l.peek().begin, &input[4]);
216+
EXPECT_EQ(l.peek().end, &input[input.size()]);
217+
l.skip();
218+
EXPECT_EQ(l.peek().type, token_type::end_of_file);
219+
EXPECT_THAT(v.errors, IsEmpty());
220+
}
221+
}
222+
116223
TEST_F(test_lex, lex_line_comments) {
117224
EXPECT_THAT(this->lex_to_eof(u8"// hello"_sv), IsEmpty());
118225
for (string8_view line_terminator : line_terminators) {

0 commit comments

Comments
 (0)