use super::*;
impl<'a> Converter<'a> {
pub(super) fn use_rule(
&mut self,
at: &oxc_css_parser::ast::AtRule<'a>,
) -> Result<(), Box<CompileError>> {
let spec = self.module_spec(at)?;
let url = spec.path.as_str();
let file = self.file_at(at.span.start);
let alias = spec.alias.map(|(text, _)| text).unwrap_or("");
if spec.filter.is_some() {
return Err(self.err(at.span.start, "SCSS 特性尚未实现(S4): @use ... show/hide"));
}
if let Some(module) = modules::Builtin::from_url(url) {
if spec.config.is_some() {
return Err(self.err(at.span.start, "Built-in modules can't be configured."));
}
if alias == "*" {
self.eval.import_builtin_star(module);
return Ok(());
}
let namespace = if alias.is_empty() {
module.default_namespace().to_string()
} else {
alias.to_string()
};
return self
.eval
.add_builtin_namespace(&namespace, module, file)
.map_err(|e| self.eval_err(e));
}
let module = self.loaded_module(at, url)?;
if let Some(config) = &spec.config {
for entry in
imports::config_entries(config).map_err(|(at, message)| self.err(at, message))?
{
if entry.default {
return Err(self.err(entry.default_at, "expected \")\"."));
}
let value = self
.eval
.eval_text(entry.value, entry.value_at)
.map_err(|e| self.eval_err(e))?;
if let Err(at) =
self.eval
.configure(module.scope, entry.name, value, entry.at, false)
{
return Err(self.err(
at,
"This variable was not declared with !default in the @used module.",
));
}
}
}
if alias == "*" {
self.eval.import_star_module(module.scope);
return Ok(());
}
let namespace = if alias.is_empty() {
default_namespace(Path::new(url))
} else {
alias.to_string()
};
self.eval
.add_namespace(&namespace, module.scope, file)
.map_err(|e| self.eval_err(e))
}
pub(super) fn forward_rule(
&mut self,
at: &oxc_css_parser::ast::AtRule<'a>,
) -> Result<(), Box<CompileError>> {
let spec = self.module_spec(at)?;
if spec.path.starts_with("sass:") {
return Err(self.err(
at.span.start,
"SCSS 特性尚未实现(S4): @forward \"sass:*\"",
));
}
let module = self.loaded_module(at, &spec.path)?;
let prefix = spec
.alias
.map(|(text, _)| text.strip_suffix('*').unwrap_or("").to_string())
.unwrap_or_default();
let (show, hide) = spec.filter.as_ref().map(member_filter).unwrap_or_default();
let edge = eval::ForwardEdge {
scope: module.scope,
at: at.span.start,
prefix,
show,
hide,
};
if let Some(config) = &spec.config {
for entry in
imports::config_entries(config).map_err(|(at, message)| self.err(at, message))?
{
let value = self
.eval
.eval_text(entry.value, entry.value_at)
.map_err(|e| self.eval_err(e))?;
if let Err(at) =
self.eval
.configure(module.scope, entry.name, value, entry.at, entry.default)
{
return Err(self.err(
at,
"This variable was not declared with !default in the @used module.",
));
}
}
}
let scope = self.eval.active_scope();
for mut entry in self.eval.pending_configs(scope) {
if self.eval.config_used(entry.id) {
continue;
}
if !edge.allows(eval::MemberKind::Var, &entry.name) {
continue;
}
let Some(inner) = edge.strip(&entry.name) else {
continue;
};
entry.name = eval::normalize_name(inner);
if let Err(at) = self.eval.push_config(module.scope, entry) {
return Err(self.err(
at,
"This variable was not declared with !default in the @used module.",
));
}
}
let file = self.file_at(at.span.start);
if self.module_file.get(&scope).copied().unwrap_or(0) != file {
self.eval.import_star_module(module.scope);
}
self.eval.add_forward(scope, edge);
Ok(())
}
pub(super) fn check_forward_conflicts(&self) -> Option<Box<CompileError>> {
for scope in 0..self.eval.scope_count() {
let mut vars: HashMap<String, (usize, String)> = HashMap::new();
let mut funcs: HashMap<String, (usize, String)> = HashMap::new();
for edge in self.eval.forwards(scope) {
for (name, _) in self.eval.export_var_names(edge.scope) {
let outward = format!("{}{}", edge.prefix, name);
if !edge.allows(eval::MemberKind::Var, &outward) {
continue;
}
let origin = self.eval.export_var_origin(edge.scope, &name);
if let Some(origin) = origin {
if let Some(prev) = vars.get(&outward) {
if *prev != origin {
return Some(
self.merged
.error_at(
edge.at,
format!(
"Two forwarded modules both define a variable named ${outward}."
),
)
.into(),
);
}
} else {
vars.insert(outward, origin);
}
}
}
let mut mixin_names = HashMap::new();
self.export_mixin_origins(edge.scope, 0, &mut mixin_names);
for (name, origin) in mixin_names {
let outward = format!("{}{}", edge.prefix, name);
if !edge.allows(eval::MemberKind::Member, &outward) {
continue;
}
if let Some(prev) = funcs.get(&outward) {
if *prev != origin {
return Some(
self.merged
.error_at(
edge.at,
format!(
"Two forwarded modules both define a function named {outward}."
),
)
.into(),
);
}
} else {
funcs.insert(outward, origin);
}
}
for name in self.eval.export_function_names(edge.scope) {
let outward = format!("{}{}", edge.prefix, name);
if !edge.allows(eval::MemberKind::Member, &outward) {
continue;
}
let Some(origin) = self.eval.export_function_origin(edge.scope, &name) else {
continue;
};
if let Some(prev) = funcs.get(&outward) {
if *prev != origin {
return Some(
self.merged
.error_at(
edge.at,
format!(
"Two forwarded modules both define a function named {outward}."
),
)
.into(),
);
}
} else {
funcs.insert(outward, origin);
}
}
}
}
None
}
fn module_spec(
&self,
at: &oxc_css_parser::ast::AtRule<'a>,
) -> Result<imports::ModuleSpec<'a>, Box<CompileError>> {
let src = self.src();
let end = imports::statement_end(src, at.span.end);
let text = src.get(at.span.start..end).unwrap_or_default();
imports::parse_module_spec(text, at.span.start)
.map_err(|(at, message)| self.err(at, message))
}
fn loaded_module(
&self,
at: &oxc_css_parser::ast::AtRule<'a>,
url: &str,
) -> Result<ModuleInfo, Box<CompileError>> {
let file_id = self.file_at(at.span.start) as usize;
let dir = self
.merged
.files
.get(file_id)
.and_then(|f| f.path.parent())
.map(Path::to_path_buf)
.unwrap_or_else(|| PathBuf::from("."));
let resolved = imports::resolve(&dir, url, &self.opts.paths)
.map_err(|e| self.err(at.span.start, e.message(url)))?;
let canonical = std::fs::canonicalize(&resolved).unwrap_or_else(|_| resolved.clone());
self.modules.get(&canonical).copied().ok_or_else(|| {
self.err(
at.span.start,
format!("Can't find stylesheet to import: {}", resolved.display()),
)
})
}
}
fn member_filter(filter: &imports::Filter<'_>) -> (Option<eval::MemberFilter>, eval::MemberFilter) {
let list = eval::MemberFilter {
vars: filter
.vars
.iter()
.map(|name| eval::normalize_name(name))
.collect(),
members: filter
.members
.iter()
.map(|name| eval::normalize_name(name))
.collect(),
};
if filter.show {
(Some(list), eval::MemberFilter::default())
} else {
(None, list)
}
}
fn default_namespace(path: &Path) -> String {
let name = path
.file_stem()
.map(|s| s.to_string_lossy().into_owned())
.unwrap_or_default();
name.trim_start_matches('_').to_string()
}