use oxc::span::Span;
use oxc_index::IndexVec;
use rolldown_common::{
EcmaModuleAstUsage, EcmaRelated, EcmaView, EcmaViewMeta, FlatOptions, ImportRecordIdx,
RawImportRecord, ResolvedId, SharedNormalizedBundlerOptions, SideEffectDetail,
side_effects::{DeterminedSideEffects, HookSideEffects},
};
use rolldown_error::BuildResult;
use rolldown_std_utils::PathExt;
use rolldown_utils::{ecmascript::legitimize_identifier_name, indexmap::FxIndexSet};
use sugar_path::SugarPath;
use crate::{
ast_scanner::{AstScanner, ScanResult},
types::module_factory::{CreateModuleContext, CreateModuleViewArgs},
utils::parse_to_ecma_ast::{ParseToEcmaAstResult, parse_to_ecma_ast},
};
pub struct CreateEcmaViewReturn {
pub ecma_view: EcmaView,
pub ecma_related: EcmaRelated,
pub raw_import_records: IndexVec<ImportRecordIdx, RawImportRecord>,
pub tla_keyword_span: Option<Span>,
}
pub async fn create_ecma_view(
ctx: &mut CreateModuleContext<'_>,
args: CreateModuleViewArgs,
) -> BuildResult<CreateEcmaViewReturn> {
let CreateModuleViewArgs { source, sourcemap_chain, hook_side_effects } = args;
let ParseToEcmaAstResult {
mut ast,
scoping,
has_lazy_export,
warnings,
preserve_jsx,
enum_member_value_map,
} = parse_to_ecma_ast(ctx, source).await?;
ctx.flat_options.set(FlatOptions::JsxPreserve, preserve_jsx);
ctx.warnings.extend(warnings);
let module_id = ctx.resolved_id.id.clone();
let repr_name = module_id.as_path().representative_file_name();
let repr_name = legitimize_identifier_name(&repr_name);
let scan_result = ast.program.with_mut(|fields| {
let program = &*fields.program;
let scanner = AstScanner::new(
ctx.module_idx,
scoping,
&repr_name,
ctx.resolved_id.module_def_format,
fields.source,
&module_id,
&program.comments,
ctx.options,
fields.allocator,
ctx.flat_options,
);
scanner.scan(program)
})?;
let ScanResult {
commonjs_exports,
named_imports,
mut named_exports,
stmt_infos,
import_records: raw_import_records,
default_export_ref,
namespace_object_ref,
imports,
exports_kind,
warnings: scan_warnings,
errors,
ast_usage,
tla_keyword_span,
symbol_ref_db: symbols,
self_referenced_class_decl_symbol_ids,
hashbang_range,
ecma_view_meta,
dynamic_import_rec_exports_usage,
new_url_references: new_url_imports,
this_expr_replace_map,
hmr_info,
hmr_hot_ref,
directive_range,
dummy_record_set,
constant_export_map,
import_attribute_map,
cjs_reexport_require_spans: _,
cjs_reexport_import_record_ids,
} = scan_result;
for (k, v) in commonjs_exports {
if v.len() == 1 {
named_exports.insert(k, v[0]);
}
}
if !errors.is_empty() {
return Err(errors.into());
}
ctx.warnings.extend(scan_warnings);
let side_effects =
normalize_side_effects(ctx.options, ctx.resolved_id, Some(&stmt_infos), hook_side_effects)
.await?;
let ecma_view = EcmaView {
source: ast.source().clone(),
named_imports,
named_exports,
imports,
default_export_ref,
exports_kind,
namespace_object_ref,
def_format: ctx.resolved_id.module_def_format,
sourcemap_chain,
import_records: IndexVec::default(),
importers: FxIndexSet::default(),
importers_idx: FxIndexSet::default(),
dynamic_importers: FxIndexSet::default(),
imported_ids: FxIndexSet::default(),
dynamically_imported_ids: FxIndexSet::default(),
side_effects,
meta: {
let mut meta = ecma_view_meta;
meta.set(EcmaViewMeta::HasLazyExport, has_lazy_export);
meta.set(
EcmaViewMeta::SafelyTreeshakeCommonjs,
ast_usage.contains(EcmaModuleAstUsage::AllStaticExportPropertyAccess)
&& !ast_usage.contains(EcmaModuleAstUsage::UnknownExportsRead),
);
meta
},
ast_usage,
self_referenced_class_decl_symbol_ids,
hashbang_range,
mutations: vec![],
new_url_references: new_url_imports,
this_expr_replace_map,
hmr_info,
hmr_hot_ref,
directive_range,
dummy_record_set,
constant_export_map,
enum_member_value_map,
import_attribute_map,
json_module_none_self_reference_included_symbol: None,
cjs_reexport_import_record_ids,
};
let ecma_related =
EcmaRelated { ast, symbols, dynamic_import_rec_exports_usage, preserve_jsx, stmt_infos };
Ok(CreateEcmaViewReturn { ecma_view, ecma_related, raw_import_records, tla_keyword_span })
}
pub async fn normalize_side_effects(
options: &SharedNormalizedBundlerOptions,
resolved_id: &ResolvedId,
stmt_infos: Option<&rolldown_common::StmtInfos>,
hook_side_effects: Option<HookSideEffects>,
) -> BuildResult<DeterminedSideEffects> {
let side_effects = match hook_side_effects {
Some(side_effects) => match side_effects {
HookSideEffects::True => lazy_check_side_effects(resolved_id, stmt_infos),
HookSideEffects::False => DeterminedSideEffects::UserDefined(false),
HookSideEffects::NoTreeshake => DeterminedSideEffects::NoTreeshake,
},
None => match options.treeshake.as_ref() {
None => DeterminedSideEffects::NoTreeshake,
Some(opt) => {
if opt.module_side_effects.is_fn() {
match opt
.module_side_effects
.ffi_resolve(&resolved_id.id, resolved_id.external.is_external())
.await?
{
Some(value) => DeterminedSideEffects::UserDefined(value),
None => lazy_check_side_effects(resolved_id, stmt_infos),
}
} else {
match opt
.module_side_effects
.native_resolve(&resolved_id.id, resolved_id.external.is_external())
{
Some(value) => DeterminedSideEffects::UserDefined(value),
None => lazy_check_side_effects(resolved_id, stmt_infos),
}
}
}
},
};
Ok(side_effects)
}
pub fn lazy_check_side_effects(
resolved_id: &ResolvedId,
stmt_infos: Option<&rolldown_common::StmtInfos>,
) -> DeterminedSideEffects {
if resolved_id.external.is_external() {
return if resolved_id.is_external_without_side_effects {
DeterminedSideEffects::UserDefined(false)
} else {
DeterminedSideEffects::NoTreeshake
};
}
let stmt_infos = stmt_infos.expect("Normal module should have stmt_infos");
resolved_id
.package_json
.as_ref()
.and_then(|p| {
let module_path_relative_to_package =
resolved_id.id.as_path().relative(p.realpath().parent()?);
p.check_side_effects_for(&module_path_relative_to_package.to_string_lossy())
.map(DeterminedSideEffects::UserDefined)
})
.unwrap_or_else(|| {
let analyzed_side_effects = stmt_infos
.iter()
.any(|stmt_info| stmt_info.side_effect.contains(SideEffectDetail::Unknown));
DeterminedSideEffects::Analyzed(analyzed_side_effects)
})
}