Skip to content

Commit 3748cbb

Browse files
committed
feat(semantic): add TS2309 error for export assignment with other exports
1 parent ef05957 commit 3748cbb

File tree

3 files changed

+208
-31
lines changed

3 files changed

+208
-31
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_span: Option<Span> = None;
489+
let mut has_other_exports = false;
490+
491+
for stmt in statements {
492+
match stmt {
493+
Statement::TSExportAssignment(export_assignment) => {
494+
export_assignment_span = Some(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 let Some(span) = export_assignment_span
511+
&& has_other_exports
512+
{
513+
ctx.error(ts_export_assignment_cannot_be_used_with_other_exports(span));
514+
}
515+
}

0 commit comments

Comments
 (0)