Skip to content

Commit 4bf1896

Browse files
arieldonstrager
authored andcommitted
feat: error on multiple export defaults in module
1 parent 457ece7 commit 4bf1896

11 files changed

+138
-5
lines changed

docs/errors/E0715.md

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
# E0715: cannot use multiple `export default` statements in one module
2+
3+
Modules in JavaScript can use two types of exports: default export and named export. While a module
4+
can use multiple named exports, it can only use a single default export.
5+
6+
7+
```javascript
8+
export default function foo() {
9+
console.log("foo");
10+
}
11+
12+
export default function bar() {
13+
console.log("bar");
14+
}
15+
```
16+
17+
18+
If you want to export several values from a module, use named exports.
19+
20+
21+
```javascript
22+
function foo(x) {
23+
console.log("foo");
24+
}
25+
26+
function bar(x) {
27+
console.log("bar");
28+
}
29+
30+
export { foo, bar };
31+
```

po/messages.pot

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2081,6 +2081,14 @@ msgstr ""
20812081
msgid "'async' keyword is not allowed on getters or setters"
20822082
msgstr ""
20832083

2084+
#: src/quick-lint-js/diag/diagnostic-metadata-generated.cpp
2085+
msgid "cannot use multiple `export default` statements in one module"
2086+
msgstr ""
2087+
2088+
#: src/quick-lint-js/diag/diagnostic-metadata-generated.cpp
2089+
msgid "export default previously appeared here"
2090+
msgstr ""
2091+
20842092
#: test/test-diagnostic-formatter.cpp
20852093
#: test/test-vim-qflist-json-diag-reporter.cpp
20862094
msgid "something happened"

src/quick-lint-js/diag/diagnostic-metadata-generated.cpp

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6407,6 +6407,24 @@ const QLJS_CONSTINIT Diagnostic_Info all_diagnostic_infos[] = {
64076407
},
64086408
},
64096409
},
6410+
6411+
// Diag_Multiple_Export_Defaults
6412+
{
6413+
.code = 715,
6414+
.severity = Diagnostic_Severity::error,
6415+
.message_formats = {
6416+
QLJS_TRANSLATABLE("cannot use multiple `export default` statements in one module"),
6417+
QLJS_TRANSLATABLE("export default previously appeared here"),
6418+
},
6419+
.message_args = {
6420+
{
6421+
Diagnostic_Message_Arg_Info(offsetof(Diag_Multiple_Export_Defaults, second_export_default), Diagnostic_Arg_Type::source_code_span),
6422+
},
6423+
{
6424+
Diagnostic_Message_Arg_Info(offsetof(Diag_Multiple_Export_Defaults, first_export_default), Diagnostic_Arg_Type::source_code_span),
6425+
},
6426+
},
6427+
},
64106428
};
64116429
}
64126430

src/quick-lint-js/diag/diagnostic-metadata-generated.h

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -439,10 +439,11 @@ namespace quick_lint_js {
439439
QLJS_DIAG_TYPE_NAME(Diag_Missing_Comma_Between_Array_Elements) \
440440
QLJS_DIAG_TYPE_NAME(Diag_Class_Generator_On_Getter_Or_Setter) \
441441
QLJS_DIAG_TYPE_NAME(Diag_Class_Async_On_Getter_Or_Setter) \
442+
QLJS_DIAG_TYPE_NAME(Diag_Multiple_Export_Defaults) \
442443
/* END */
443444
// clang-format on
444445

445-
inline constexpr int Diag_Type_Count = 428;
446+
inline constexpr int Diag_Type_Count = 429;
446447

447448
extern const Diagnostic_Info all_diagnostic_infos[Diag_Type_Count];
448449
}

src/quick-lint-js/diag/diagnostic-types-2.h

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3307,6 +3307,17 @@ struct Diag_Class_Async_On_Getter_Or_Setter {
33073307
Source_Code_Span async_keyword;
33083308
Source_Code_Span getter_setter_keyword;
33093309
};
3310+
3311+
struct Diag_Multiple_Export_Defaults {
3312+
[[qljs::diag("E0715", Diagnostic_Severity::error)]] //
3313+
[[qljs::message(
3314+
"cannot use multiple `export default` statements in one module",
3315+
ARG(second_export_default))]] //
3316+
[[qljs::message("export default previously appeared here",
3317+
ARG(first_export_default))]] //
3318+
Source_Code_Span second_export_default;
3319+
Source_Code_Span first_export_default;
3320+
};
33103321
}
33113322
QLJS_WARNING_POP
33123323

src/quick-lint-js/fe/parse-statement.cpp

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,8 @@ bool Parser::parse_and_visit_module_catching_fatal_parse_errors(
3535
}
3636

3737
void Parser::parse_and_visit_module(Parse_Visitor_Base &v) {
38+
QLJS_ASSERT(
39+
!this->first_export_default_statement_default_keyword_.has_value());
3840
bool done = false;
3941
Parse_Statement_Options statement_options = {
4042
.possibly_followed_by_another_statement = true,
@@ -1011,6 +1013,17 @@ void Parser::parse_and_visit_export(Parse_Visitor_Base &v,
10111013
switch (this->peek().type) {
10121014
// export default class C {}
10131015
case Token_Type::kw_default:
1016+
if (this->first_export_default_statement_default_keyword_.has_value()) {
1017+
this->diag_reporter_->report(Diag_Multiple_Export_Defaults{
1018+
.second_export_default = this->peek().span(),
1019+
.first_export_default =
1020+
*this->first_export_default_statement_default_keyword_,
1021+
});
1022+
} else {
1023+
this->first_export_default_statement_default_keyword_ =
1024+
this->peek().span();
1025+
}
1026+
10141027
this->is_current_typescript_namespace_non_empty_ = true;
10151028
if (this->in_typescript_namespace_or_module_.has_value() &&
10161029
!this->in_typescript_module_) {

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

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -545,6 +545,11 @@ class Parser {
545545
void parse_and_visit_named_exports_for_typescript_type_only_import(
546546
Parse_Visitor_Base &v, Source_Code_Span type_keyword);
547547

548+
// If set, refers to the first `export default` statement in this module. A
549+
// module cannot contain more than one `export default`.
550+
std::optional<Source_Code_Span>
551+
first_export_default_statement_default_keyword_ = std::nullopt;
552+
548553
struct Parse_Export_Options {
549554
TypeScript_Declare_Context declare_context;
550555

src/quick-lint-js/i18n/translation-table-generated.cpp

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -206,6 +206,7 @@ const Translation_Table translation_data = {
206206
{0, 0, 0, 67, 0, 53}, //
207207
{0, 0, 0, 0, 0, 37}, //
208208
{0, 0, 0, 0, 0, 43}, //
209+
{0, 0, 0, 0, 0, 62}, //
209210
{0, 0, 0, 50, 0, 47}, //
210211
{72, 31, 71, 68, 56, 61}, //
211212
{34, 30, 0, 46, 0, 40}, //
@@ -259,7 +260,8 @@ const Translation_Table translation_data = {
259260
{33, 27, 36, 45, 0, 35}, //
260261
{39, 42, 0, 49, 0, 41}, //
261262
{24, 24, 0, 24, 0, 24}, //
262-
{22, 22, 42, 22, 40, 22}, //
263+
{0, 0, 0, 0, 0, 22}, //
264+
{22, 22, 42, 22, 40, 40}, //
263265
{32, 30, 35, 26, 30, 29}, //
264266
{0, 0, 0, 27, 0, 32}, //
265267
{35, 45, 38, 53, 33, 46}, //
@@ -1998,6 +2000,7 @@ const Translation_Table translation_data = {
19982000
u8"cannot update variable with '{0}' while declaring it\0"
19992001
u8"cannot use '...' on 'this' parameter\0"
20002002
u8"cannot use 'declare' keyword with 'import'\0"
2003+
u8"cannot use multiple `export default` statements in one module\0"
20012004
u8"cannot use type directly in its own definition\0"
20022005
u8"catch variable can only be typed as '*', 'any', or 'unknown'\0"
20032006
u8"character is not allowed in identifiers\0"
@@ -2052,6 +2055,7 @@ const Translation_Table translation_data = {
20522055
u8"expected variable name for 'import'-'as'\0"
20532056
u8"expected {1:headlinese}\0"
20542057
u8"expected {1:singular}\0"
2058+
u8"export default previously appeared here\0"
20552059
u8"exporting requires 'default'\0"
20562060
u8"exporting requires '{{' and '}'\0"
20572061
u8"extra ',' is not allowed between enum members\0"

src/quick-lint-js/i18n/translation-table-generated.h

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -18,8 +18,8 @@ namespace quick_lint_js {
1818
using namespace std::literals::string_view_literals;
1919

2020
constexpr std::uint32_t translation_table_locale_count = 5;
21-
constexpr std::uint16_t translation_table_mapping_table_size = 522;
22-
constexpr std::size_t translation_table_string_table_size = 79941;
21+
constexpr std::uint16_t translation_table_mapping_table_size = 524;
22+
constexpr std::size_t translation_table_string_table_size = 80043;
2323
constexpr std::size_t translation_table_locale_table_size = 35;
2424

2525
QLJS_CONSTEVAL std::uint16_t translation_table_const_look_up(
@@ -220,6 +220,7 @@ QLJS_CONSTEVAL std::uint16_t translation_table_const_look_up(
220220
"cannot update variable with '{0}' while declaring it"sv,
221221
"cannot use '...' on 'this' parameter"sv,
222222
"cannot use 'declare' keyword with 'import'"sv,
223+
"cannot use multiple `export default` statements in one module"sv,
223224
"cannot use type directly in its own definition"sv,
224225
"catch variable can only be typed as '*', 'any', or 'unknown'"sv,
225226
"character is not allowed in identifiers"sv,
@@ -274,6 +275,7 @@ QLJS_CONSTEVAL std::uint16_t translation_table_const_look_up(
274275
"expected variable name for 'import'-'as'"sv,
275276
"expected {1:headlinese}"sv,
276277
"expected {1:singular}"sv,
278+
"export default previously appeared here"sv,
277279
"exporting requires 'default'"sv,
278280
"exporting requires '{{' and '}'"sv,
279281
"extra ',' is not allowed between enum members"sv,

src/quick-lint-js/i18n/translation-table-test-generated.h

Lines changed: 23 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@ struct Translated_String {
2727
};
2828

2929
// clang-format off
30-
inline const Translated_String test_translation_table[521] = {
30+
inline const Translated_String test_translation_table[523] = {
3131
{
3232
"\"global-groups\" entries must be strings"_translatable,
3333
u8"\"global-groups\" entries must be strings",
@@ -2162,6 +2162,17 @@ inline const Translated_String test_translation_table[521] = {
21622162
u8"cannot use 'declare' keyword with 'import'",
21632163
},
21642164
},
2165+
{
2166+
"cannot use multiple `export default` statements in one module"_translatable,
2167+
u8"cannot use multiple `export default` statements in one module",
2168+
{
2169+
u8"cannot use multiple `export default` statements in one module",
2170+
u8"cannot use multiple `export default` statements in one module",
2171+
u8"cannot use multiple `export default` statements in one module",
2172+
u8"cannot use multiple `export default` statements in one module",
2173+
u8"cannot use multiple `export default` statements in one module",
2174+
},
2175+
},
21652176
{
21662177
"cannot use type directly in its own definition"_translatable,
21672178
u8"cannot use type directly in its own definition",
@@ -2756,6 +2767,17 @@ inline const Translated_String test_translation_table[521] = {
27562767
u8"expected {1:singular}",
27572768
},
27582769
},
2770+
{
2771+
"export default previously appeared here"_translatable,
2772+
u8"export default previously appeared here",
2773+
{
2774+
u8"export default previously appeared here",
2775+
u8"export default previously appeared here",
2776+
u8"export default previously appeared here",
2777+
u8"export default previously appeared here",
2778+
u8"export default previously appeared here",
2779+
},
2780+
},
27592781
{
27602782
"exporting requires 'default'"_translatable,
27612783
u8"exporting requires 'default'",

0 commit comments

Comments
 (0)