mirror of
https://github.com/danbulant/oxc
synced 2026-05-19 12:19:15 +00:00
fix(semantic): export default foo should have ExportLocalName::Default(NameSpan) entry (#3823)
This commit is contained in:
parent
7302429b2b
commit
99a40ce6ac
5 changed files with 83 additions and 102 deletions
|
|
@ -43,7 +43,9 @@ impl Rule for NoPageCustomFont {
|
||||||
let AstKind::JSXOpeningElement(element) = node.kind() else {
|
let AstKind::JSXOpeningElement(element) = node.kind() else {
|
||||||
return;
|
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;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -58,63 +60,12 @@ impl Rule for NoPageCustomFont {
|
||||||
return;
|
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| {
|
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."))
|
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 span = ctx.nodes().parent_kind(node.id()).unwrap().span();
|
||||||
let diagnostic = if in_document {
|
let diagnostic = if in_document {
|
||||||
if is_inside_export_default {
|
if is_inside_export_default(node, ctx) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
link_outside_of_head(span)
|
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]
|
#[test]
|
||||||
fn test() {
|
fn test() {
|
||||||
use std::path::PathBuf;
|
use std::path::PathBuf;
|
||||||
|
|
@ -206,7 +198,7 @@ fn test() {
|
||||||
</Html>
|
</Html>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
export default CustomDocument;
|
export default CustomDocument;
|
||||||
"#,
|
"#,
|
||||||
None,
|
None,
|
||||||
|
|
@ -230,7 +222,7 @@ fn test() {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export default MyDocument;"#,
|
export default MyDocument;"#,
|
||||||
None,
|
None,
|
||||||
None,
|
None,
|
||||||
|
|
@ -297,8 +289,8 @@ fn test() {
|
||||||
(
|
(
|
||||||
r#"
|
r#"
|
||||||
import Head from 'next/head'
|
import Head from 'next/head'
|
||||||
|
|
||||||
|
|
||||||
function Links() {
|
function Links() {
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
|
|
@ -313,7 +305,7 @@ fn test() {
|
||||||
</>
|
</>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
export default function IndexPage() {
|
export default function IndexPage() {
|
||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
|
|
|
||||||
|
|
@ -11,8 +11,7 @@ use oxc_cfg::{
|
||||||
use oxc_diagnostics::OxcDiagnostic;
|
use oxc_diagnostics::OxcDiagnostic;
|
||||||
use oxc_span::{CompactStr, SourceType, Span};
|
use oxc_span::{CompactStr, SourceType, Span};
|
||||||
use oxc_syntax::{
|
use oxc_syntax::{
|
||||||
identifier::is_identifier_name,
|
module_record::{ExportImportName, ModuleRecord},
|
||||||
module_record::{ExportImportName, ExportLocalName, ModuleRecord},
|
|
||||||
operator::AssignmentOperator,
|
operator::AssignmentOperator,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
@ -399,23 +398,10 @@ impl<'a> SemanticBuilder<'a> {
|
||||||
});
|
});
|
||||||
|
|
||||||
self.module_record.local_export_entries.iter().for_each(|entry| {
|
self.module_record.local_export_entries.iter().for_each(|entry| {
|
||||||
match &entry.local_name {
|
if let Some(name) = entry.local_name.name() {
|
||||||
ExportLocalName::Name(name_span) => {
|
if let Some(symbol_id) = self.scope.get_root_binding(name.as_str()) {
|
||||||
if let Some(symbol_id) = self.scope.get_root_binding(name_span.name()) {
|
self.symbols.union_flag(symbol_id, SymbolFlags::Export);
|
||||||
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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
ExportLocalName::Null => {}
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
@ -1726,7 +1712,7 @@ impl<'a> SemanticBuilder<'a> {
|
||||||
AstKind::Class(class) => {
|
AstKind::Class(class) => {
|
||||||
self.current_node_flags |= NodeFlags::Class;
|
self.current_node_flags |= NodeFlags::Class;
|
||||||
class.bind(self);
|
class.bind(self);
|
||||||
self.remove_export_flag();
|
self.current_symbol_flags -= SymbolFlags::Export;
|
||||||
self.make_all_namespaces_valuelike();
|
self.make_all_namespaces_valuelike();
|
||||||
}
|
}
|
||||||
AstKind::ClassBody(body) => {
|
AstKind::ClassBody(body) => {
|
||||||
|
|
@ -1748,7 +1734,7 @@ impl<'a> SemanticBuilder<'a> {
|
||||||
}
|
}
|
||||||
AstKind::FormalParameters(_) => {
|
AstKind::FormalParameters(_) => {
|
||||||
self.current_node_flags |= NodeFlags::Parameter;
|
self.current_node_flags |= NodeFlags::Parameter;
|
||||||
self.remove_export_flag();
|
self.current_symbol_flags -= SymbolFlags::Export;
|
||||||
}
|
}
|
||||||
AstKind::FormalParameter(param) => {
|
AstKind::FormalParameter(param) => {
|
||||||
param.bind(self);
|
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) {
|
fn add_current_node_id_to_current_scope(&mut self) {
|
||||||
self.scope.add_node_id(self.current_scope_id, self.current_node_id);
|
self.scope.add_node_id(self.current_scope_id, self.current_node_id);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -243,22 +243,28 @@ impl ModuleRecordBuilder {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
let exported_name = &decl.exported;
|
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 {
|
let local_name = match &decl.declaration {
|
||||||
match_expression!(ExportDefaultDeclarationKind) => None,
|
ExportDefaultDeclarationKind::Identifier(ident) => {
|
||||||
ExportDefaultDeclarationKind::FunctionDeclaration(func) => func.id.as_ref(),
|
ExportLocalName::Default(NameSpan::new(ident.name.to_compact_str(), ident.span))
|
||||||
ExportDefaultDeclarationKind::ClassDeclaration(class) => class.id.as_ref(),
|
}
|
||||||
ExportDefaultDeclarationKind::TSInterfaceDeclaration(_) => return,
|
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 {
|
let export_entry = ExportEntry {
|
||||||
export_name: ExportExportName::Default(exported_name.span()),
|
export_name: ExportExportName::Default(exported_name.span()),
|
||||||
local_name: id.as_ref().map_or_else(
|
local_name,
|
||||||
|| ExportLocalName::Default(exported_name.span()),
|
|
||||||
|ident| {
|
|
||||||
ExportLocalName::Name(NameSpan::new(ident.name.to_compact_str(), ident.span))
|
|
||||||
},
|
|
||||||
),
|
|
||||||
span: decl.declaration.span(),
|
span: decl.declaration.span(),
|
||||||
..ExportEntry::default()
|
..ExportEntry::default()
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -217,7 +217,7 @@ mod module_record_tests {
|
||||||
let module_record = build("export default function() {}");
|
let module_record = build("export default function() {}");
|
||||||
let export_entry = ExportEntry {
|
let export_entry = ExportEntry {
|
||||||
export_name: ExportExportName::Default(Span::new(7, 14)),
|
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),
|
span: Span::new(15, 28),
|
||||||
..ExportEntry::default()
|
..ExportEntry::default()
|
||||||
};
|
};
|
||||||
|
|
@ -231,7 +231,7 @@ mod module_record_tests {
|
||||||
let module_record = build("export default 42");
|
let module_record = build("export default 42");
|
||||||
let export_entry = ExportEntry {
|
let export_entry = ExportEntry {
|
||||||
export_name: ExportExportName::Default(Span::new(7, 14)),
|
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),
|
span: Span::new(15, 17),
|
||||||
..ExportEntry::default()
|
..ExportEntry::default()
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -246,7 +246,8 @@ impl ExportExportName {
|
||||||
#[derive(Debug, Default, Clone, PartialEq, Eq)]
|
#[derive(Debug, Default, Clone, PartialEq, Eq)]
|
||||||
pub enum ExportLocalName {
|
pub enum ExportLocalName {
|
||||||
Name(NameSpan),
|
Name(NameSpan),
|
||||||
Default(Span),
|
/// `export default name_span`
|
||||||
|
Default(NameSpan),
|
||||||
#[default]
|
#[default]
|
||||||
Null,
|
Null,
|
||||||
}
|
}
|
||||||
|
|
@ -262,8 +263,8 @@ impl ExportLocalName {
|
||||||
|
|
||||||
pub const fn name(&self) -> Option<&CompactStr> {
|
pub const fn name(&self) -> Option<&CompactStr> {
|
||||||
match self {
|
match self {
|
||||||
Self::Name(name) => Some(name.name()),
|
Self::Name(name) | Self::Default(name) => Some(name.name()),
|
||||||
Self::Default(_) | Self::Null => None,
|
Self::Null => None,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -332,11 +333,11 @@ mod test {
|
||||||
fn export_local_name() {
|
fn export_local_name() {
|
||||||
let name = NameSpan::new("name".into(), Span::new(0, 0));
|
let name = NameSpan::new("name".into(), Span::new(0, 0));
|
||||||
assert!(!ExportLocalName::Name(name.clone()).is_default());
|
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::Null.is_default());
|
||||||
|
|
||||||
assert!(!ExportLocalName::Name(name).is_null());
|
assert!(!ExportLocalName::Name(name.clone()).is_null());
|
||||||
assert!(!ExportLocalName::Default(Span::new(0, 0)).is_null());
|
assert!(!ExportLocalName::Default(name.clone()).is_null());
|
||||||
assert!(ExportLocalName::Null.is_null());
|
assert!(ExportLocalName::Null.is_null());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue