Skip to content

Commit 68bddc3

Browse files
committed
Make 'delete x' not warn if x is global
'delete x' is a no-op if the module defines 'x' using 'var', 'let', etc. However, if 'x' is a global variable, then 'delete x' has side effects. quick-lint-js reports a warning in both cases, meaning it reports a false positive in the latter case. Make quick-lint-js report a warning on 'delete x' only if x is not a global variable.
1 parent a114d08 commit 68bddc3

File tree

3 files changed

+127
-8
lines changed

3 files changed

+127
-8
lines changed

src/lint.cpp

Lines changed: 33 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -289,6 +289,15 @@ void linter::declare_variable(scope &scope, identifier name, variable_kind kind,
289289
error_function_call_before_declaration_in_block_scope{used_var.name,
290290
name});
291291
}
292+
if (used_var.kind == used_variable_kind::_delete) {
293+
// TODO(strager): What if the variable was parenthesized? We should
294+
// include the closing parenthesis.
295+
this->error_reporter_->report(
296+
error_redundant_delete_statement_on_variable{
297+
.delete_expression = source_code_span(
298+
used_var.delete_keyword_begin, used_var.name.span().end()),
299+
});
300+
}
292301
if (kind == variable_kind::_class || kind == variable_kind::_const ||
293302
kind == variable_kind::_let) {
294303
switch (used_var.kind) {
@@ -302,6 +311,9 @@ void linter::declare_variable(scope &scope, identifier name, variable_kind kind,
302311
this->error_reporter_->report(
303312
error_variable_used_before_declaration{used_var.name, name});
304313
break;
314+
case used_variable_kind::_delete:
315+
// Use before declaration is legal for delete.
316+
break;
305317
case used_variable_kind::_export:
306318
// Use before declaration is legal for variable exports.
307319
break;
@@ -324,6 +336,7 @@ void linter::declare_variable(scope &scope, identifier name, variable_kind kind,
324336
// TODO(strager): This shouldn't happen. export statements are
325337
// not allowed inside functions.
326338
break;
339+
case used_variable_kind::_delete:
327340
case used_variable_kind::_typeof:
328341
case used_variable_kind::use:
329342
break;
@@ -347,14 +360,23 @@ void linter::visit_variable_assignment(identifier name) {
347360

348361
void linter::visit_variable_delete_use(identifier name,
349362
source_code_span delete_keyword) {
350-
this->visit_variable_use(name, used_variable_kind::use);
351363
QLJS_ASSERT(delete_keyword.end() <= name.span().begin());
352-
// TODO(strager): What if the variable was parenthesized? We should include
353-
// the closing parenthesis.
354-
this->error_reporter_->report(error_redundant_delete_statement_on_variable{
355-
.delete_expression =
356-
source_code_span(delete_keyword.begin(), name.span().end()),
357-
});
364+
365+
QLJS_ASSERT(!this->scopes_.empty());
366+
scope &current_scope = this->current_scope();
367+
bool variable_is_declared =
368+
current_scope.declared_variables.find(name) != nullptr;
369+
if (variable_is_declared) {
370+
// TODO(strager): What if the variable was parenthesized? We should include
371+
// the closing parenthesis.
372+
this->error_reporter_->report(error_redundant_delete_statement_on_variable{
373+
.delete_expression =
374+
source_code_span(delete_keyword.begin(), name.span().end()),
375+
});
376+
} else {
377+
current_scope.variables_used.emplace_back(name, used_variable_kind::_delete,
378+
delete_keyword.begin());
379+
}
358380
}
359381

360382
void linter::visit_variable_export_use(identifier name) {
@@ -432,6 +454,10 @@ void linter::visit_end_of_module() {
432454
this->error_reporter_->report(
433455
error_assignment_to_undeclared_variable{used_var.name});
434456
break;
457+
case used_variable_kind::_delete:
458+
// TODO(strager): Report a warning if the global variable is not
459+
// deletable.
460+
break;
435461
case used_variable_kind::_export:
436462
case used_variable_kind::use:
437463
this->error_reporter_->report(

src/quick-lint-js/lint.h

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
#define QUICK_LINT_JS_LINT_H
66

77
#include <optional>
8+
#include <quick-lint-js/assert.h>
89
#include <quick-lint-js/char8.h>
910
#include <quick-lint-js/language.h>
1011
#include <quick-lint-js/lex.h>
@@ -102,6 +103,7 @@ class linter {
102103
};
103104

104105
enum class used_variable_kind {
106+
_delete,
105107
_export,
106108
_typeof,
107109
assignment,
@@ -110,9 +112,19 @@ class linter {
110112

111113
struct used_variable {
112114
explicit used_variable(identifier name, used_variable_kind kind) noexcept
113-
: name(name), kind(kind) {}
115+
: name(name), kind(kind) {
116+
QLJS_ASSERT(kind != used_variable_kind::_delete);
117+
}
118+
119+
// kind must be used_variable_kind::_delete.
120+
explicit used_variable(identifier name, used_variable_kind kind,
121+
const char8 *delete_keyword_begin) noexcept
122+
: name(name), delete_keyword_begin(delete_keyword_begin), kind(kind) {
123+
QLJS_ASSERT(kind == used_variable_kind::_delete);
124+
}
114125

115126
identifier name;
127+
const char8 *delete_keyword_begin; // used_variable_kind::_delete only
116128
used_variable_kind kind;
117129
};
118130

test/test-lint.cpp

Lines changed: 81 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2736,6 +2736,87 @@ TEST(test_lint_delete, deleting_local_variable_is_a_warning) {
27362736
offsets_matcher(&delete_expression, 0, u8"delete x"))));
27372737
}
27382738

2739+
TEST(test_lint_delete, deleting_local_variable_declared_later_is_a_warning) {
2740+
const char8 declaration[] = u8"v";
2741+
padded_string delete_expression(u8"delete v"_sv);
2742+
source_code_span delete_keyword_span(delete_expression.data(),
2743+
delete_expression.data() + 6);
2744+
ASSERT_EQ(delete_keyword_span.string_view(), u8"delete"_sv);
2745+
source_code_span deleted_variable_span(delete_expression.data() + 7,
2746+
delete_expression.data() + 8);
2747+
ASSERT_EQ(deleted_variable_span.string_view(), u8"v"_sv);
2748+
2749+
// (() => {
2750+
// delete v;
2751+
// let v;
2752+
// });
2753+
error_collector v;
2754+
linter l(&v, &default_globals);
2755+
l.visit_enter_function_scope();
2756+
l.visit_enter_function_scope_body();
2757+
l.visit_variable_delete_use(identifier(deleted_variable_span),
2758+
delete_keyword_span);
2759+
l.visit_variable_declaration(identifier_of(declaration), variable_kind::_let);
2760+
l.visit_exit_function_scope();
2761+
l.visit_end_of_module();
2762+
2763+
EXPECT_THAT(
2764+
v.errors,
2765+
ElementsAre(ERROR_TYPE_FIELD(
2766+
error_redundant_delete_statement_on_variable, delete_expression,
2767+
offsets_matcher(&delete_expression, 0, u8"delete x"))));
2768+
}
2769+
2770+
TEST(test_lint_delete, deleting_declared_module_variable_is_a_warning) {
2771+
const char8 declaration[] = u8"v";
2772+
padded_string delete_expression(u8"delete v"_sv);
2773+
source_code_span delete_keyword_span(delete_expression.data(),
2774+
delete_expression.data() + 6);
2775+
ASSERT_EQ(delete_keyword_span.string_view(), u8"delete"_sv);
2776+
source_code_span deleted_variable_span(delete_expression.data() + 7,
2777+
delete_expression.data() + 8);
2778+
ASSERT_EQ(deleted_variable_span.string_view(), u8"v"_sv);
2779+
2780+
// let v;
2781+
// delete v;
2782+
error_collector v;
2783+
linter l(&v, &default_globals);
2784+
l.visit_variable_declaration(identifier_of(declaration), variable_kind::_let);
2785+
l.visit_variable_delete_use(identifier(deleted_variable_span),
2786+
delete_keyword_span);
2787+
l.visit_end_of_module();
2788+
2789+
EXPECT_THAT(
2790+
v.errors,
2791+
ElementsAre(ERROR_TYPE_FIELD(
2792+
error_redundant_delete_statement_on_variable, delete_expression,
2793+
offsets_matcher(&delete_expression, 0, u8"delete x"))));
2794+
}
2795+
2796+
TEST(test_lint_delete, deleting_declared_global_variable_is_a_warning) {
2797+
padded_string code(u8"delete myGlobalVariable"_sv);
2798+
source_code_span delete_keyword_span(code.data(), code.data() + 6);
2799+
ASSERT_EQ(delete_keyword_span.string_view(), u8"delete"_sv);
2800+
source_code_span deleted_variable_span(code.data() + 7, code.data() + 23);
2801+
ASSERT_EQ(deleted_variable_span.string_view(), u8"myGlobalVariable"_sv);
2802+
2803+
configuration config;
2804+
config.add_global_variable(global_declared_variable{
2805+
.name = u8"myGlobalVariable",
2806+
.is_writable = true,
2807+
.is_shadowable = true,
2808+
});
2809+
2810+
// delete myGlobalVariable;
2811+
error_collector v;
2812+
linter l(&v, &config.globals());
2813+
l.visit_variable_delete_use(identifier(deleted_variable_span),
2814+
delete_keyword_span);
2815+
l.visit_end_of_module();
2816+
2817+
EXPECT_THAT(v.errors, IsEmpty());
2818+
}
2819+
27392820
TEST(test_lint_typeof, using_undeclared_variable_in_typeof_is_not_an_error) {
27402821
const char8 use[] = u8"v";
27412822

0 commit comments

Comments
 (0)