use std::borrow::Cow;
use itertools::Itertools;
use rspack_core::parse_resource;
use rspack_error::{Diagnostic, Severity};
use rspack_util::{json_stringify_str, quote_meta};
use super::create_traceable_error;
use crate::utils::eval::{BasicEvaluatedExpression, TemplateStringKind};
pub fn create_context_dependency(
param: &BasicEvaluatedExpression,
parser: &mut crate::visitors::JavascriptParser,
) -> ContextModuleScanResult {
let mut critical = None;
let wrapped_context_reg_exp = parser
.javascript_options
.wrapped_context_reg_exp
.as_ref()
.expect("should have wrapped_context_reg_exp")
.source();
if param.is_template_string() {
let quasis = param.quasis();
let Some(prefix) = quasis.first() else {
unreachable!("the len of quasis should be great than 0")
};
let prefix_raw = prefix.string();
let postfix_raw = if quasis.len() > 1 {
Cow::Borrowed(quasis[quasis.len() - 1].string())
} else {
Cow::Owned(String::new())
};
let (context, prefix) = split_context_from_prefix(prefix_raw.clone());
let (postfix, query, fragment) = match parse_resource(&postfix_raw) {
Some(data) => (
data.path.as_str().to_string(),
data.query.unwrap_or_default(),
data.fragment.unwrap_or_default(),
),
None => (postfix_raw.into_owned(), String::new(), String::new()),
};
let inner_quasis = if quasis.len() > 1 {
quasis[1..quasis.len() - 1]
.iter()
.map(|q| quote_meta(q.string().as_str()) + wrapped_context_reg_exp)
.join("")
} else {
String::new()
};
let reg = format!(
"^{}{}{}{}$",
quote_meta(&prefix),
wrapped_context_reg_exp,
inner_quasis,
quote_meta(&postfix)
);
let mut replaces = Vec::new();
let parts = param.parts();
for (i, part) in parts.iter().enumerate() {
if i % 2 == 0 {
if i == 0 {
let value = format!(
"{}{prefix}",
match param.template_string_kind() {
TemplateStringKind::Cooked => "`",
TemplateStringKind::Raw => "String.raw`",
}
);
replaces.push((value, param.range().0, part.range().1));
} else if i == parts.len() - 1 {
let value = format!("{postfix}`");
replaces.push((value, part.range().0, param.range().1));
} else {
let value = match param.template_string_kind() {
TemplateStringKind::Cooked => json_stringify_str(part.string())
.trim_matches('"')
.to_owned(),
TemplateStringKind::Raw => part.string().to_owned(),
};
let range = part.range();
replaces.push((value, range.0, range.1));
}
} else if let Some(expr) = part.expression() {
parser.walk_expression(expr);
}
}
if let Some(true) = parser.javascript_options.wrapped_context_critical {
let mut warn: Diagnostic = Diagnostic::from(create_traceable_error(
"Critical dependency".into(),
"a part of the request of a dependency is an expression".to_string(),
parser.source.to_string(),
param.range().into(),
));
warn.severity = Severity::Warning;
warn.module_identifier = Some(*parser.module_identifier);
critical = Some(warn);
}
ContextModuleScanResult {
context,
reg,
query,
fragment,
replaces,
critical,
}
} else if param.is_wrapped()
&& let prefix_is_string = param
.prefix()
.map(|prefix| prefix.is_string())
.unwrap_or_default()
&& let postfix_is_string = param
.postfix()
.map(|postfix| postfix.is_string())
.unwrap_or_default()
&& (prefix_is_string || postfix_is_string)
{
let (prefix_raw, prefix_range) = if prefix_is_string {
let prefix = param.prefix().expect("must exist");
(Cow::Borrowed(prefix.string()), Some(prefix.range()))
} else {
(Cow::Owned(String::new()), None)
};
let (postfix_raw, postfix_range) = if postfix_is_string {
let postfix = param.postfix().expect("must exist");
(Cow::Borrowed(postfix.string()), Some(postfix.range()))
} else {
(Cow::Owned(String::new()), None)
};
let (context, prefix) = split_context_from_prefix(prefix_raw.to_string());
let (postfix, query, fragment) = match parse_resource(&postfix_raw) {
Some(data) => (
data.path.as_str().to_string(),
data.query.unwrap_or_default(),
data.fragment.unwrap_or_default(),
),
None => (postfix_raw.into_owned(), String::new(), String::new()),
};
let reg = format!(
"^{}{wrapped_context_reg_exp}{}$",
quote_meta(&prefix),
quote_meta(&postfix)
);
let mut replaces = Vec::new();
if let Some(prefix_range) = prefix_range {
replaces.push((json_stringify_str(&prefix), prefix_range.0, prefix_range.1))
}
if let Some(postfix_range) = postfix_range {
replaces.push((
json_stringify_str(&postfix),
postfix_range.0,
postfix_range.1,
))
}
if let Some(true) = parser.javascript_options.wrapped_context_critical {
let mut warn: Diagnostic = Diagnostic::from(create_traceable_error(
"Critical dependency".into(),
"a part of the request of a dependency is an expression".to_string(),
parser.source.to_string(),
param.range().into(),
));
warn.severity = Severity::Warning;
warn.module_identifier = Some(*parser.module_identifier);
critical = Some(warn);
}
if let Some(inner_expressions) = param.wrapped_inner_expressions() {
for part in inner_expressions {
if let Some(inner_expression) = part.expression() {
parser.walk_expression(inner_expression);
}
}
}
ContextModuleScanResult {
context,
reg,
query,
fragment,
replaces,
critical,
}
} else {
if let Some(true) = parser.javascript_options.expr_context_critical {
let mut warn: Diagnostic = Diagnostic::from(create_traceable_error(
"Critical dependency".into(),
"the request of a dependency is an expression".to_string(),
parser.source.to_string(),
param.range().into(),
));
warn.severity = Severity::Warning;
warn.module_identifier = Some(*parser.module_identifier);
critical = Some(warn);
}
if let Some(expr) = param.expression() {
parser.walk_expression(expr);
}
ContextModuleScanResult {
context: String::from("."),
reg: String::new(),
query: String::new(),
fragment: String::new(),
replaces: Vec::new(),
critical,
}
}
}
#[derive(Debug)]
pub struct ContextModuleScanResult {
pub context: String,
pub reg: String,
pub query: String,
pub fragment: String,
pub replaces: Vec<(String, u32, u32)>,
pub critical: Option<Diagnostic>,
}
pub(super) fn split_context_from_prefix(prefix: String) -> (String, String) {
if let Some(idx) = prefix.rfind('/') {
(prefix[..idx].to_string(), format!(".{}", &prefix[idx..]))
} else {
(".".to_string(), prefix)
}
}