#[allow(clippy::wildcard_imports, reason = "many DI visitor AST types used")]
use oxc_ast::ast::*;
use fallow_types::extract::{DiFramework, DiKeySite, DiRole};
use super::{
ModuleInfoExtractor, angular_inject_is_optional, angular_param_inject_token,
angular_param_is_optional, object_has_any_key, then_callback_member_class,
};
impl ModuleInfoExtractor {
fn classify_di_call_site(&self, expr: &CallExpression<'_>) -> Option<(DiFramework, DiRole)> {
match &expr.callee {
Expression::Identifier(callee) => {
let name = callee.name.as_str();
if self.nested_scope_shadows(name) {
None
} else if name == "provide"
&& (self.is_named_import_from(name, "vue", "provide")
|| self.is_named_import_from(name, "@vue/runtime-core", "provide"))
{
Some((DiFramework::Vue, DiRole::Provide))
} else if name == "inject"
&& (self.is_named_import_from(name, "vue", "inject")
|| self.is_named_import_from(name, "@vue/runtime-core", "inject"))
{
Some((DiFramework::Vue, DiRole::Inject))
} else if name == "setContext"
&& self.is_named_import_from(name, "svelte", "setContext")
{
Some((DiFramework::Svelte, DiRole::Provide))
} else if name == "getContext"
&& self.is_named_import_from(name, "svelte", "getContext")
{
Some((DiFramework::Svelte, DiRole::Inject))
} else if name == "inject"
&& self.is_named_import_from(name, "@angular/core", "inject")
{
Some((DiFramework::Angular, DiRole::Inject))
} else {
None
}
}
Expression::StaticMemberExpression(member)
if member.property.name == "provide" && expr.arguments.len() == 2 =>
{
Some((DiFramework::Vue, DiRole::Provide))
}
_ => None,
}
}
pub(super) fn record_di_key_site(&mut self, expr: &CallExpression<'_>) {
let Some((framework, role)) = self.classify_di_call_site(expr) else {
return;
};
if framework == DiFramework::Angular
&& role == DiRole::Inject
&& angular_inject_is_optional(expr)
{
return;
}
let Some(first) = expr.arguments.first() else {
return;
};
match first {
Argument::Identifier(ident) if !self.nested_scope_shadows(ident.name.as_str()) => {
self.di_key_sites.push(DiKeySite {
key_local: ident.name.to_string(),
role,
framework,
span_start: expr.span.start,
});
}
Argument::StringLiteral(_) => {}
Argument::TemplateLiteral(t) if t.expressions.is_empty() => {}
_ => {
if role == DiRole::Provide {
self.has_dynamic_provide = true;
}
}
}
}
pub(super) fn record_angular_param_inject(&mut self, param: &FormalParameter<'_>) {
if param.decorators.is_empty() {
return;
}
let is_named_import = |local: &str, source: &str, imported: &str| {
self.is_named_import_from(local, source, imported)
};
if angular_param_is_optional(param, &is_named_import) {
return;
}
let token = param
.decorators
.iter()
.find_map(|decorator| angular_param_inject_token(decorator, &is_named_import));
let Some(token) = token else {
return;
};
self.di_key_sites.push(DiKeySite {
key_local: token.to_string(),
role: DiRole::Inject,
framework: DiFramework::Angular,
span_start: param.span.start,
});
}
pub(super) fn record_angular_provider_object(&mut self, obj: &ObjectExpression<'_>) {
for prop in &obj.properties {
let ObjectPropertyKind::ObjectProperty(p) = prop else {
continue;
};
if p.key.static_name().as_deref() == Some("providers")
&& let Expression::ArrayExpression(arr) = &p.value
&& arr
.elements
.iter()
.any(|elem| matches!(elem, ArrayExpressionElement::SpreadElement(_)))
{
self.has_dynamic_provide = true;
}
}
if !object_has_any_key(obj, &["useClass", "useValue", "useFactory", "useExisting"]) {
return;
}
for prop in &obj.properties {
let ObjectPropertyKind::ObjectProperty(p) = prop else {
continue;
};
if p.key.static_name().as_deref() != Some("provide") {
continue;
}
match &p.value {
Expression::Identifier(ident)
if !self.nested_scope_shadows(ident.name.as_str()) =>
{
self.di_key_sites.push(DiKeySite {
key_local: ident.name.to_string(),
role: DiRole::Provide,
framework: DiFramework::Angular,
span_start: p.span.start,
});
}
_ => {
self.has_dynamic_provide = true;
}
}
}
}
pub(super) fn record_angular_dynamic_providers(&mut self, expr: &CallExpression<'_>) {
let Expression::Identifier(callee) = &expr.callee else {
return;
};
let name = callee.name.as_str();
if (name == "importProvidersFrom"
&& self.is_named_import_from(name, "@angular/core", "importProvidersFrom"))
|| (name == "makeEnvironmentProviders"
&& self.is_named_import_from(name, "@angular/core", "makeEnvironmentProviders"))
{
self.has_dynamic_provide = true;
}
}
pub(in crate::visitor) fn finalize_di_key_sites(&mut self) {
if self.string_keyed_di_consts.is_empty() {
return;
}
let sites = std::mem::take(&mut self.di_key_sites);
self.di_key_sites = sites
.into_iter()
.filter(|site| !self.string_keyed_di_consts.contains(&site.key_local))
.collect();
}
pub(super) fn record_angular_entry_component_refs(&mut self, prop: &ObjectProperty<'_>) {
let Some(key) = prop.key.static_name() else {
return;
};
match key.as_ref() {
"component" => {
if let Expression::Identifier(ident) = &prop.value {
self.angular_entry_component_refs
.push(ident.name.to_string());
}
}
"loadComponent" => {
if let Some(name) = then_callback_member_class(&prop.value) {
self.angular_entry_component_refs.push(name);
}
}
"bootstrap" => {
if let Expression::ArrayExpression(arr) = &prop.value {
for elem in &arr.elements {
if let ArrayExpressionElement::Identifier(ident) = elem {
self.angular_entry_component_refs
.push(ident.name.to_string());
}
}
}
}
_ => {}
}
}
pub(super) fn record_angular_dynamic_component_render(&mut self, expr: &CallExpression<'_>) {
let is_create_component = match &expr.callee {
Expression::StaticMemberExpression(member) => {
member.property.name.as_str() == "createComponent"
}
Expression::Identifier(ident) => ident.name.as_str() == "createComponent",
_ => false,
};
if is_create_component {
self.has_dynamic_component_render = true;
}
}
pub(super) fn record_angular_bootstrap_call(&mut self, expr: &CallExpression<'_>) {
let Expression::Identifier(callee) = &expr.callee else {
return;
};
if callee.name.as_str() != "bootstrapApplication" {
return;
}
if let Some(Argument::Identifier(ident)) = expr.arguments.first() {
self.angular_entry_component_refs
.push(ident.name.to_string());
}
}
}