Skip to content

Commit 2ad855f

Browse files
Disable variable lookup errors in with blocks
Fixes #94
1 parent 631e984 commit 2ad855f

15 files changed

+220
-32
lines changed

src/lint.cpp

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -219,6 +219,8 @@ linter::linter(error_reporter *error_reporter)
219219

220220
void linter::visit_enter_block_scope() { this->scopes_.push(); }
221221

222+
void linter::visit_enter_with_scope() { this->scopes_.push(); }
223+
222224
void linter::visit_enter_class_scope() { this->scopes_.push(); }
223225

224226
void linter::visit_enter_for_scope() { this->scopes_.push(); }
@@ -247,6 +249,13 @@ void linter::visit_exit_block_scope() {
247249
this->scopes_.pop();
248250
}
249251

252+
void linter::visit_exit_with_scope() {
253+
QLJS_ASSERT(!this->scopes_.empty());
254+
// Don't propagate variable uses, only declarations
255+
this->propagate_variable_declarations_to_parent_scope();
256+
this->scopes_.pop();
257+
}
258+
250259
void linter::visit_exit_class_scope() {
251260
QLJS_ASSERT(!this->scopes_.empty());
252261
this->propagate_variable_uses_to_parent_scope(

src/main.cpp

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -180,6 +180,8 @@ class debug_visitor {
180180

181181
void visit_enter_block_scope() { std::cerr << "entered block scope\n"; }
182182

183+
void visit_enter_with_scope() { std::cerr << "entered with scope\n"; }
184+
183185
void visit_enter_class_scope() { std::cerr << "entered class scope\n"; }
184186

185187
void visit_enter_for_scope() { std::cerr << "entered for scope\n"; }
@@ -196,6 +198,8 @@ class debug_visitor {
196198

197199
void visit_exit_block_scope() { std::cerr << "exited block scope\n"; }
198200

201+
void visit_exit_with_scope() { std::cerr << "exited with scope\n"; }
202+
199203
void visit_exit_class_scope() { std::cerr << "exited class scope\n"; }
200204

201205
void visit_exit_for_scope() { std::cerr << "exited for scope\n"; }
@@ -253,6 +257,11 @@ class multi_visitor {
253257
this->visitor_2_->visit_enter_block_scope();
254258
}
255259

260+
void visit_enter_with_scope() {
261+
this->visitor_1_->visit_enter_with_scope();
262+
this->visitor_2_->visit_enter_with_scope();
263+
}
264+
256265
void visit_enter_class_scope() {
257266
this->visitor_1_->visit_enter_class_scope();
258267
this->visitor_2_->visit_enter_class_scope();
@@ -283,6 +292,11 @@ class multi_visitor {
283292
this->visitor_2_->visit_exit_block_scope();
284293
}
285294

295+
void visit_exit_with_scope() {
296+
this->visitor_1_->visit_exit_with_scope();
297+
this->visitor_2_->visit_exit_with_scope();
298+
}
299+
286300
void visit_exit_class_scope() {
287301
this->visitor_1_->visit_exit_class_scope();
288302
this->visitor_2_->visit_exit_class_scope();

src/quick-lint-js/buffering-visitor.h

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,9 @@ class buffering_visitor {
3838
case visit_kind::enter_block_scope:
3939
target.visit_enter_block_scope();
4040
break;
41+
case visit_kind::enter_with_scope:
42+
target.visit_enter_with_scope();
43+
break;
4144
case visit_kind::enter_class_scope:
4245
target.visit_enter_class_scope();
4346
break;
@@ -56,6 +59,9 @@ class buffering_visitor {
5659
case visit_kind::exit_block_scope:
5760
target.visit_exit_block_scope();
5861
break;
62+
case visit_kind::exit_with_scope:
63+
target.visit_exit_with_scope();
64+
break;
5965
case visit_kind::exit_class_scope:
6066
target.visit_exit_class_scope();
6167
break;
@@ -98,6 +104,10 @@ class buffering_visitor {
98104
this->visits_.emplace_back(visit_kind::enter_block_scope);
99105
}
100106

107+
void visit_enter_with_scope() {
108+
this->visits_.emplace_back(visit_kind::enter_with_scope);
109+
}
110+
101111
void visit_enter_class_scope() {
102112
this->visits_.emplace_back(visit_kind::enter_class_scope);
103113
}
@@ -122,6 +132,10 @@ class buffering_visitor {
122132
this->visits_.emplace_back(visit_kind::exit_block_scope);
123133
}
124134

135+
void visit_exit_with_scope() {
136+
this->visits_.emplace_back(visit_kind::exit_with_scope);
137+
}
138+
125139
void visit_exit_class_scope() {
126140
this->visits_.emplace_back(visit_kind::exit_class_scope);
127141
}
@@ -167,12 +181,14 @@ class buffering_visitor {
167181
enum class visit_kind {
168182
end_of_module,
169183
enter_block_scope,
184+
enter_with_scope,
170185
enter_class_scope,
171186
enter_for_scope,
172187
enter_function_scope,
173188
enter_function_scope_body,
174189
enter_named_function_scope,
175190
exit_block_scope,
191+
exit_with_scope,
176192
exit_class_scope,
177193
exit_for_scope,
178194
exit_function_scope,

src/quick-lint-js/lint.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,12 +27,14 @@ class linter {
2727
explicit linter(error_reporter *error_reporter);
2828

2929
void visit_enter_block_scope();
30+
void visit_enter_with_scope();
3031
void visit_enter_class_scope();
3132
void visit_enter_for_scope();
3233
void visit_enter_function_scope();
3334
void visit_enter_function_scope_body();
3435
void visit_enter_named_function_scope(identifier);
3536
void visit_exit_block_scope();
37+
void visit_exit_with_scope();
3638
void visit_exit_class_scope();
3739
void visit_exit_for_scope();
3840
void visit_exit_function_scope();

src/quick-lint-js/null-visitor.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,12 +13,14 @@ class null_visitor {
1313
public:
1414
void visit_end_of_module() {}
1515
void visit_enter_block_scope() {}
16+
void visit_enter_with_scope() {}
1617
void visit_enter_class_scope() {}
1718
void visit_enter_for_scope() {}
1819
void visit_enter_function_scope() {}
1920
void visit_enter_function_scope_body() {}
2021
void visit_enter_named_function_scope(identifier) {}
2122
void visit_exit_block_scope() {}
23+
void visit_exit_with_scope() {}
2224
void visit_exit_class_scope() {}
2325
void visit_exit_for_scope() {}
2426
void visit_exit_function_scope() {}

src/quick-lint-js/parse-visitor.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,12 +34,14 @@ concept parse_visitor = requires(Visitor v, identifier name,
3434
variable_kind var_kind) {
3535
{v.visit_end_of_module()};
3636
{v.visit_enter_block_scope()};
37+
{v.visit_enter_with_scope()};
3738
{v.visit_enter_class_scope()};
3839
{v.visit_enter_for_scope()};
3940
{v.visit_enter_function_scope()};
4041
{v.visit_enter_function_scope_body()};
4142
{v.visit_enter_named_function_scope(name)};
4243
{v.visit_exit_block_scope()};
44+
{v.visit_exit_with_scope()};
4345
{v.visit_exit_class_scope()};
4446
{v.visit_exit_for_scope()};
4547
{v.visit_exit_function_scope()};

src/quick-lint-js/parse.h

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2450,11 +2450,14 @@ class parser {
24502450
this->error_on_class_statement(statement_kind::with_statement);
24512451
this->error_on_function_statement(statement_kind::with_statement);
24522452
this->error_on_lexical_declaration(statement_kind::with_statement);
2453+
2454+
v.visit_enter_with_scope();
24532455
bool parsed_body =
24542456
this->parse_and_visit_statement_disallowing_declaration(v);
24552457
if (!parsed_body) {
24562458
QLJS_PARSER_UNIMPLEMENTED();
24572459
}
2460+
v.visit_exit_with_scope();
24582461
}
24592462

24602463
template <QLJS_PARSE_VISITOR Visitor>

test/quick-lint-js/spy-visitor.h

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,10 @@ struct spy_visitor : public error_collector {
2727
this->visits.emplace_back("visit_enter_block_scope");
2828
}
2929

30+
void visit_enter_with_scope() {
31+
this->visits.emplace_back("visit_enter_with_scope");
32+
}
33+
3034
void visit_enter_class_scope() {
3135
this->visits.emplace_back("visit_enter_class_scope");
3236
}
@@ -66,6 +70,10 @@ struct spy_visitor : public error_collector {
6670
this->visits.emplace_back("visit_exit_block_scope");
6771
}
6872

73+
void visit_exit_with_scope() {
74+
this->visits.emplace_back("visit_exit_with_scope");
75+
}
76+
6977
void visit_exit_class_scope() {
7078
this->visits.emplace_back("visit_exit_class_scope");
7179
}

test/test-buffering-visitor.cpp

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,12 +34,14 @@ TEST(test_buffering_visitor, buffers_all_visits) {
3434
buffering_visitor v(new_delete_resource());
3535
v.visit_end_of_module();
3636
v.visit_enter_block_scope();
37+
v.visit_enter_with_scope();
3738
v.visit_enter_class_scope();
3839
v.visit_enter_for_scope();
3940
v.visit_enter_named_function_scope(identifier_of(function_name));
4041
v.visit_enter_function_scope();
4142
v.visit_enter_function_scope_body();
4243
v.visit_exit_block_scope();
44+
v.visit_exit_with_scope();
4345
v.visit_exit_class_scope();
4446
v.visit_exit_for_scope();
4547
v.visit_exit_function_scope();
@@ -57,12 +59,14 @@ TEST(test_buffering_visitor, buffers_all_visits) {
5759
EXPECT_THAT(spy.visits,
5860
ElementsAre("visit_end_of_module", //
5961
"visit_enter_block_scope", //
62+
"visit_enter_with_scope", //
6063
"visit_enter_class_scope", //
6164
"visit_enter_for_scope", //
6265
"visit_enter_named_function_scope", //
6366
"visit_enter_function_scope", //
6467
"visit_enter_function_scope_body", //
6568
"visit_exit_block_scope", //
69+
"visit_exit_with_scope", //
6670
"visit_exit_class_scope", //
6771
"visit_exit_for_scope", //
6872
"visit_exit_function_scope", //

test/test-lint.cpp

Lines changed: 104 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2386,6 +2386,110 @@ TEST(
23862386
"with 'b'";
23872387
}
23882388

2389+
TEST(test_lint, with_does_not_propagate_variable_uses) {
2390+
const char8 declaration[] = u8"a";
2391+
const char8 assignment[] = u8"a";
2392+
const char8 use[] = u8"a";
2393+
2394+
{
2395+
// with({})
2396+
// a;
2397+
error_collector v;
2398+
linter l(&v);
2399+
l.visit_enter_with_scope();
2400+
l.visit_variable_use(identifier_of(use));
2401+
l.visit_exit_with_scope();
2402+
l.visit_end_of_module();
2403+
2404+
EXPECT_THAT(v.errors, IsEmpty()) << "use of undeclared variable should not "
2405+
"be an error inside with scope";
2406+
}
2407+
2408+
{
2409+
// const a = 1;
2410+
// with ({})
2411+
// a = 2;
2412+
2413+
error_collector v;
2414+
linter l(&v);
2415+
l.visit_variable_declaration(identifier_of(declaration),
2416+
variable_kind::_const);
2417+
l.visit_enter_with_scope();
2418+
l.visit_variable_assignment(identifier_of(assignment));
2419+
l.visit_exit_with_scope();
2420+
l.visit_end_of_module();
2421+
2422+
EXPECT_THAT(v.errors, IsEmpty()) << "assigning to 'a' should not "
2423+
"be an error inside with scope";
2424+
}
2425+
2426+
{
2427+
// with ({})
2428+
// a = 2;
2429+
// let a;
2430+
2431+
error_collector v;
2432+
linter l(&v);
2433+
l.visit_enter_with_scope();
2434+
l.visit_variable_assignment(identifier_of(assignment));
2435+
l.visit_exit_with_scope();
2436+
l.visit_variable_declaration(identifier_of(declaration),
2437+
variable_kind::_let);
2438+
l.visit_end_of_module();
2439+
2440+
EXPECT_THAT(v.errors, IsEmpty()) << "assigning to 'a' should not "
2441+
"be an error inside with scope";
2442+
}
2443+
2444+
{
2445+
// with ({}) {
2446+
// const a = 1;
2447+
// a = 2;
2448+
// }
2449+
2450+
error_collector v;
2451+
linter l(&v);
2452+
l.visit_enter_with_scope();
2453+
l.visit_enter_block_scope();
2454+
l.visit_variable_declaration(identifier_of(declaration),
2455+
variable_kind::_const);
2456+
l.visit_variable_assignment(identifier_of(assignment));
2457+
l.visit_exit_block_scope();
2458+
l.visit_exit_with_scope();
2459+
l.visit_end_of_module();
2460+
2461+
EXPECT_THAT(v.errors, ElementsAre(ERROR_TYPE_3_FIELDS(
2462+
error_assignment_to_const_variable, //
2463+
assignment, span_matcher(assignment), //
2464+
declaration, span_matcher(declaration), //
2465+
var_kind, variable_kind::_const)));
2466+
}
2467+
2468+
{
2469+
// with ({}) {
2470+
// function f() {
2471+
// a;
2472+
// }
2473+
// }
2474+
2475+
error_collector v;
2476+
linter l(&v);
2477+
l.visit_enter_with_scope();
2478+
l.visit_enter_block_scope();
2479+
l.visit_enter_function_scope();
2480+
l.visit_enter_function_scope_body();
2481+
l.visit_variable_use(identifier_of(use));
2482+
l.visit_exit_function_scope();
2483+
l.visit_exit_block_scope();
2484+
l.visit_exit_with_scope();
2485+
l.visit_end_of_module();
2486+
2487+
EXPECT_THAT(v.errors, IsEmpty()) << "use of undeclared variable should not "
2488+
"be an error inside a function inside a"
2489+
"with scope";
2490+
}
2491+
}
2492+
23892493
TEST(test_lint_magic_arguments,
23902494
arguments_magic_variable_is_usable_within_functions) {
23912495
const char8 arguments_use[] = u8"arguments";

0 commit comments

Comments
 (0)