Skip to content

Commit f317b33

Browse files
committed
feat(semantic): add TS2309 error for export assignment with other exports
1 parent 3543adf commit f317b33

File tree

5 files changed

+231
-32
lines changed

5 files changed

+231
-32
lines changed

crates/oxc_semantic/src/checker/mod.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ pub fn check<'a>(kind: AstKind<'a>, ctx: &SemanticBuilder<'a>) {
1414
AstKind::Program(program) => {
1515
js::check_duplicate_class_elements(ctx);
1616
js::check_unresolved_exports(program, ctx);
17+
ts::check_ts_export_assignment_in_program(program, ctx);
1718
}
1819
AstKind::BindingIdentifier(ident) => {
1920
js::check_identifier(&ident.name, ident.span, ident.symbol_id.get(), ctx);

crates/oxc_semantic/src/checker/typescript.rs

Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -157,6 +157,7 @@ fn not_allowed_namespace_declaration(span: Span) -> OxcDiagnostic {
157157

158158
pub fn check_ts_module_declaration<'a>(decl: &TSModuleDeclaration<'a>, ctx: &SemanticBuilder<'a>) {
159159
check_ts_module_or_global_declaration(decl.span, ctx);
160+
check_ts_export_assignment_in_module_decl(decl, ctx);
160161
}
161162

162163
pub fn check_ts_global_declaration<'a>(decl: &TSGlobalDeclaration<'a>, ctx: &SemanticBuilder<'a>) {
@@ -449,3 +450,66 @@ pub fn check_jsx_expression_container(
449450
ctx.error(jsx_expressions_may_not_use_the_comma_operator(container.expression.span()));
450451
}
451452
}
453+
454+
fn ts_export_assignment_cannot_be_used_with_other_exports(span: Span) -> OxcDiagnostic {
455+
ts_error("2309", "An export assignment cannot be used in a module with other exported elements")
456+
.with_label(span)
457+
.with_help("If you want to use `export =`, remove other `export`s and put all of them to the right hand value of `export =`. If you want to use `export`s, remove `export =` statement.")
458+
}
459+
460+
pub fn check_ts_export_assignment_in_program<'a>(program: &Program<'a>, ctx: &SemanticBuilder<'a>) {
461+
if !ctx.source_type.is_typescript() {
462+
return;
463+
}
464+
check_ts_export_assignment_in_statements(&program.body, ctx);
465+
}
466+
467+
fn check_ts_export_assignment_in_module_decl<'a>(
468+
module_decl: &TSModuleDeclaration<'a>,
469+
ctx: &SemanticBuilder<'a>,
470+
) {
471+
let Some(body) = &module_decl.body else {
472+
return;
473+
};
474+
match body {
475+
TSModuleDeclarationBody::TSModuleDeclaration(nested) => {
476+
check_ts_export_assignment_in_module_decl(nested, ctx);
477+
}
478+
TSModuleDeclarationBody::TSModuleBlock(block) => {
479+
check_ts_export_assignment_in_statements(&block.body, ctx);
480+
}
481+
}
482+
}
483+
484+
fn check_ts_export_assignment_in_statements<'a>(
485+
statements: &[Statement<'a>],
486+
ctx: &SemanticBuilder<'a>,
487+
) {
488+
let mut export_assignment_spans = vec![];
489+
let mut has_other_exports = false;
490+
491+
for stmt in statements {
492+
match stmt {
493+
Statement::TSExportAssignment(export_assignment) => {
494+
export_assignment_spans.push(export_assignment.span);
495+
}
496+
Statement::ExportNamedDeclaration(export_decl) => {
497+
// ignore `export {}`
498+
if export_decl.declaration.is_none() && export_decl.specifiers.is_empty() {
499+
continue;
500+
}
501+
has_other_exports = true;
502+
}
503+
Statement::ExportDefaultDeclaration(_) | Statement::ExportAllDeclaration(_) => {
504+
has_other_exports = true;
505+
}
506+
_ => {}
507+
}
508+
}
509+
510+
if has_other_exports {
511+
for span in export_assignment_spans {
512+
ctx.error(ts_export_assignment_cannot_be_used_with_other_exports(span));
513+
}
514+
}
515+
}
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
export const foo = 1;
2+
export const bar = 2;
3+
4+
export = 3;
5+
export = 4;

tasks/coverage/snapshots/parser_misc.snap

Lines changed: 18 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
parser_misc Summary:
22
AST Parsed : 52/52 (100.00%)
33
Positive Passed: 52/52 (100.00%)
4-
Negative Passed: 120/120 (100.00%)
4+
Negative Passed: 121/121 (100.00%)
55

66
× Cannot assign to 'arguments' in strict mode
77
╭─[misc/fail/arguments-eval.ts:1:10]
@@ -79,6 +79,23 @@ Negative Passed: 120/120 (100.00%)
7979
╰────
8080
help: for octal literals use the '0o' prefix instead
8181

82+
× TS(2309): An export assignment cannot be used in a module with other exported elements
83+
╭─[misc/fail/export-equal-with-normal-export.ts:4:1]
84+
3
85+
4export = 3;
86+
· ───────────
87+
5export = 4;
88+
╰────
89+
help: If you want to use `export =`, remove other `export`s and put all of them to the right hand value of `export =`. If you want to use `export`s, remove `export =` statement.
90+
91+
× TS(2309): An export assignment cannot be used in a module with other exported elements
92+
╭─[misc/fail/export-equal-with-normal-export.ts:5:1]
93+
4export = 3;
94+
5export = 4;
95+
· ───────────
96+
╰────
97+
help: If you want to use `export =`, remove other `export`s and put all of them to the right hand value of `export =`. If you want to use `export`s, remove `export =` statement.
98+
8299
× Expected `,` or `]` but found `const`
83100
╭─[misc/fail/imbalanced-array-expr.js:2:1]
84101
1const foo = [0, 1

0 commit comments

Comments
 (0)