use indexmap::{IndexMap, map::Entry as IndexMapEntry};
use oxc_ast::{NONE, ast::*};
use oxc_semantic::ReferenceFlags;
use oxc_span::SPAN;
use oxc_str::Str;
use oxc_syntax::symbol::SymbolId;
use oxc_traverse::BoundIdentifier;
use crate::context::TraverseCtx;
pub struct NamedImport<'a> {
imported: Str<'a>,
local: BoundIdentifier<'a>,
}
pub enum Import<'a> {
Named(NamedImport<'a>),
Default(BoundIdentifier<'a>),
}
pub struct ModuleImportsStore<'a> {
pub(crate) imports: IndexMap<Str<'a>, Vec<Import<'a>>>,
}
impl<'a> ModuleImportsStore<'a> {
pub fn new() -> Self {
Self { imports: IndexMap::default() }
}
pub fn add_default_import(&mut self, source: Str<'a>, local: BoundIdentifier<'a>, front: bool) {
self.add_import(source, Import::Default(local), front);
}
pub fn add_named_import(
&mut self,
source: Str<'a>,
imported: Str<'a>,
local: BoundIdentifier<'a>,
front: bool,
) {
self.add_import(source, Import::Named(NamedImport { imported, local }), front);
}
pub fn is_empty(&self) -> bool {
self.imports.is_empty()
}
}
impl<'a> ModuleImportsStore<'a> {
fn add_import(&mut self, source: Str<'a>, import: Import<'a>, front: bool) {
match self.imports.entry(source) {
IndexMapEntry::Occupied(mut entry) => {
entry.get_mut().push(import);
if front && entry.index() != 0 {
entry.move_index(0);
}
}
IndexMapEntry::Vacant(entry) => {
let named_imports = vec![import];
if front {
entry.shift_insert(0, named_imports);
} else {
entry.insert(named_imports);
}
}
}
}
pub(crate) fn get_import(
source: Str<'a>,
names: Vec<Import<'a>>,
ctx: &TraverseCtx<'a>,
) -> Statement<'a> {
let specifiers = ctx.ast.vec_from_iter(names.into_iter().map(|import| match import {
Import::Named(import) => {
ImportDeclarationSpecifier::ImportSpecifier(ctx.ast.alloc_import_specifier(
SPAN,
ModuleExportName::IdentifierName(
ctx.ast.identifier_name(SPAN, import.imported),
),
import.local.create_binding_identifier(ctx),
ImportOrExportKind::Value,
))
}
Import::Default(local) => ImportDeclarationSpecifier::ImportDefaultSpecifier(
ctx.ast.alloc_import_default_specifier(SPAN, local.create_binding_identifier(ctx)),
),
}));
Statement::from(ctx.ast.module_declaration_import_declaration(
SPAN,
Some(specifiers),
ctx.ast.string_literal(SPAN, source, None),
None,
NONE,
ImportOrExportKind::Value,
))
}
pub(crate) fn get_require(
source: Str<'a>,
names: std::vec::Vec<Import<'a>>,
require_symbol_id: Option<SymbolId>,
ctx: &mut TraverseCtx<'a>,
) -> Statement<'a> {
let callee = ctx.create_ident_expr(
SPAN,
ctx.ast.ident("require"),
require_symbol_id,
ReferenceFlags::read(),
);
let args = {
let arg = Argument::from(ctx.ast.expression_string_literal(SPAN, source, None));
ctx.ast.vec1(arg)
};
let Some(Import::Default(local)) = names.into_iter().next() else { unreachable!() };
let id = local.create_binding_pattern(ctx);
let var_kind = VariableDeclarationKind::Var;
let decl = {
let init = ctx.ast.expression_call(SPAN, callee, NONE, args, false);
let decl = ctx.ast.variable_declarator(SPAN, var_kind, id, NONE, Some(init), false);
ctx.ast.vec1(decl)
};
Statement::from(ctx.ast.declaration_variable(SPAN, var_kind, decl, false))
}
}