Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: error on multiple export defaults in module #1096

Closed
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
31 changes: 31 additions & 0 deletions docs/errors/E0715.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
# E0715: cannot use multiple `export default` statements in one module

Modules in JavaScript can use two types of exports: default export and named export. While a module
can use multiple named exports, it can only use a single default export.


```javascript
export default function foo() {
console.log("foo");
}

export default function bar() {
console.log("bar");
}
```


If you want to export several values from a module, use named exports.


```javascript
function foo(x) {
console.log("foo");
}

function bar(x) {
console.log("bar");
}

export { foo, bar };
```
8 changes: 8 additions & 0 deletions po/messages.pot
Original file line number Diff line number Diff line change
Expand Up @@ -2081,6 +2081,14 @@ msgstr ""
msgid "'async' keyword is not allowed on getters or setters"
msgstr ""

#: src/quick-lint-js/diag/diagnostic-metadata-generated.cpp
msgid "cannot use multiple `export default` statements in one module"
msgstr ""

#: src/quick-lint-js/diag/diagnostic-metadata-generated.cpp
msgid "export default previously appeared here"
msgstr ""

#: test/test-diagnostic-formatter.cpp
#: test/test-vim-qflist-json-diag-reporter.cpp
msgid "something happened"
Expand Down
18 changes: 18 additions & 0 deletions src/quick-lint-js/diag/diagnostic-metadata-generated.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -6407,6 +6407,24 @@ const QLJS_CONSTINIT Diagnostic_Info all_diagnostic_infos[] = {
},
},
},

// Diag_Multiple_Export_Defaults
{
.code = 715,
.severity = Diagnostic_Severity::error,
.message_formats = {
QLJS_TRANSLATABLE("cannot use multiple `export default` statements in one module"),
QLJS_TRANSLATABLE("export default previously appeared here"),
},
.message_args = {
{
Diagnostic_Message_Arg_Info(offsetof(Diag_Multiple_Export_Defaults, second_export_default), Diagnostic_Arg_Type::source_code_span),
},
{
Diagnostic_Message_Arg_Info(offsetof(Diag_Multiple_Export_Defaults, first_export_default), Diagnostic_Arg_Type::source_code_span),
},
},
},
};
}

Expand Down
3 changes: 2 additions & 1 deletion src/quick-lint-js/diag/diagnostic-metadata-generated.h
Original file line number Diff line number Diff line change
Expand Up @@ -439,10 +439,11 @@ namespace quick_lint_js {
QLJS_DIAG_TYPE_NAME(Diag_Missing_Comma_Between_Array_Elements) \
QLJS_DIAG_TYPE_NAME(Diag_Class_Generator_On_Getter_Or_Setter) \
QLJS_DIAG_TYPE_NAME(Diag_Class_Async_On_Getter_Or_Setter) \
QLJS_DIAG_TYPE_NAME(Diag_Multiple_Export_Defaults) \
/* END */
// clang-format on

inline constexpr int Diag_Type_Count = 428;
inline constexpr int Diag_Type_Count = 429;

extern const Diagnostic_Info all_diagnostic_infos[Diag_Type_Count];
}
Expand Down
11 changes: 11 additions & 0 deletions src/quick-lint-js/diag/diagnostic-types-2.h
Original file line number Diff line number Diff line change
Expand Up @@ -3307,6 +3307,17 @@ struct Diag_Class_Async_On_Getter_Or_Setter {
Source_Code_Span async_keyword;
Source_Code_Span getter_setter_keyword;
};

struct Diag_Multiple_Export_Defaults {
[[qljs::diag("E0715", Diagnostic_Severity::error)]] //
[[qljs::message(
"cannot use multiple `export default` statements in one module",
ARG(second_export_default))]] //
[[qljs::message("export default previously appeared here",
ARG(first_export_default))]] //
Source_Code_Span second_export_default;
Source_Code_Span first_export_default;
};
}
QLJS_WARNING_POP

Expand Down
13 changes: 13 additions & 0 deletions src/quick-lint-js/fe/parse-statement.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,8 @@ bool Parser::parse_and_visit_module_catching_fatal_parse_errors(
}

void Parser::parse_and_visit_module(Parse_Visitor_Base &v) {
QLJS_ASSERT(
!this->first_export_default_statement_default_keyword_.has_value());
bool done = false;
Parse_Statement_Options statement_options = {
.possibly_followed_by_another_statement = true,
Expand Down Expand Up @@ -1011,6 +1013,17 @@ void Parser::parse_and_visit_export(Parse_Visitor_Base &v,
switch (this->peek().type) {
// export default class C {}
case Token_Type::kw_default:
if (this->first_export_default_statement_default_keyword_.has_value()) {
this->diag_reporter_->report(Diag_Multiple_Export_Defaults{
.second_export_default = this->peek().span(),
.first_export_default =
*this->first_export_default_statement_default_keyword_,
});
} else {
this->first_export_default_statement_default_keyword_ =
this->peek().span();
}

this->is_current_typescript_namespace_non_empty_ = true;
if (this->in_typescript_namespace_or_module_.has_value() &&
!this->in_typescript_module_) {
Expand Down
5 changes: 5 additions & 0 deletions src/quick-lint-js/fe/parse.h
Original file line number Diff line number Diff line change
Expand Up @@ -545,6 +545,11 @@ class Parser {
void parse_and_visit_named_exports_for_typescript_type_only_import(
Parse_Visitor_Base &v, Source_Code_Span type_keyword);

// If set, refers to the first `export default` statement in this module. A
// module cannot contain more than one `export default`.
std::optional<Source_Code_Span>
first_export_default_statement_default_keyword_ = std::nullopt;

struct Parse_Export_Options {
TypeScript_Declare_Context declare_context;

Expand Down
6 changes: 5 additions & 1 deletion src/quick-lint-js/i18n/translation-table-generated.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -206,6 +206,7 @@ const Translation_Table translation_data = {
{0, 0, 0, 67, 0, 53}, //
{0, 0, 0, 0, 0, 37}, //
{0, 0, 0, 0, 0, 43}, //
{0, 0, 0, 0, 0, 62}, //
{0, 0, 0, 50, 0, 47}, //
{72, 31, 71, 68, 56, 61}, //
{34, 30, 0, 46, 0, 40}, //
Expand Down Expand Up @@ -259,7 +260,8 @@ const Translation_Table translation_data = {
{33, 27, 36, 45, 0, 35}, //
{39, 42, 0, 49, 0, 41}, //
{24, 24, 0, 24, 0, 24}, //
{22, 22, 42, 22, 40, 22}, //
{0, 0, 0, 0, 0, 22}, //
{22, 22, 42, 22, 40, 40}, //
{32, 30, 35, 26, 30, 29}, //
{0, 0, 0, 27, 0, 32}, //
{35, 45, 38, 53, 33, 46}, //
Expand Down Expand Up @@ -1998,6 +2000,7 @@ const Translation_Table translation_data = {
u8"cannot update variable with '{0}' while declaring it\0"
u8"cannot use '...' on 'this' parameter\0"
u8"cannot use 'declare' keyword with 'import'\0"
u8"cannot use multiple `export default` statements in one module\0"
u8"cannot use type directly in its own definition\0"
u8"catch variable can only be typed as '*', 'any', or 'unknown'\0"
u8"character is not allowed in identifiers\0"
Expand Down Expand Up @@ -2052,6 +2055,7 @@ const Translation_Table translation_data = {
u8"expected variable name for 'import'-'as'\0"
u8"expected {1:headlinese}\0"
u8"expected {1:singular}\0"
u8"export default previously appeared here\0"
u8"exporting requires 'default'\0"
u8"exporting requires '{{' and '}'\0"
u8"extra ',' is not allowed between enum members\0"
Expand Down
6 changes: 4 additions & 2 deletions src/quick-lint-js/i18n/translation-table-generated.h
Original file line number Diff line number Diff line change
Expand Up @@ -18,8 +18,8 @@ namespace quick_lint_js {
using namespace std::literals::string_view_literals;

constexpr std::uint32_t translation_table_locale_count = 5;
constexpr std::uint16_t translation_table_mapping_table_size = 522;
constexpr std::size_t translation_table_string_table_size = 79941;
constexpr std::uint16_t translation_table_mapping_table_size = 524;
constexpr std::size_t translation_table_string_table_size = 80043;
constexpr std::size_t translation_table_locale_table_size = 35;

QLJS_CONSTEVAL std::uint16_t translation_table_const_look_up(
Expand Down Expand Up @@ -220,6 +220,7 @@ QLJS_CONSTEVAL std::uint16_t translation_table_const_look_up(
"cannot update variable with '{0}' while declaring it"sv,
"cannot use '...' on 'this' parameter"sv,
"cannot use 'declare' keyword with 'import'"sv,
"cannot use multiple `export default` statements in one module"sv,
"cannot use type directly in its own definition"sv,
"catch variable can only be typed as '*', 'any', or 'unknown'"sv,
"character is not allowed in identifiers"sv,
Expand Down Expand Up @@ -274,6 +275,7 @@ QLJS_CONSTEVAL std::uint16_t translation_table_const_look_up(
"expected variable name for 'import'-'as'"sv,
"expected {1:headlinese}"sv,
"expected {1:singular}"sv,
"export default previously appeared here"sv,
"exporting requires 'default'"sv,
"exporting requires '{{' and '}'"sv,
"extra ',' is not allowed between enum members"sv,
Expand Down
24 changes: 23 additions & 1 deletion src/quick-lint-js/i18n/translation-table-test-generated.h
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ struct Translated_String {
};

// clang-format off
inline const Translated_String test_translation_table[521] = {
inline const Translated_String test_translation_table[523] = {
{
"\"global-groups\" entries must be strings"_translatable,
u8"\"global-groups\" entries must be strings",
Expand Down Expand Up @@ -2162,6 +2162,17 @@ inline const Translated_String test_translation_table[521] = {
u8"cannot use 'declare' keyword with 'import'",
},
},
{
"cannot use multiple `export default` statements in one module"_translatable,
u8"cannot use multiple `export default` statements in one module",
{
u8"cannot use multiple `export default` statements in one module",
u8"cannot use multiple `export default` statements in one module",
u8"cannot use multiple `export default` statements in one module",
u8"cannot use multiple `export default` statements in one module",
u8"cannot use multiple `export default` statements in one module",
},
},
{
"cannot use type directly in its own definition"_translatable,
u8"cannot use type directly in its own definition",
Expand Down Expand Up @@ -2756,6 +2767,17 @@ inline const Translated_String test_translation_table[521] = {
u8"expected {1:singular}",
},
},
{
"export default previously appeared here"_translatable,
u8"export default previously appeared here",
{
u8"export default previously appeared here",
u8"export default previously appeared here",
u8"export default previously appeared here",
u8"export default previously appeared here",
u8"export default previously appeared here",
},
},
{
"exporting requires 'default'"_translatable,
u8"exporting requires 'default'",
Expand Down
18 changes: 18 additions & 0 deletions test/test-parse-module.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -162,6 +162,24 @@ TEST_F(Test_Parse_Module, export_default) {
}));
}

{
Spy_Visitor p = test_parse_and_visit_module(
u8"export default class A {} export default class B {}"_sv, //
u8" ^^^^^^^ Diag_Multiple_Export_Defaults.first_export_default\n"
u8" ^^^^^^^ .second_export_default"_diag);
EXPECT_THAT(p.visits, ElementsAreArray({
"visit_enter_class_scope", // A
"visit_enter_class_scope_body", //
"visit_exit_class_scope", //
"visit_variable_declaration", //
"visit_enter_class_scope", // B
"visit_enter_class_scope_body", //
"visit_exit_class_scope", //
"visit_variable_declaration", //
"visit_end_of_module",
}));
}

{
Spy_Visitor p = test_parse_and_visit_statement(
u8"export default async (a) => b;"_sv, no_diags, javascript_options);
Expand Down
Loading