From 99a40ce6ac3b0395396fc8abadb0037a0db78192 Mon Sep 17 00:00:00 2001 From: Boshen <1430279+Boshen@users.noreply.github.com> Date: Sat, 22 Jun 2024 11:09:22 +0000 Subject: [PATCH] fix(semantic): `export default foo` should have `ExportLocalName::Default(NameSpan)` entry (#3823) --- .../src/rules/nextjs/no_page_custom_font.rs | 108 ++++++++---------- crates/oxc_semantic/src/builder.rs | 30 +---- .../oxc_semantic/src/module_record/builder.rs | 30 +++-- crates/oxc_semantic/src/module_record/mod.rs | 4 +- crates/oxc_syntax/src/module_record.rs | 13 ++- 5 files changed, 83 insertions(+), 102 deletions(-) diff --git a/crates/oxc_linter/src/rules/nextjs/no_page_custom_font.rs b/crates/oxc_linter/src/rules/nextjs/no_page_custom_font.rs index 20c57b4b583af..6d74b15e84b14 100644 --- a/crates/oxc_linter/src/rules/nextjs/no_page_custom_font.rs +++ b/crates/oxc_linter/src/rules/nextjs/no_page_custom_font.rs @@ -43,7 +43,9 @@ impl Rule for NoPageCustomFont { let AstKind::JSXOpeningElement(element) = node.kind() else { return; }; - if matches!(&element.name, JSXElementName::Identifier(ident) if ident.name != "link") { + let JSXElementName::Identifier(ident) = &element.name else { return }; + + if ident.name != "link" { return; } @@ -58,63 +60,12 @@ impl Rule for NoPageCustomFont { return; } - let mut is_inside_export_default = false; - for parent_node in ctx.nodes().iter_parents(node.id()) { - // export default function/class - let kind = parent_node.kind(); - if matches!(kind, AstKind::ExportDefaultDeclaration(_)) { - is_inside_export_default = true; - break; - } - - // function variable() {}; export default variable; - let id = match kind { - AstKind::ArrowFunctionExpression(_) => None, - AstKind::Function(Function { id, .. }) | AstKind::Class(Class { id, .. }) => { - id.clone() - } - _ => continue, - }; - - let name = id.map_or_else( - || { - let parent_parent_kind = ctx.nodes().parent_kind(parent_node.id())?; - - let AstKind::VariableDeclarator(declarator) = parent_parent_kind else { - return None; - }; - declarator.id.get_identifier().map(|id| id.to_string()) - }, - |id| Some(id.name.to_string()), - ); - let Some(name) = name else { - continue; - }; - if let Some(symbol_id) = ctx.scopes().get_root_binding(&name) { - if ctx.symbols().get_flag(symbol_id).is_export() { - let is_export_default = - ctx.symbols().get_resolved_references(symbol_id).any(|reference| { - reference.is_read() - && matches!( - ctx.nodes().parent_kind(reference.node_id()), - Some(AstKind::ExportDefaultDeclaration(_)) - ) - }); - - if is_export_default { - is_inside_export_default = true; - break; - } - } - } - } - let in_document = ctx.file_path().file_name().map_or(false, |file_name| { file_name.to_str().map_or(false, |file_name| file_name.starts_with("_document.")) }); let span = ctx.nodes().parent_kind(node.id()).unwrap().span(); let diagnostic = if in_document { - if is_inside_export_default { + if is_inside_export_default(node, ctx) { return; } link_outside_of_head(span) @@ -125,6 +76,47 @@ impl Rule for NoPageCustomFont { } } +fn is_inside_export_default(node: &AstNode<'_>, ctx: &LintContext<'_>) -> bool { + let mut is_inside_export_default = false; + for parent_node in ctx.nodes().iter_parents(node.id()) { + // export default function/class + let kind = parent_node.kind(); + if matches!(kind, AstKind::ExportDefaultDeclaration(_)) { + is_inside_export_default = true; + break; + } + + // function variable() {}; export default variable; + let id = match kind { + AstKind::ArrowFunctionExpression(_) => None, + AstKind::Function(Function { id, .. }) | AstKind::Class(Class { id, .. }) => id.clone(), + _ => continue, + }; + + let name = id.map_or_else( + || { + let parent_parent_kind = ctx.nodes().parent_kind(parent_node.id())?; + + let AstKind::VariableDeclarator(declarator) = parent_parent_kind else { + return None; + }; + declarator.id.get_identifier().map(|id| id.to_string()) + }, + |id| Some(id.name.to_string()), + ); + let Some(name) = name else { + continue; + }; + if ctx.module_record().local_export_entries.iter().any(|e| { + e.local_name.is_default() + && e.local_name.name().is_some_and(|n| n.as_str() == name.as_str()) + }) { + is_inside_export_default = true; + } + } + is_inside_export_default +} + #[test] fn test() { use std::path::PathBuf; @@ -206,7 +198,7 @@ fn test() { ) } - + export default CustomDocument; "#, None, @@ -230,7 +222,7 @@ fn test() { ); } } - + export default MyDocument;"#, None, None, @@ -297,8 +289,8 @@ fn test() { ( r#" import Head from 'next/head' - - + + function Links() { return ( <> @@ -313,7 +305,7 @@ fn test() { ) } - + export default function IndexPage() { return (
diff --git a/crates/oxc_semantic/src/builder.rs b/crates/oxc_semantic/src/builder.rs index deafcfb667866..5ea61c63fb23c 100644 --- a/crates/oxc_semantic/src/builder.rs +++ b/crates/oxc_semantic/src/builder.rs @@ -11,8 +11,7 @@ use oxc_cfg::{ use oxc_diagnostics::OxcDiagnostic; use oxc_span::{CompactStr, SourceType, Span}; use oxc_syntax::{ - identifier::is_identifier_name, - module_record::{ExportImportName, ExportLocalName, ModuleRecord}, + module_record::{ExportImportName, ModuleRecord}, operator::AssignmentOperator, }; @@ -399,23 +398,10 @@ impl<'a> SemanticBuilder<'a> { }); self.module_record.local_export_entries.iter().for_each(|entry| { - match &entry.local_name { - ExportLocalName::Name(name_span) => { - if let Some(symbol_id) = self.scope.get_root_binding(name_span.name()) { - self.symbols.union_flag(symbol_id, SymbolFlags::Export); - } - } - ExportLocalName::Default(_) => { - // export default identifier - // ^^^^^^^^^^ - let identifier = entry.span.source_text(self.source_text); - if is_identifier_name(identifier) { - if let Some(symbol_id) = self.scope.get_root_binding(identifier) { - self.symbols.union_flag(symbol_id, SymbolFlags::Export); - } - } + if let Some(name) = entry.local_name.name() { + if let Some(symbol_id) = self.scope.get_root_binding(name.as_str()) { + self.symbols.union_flag(symbol_id, SymbolFlags::Export); } - ExportLocalName::Null => {} } }); } @@ -1726,7 +1712,7 @@ impl<'a> SemanticBuilder<'a> { AstKind::Class(class) => { self.current_node_flags |= NodeFlags::Class; class.bind(self); - self.remove_export_flag(); + self.current_symbol_flags -= SymbolFlags::Export; self.make_all_namespaces_valuelike(); } AstKind::ClassBody(body) => { @@ -1748,7 +1734,7 @@ impl<'a> SemanticBuilder<'a> { } AstKind::FormalParameters(_) => { self.current_node_flags |= NodeFlags::Parameter; - self.remove_export_flag(); + self.current_symbol_flags -= SymbolFlags::Export; } AstKind::FormalParameter(param) => { param.bind(self); @@ -1878,10 +1864,6 @@ impl<'a> SemanticBuilder<'a> { } } - fn remove_export_flag(&mut self) { - self.current_symbol_flags -= SymbolFlags::Export; - } - fn add_current_node_id_to_current_scope(&mut self) { self.scope.add_node_id(self.current_scope_id, self.current_node_id); } diff --git a/crates/oxc_semantic/src/module_record/builder.rs b/crates/oxc_semantic/src/module_record/builder.rs index 2d0ba15b0c620..d3e326a0db7e7 100644 --- a/crates/oxc_semantic/src/module_record/builder.rs +++ b/crates/oxc_semantic/src/module_record/builder.rs @@ -243,22 +243,28 @@ impl ModuleRecordBuilder { return; } let exported_name = &decl.exported; - self.add_default_export(exported_name.span()); + let exported_name_span = decl.exported.span(); + self.add_default_export(exported_name_span); - let id = match &decl.declaration { - match_expression!(ExportDefaultDeclarationKind) => None, - ExportDefaultDeclarationKind::FunctionDeclaration(func) => func.id.as_ref(), - ExportDefaultDeclarationKind::ClassDeclaration(class) => class.id.as_ref(), - ExportDefaultDeclarationKind::TSInterfaceDeclaration(_) => return, + let local_name = match &decl.declaration { + ExportDefaultDeclarationKind::Identifier(ident) => { + ExportLocalName::Default(NameSpan::new(ident.name.to_compact_str(), ident.span)) + } + ExportDefaultDeclarationKind::FunctionDeclaration(func) => { + func.id.as_ref().map_or_else( + || ExportLocalName::Null, + |id| ExportLocalName::Name(NameSpan::new(id.name.to_compact_str(), id.span)), + ) + } + ExportDefaultDeclarationKind::ClassDeclaration(class) => class.id.as_ref().map_or_else( + || ExportLocalName::Null, + |id| ExportLocalName::Name(NameSpan::new(id.name.to_compact_str(), id.span)), + ), + _ => ExportLocalName::Null, }; let export_entry = ExportEntry { export_name: ExportExportName::Default(exported_name.span()), - local_name: id.as_ref().map_or_else( - || ExportLocalName::Default(exported_name.span()), - |ident| { - ExportLocalName::Name(NameSpan::new(ident.name.to_compact_str(), ident.span)) - }, - ), + local_name, span: decl.declaration.span(), ..ExportEntry::default() }; diff --git a/crates/oxc_semantic/src/module_record/mod.rs b/crates/oxc_semantic/src/module_record/mod.rs index 67fc12d354241..dda753698374e 100644 --- a/crates/oxc_semantic/src/module_record/mod.rs +++ b/crates/oxc_semantic/src/module_record/mod.rs @@ -217,7 +217,7 @@ mod module_record_tests { let module_record = build("export default function() {}"); let export_entry = ExportEntry { export_name: ExportExportName::Default(Span::new(7, 14)), - local_name: ExportLocalName::Default(Span::new(7, 14)), + local_name: ExportLocalName::Null, span: Span::new(15, 28), ..ExportEntry::default() }; @@ -231,7 +231,7 @@ mod module_record_tests { let module_record = build("export default 42"); let export_entry = ExportEntry { export_name: ExportExportName::Default(Span::new(7, 14)), - local_name: ExportLocalName::Default(Span::new(7, 14)), + local_name: ExportLocalName::Null, span: Span::new(15, 17), ..ExportEntry::default() }; diff --git a/crates/oxc_syntax/src/module_record.rs b/crates/oxc_syntax/src/module_record.rs index f95ab210793f3..b92578f1b1081 100644 --- a/crates/oxc_syntax/src/module_record.rs +++ b/crates/oxc_syntax/src/module_record.rs @@ -246,7 +246,8 @@ impl ExportExportName { #[derive(Debug, Default, Clone, PartialEq, Eq)] pub enum ExportLocalName { Name(NameSpan), - Default(Span), + /// `export default name_span` + Default(NameSpan), #[default] Null, } @@ -262,8 +263,8 @@ impl ExportLocalName { pub const fn name(&self) -> Option<&CompactStr> { match self { - Self::Name(name) => Some(name.name()), - Self::Default(_) | Self::Null => None, + Self::Name(name) | Self::Default(name) => Some(name.name()), + Self::Null => None, } } } @@ -332,11 +333,11 @@ mod test { fn export_local_name() { let name = NameSpan::new("name".into(), Span::new(0, 0)); assert!(!ExportLocalName::Name(name.clone()).is_default()); - assert!(ExportLocalName::Default(Span::new(0, 0)).is_default()); + assert!(ExportLocalName::Default(name.clone()).is_default()); assert!(!ExportLocalName::Null.is_default()); - assert!(!ExportLocalName::Name(name).is_null()); - assert!(!ExportLocalName::Default(Span::new(0, 0)).is_null()); + assert!(!ExportLocalName::Name(name.clone()).is_null()); + assert!(!ExportLocalName::Default(name.clone()).is_null()); assert!(ExportLocalName::Null.is_null()); } }