use rustc_hash::FxHashMap;
use fallow_types::discover::FileId;
use super::ResolvedModule;
use super::path_info::is_bare_specifier;
use super::types::ResolveResult;
pub(super) fn apply_specifier_upgrades(resolved: &mut [ResolvedModule]) {
let mut specifier_upgrades: FxHashMap<String, FileId> = FxHashMap::default();
for module in resolved.iter() {
for imp in module
.resolved_imports
.iter()
.chain(module.resolved_dynamic_imports.iter())
{
if is_bare_specifier(&imp.info.source)
&& let ResolveResult::InternalModule(file_id) = &imp.target
{
specifier_upgrades
.entry(imp.info.source.clone())
.or_insert(*file_id);
}
}
for re in &module.re_exports {
if is_bare_specifier(&re.info.source)
&& let ResolveResult::InternalModule(file_id) = &re.target
{
specifier_upgrades
.entry(re.info.source.clone())
.or_insert(*file_id);
}
}
}
if specifier_upgrades.is_empty() {
return;
}
for module in resolved.iter_mut() {
for imp in module
.resolved_imports
.iter_mut()
.chain(module.resolved_dynamic_imports.iter_mut())
{
if matches!(imp.target, ResolveResult::NpmPackage(_))
&& let Some(&file_id) = specifier_upgrades.get(&imp.info.source)
{
imp.target = ResolveResult::InternalModule(file_id);
}
}
for re in &mut module.re_exports {
if matches!(re.target, ResolveResult::NpmPackage(_))
&& let Some(&file_id) = specifier_upgrades.get(&re.info.source)
{
re.target = ResolveResult::InternalModule(file_id);
}
}
}
}
#[cfg(test)]
mod tests {
use std::path::PathBuf;
use rustc_hash::FxHashSet;
use fallow_types::discover::FileId;
use fallow_types::extract::{ImportInfo, ImportedName, ReExportInfo};
use oxc_span::Span;
use super::super::types::{ResolvedImport, ResolvedReExport};
use super::*;
fn empty_module(file_id: FileId) -> ResolvedModule {
ResolvedModule {
file_id,
path: PathBuf::from(format!("/project/src/file_{}.ts", file_id.0)),
exports: vec![],
re_exports: vec![],
resolved_imports: vec![],
resolved_dynamic_imports: vec![],
resolved_dynamic_patterns: vec![],
member_accesses: vec![],
whole_object_uses: vec![],
has_cjs_exports: false,
unused_import_bindings: FxHashSet::default(),
}
}
fn make_import(source: &str, target: ResolveResult) -> ResolvedImport {
ResolvedImport {
info: ImportInfo {
source: source.to_string(),
imported_name: ImportedName::Default,
local_name: "x".to_string(),
is_type_only: false,
span: Span::new(0, 0),
source_span: Span::new(0, 0),
},
target,
}
}
fn make_re_export(source: &str, target: ResolveResult) -> ResolvedReExport {
ResolvedReExport {
info: ReExportInfo {
source: source.to_string(),
imported_name: "*".to_string(),
exported_name: "*".to_string(),
is_type_only: false,
span: oxc_span::Span::default(),
},
target,
}
}
#[test]
fn empty_modules_no_crash() {
let mut resolved: Vec<ResolvedModule> = vec![];
apply_specifier_upgrades(&mut resolved);
assert!(resolved.is_empty());
}
#[test]
fn all_internal_no_changes() {
let mut m = empty_module(FileId(0));
m.resolved_imports = vec![
make_import("preact/hooks", ResolveResult::InternalModule(FileId(1))),
make_import("preact", ResolveResult::InternalModule(FileId(2))),
];
let mut resolved = vec![m];
apply_specifier_upgrades(&mut resolved);
assert!(matches!(
resolved[0].resolved_imports[0].target,
ResolveResult::InternalModule(FileId(1))
));
assert!(matches!(
resolved[0].resolved_imports[1].target,
ResolveResult::InternalModule(FileId(2))
));
}
#[test]
fn single_import_upgraded_from_npm_to_internal() {
let mut m0 = empty_module(FileId(0));
m0.resolved_imports = vec![make_import(
"preact/hooks",
ResolveResult::InternalModule(FileId(10)),
)];
let mut m1 = empty_module(FileId(1));
m1.resolved_imports = vec![make_import(
"preact/hooks",
ResolveResult::NpmPackage("preact".to_string()),
)];
let mut resolved = vec![m0, m1];
apply_specifier_upgrades(&mut resolved);
assert!(matches!(
resolved[0].resolved_imports[0].target,
ResolveResult::InternalModule(FileId(10))
));
assert!(matches!(
resolved[1].resolved_imports[0].target,
ResolveResult::InternalModule(FileId(10))
));
}
#[test]
fn re_export_specifier_upgraded() {
let mut m0 = empty_module(FileId(0));
m0.resolved_imports = vec![make_import(
"preact/hooks",
ResolveResult::InternalModule(FileId(10)),
)];
let mut m1 = empty_module(FileId(1));
m1.re_exports = vec![make_re_export(
"preact/hooks",
ResolveResult::NpmPackage("preact".to_string()),
)];
let mut resolved = vec![m0, m1];
apply_specifier_upgrades(&mut resolved);
assert!(matches!(
resolved[1].re_exports[0].target,
ResolveResult::InternalModule(FileId(10))
));
}
#[test]
fn multiple_imports_mixed_only_npm_upgraded() {
let mut m0 = empty_module(FileId(0));
m0.resolved_imports = vec![make_import(
"preact/hooks",
ResolveResult::InternalModule(FileId(10)),
)];
let mut m1 = empty_module(FileId(1));
m1.resolved_imports = vec![
make_import("preact/hooks", ResolveResult::InternalModule(FileId(10))),
make_import(
"preact/hooks",
ResolveResult::NpmPackage("preact".to_string()),
),
];
let mut resolved = vec![m0, m1];
apply_specifier_upgrades(&mut resolved);
assert!(matches!(
resolved[1].resolved_imports[0].target,
ResolveResult::InternalModule(FileId(10))
));
assert!(matches!(
resolved[1].resolved_imports[1].target,
ResolveResult::InternalModule(FileId(10))
));
}
#[test]
fn upgrade_map_empty_no_changes() {
let mut m = empty_module(FileId(0));
m.resolved_imports = vec![
make_import("lodash", ResolveResult::NpmPackage("lodash".to_string())),
make_import("react", ResolveResult::NpmPackage("react".to_string())),
];
let mut resolved = vec![m];
apply_specifier_upgrades(&mut resolved);
assert!(matches!(
resolved[0].resolved_imports[0].target,
ResolveResult::NpmPackage(_)
));
assert!(matches!(
resolved[0].resolved_imports[1].target,
ResolveResult::NpmPackage(_)
));
}
#[test]
fn specifier_not_in_upgrade_map_unchanged() {
let mut m0 = empty_module(FileId(0));
m0.resolved_imports = vec![make_import(
"preact/hooks",
ResolveResult::InternalModule(FileId(10)),
)];
let mut m1 = empty_module(FileId(1));
m1.resolved_imports = vec![make_import(
"lodash",
ResolveResult::NpmPackage("lodash".to_string()),
)];
let mut resolved = vec![m0, m1];
apply_specifier_upgrades(&mut resolved);
assert!(matches!(
resolved[1].resolved_imports[0].target,
ResolveResult::NpmPackage(_)
));
}
#[test]
fn dynamic_imports_also_upgraded() {
let mut m0 = empty_module(FileId(0));
m0.resolved_imports = vec![make_import(
"preact/hooks",
ResolveResult::InternalModule(FileId(10)),
)];
let mut m1 = empty_module(FileId(1));
m1.resolved_dynamic_imports = vec![make_import(
"preact/hooks",
ResolveResult::NpmPackage("preact".to_string()),
)];
let mut resolved = vec![m0, m1];
apply_specifier_upgrades(&mut resolved);
assert!(matches!(
resolved[1].resolved_dynamic_imports[0].target,
ResolveResult::InternalModule(FileId(10))
));
}
#[test]
fn relative_specifier_not_treated_as_bare() {
let mut m0 = empty_module(FileId(0));
m0.resolved_imports = vec![make_import(
"./utils",
ResolveResult::InternalModule(FileId(5)),
)];
let mut m1 = empty_module(FileId(1));
m1.resolved_imports = vec![make_import(
"./utils",
ResolveResult::NpmPackage("utils".to_string()),
)];
let mut resolved = vec![m0, m1];
apply_specifier_upgrades(&mut resolved);
assert!(matches!(
resolved[1].resolved_imports[0].target,
ResolveResult::NpmPackage(_)
));
}
#[test]
fn first_internal_file_id_wins() {
let mut m0 = empty_module(FileId(0));
m0.resolved_imports = vec![make_import(
"preact/hooks",
ResolveResult::InternalModule(FileId(10)),
)];
let mut m1 = empty_module(FileId(1));
m1.resolved_imports = vec![make_import(
"preact/hooks",
ResolveResult::InternalModule(FileId(20)),
)];
let mut m2 = empty_module(FileId(2));
m2.resolved_imports = vec![make_import(
"preact/hooks",
ResolveResult::NpmPackage("preact".to_string()),
)];
let mut resolved = vec![m0, m1, m2];
apply_specifier_upgrades(&mut resolved);
assert!(matches!(
resolved[2].resolved_imports[0].target,
ResolveResult::InternalModule(FileId(10))
));
}
#[test]
fn re_export_internal_creates_upgrade_entry() {
let mut m0 = empty_module(FileId(0));
m0.re_exports = vec![make_re_export(
"preact/hooks",
ResolveResult::InternalModule(FileId(10)),
)];
let mut m1 = empty_module(FileId(1));
m1.resolved_imports = vec![make_import(
"preact/hooks",
ResolveResult::NpmPackage("preact".to_string()),
)];
let mut resolved = vec![m0, m1];
apply_specifier_upgrades(&mut resolved);
assert!(matches!(
resolved[1].resolved_imports[0].target,
ResolveResult::InternalModule(FileId(10))
));
}
}