various module system improvements and bug fixes
This commit is contained in:
parent
28c2269bb1
commit
8363ca1dd3
@ -7,6 +7,8 @@
|
|||||||
|
|
||||||
-->
|
-->
|
||||||
|
|
||||||
|
# 0.13.0
|
||||||
|
|
||||||
# 0.12.4
|
# 0.12.4
|
||||||
|
|
||||||
- implement builtin map-module functions `map.deep-merge(..)` and `map.deep-remove(..)`
|
- implement builtin map-module functions `map.deep-merge(..)` and `map.deep-remove(..)`
|
||||||
|
11
README.md
11
README.md
@ -29,13 +29,6 @@ All known missing features and bugs are tracked in [#19](https://github.com/conn
|
|||||||
|
|
||||||
`grass` is benchmarked against `dart-sass` and `sassc` (`libsass`) [here](https://github.com/connorskees/sass-perf). In general, `grass` appears to be ~2x faster than `dart-sass` and ~1.7x faster than `sassc`.
|
`grass` is benchmarked against `dart-sass` and `sassc` (`libsass`) [here](https://github.com/connorskees/sass-perf). In general, `grass` appears to be ~2x faster than `dart-sass` and ~1.7x faster than `sassc`.
|
||||||
|
|
||||||
## Web Assembly
|
|
||||||
|
|
||||||
`grass` experimentally releases a
|
|
||||||
[WASM version of the library to npm](https://www.npmjs.com/package/@connorskees/grass),
|
|
||||||
compiled using wasm-bindgen. To use `grass` in your JavaScript projects, run
|
|
||||||
`npm install @connorskees/grass` to add it to your package.json. This version of grass is not currently well documented, but one can find example usage in the [`grassmeister` repository](https://github.com/connorskees/grassmeister).
|
|
||||||
|
|
||||||
## Cargo Features
|
## Cargo Features
|
||||||
|
|
||||||
### commandline
|
### commandline
|
||||||
@ -84,9 +77,9 @@ The spec runner does not work on Windows.
|
|||||||
Using a modified version of the spec runner that ignores warnings and error spans (but does include error messages), `grass` achieves the following results:
|
Using a modified version of the spec runner that ignores warnings and error spans (but does include error messages), `grass` achieves the following results:
|
||||||
|
|
||||||
```
|
```
|
||||||
2022-05-11
|
2022-05-20
|
||||||
PASSING: 6277
|
PASSING: 6277
|
||||||
FAILING: 596
|
FAILING: 548
|
||||||
TOTAL: 6905
|
TOTAL: 6905
|
||||||
```
|
```
|
||||||
|
|
||||||
|
@ -415,6 +415,13 @@ impl ConfiguredValue {
|
|||||||
configuration_span: Some(configuration_span),
|
configuration_span: Some(configuration_span),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn implicit(value: Value) -> Self {
|
||||||
|
Self {
|
||||||
|
value,
|
||||||
|
configuration_span: None,
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
|
@ -14,7 +14,9 @@ use crate::{
|
|||||||
error::SassResult,
|
error::SassResult,
|
||||||
evaluate::{Environment, Visitor},
|
evaluate::{Environment, Visitor},
|
||||||
selector::ExtensionStore,
|
selector::ExtensionStore,
|
||||||
utils::{BaseMapView, MapView, MergedMapView, PrefixedMapView, PublicMemberMapView},
|
utils::{
|
||||||
|
BaseMapView, LimitedMapView, MapView, MergedMapView, PrefixedMapView, PublicMemberMapView,
|
||||||
|
},
|
||||||
value::{SassFunction, SassMap, Value},
|
value::{SassFunction, SassMap, Value},
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -28,6 +30,82 @@ mod meta;
|
|||||||
mod selector;
|
mod selector;
|
||||||
mod string;
|
mod string;
|
||||||
|
|
||||||
|
/// A [Module] that only exposes members that aren't shadowed by a given
|
||||||
|
/// blocklist of member names.
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
|
pub(crate) struct ShadowedModule {
|
||||||
|
#[allow(dead_code)]
|
||||||
|
inner: Arc<RefCell<Module>>,
|
||||||
|
scope: ModuleScope,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ShadowedModule {
|
||||||
|
pub fn new(
|
||||||
|
module: Arc<RefCell<Module>>,
|
||||||
|
variables: Option<&HashSet<Identifier>>,
|
||||||
|
functions: Option<&HashSet<Identifier>>,
|
||||||
|
mixins: Option<&HashSet<Identifier>>,
|
||||||
|
) -> Self {
|
||||||
|
let module_scope = module.borrow().scope();
|
||||||
|
|
||||||
|
let variables = Self::shadowed_map(Arc::clone(&module_scope.variables), variables);
|
||||||
|
let functions = Self::shadowed_map(Arc::clone(&module_scope.functions), functions);
|
||||||
|
let mixins = Self::shadowed_map(Arc::clone(&module_scope.mixins), mixins);
|
||||||
|
|
||||||
|
let new_scope = ModuleScope {
|
||||||
|
variables,
|
||||||
|
functions,
|
||||||
|
mixins,
|
||||||
|
};
|
||||||
|
|
||||||
|
Self {
|
||||||
|
inner: module,
|
||||||
|
scope: new_scope,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn needs_blocklist<V: fmt::Debug + Clone>(
|
||||||
|
map: Arc<dyn MapView<Value = V>>,
|
||||||
|
blocklist: Option<&HashSet<Identifier>>,
|
||||||
|
) -> bool {
|
||||||
|
blocklist.is_some()
|
||||||
|
&& !map.is_empty()
|
||||||
|
&& blocklist.unwrap().iter().any(|key| map.contains_key(*key))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn shadowed_map<V: fmt::Debug + Clone + 'static>(
|
||||||
|
map: Arc<dyn MapView<Value = V>>,
|
||||||
|
blocklist: Option<&HashSet<Identifier>>,
|
||||||
|
) -> Arc<dyn MapView<Value = V>> {
|
||||||
|
match blocklist {
|
||||||
|
Some(..) if !Self::needs_blocklist(Arc::clone(&map), blocklist) => map,
|
||||||
|
Some(blocklist) => Arc::new(LimitedMapView::blocklist(map, blocklist)),
|
||||||
|
None => map,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn if_necessary(
|
||||||
|
module: Arc<RefCell<Module>>,
|
||||||
|
variables: Option<&HashSet<Identifier>>,
|
||||||
|
functions: Option<&HashSet<Identifier>>,
|
||||||
|
mixins: Option<&HashSet<Identifier>>,
|
||||||
|
) -> Option<Arc<RefCell<Module>>> {
|
||||||
|
let module_scope = module.borrow().scope();
|
||||||
|
|
||||||
|
let needs_blocklist = Self::needs_blocklist(Arc::clone(&module_scope.variables), variables)
|
||||||
|
|| Self::needs_blocklist(Arc::clone(&module_scope.functions), functions)
|
||||||
|
|| Self::needs_blocklist(Arc::clone(&module_scope.mixins), mixins);
|
||||||
|
|
||||||
|
if needs_blocklist {
|
||||||
|
Some(Arc::new(RefCell::new(Module::Shadowed(Self::new(
|
||||||
|
module, variables, functions, mixins,
|
||||||
|
)))))
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
pub(crate) struct ForwardedModule {
|
pub(crate) struct ForwardedModule {
|
||||||
inner: Arc<RefCell<Module>>,
|
inner: Arc<RefCell<Module>>,
|
||||||
@ -149,10 +227,11 @@ pub(crate) enum Module {
|
|||||||
scope: ModuleScope,
|
scope: ModuleScope,
|
||||||
},
|
},
|
||||||
Forwarded(ForwardedModule),
|
Forwarded(ForwardedModule),
|
||||||
|
Shadowed(ShadowedModule),
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
pub(crate) struct Modules(BTreeMap<Identifier, Arc<RefCell<Module>>>);
|
pub(crate) struct Modules(pub BTreeMap<Identifier, Arc<RefCell<Module>>>);
|
||||||
|
|
||||||
impl Modules {
|
impl Modules {
|
||||||
pub fn new() -> Self {
|
pub fn new() -> Self {
|
||||||
@ -279,16 +358,20 @@ impl Module {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn scope(&self) -> ModuleScope {
|
pub(crate) fn scope(&self) -> ModuleScope {
|
||||||
match self {
|
match self {
|
||||||
Self::Builtin { scope } | Self::Environment { scope, .. } => scope.clone(),
|
Self::Builtin { scope }
|
||||||
|
| Self::Environment { scope, .. }
|
||||||
|
| Self::Shadowed(ShadowedModule { scope, .. }) => scope.clone(),
|
||||||
Self::Forwarded(forwarded) => (*forwarded.inner).borrow().scope(),
|
Self::Forwarded(forwarded) => (*forwarded.inner).borrow().scope(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn set_scope(&mut self, new_scope: ModuleScope) {
|
fn set_scope(&mut self, new_scope: ModuleScope) {
|
||||||
match self {
|
match self {
|
||||||
Self::Builtin { scope } | Self::Environment { scope, .. } => *scope = new_scope,
|
Self::Builtin { scope }
|
||||||
|
| Self::Environment { scope, .. }
|
||||||
|
| Self::Shadowed(ShadowedModule { scope, .. }) => *scope = new_scope,
|
||||||
Self::Forwarded(forwarded) => (*forwarded.inner).borrow_mut().set_scope(new_scope),
|
Self::Forwarded(forwarded) => (*forwarded.inner).borrow_mut().set_scope(new_scope),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -319,7 +402,9 @@ impl Module {
|
|||||||
Self::Builtin { .. } => {
|
Self::Builtin { .. } => {
|
||||||
return Err(("Cannot modify built-in variable.", name.span).into())
|
return Err(("Cannot modify built-in variable.", name.span).into())
|
||||||
}
|
}
|
||||||
Self::Environment { scope, .. } => scope.clone(),
|
Self::Environment { scope, .. } | Self::Shadowed(ShadowedModule { scope, .. }) => {
|
||||||
|
scope.clone()
|
||||||
|
}
|
||||||
Self::Forwarded(forwarded) => (*forwarded.inner).borrow_mut().scope(),
|
Self::Forwarded(forwarded) => (*forwarded.inner).borrow_mut().scope(),
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -1,14 +1,20 @@
|
|||||||
use codemap::{Span, Spanned};
|
use codemap::{Span, Spanned};
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
ast::{AstForwardRule, Configuration, Mixin},
|
ast::{AstForwardRule, Configuration, ConfiguredValue, Mixin},
|
||||||
builtin::modules::{ForwardedModule, Module, ModuleScope, Modules},
|
builtin::modules::{ForwardedModule, Module, ModuleScope, Modules, ShadowedModule},
|
||||||
common::Identifier,
|
common::Identifier,
|
||||||
error::SassResult,
|
error::SassResult,
|
||||||
selector::ExtensionStore,
|
selector::ExtensionStore,
|
||||||
value::{SassFunction, Value},
|
value::{SassFunction, Value},
|
||||||
};
|
};
|
||||||
use std::{cell::RefCell, collections::BTreeMap, sync::Arc};
|
use std::{
|
||||||
|
cell::RefCell,
|
||||||
|
collections::{BTreeMap, HashSet},
|
||||||
|
sync::Arc,
|
||||||
|
};
|
||||||
|
|
||||||
|
type Mutable<T> = Arc<RefCell<T>>;
|
||||||
|
|
||||||
use super::{scope::Scopes, visitor::CallableContentBlock};
|
use super::{scope::Scopes, visitor::CallableContentBlock};
|
||||||
|
|
||||||
@ -19,6 +25,9 @@ pub(crate) struct Environment {
|
|||||||
pub global_modules: Vec<Arc<RefCell<Module>>>,
|
pub global_modules: Vec<Arc<RefCell<Module>>>,
|
||||||
pub content: Option<Arc<CallableContentBlock>>,
|
pub content: Option<Arc<CallableContentBlock>>,
|
||||||
pub forwarded_modules: Arc<RefCell<Vec<Arc<RefCell<Module>>>>>,
|
pub forwarded_modules: Arc<RefCell<Vec<Arc<RefCell<Module>>>>>,
|
||||||
|
pub imported_modules: Arc<RefCell<Vec<Arc<RefCell<Module>>>>>,
|
||||||
|
#[allow(clippy::type_complexity)]
|
||||||
|
pub nested_forwarded_modules: Option<Mutable<Vec<Mutable<Vec<Mutable<Module>>>>>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Environment {
|
impl Environment {
|
||||||
@ -29,6 +38,8 @@ impl Environment {
|
|||||||
global_modules: Vec::new(),
|
global_modules: Vec::new(),
|
||||||
content: None,
|
content: None,
|
||||||
forwarded_modules: Arc::new(RefCell::new(Vec::new())),
|
forwarded_modules: Arc::new(RefCell::new(Vec::new())),
|
||||||
|
imported_modules: Arc::new(RefCell::new(Vec::new())),
|
||||||
|
nested_forwarded_modules: None,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -39,6 +50,8 @@ impl Environment {
|
|||||||
global_modules: self.global_modules.iter().map(Arc::clone).collect(),
|
global_modules: self.global_modules.iter().map(Arc::clone).collect(),
|
||||||
content: self.content.as_ref().map(Arc::clone),
|
content: self.content.as_ref().map(Arc::clone),
|
||||||
forwarded_modules: Arc::clone(&self.forwarded_modules),
|
forwarded_modules: Arc::clone(&self.forwarded_modules),
|
||||||
|
imported_modules: Arc::clone(&self.imported_modules),
|
||||||
|
nested_forwarded_modules: self.nested_forwarded_modules.as_ref().map(Arc::clone),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -49,6 +62,8 @@ impl Environment {
|
|||||||
global_modules: Vec::new(),
|
global_modules: Vec::new(),
|
||||||
content: self.content.as_ref().map(Arc::clone),
|
content: self.content.as_ref().map(Arc::clone),
|
||||||
forwarded_modules: Arc::clone(&self.forwarded_modules),
|
forwarded_modules: Arc::clone(&self.forwarded_modules),
|
||||||
|
imported_modules: Arc::clone(&self.imported_modules),
|
||||||
|
nested_forwarded_modules: self.nested_forwarded_modules.as_ref().map(Arc::clone),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -61,101 +76,167 @@ impl Environment {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Makes the members forwarded by [module] available in the current
|
||||||
|
/// environment.
|
||||||
|
///
|
||||||
|
/// This is called when [module] is `@import`ed.
|
||||||
pub fn import_forwards(&mut self, _env: Module) {
|
pub fn import_forwards(&mut self, _env: Module) {
|
||||||
// if (module is _EnvironmentModule) {
|
if let Module::Environment { env, .. } = _env {
|
||||||
// var forwarded = module._environment._forwardedModules;
|
let mut forwarded = env.forwarded_modules;
|
||||||
// if (forwarded == null) return;
|
|
||||||
|
|
||||||
// // Omit modules from [forwarded] that are already globally available and
|
if (*forwarded).borrow().is_empty() {
|
||||||
// // forwarded in this module.
|
return;
|
||||||
// var forwardedModules = _forwardedModules;
|
}
|
||||||
// if (forwardedModules != null) {
|
|
||||||
// forwarded = {
|
|
||||||
// for (var entry in forwarded.entries)
|
|
||||||
// if (!forwardedModules.containsKey(entry.key) ||
|
|
||||||
// !_globalModules.containsKey(entry.key))
|
|
||||||
// entry.key: entry.value,
|
|
||||||
// };
|
|
||||||
// } else {
|
|
||||||
// forwardedModules = _forwardedModules ??= {};
|
|
||||||
// }
|
|
||||||
|
|
||||||
// var forwardedVariableNames =
|
// Omit modules from [forwarded] that are already globally available and
|
||||||
// forwarded.keys.expand((module) => module.variables.keys).toSet();
|
// forwarded in this module.
|
||||||
// var forwardedFunctionNames =
|
let forwarded_modules = Arc::clone(&self.forwarded_modules);
|
||||||
// forwarded.keys.expand((module) => module.functions.keys).toSet();
|
if !(*forwarded_modules).borrow().is_empty() {
|
||||||
// var forwardedMixinNames =
|
// todo: intermediate name
|
||||||
// forwarded.keys.expand((module) => module.mixins.keys).toSet();
|
let mut x = Vec::new();
|
||||||
|
for entry in (*forwarded).borrow().iter() {
|
||||||
|
if !forwarded_modules
|
||||||
|
.borrow()
|
||||||
|
.iter()
|
||||||
|
.any(|module| Arc::ptr_eq(module, entry))
|
||||||
|
|| !self
|
||||||
|
.global_modules
|
||||||
|
.iter()
|
||||||
|
.any(|module| Arc::ptr_eq(module, entry))
|
||||||
|
{
|
||||||
|
x.push(Arc::clone(entry));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// if (atRoot) {
|
forwarded = Arc::new(RefCell::new(x));
|
||||||
// // Hide members from modules that have already been imported or
|
}
|
||||||
// // forwarded that would otherwise conflict with the @imported members.
|
|
||||||
// for (var entry in _importedModules.entries.toList()) {
|
|
||||||
// var module = entry.key;
|
|
||||||
// var shadowed = ShadowedModuleView.ifNecessary(module,
|
|
||||||
// variables: forwardedVariableNames,
|
|
||||||
// mixins: forwardedMixinNames,
|
|
||||||
// functions: forwardedFunctionNames);
|
|
||||||
// if (shadowed != null) {
|
|
||||||
// _importedModules.remove(module);
|
|
||||||
// if (!shadowed.isEmpty) _importedModules[shadowed] = entry.value;
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
|
|
||||||
// for (var entry in forwardedModules.entries.toList()) {
|
let forwarded_var_names = forwarded
|
||||||
// var module = entry.key;
|
.borrow()
|
||||||
// var shadowed = ShadowedModuleView.ifNecessary(module,
|
.iter()
|
||||||
// variables: forwardedVariableNames,
|
.flat_map(|module| (*module).borrow().scope().variables.keys())
|
||||||
// mixins: forwardedMixinNames,
|
.collect::<HashSet<Identifier>>();
|
||||||
// functions: forwardedFunctionNames);
|
let forwarded_fn_names = forwarded
|
||||||
// if (shadowed != null) {
|
.borrow()
|
||||||
// forwardedModules.remove(module);
|
.iter()
|
||||||
// if (!shadowed.isEmpty) forwardedModules[shadowed] = entry.value;
|
.flat_map(|module| (*module).borrow().scope().functions.keys())
|
||||||
// }
|
.collect::<HashSet<Identifier>>();
|
||||||
// }
|
let forwarded_mixin_names = forwarded
|
||||||
|
.borrow()
|
||||||
|
.iter()
|
||||||
|
.flat_map(|module| (*module).borrow().scope().mixins.keys())
|
||||||
|
.collect::<HashSet<Identifier>>();
|
||||||
|
|
||||||
// _importedModules.addAll(forwarded);
|
if self.at_root() {
|
||||||
// forwardedModules.addAll(forwarded);
|
let mut to_remove = Vec::new();
|
||||||
// } else {
|
|
||||||
// (_nestedForwardedModules ??=
|
|
||||||
// List.generate(_variables.length - 1, (_) => []))
|
|
||||||
// .last
|
|
||||||
// .addAll(forwarded.keys);
|
|
||||||
// }
|
|
||||||
|
|
||||||
// // Remove existing member definitions that are now shadowed by the
|
// Hide members from modules that have already been imported or
|
||||||
// // forwarded modules.
|
// forwarded that would otherwise conflict with the @imported members.
|
||||||
// for (var variable in forwardedVariableNames) {
|
for (idx, module) in (*self.imported_modules).borrow().iter().enumerate() {
|
||||||
// _variableIndices.remove(variable);
|
let shadowed = ShadowedModule::if_necessary(
|
||||||
// _variables.last.remove(variable);
|
Arc::clone(module),
|
||||||
// _variableNodes.last.remove(variable);
|
Some(&forwarded_var_names),
|
||||||
// }
|
Some(&forwarded_fn_names),
|
||||||
// for (var function in forwardedFunctionNames) {
|
Some(&forwarded_mixin_names),
|
||||||
// _functionIndices.remove(function);
|
);
|
||||||
// _functions.last.remove(function);
|
|
||||||
// }
|
if shadowed.is_some() {
|
||||||
// for (var mixin in forwardedMixinNames) {
|
to_remove.push(idx);
|
||||||
// _mixinIndices.remove(mixin);
|
}
|
||||||
// _mixins.last.remove(mixin);
|
}
|
||||||
// }
|
|
||||||
// }
|
let mut imported_modules = (*self.imported_modules).borrow_mut();
|
||||||
// todo!()
|
|
||||||
|
for &idx in to_remove.iter().rev() {
|
||||||
|
imported_modules.remove(idx);
|
||||||
|
}
|
||||||
|
|
||||||
|
to_remove.clear();
|
||||||
|
|
||||||
|
for (idx, module) in (*self.forwarded_modules).borrow().iter().enumerate() {
|
||||||
|
let shadowed = ShadowedModule::if_necessary(
|
||||||
|
Arc::clone(module),
|
||||||
|
Some(&forwarded_var_names),
|
||||||
|
Some(&forwarded_fn_names),
|
||||||
|
Some(&forwarded_mixin_names),
|
||||||
|
);
|
||||||
|
|
||||||
|
if shadowed.is_some() {
|
||||||
|
to_remove.push(idx);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut forwarded_modules = (*self.forwarded_modules).borrow_mut();
|
||||||
|
|
||||||
|
for &idx in to_remove.iter().rev() {
|
||||||
|
forwarded_modules.remove(idx);
|
||||||
|
}
|
||||||
|
|
||||||
|
imported_modules.extend(forwarded.borrow().iter().map(Arc::clone));
|
||||||
|
forwarded_modules.extend(forwarded.borrow().iter().map(Arc::clone));
|
||||||
|
} else {
|
||||||
|
self.scopes.last_variable_index = None;
|
||||||
|
self.nested_forwarded_modules
|
||||||
|
.get_or_insert_with(|| {
|
||||||
|
Arc::new(RefCell::new(
|
||||||
|
(0..self.scopes.len())
|
||||||
|
.map(|_| Arc::new(RefCell::new(Vec::new())))
|
||||||
|
.collect(),
|
||||||
|
))
|
||||||
|
})
|
||||||
|
.borrow_mut()
|
||||||
|
.last_mut()
|
||||||
|
.unwrap()
|
||||||
|
.borrow_mut()
|
||||||
|
.extend(forwarded.borrow().iter().map(Arc::clone));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Remove existing member definitions that are now shadowed by the
|
||||||
|
// forwarded modules.
|
||||||
|
for variable in forwarded_var_names {
|
||||||
|
(*self.scopes.variables)
|
||||||
|
.borrow_mut()
|
||||||
|
.last_mut()
|
||||||
|
.unwrap()
|
||||||
|
.borrow_mut()
|
||||||
|
.remove(&variable);
|
||||||
|
}
|
||||||
|
self.scopes.last_variable_index = None;
|
||||||
|
|
||||||
|
for func in forwarded_fn_names {
|
||||||
|
(*self.scopes.functions)
|
||||||
|
.borrow_mut()
|
||||||
|
.last_mut()
|
||||||
|
.unwrap()
|
||||||
|
.borrow_mut()
|
||||||
|
.remove(&func);
|
||||||
|
}
|
||||||
|
for mixin in forwarded_mixin_names {
|
||||||
|
(*self.scopes.mixins)
|
||||||
|
.borrow_mut()
|
||||||
|
.last_mut()
|
||||||
|
.unwrap()
|
||||||
|
.borrow_mut()
|
||||||
|
.remove(&mixin);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn to_implicit_configuration(&self) -> Configuration {
|
pub fn to_implicit_configuration(&self) -> Configuration {
|
||||||
// var configuration = <String, ConfiguredValue>{};
|
let mut configuration = BTreeMap::new();
|
||||||
// for (var i = 0; i < _variables.length; i++) {
|
|
||||||
// var values = _variables[i];
|
let variables = (*self.scopes.variables).borrow();
|
||||||
// var nodes = _variableNodes[i];
|
|
||||||
// for (var entry in values.entries) {
|
for variables in variables.iter() {
|
||||||
// // Implicit configurations are never invalid, making [configurationSpan]
|
let entries = (**variables).borrow();
|
||||||
// // unnecessary, so we pass null here to avoid having to compute it.
|
for (key, value) in entries.iter() {
|
||||||
// configuration[entry.key] =
|
// Implicit configurations are never invalid, making [configurationSpan]
|
||||||
// ConfiguredValue.implicit(entry.value, nodes[entry.key]!);
|
// unnecessary, so we pass null here to avoid having to compute it.
|
||||||
// }
|
configuration.insert(*key, ConfiguredValue::implicit(value.clone()));
|
||||||
// }
|
}
|
||||||
// return Configuration.implicit(configuration);
|
}
|
||||||
todo!()
|
|
||||||
|
Configuration::implicit(configuration)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn forward_module(&mut self, module: Arc<RefCell<Module>>, rule: AstForwardRule) {
|
pub fn forward_module(&mut self, module: Arc<RefCell<Module>>, rule: AstForwardRule) {
|
||||||
@ -274,24 +355,22 @@ impl Environment {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if is_global || self.at_root() {
|
if is_global || self.at_root() {
|
||||||
// // Don't set the index if there's already a variable with the given name,
|
// If this module doesn't already contain a variable named [name], try
|
||||||
// // since local accesses should still return the local variable.
|
// setting it in a global module.
|
||||||
// _variableIndices.putIfAbsent(name, () {
|
if !self.scopes.global_var_exists(name.node) {
|
||||||
// _lastVariableName = name;
|
let module_with_name = self.from_one_module(name.node, "variable", |module| {
|
||||||
// _lastVariableIndex = 0;
|
if module.borrow().var_exists(*name) {
|
||||||
// return 0;
|
Some(Arc::clone(module))
|
||||||
// });
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
// // If this module doesn't already contain a variable named [name], try
|
if let Some(module_with_name) = module_with_name {
|
||||||
// // setting it in a global module.
|
module_with_name.borrow_mut().update_var(name, value)?;
|
||||||
// if (!_variables.first.containsKey(name)) {
|
return Ok(());
|
||||||
// var moduleWithName = _fromOneModule(name, "variable",
|
}
|
||||||
// (module) => module.variables.containsKey(name) ? module : null);
|
}
|
||||||
// if (moduleWithName != null) {
|
|
||||||
// moduleWithName.setVariable(name, value, nodeWithSpan);
|
|
||||||
// return;
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
|
|
||||||
self.scopes.insert_var(0, name.node, value);
|
self.scopes.insert_var(0, name.node, value);
|
||||||
return Ok(());
|
return Ok(());
|
||||||
@ -334,33 +413,19 @@ impl Environment {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn get_variable_from_global_modules(&self, name: Identifier) -> Option<Value> {
|
fn get_variable_from_global_modules(&self, name: Identifier) -> Option<Value> {
|
||||||
for module in &self.global_modules {
|
self.from_one_module(name, "variable", |module| {
|
||||||
if (**module).borrow().var_exists(name) {
|
(**module).borrow().get_var_no_err(name)
|
||||||
return (**module).borrow().get_var_no_err(name);
|
})
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
None
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn get_function_from_global_modules(&self, name: Identifier) -> Option<SassFunction> {
|
fn get_function_from_global_modules(&self, name: Identifier) -> Option<SassFunction> {
|
||||||
for module in &self.global_modules {
|
self.from_one_module(name, "function", |module| (**module).borrow().get_fn(name))
|
||||||
if (**module).borrow().fn_exists(name) {
|
|
||||||
return (**module).borrow().get_fn(name);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
None
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn get_mixin_from_global_modules(&self, name: Identifier) -> Option<Mixin> {
|
fn get_mixin_from_global_modules(&self, name: Identifier) -> Option<Mixin> {
|
||||||
for module in &self.global_modules {
|
self.from_one_module(name, "mixin", |module| {
|
||||||
if (**module).borrow().mixin_exists(name) {
|
(**module).borrow().get_mixin_no_err(name)
|
||||||
return (**module).borrow().get_mixin_no_err(name);
|
})
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
None
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn add_module(
|
pub fn add_module(
|
||||||
@ -396,4 +461,61 @@ impl Environment {
|
|||||||
|
|
||||||
Arc::new(RefCell::new(Module::new_env(self, extension_store)))
|
Arc::new(RefCell::new(Module::new_env(self, extension_store)))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn from_one_module<T>(
|
||||||
|
&self,
|
||||||
|
_name: Identifier,
|
||||||
|
_ty: &str,
|
||||||
|
callback: impl Fn(&Arc<RefCell<Module>>) -> Option<T>,
|
||||||
|
) -> Option<T> {
|
||||||
|
if let Some(nested_forwarded_modules) = &self.nested_forwarded_modules {
|
||||||
|
for modules in nested_forwarded_modules.borrow().iter().rev() {
|
||||||
|
for module in modules.borrow().iter().rev() {
|
||||||
|
if let Some(value) = callback(module) {
|
||||||
|
return Some(value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for module in self.imported_modules.borrow().iter() {
|
||||||
|
if let Some(value) = callback(module) {
|
||||||
|
return Some(value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut value: Option<T> = None;
|
||||||
|
// Object? identity;
|
||||||
|
|
||||||
|
for module in self.global_modules.iter() {
|
||||||
|
let value_in_module = match callback(module) {
|
||||||
|
Some(v) => v,
|
||||||
|
None => continue,
|
||||||
|
};
|
||||||
|
|
||||||
|
value = Some(value_in_module);
|
||||||
|
|
||||||
|
// Object? identityFromModule = valueInModule is AsyncCallable
|
||||||
|
// ? valueInModule
|
||||||
|
// : module.variableIdentity(name);
|
||||||
|
// if (identityFromModule == identity) continue;
|
||||||
|
|
||||||
|
// if (value != null) {
|
||||||
|
// var spans = _globalModules.entries.map(
|
||||||
|
// (entry) => callback(entry.key).andThen((_) => entry.value.span));
|
||||||
|
|
||||||
|
// throw MultiSpanSassScriptException(
|
||||||
|
// 'This $type is available from multiple global modules.',
|
||||||
|
// '$type use', {
|
||||||
|
// for (var span in spans)
|
||||||
|
// if (span != null) span: 'includes $type'
|
||||||
|
// });
|
||||||
|
// }
|
||||||
|
|
||||||
|
// value = valueInModule;
|
||||||
|
// identity = identityFromModule;
|
||||||
|
}
|
||||||
|
|
||||||
|
value
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -17,9 +17,9 @@ use crate::{
|
|||||||
#[allow(clippy::type_complexity)]
|
#[allow(clippy::type_complexity)]
|
||||||
#[derive(Debug, Default, Clone)]
|
#[derive(Debug, Default, Clone)]
|
||||||
pub(crate) struct Scopes {
|
pub(crate) struct Scopes {
|
||||||
variables: Arc<RefCell<Vec<Arc<RefCell<BTreeMap<Identifier, Value>>>>>>,
|
pub(crate) variables: Arc<RefCell<Vec<Arc<RefCell<BTreeMap<Identifier, Value>>>>>>,
|
||||||
mixins: Arc<RefCell<Vec<Arc<RefCell<BTreeMap<Identifier, Mixin>>>>>>,
|
pub(crate) mixins: Arc<RefCell<Vec<Arc<RefCell<BTreeMap<Identifier, Mixin>>>>>>,
|
||||||
functions: Arc<RefCell<Vec<Arc<RefCell<BTreeMap<Identifier, SassFunction>>>>>>,
|
pub(crate) functions: Arc<RefCell<Vec<Arc<RefCell<BTreeMap<Identifier, SassFunction>>>>>>,
|
||||||
len: Arc<Cell<usize>>,
|
len: Arc<Cell<usize>>,
|
||||||
pub last_variable_index: Option<(Identifier, usize)>,
|
pub last_variable_index: Option<(Identifier, usize)>,
|
||||||
}
|
}
|
||||||
@ -167,6 +167,10 @@ impl Scopes {
|
|||||||
|
|
||||||
false
|
false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn global_var_exists(&self, name: Identifier) -> bool {
|
||||||
|
self.global_variables().borrow().contains_key(&name)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Mixins
|
/// Mixins
|
||||||
|
@ -113,6 +113,8 @@ pub struct Visitor<'a> {
|
|||||||
pub(crate) extender: ExtensionStore,
|
pub(crate) extender: ExtensionStore,
|
||||||
pub(crate) current_import_path: PathBuf,
|
pub(crate) current_import_path: PathBuf,
|
||||||
pub(crate) is_plain_css: bool,
|
pub(crate) is_plain_css: bool,
|
||||||
|
pub(crate) modules: BTreeMap<PathBuf, Arc<RefCell<Module>>>,
|
||||||
|
pub(crate) active_modules: BTreeSet<PathBuf>,
|
||||||
css_tree: CssTree,
|
css_tree: CssTree,
|
||||||
parent: Option<CssTreeIdx>,
|
parent: Option<CssTreeIdx>,
|
||||||
configuration: Arc<RefCell<Configuration>>,
|
configuration: Arc<RefCell<Configuration>>,
|
||||||
@ -157,6 +159,8 @@ impl<'a> Visitor<'a> {
|
|||||||
configuration: Arc::new(RefCell::new(Configuration::empty())),
|
configuration: Arc::new(RefCell::new(Configuration::empty())),
|
||||||
is_plain_css: false,
|
is_plain_css: false,
|
||||||
import_nodes: Vec::new(),
|
import_nodes: Vec::new(),
|
||||||
|
modules: BTreeMap::new(),
|
||||||
|
active_modules: BTreeSet::new(),
|
||||||
options,
|
options,
|
||||||
span_before,
|
span_before,
|
||||||
map,
|
map,
|
||||||
@ -166,6 +170,7 @@ impl<'a> Visitor<'a> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn visit_stylesheet(&mut self, mut style_sheet: StyleSheet) -> SassResult<()> {
|
pub(crate) fn visit_stylesheet(&mut self, mut style_sheet: StyleSheet) -> SassResult<()> {
|
||||||
|
self.active_modules.insert(style_sheet.url.clone());
|
||||||
let was_in_plain_css = self.is_plain_css;
|
let was_in_plain_css = self.is_plain_css;
|
||||||
self.is_plain_css = style_sheet.is_plain_css;
|
self.is_plain_css = style_sheet.is_plain_css;
|
||||||
mem::swap(&mut self.current_import_path, &mut style_sheet.url);
|
mem::swap(&mut self.current_import_path, &mut style_sheet.url);
|
||||||
@ -178,6 +183,8 @@ impl<'a> Visitor<'a> {
|
|||||||
mem::swap(&mut self.current_import_path, &mut style_sheet.url);
|
mem::swap(&mut self.current_import_path, &mut style_sheet.url);
|
||||||
self.is_plain_css = was_in_plain_css;
|
self.is_plain_css = was_in_plain_css;
|
||||||
|
|
||||||
|
self.active_modules.remove(&style_sheet.url);
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -366,6 +373,8 @@ impl<'a> Visitor<'a> {
|
|||||||
)))
|
)))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Remove configured values from [upstream] that have been removed from
|
||||||
|
/// [downstream], unless they match a name in [except].
|
||||||
fn remove_used_configuration(
|
fn remove_used_configuration(
|
||||||
upstream: &Arc<RefCell<Configuration>>,
|
upstream: &Arc<RefCell<Configuration>>,
|
||||||
downstream: &Arc<RefCell<Configuration>>,
|
downstream: &Arc<RefCell<Configuration>>,
|
||||||
@ -534,6 +543,40 @@ impl<'a> Visitor<'a> {
|
|||||||
// todo: different errors based on this
|
// todo: different errors based on this
|
||||||
_names_in_errors: bool,
|
_names_in_errors: bool,
|
||||||
) -> SassResult<Arc<RefCell<Module>>> {
|
) -> SassResult<Arc<RefCell<Module>>> {
|
||||||
|
let url = stylesheet.url.clone();
|
||||||
|
|
||||||
|
// todo: use canonical url for modules
|
||||||
|
if let Some(already_loaded) = self.modules.get(&stylesheet.url) {
|
||||||
|
let current_configuration =
|
||||||
|
configuration.unwrap_or_else(|| Arc::clone(&self.configuration));
|
||||||
|
|
||||||
|
if !current_configuration.borrow().is_implicit() {
|
||||||
|
// if (!_moduleConfigurations[url]!.sameOriginal(currentConfiguration) &&
|
||||||
|
// currentConfiguration is ExplicitConfiguration) {
|
||||||
|
// var message = namesInErrors
|
||||||
|
// ? "${p.prettyUri(url)} was already loaded, so it can't be "
|
||||||
|
// "configured using \"with\"."
|
||||||
|
// : "This module was already loaded, so it can't be configured using "
|
||||||
|
// "\"with\".";
|
||||||
|
|
||||||
|
// var existingSpan = _moduleNodes[url]?.span;
|
||||||
|
// var configurationSpan = configuration == null
|
||||||
|
// ? currentConfiguration.nodeWithSpan.span
|
||||||
|
// : null;
|
||||||
|
// var secondarySpans = {
|
||||||
|
// if (existingSpan != null) existingSpan: "original load",
|
||||||
|
// if (configurationSpan != null) configurationSpan: "configuration"
|
||||||
|
// };
|
||||||
|
|
||||||
|
// throw secondarySpans.isEmpty
|
||||||
|
// ? _exception(message)
|
||||||
|
// : _multiSpanException(message, "new load", secondarySpans);
|
||||||
|
// }
|
||||||
|
}
|
||||||
|
|
||||||
|
return Ok(Arc::clone(already_loaded));
|
||||||
|
}
|
||||||
|
|
||||||
let env = Environment::new();
|
let env = Environment::new();
|
||||||
let mut extension_store = ExtensionStore::new(self.span_before);
|
let mut extension_store = ExtensionStore::new(self.span_before);
|
||||||
|
|
||||||
@ -589,6 +632,8 @@ impl<'a> Visitor<'a> {
|
|||||||
|
|
||||||
let module = env.to_module(extension_store);
|
let module = env.to_module(extension_store);
|
||||||
|
|
||||||
|
self.modules.insert(url, Arc::clone(&module));
|
||||||
|
|
||||||
Ok(module)
|
Ok(module)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -635,7 +680,7 @@ impl<'a> Visitor<'a> {
|
|||||||
callback(
|
callback(
|
||||||
self,
|
self,
|
||||||
Arc::new(RefCell::new(builtin)),
|
Arc::new(RefCell::new(builtin)),
|
||||||
StyleSheet::new(false, PathBuf::from("")),
|
StyleSheet::new(false, url.to_path_buf()),
|
||||||
)?;
|
)?;
|
||||||
return Ok(());
|
return Ok(());
|
||||||
}
|
}
|
||||||
@ -643,8 +688,22 @@ impl<'a> Visitor<'a> {
|
|||||||
// todo: decide on naming convention for style_sheet vs stylesheet
|
// todo: decide on naming convention for style_sheet vs stylesheet
|
||||||
let stylesheet = self.load_style_sheet(url.to_string_lossy().as_ref(), false, span)?;
|
let stylesheet = self.load_style_sheet(url.to_string_lossy().as_ref(), false, span)?;
|
||||||
|
|
||||||
|
let canonical_url = self
|
||||||
|
.options
|
||||||
|
.fs
|
||||||
|
.canonicalize(&stylesheet.url)
|
||||||
|
.unwrap_or_else(|_| stylesheet.url.clone());
|
||||||
|
|
||||||
|
if self.active_modules.contains(&canonical_url) {
|
||||||
|
return Err(("Module loop: this module is already being loaded.", span).into());
|
||||||
|
}
|
||||||
|
|
||||||
|
self.active_modules.insert(canonical_url.clone());
|
||||||
|
|
||||||
let module = self.execute(stylesheet.clone(), configuration, names_in_errors)?;
|
let module = self.execute(stylesheet.clone(), configuration, names_in_errors)?;
|
||||||
|
|
||||||
|
self.active_modules.remove(&canonical_url);
|
||||||
|
|
||||||
callback(self, module, stylesheet)?;
|
callback(self, module, stylesheet)?;
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
@ -832,6 +891,7 @@ impl<'a> Visitor<'a> {
|
|||||||
span: Span,
|
span: Span,
|
||||||
) -> SassResult<StyleSheet> {
|
) -> SassResult<StyleSheet> {
|
||||||
if let Some(name) = self.find_import(url.as_ref()) {
|
if let Some(name) = self.find_import(url.as_ref()) {
|
||||||
|
let name = self.options.fs.canonicalize(&name).unwrap_or(name);
|
||||||
if let Some(style_sheet) = self.import_cache.get(&name) {
|
if let Some(style_sheet) = self.import_cache.get(&name) {
|
||||||
return Ok(style_sheet.clone());
|
return Ok(style_sheet.clone());
|
||||||
}
|
}
|
||||||
@ -876,6 +936,14 @@ impl<'a> Visitor<'a> {
|
|||||||
fn visit_dynamic_import_rule(&mut self, dynamic_import: &AstSassImport) -> SassResult<()> {
|
fn visit_dynamic_import_rule(&mut self, dynamic_import: &AstSassImport) -> SassResult<()> {
|
||||||
let stylesheet = self.load_style_sheet(&dynamic_import.url, true, dynamic_import.span)?;
|
let stylesheet = self.load_style_sheet(&dynamic_import.url, true, dynamic_import.span)?;
|
||||||
|
|
||||||
|
let url = stylesheet.url.clone();
|
||||||
|
|
||||||
|
if self.active_modules.contains(&url) {
|
||||||
|
return Err(("This file is already being loaded.", dynamic_import.span).into());
|
||||||
|
}
|
||||||
|
|
||||||
|
self.active_modules.insert(url.clone());
|
||||||
|
|
||||||
// If the imported stylesheet doesn't use any modules, we can inject its
|
// If the imported stylesheet doesn't use any modules, we can inject its
|
||||||
// CSS directly into the current stylesheet. If it does use modules, we
|
// CSS directly into the current stylesheet. If it does use modules, we
|
||||||
// need to put its CSS into an intermediate [ModifiableCssStylesheet] so
|
// need to put its CSS into an intermediate [ModifiableCssStylesheet] so
|
||||||
@ -924,6 +992,7 @@ impl<'a> Visitor<'a> {
|
|||||||
self.env.import_forwards(module);
|
self.env.import_forwards(module);
|
||||||
|
|
||||||
if loads_user_defined_modules {
|
if loads_user_defined_modules {
|
||||||
|
// todo:
|
||||||
// if (module.transitivelyContainsCss) {
|
// if (module.transitivelyContainsCss) {
|
||||||
// // If any transitively used module contains extensions, we need to
|
// // If any transitively used module contains extensions, we need to
|
||||||
// // clone all modules' CSS. Otherwise, it's possible that they'll be
|
// // clone all modules' CSS. Otherwise, it's possible that they'll be
|
||||||
@ -940,6 +1009,8 @@ impl<'a> Visitor<'a> {
|
|||||||
// }
|
// }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
self.active_modules.remove(&url);
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
use std::{
|
use std::{
|
||||||
io::{self, Error, ErrorKind},
|
io::{self, Error, ErrorKind},
|
||||||
path::Path,
|
path::{Path, PathBuf},
|
||||||
};
|
};
|
||||||
|
|
||||||
/// A trait to allow replacing the file system lookup mechanisms.
|
/// A trait to allow replacing the file system lookup mechanisms.
|
||||||
@ -18,6 +18,11 @@ pub trait Fs: std::fmt::Debug {
|
|||||||
fn is_file(&self, path: &Path) -> bool;
|
fn is_file(&self, path: &Path) -> bool;
|
||||||
/// Read the entire contents of a file into a bytes vector.
|
/// Read the entire contents of a file into a bytes vector.
|
||||||
fn read(&self, path: &Path) -> io::Result<Vec<u8>>;
|
fn read(&self, path: &Path) -> io::Result<Vec<u8>>;
|
||||||
|
|
||||||
|
/// Canonicalize a file path
|
||||||
|
fn canonicalize(&self, path: &Path) -> io::Result<PathBuf> {
|
||||||
|
Ok(path.to_path_buf())
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Use [`std::fs`] to read any files from disk.
|
/// Use [`std::fs`] to read any files from disk.
|
||||||
@ -41,6 +46,11 @@ impl Fs for StdFs {
|
|||||||
fn read(&self, path: &Path) -> io::Result<Vec<u8>> {
|
fn read(&self, path: &Path) -> io::Result<Vec<u8>> {
|
||||||
std::fs::read(path)
|
std::fs::read(path)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
fn canonicalize(&self, path: &Path) -> io::Result<PathBuf> {
|
||||||
|
std::fs::canonicalize(path)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// A file system implementation that acts like it’s completely empty.
|
/// A file system implementation that acts like it’s completely empty.
|
||||||
|
@ -38,15 +38,15 @@ impl<'a> BaseParser<'a> for CssParser<'a> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl<'a> StylesheetParser<'a> for CssParser<'a> {
|
impl<'a> StylesheetParser<'a> for CssParser<'a> {
|
||||||
fn is_plain_css(&mut self) -> bool {
|
fn is_plain_css(&self) -> bool {
|
||||||
true
|
true
|
||||||
}
|
}
|
||||||
|
|
||||||
fn is_indented(&mut self) -> bool {
|
fn is_indented(&self) -> bool {
|
||||||
false
|
false
|
||||||
}
|
}
|
||||||
|
|
||||||
fn path(&mut self) -> &'a Path {
|
fn path(&self) -> &'a Path {
|
||||||
self.path
|
self.path
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -58,7 +58,7 @@ impl<'a> StylesheetParser<'a> for CssParser<'a> {
|
|||||||
self.options
|
self.options
|
||||||
}
|
}
|
||||||
|
|
||||||
fn flags(&mut self) -> &ContextFlags {
|
fn flags(&self) -> &ContextFlags {
|
||||||
&self.flags
|
&self.flags
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -71,15 +71,15 @@ impl<'a> BaseParser<'a> for SassParser<'a> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl<'a> StylesheetParser<'a> for SassParser<'a> {
|
impl<'a> StylesheetParser<'a> for SassParser<'a> {
|
||||||
fn is_plain_css(&mut self) -> bool {
|
fn is_plain_css(&self) -> bool {
|
||||||
false
|
false
|
||||||
}
|
}
|
||||||
|
|
||||||
fn is_indented(&mut self) -> bool {
|
fn is_indented(&self) -> bool {
|
||||||
true
|
true
|
||||||
}
|
}
|
||||||
|
|
||||||
fn path(&mut self) -> &'a Path {
|
fn path(&self) -> &'a Path {
|
||||||
self.path
|
self.path
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -91,7 +91,7 @@ impl<'a> StylesheetParser<'a> for SassParser<'a> {
|
|||||||
self.options
|
self.options
|
||||||
}
|
}
|
||||||
|
|
||||||
fn flags(&mut self) -> &ContextFlags {
|
fn flags(&self) -> &ContextFlags {
|
||||||
&self.flags
|
&self.flags
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -50,15 +50,15 @@ impl<'a> BaseParser<'a> for ScssParser<'a> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl<'a> StylesheetParser<'a> for ScssParser<'a> {
|
impl<'a> StylesheetParser<'a> for ScssParser<'a> {
|
||||||
fn is_plain_css(&mut self) -> bool {
|
fn is_plain_css(&self) -> bool {
|
||||||
false
|
false
|
||||||
}
|
}
|
||||||
|
|
||||||
fn is_indented(&mut self) -> bool {
|
fn is_indented(&self) -> bool {
|
||||||
false
|
false
|
||||||
}
|
}
|
||||||
|
|
||||||
fn path(&mut self) -> &'a Path {
|
fn path(&self) -> &'a Path {
|
||||||
self.path
|
self.path
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -74,7 +74,7 @@ impl<'a> StylesheetParser<'a> for ScssParser<'a> {
|
|||||||
0
|
0
|
||||||
}
|
}
|
||||||
|
|
||||||
fn flags(&mut self) -> &ContextFlags {
|
fn flags(&self) -> &ContextFlags {
|
||||||
&self.flags
|
&self.flags
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -28,15 +28,15 @@ use super::{
|
|||||||
/// SCSS share the behavior
|
/// SCSS share the behavior
|
||||||
pub(crate) trait StylesheetParser<'a>: BaseParser<'a> + Sized {
|
pub(crate) trait StylesheetParser<'a>: BaseParser<'a> + Sized {
|
||||||
// todo: make constant?
|
// todo: make constant?
|
||||||
fn is_plain_css(&mut self) -> bool;
|
fn is_plain_css(&self) -> bool;
|
||||||
// todo: make constant?
|
// todo: make constant?
|
||||||
fn is_indented(&mut self) -> bool;
|
fn is_indented(&self) -> bool;
|
||||||
fn options(&self) -> &Options;
|
fn options(&self) -> &Options;
|
||||||
fn path(&mut self) -> &Path;
|
fn path(&self) -> &Path;
|
||||||
fn map(&mut self) -> &mut CodeMap;
|
fn map(&mut self) -> &mut CodeMap;
|
||||||
fn span_before(&self) -> Span;
|
fn span_before(&self) -> Span;
|
||||||
fn current_indentation(&self) -> usize;
|
fn current_indentation(&self) -> usize;
|
||||||
fn flags(&mut self) -> &ContextFlags;
|
fn flags(&self) -> &ContextFlags;
|
||||||
fn flags_mut(&mut self) -> &mut ContextFlags;
|
fn flags_mut(&mut self) -> &mut ContextFlags;
|
||||||
|
|
||||||
#[allow(clippy::type_complexity)]
|
#[allow(clippy::type_complexity)]
|
||||||
@ -185,7 +185,13 @@ pub(crate) trait StylesheetParser<'a>: BaseParser<'a> + Sized {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn __parse(&mut self) -> SassResult<StyleSheet> {
|
fn __parse(&mut self) -> SassResult<StyleSheet> {
|
||||||
let mut style_sheet = StyleSheet::new(self.is_plain_css(), self.path().to_path_buf());
|
let mut style_sheet = StyleSheet::new(
|
||||||
|
self.is_plain_css(),
|
||||||
|
self.options()
|
||||||
|
.fs
|
||||||
|
.canonicalize(self.path())
|
||||||
|
.unwrap_or_else(|_| self.path().to_path_buf()),
|
||||||
|
);
|
||||||
|
|
||||||
// Allow a byte-order mark at the beginning of the document.
|
// Allow a byte-order mark at the beginning of the document.
|
||||||
self.scan_char('\u{feff}');
|
self.scan_char('\u{feff}');
|
||||||
|
@ -180,6 +180,17 @@ impl<V: fmt::Debug + Clone, T: MapView<Value = V> + Clone> MapView for PrefixedM
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// A mostly-unmodifiable view of a map that only allows certain keys to be
|
||||||
|
/// accessed.
|
||||||
|
///
|
||||||
|
/// Whether or not the underlying map contains keys that aren't allowed, this
|
||||||
|
/// view will behave as though it doesn't contain them.
|
||||||
|
///
|
||||||
|
/// The underlying map's values may change independently of this view, but its
|
||||||
|
/// set of keys may not.
|
||||||
|
///
|
||||||
|
/// This is unmodifiable *except for the [remove] method*, which is used for
|
||||||
|
/// `@used with` to mark configured variables as used.
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
pub(crate) struct LimitedMapView<V: fmt::Debug + Clone, T: MapView<Value = V> + Clone>(
|
pub(crate) struct LimitedMapView<V: fmt::Debug + Clone, T: MapView<Value = V> + Clone>(
|
||||||
pub T,
|
pub T,
|
||||||
@ -197,11 +208,11 @@ impl<V: fmt::Debug + Clone, T: MapView<Value = V> + Clone> LimitedMapView<V, T>
|
|||||||
Self(map, keys)
|
Self(map, keys)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn blocklist(map: T, keys: &HashSet<Identifier>) -> Self {
|
pub fn blocklist(map: T, blocklist: &HashSet<Identifier>) -> Self {
|
||||||
let keys = keys
|
let keys = map
|
||||||
.iter()
|
.keys()
|
||||||
.copied()
|
.into_iter()
|
||||||
.filter(|key| !map.contains_key(*key))
|
.filter(|key| !blocklist.contains(key))
|
||||||
.collect();
|
.collect();
|
||||||
|
|
||||||
Self(map, keys)
|
Self(map, keys)
|
||||||
|
@ -334,6 +334,173 @@ fn forward_module_with_error() {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn use_with_multi_load_forward() {
|
||||||
|
let mut fs = TestFs::new();
|
||||||
|
|
||||||
|
fs.add_file(
|
||||||
|
"_midstream.scss",
|
||||||
|
r#"
|
||||||
|
@forward "upstream";
|
||||||
|
"#,
|
||||||
|
);
|
||||||
|
fs.add_file(
|
||||||
|
"_upstream.scss",
|
||||||
|
r#"
|
||||||
|
$a: original !default;
|
||||||
|
"#,
|
||||||
|
);
|
||||||
|
|
||||||
|
let input = r#"
|
||||||
|
@use "upstream" with ($a: configured);
|
||||||
|
@use "midstream";
|
||||||
|
b {c: midstream.$a}
|
||||||
|
"#;
|
||||||
|
|
||||||
|
assert_eq!(
|
||||||
|
"b {\n c: configured;\n}\n",
|
||||||
|
&grass::from_string(input.to_string(), &grass::Options::default().fs(&fs)).expect(input)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn forward_member_import_precedence_nested() {
|
||||||
|
let mut fs = TestFs::new();
|
||||||
|
|
||||||
|
fs.add_file(
|
||||||
|
"_midstream.scss",
|
||||||
|
r#"
|
||||||
|
@forward "upstream";
|
||||||
|
"#,
|
||||||
|
);
|
||||||
|
fs.add_file(
|
||||||
|
"_upstream.scss",
|
||||||
|
r#"
|
||||||
|
$a: in-upstream;
|
||||||
|
"#,
|
||||||
|
);
|
||||||
|
|
||||||
|
let input = r#"
|
||||||
|
b {
|
||||||
|
$a: in-input;
|
||||||
|
|
||||||
|
@import "midstream";
|
||||||
|
|
||||||
|
c: $a;
|
||||||
|
}
|
||||||
|
"#;
|
||||||
|
|
||||||
|
assert_eq!(
|
||||||
|
"b {\n c: in-upstream;\n}\n",
|
||||||
|
&grass::from_string(input.to_string(), &grass::Options::default().fs(&fs)).expect(input)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn forward_with_through_forward_hide() {
|
||||||
|
let mut fs = TestFs::new();
|
||||||
|
|
||||||
|
fs.add_file(
|
||||||
|
"_downstream.scss",
|
||||||
|
r#"
|
||||||
|
@forward "midstream" with ($a: configured);
|
||||||
|
"#,
|
||||||
|
);
|
||||||
|
fs.add_file(
|
||||||
|
"_midstream.scss",
|
||||||
|
r#"
|
||||||
|
@forward "upstream" hide $b;
|
||||||
|
"#,
|
||||||
|
);
|
||||||
|
fs.add_file(
|
||||||
|
"_upstream.scss",
|
||||||
|
r#"
|
||||||
|
$a: original !default;
|
||||||
|
b {c: $a}
|
||||||
|
"#,
|
||||||
|
);
|
||||||
|
|
||||||
|
let input = r#"
|
||||||
|
@use "downstream";
|
||||||
|
"#;
|
||||||
|
|
||||||
|
assert_eq!(
|
||||||
|
"b {\n c: configured;\n}\n",
|
||||||
|
&grass::from_string(input.to_string(), &grass::Options::default().fs(&fs)).expect(input)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn forward_with_through_forward_show() {
|
||||||
|
let mut fs = TestFs::new();
|
||||||
|
|
||||||
|
fs.add_file(
|
||||||
|
"_downstream.scss",
|
||||||
|
r#"
|
||||||
|
@forward "midstream" with ($a: configured);
|
||||||
|
"#,
|
||||||
|
);
|
||||||
|
fs.add_file(
|
||||||
|
"_midstream.scss",
|
||||||
|
r#"
|
||||||
|
@forward "upstream" show $a;
|
||||||
|
"#,
|
||||||
|
);
|
||||||
|
fs.add_file(
|
||||||
|
"_upstream.scss",
|
||||||
|
r#"
|
||||||
|
$a: original !default;
|
||||||
|
b {c: $a}
|
||||||
|
"#,
|
||||||
|
);
|
||||||
|
|
||||||
|
let input = r#"
|
||||||
|
@use "downstream";
|
||||||
|
"#;
|
||||||
|
|
||||||
|
assert_eq!(
|
||||||
|
"b {\n c: configured;\n}\n",
|
||||||
|
&grass::from_string(input.to_string(), &grass::Options::default().fs(&fs)).expect(input)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
#[ignore = "incorrectly thinks there's a module loop"]
|
||||||
|
fn import_forwarded_first_no_use() {
|
||||||
|
let mut fs = TestFs::new();
|
||||||
|
|
||||||
|
fs.add_file(
|
||||||
|
"first.scss",
|
||||||
|
r#"
|
||||||
|
$variable: value;
|
||||||
|
"#,
|
||||||
|
);
|
||||||
|
fs.add_file(
|
||||||
|
"first.import.scss",
|
||||||
|
r#"
|
||||||
|
@forward "first";
|
||||||
|
"#,
|
||||||
|
);
|
||||||
|
fs.add_file(
|
||||||
|
"second.scss",
|
||||||
|
r#"
|
||||||
|
a {
|
||||||
|
b: $variable;
|
||||||
|
}
|
||||||
|
"#,
|
||||||
|
);
|
||||||
|
|
||||||
|
let input = r#"
|
||||||
|
@import "first";
|
||||||
|
@import "second";
|
||||||
|
"#;
|
||||||
|
|
||||||
|
assert_eq!(
|
||||||
|
"a {\n b: value;\n}\n",
|
||||||
|
&grass::from_string(input.to_string(), &grass::Options::default().fs(&fs)).expect(input)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
error!(
|
error!(
|
||||||
after_style_rule,
|
after_style_rule,
|
||||||
r#"
|
r#"
|
||||||
|
@ -349,6 +349,22 @@ fn imports_same_file_thrice() {
|
|||||||
&grass::from_string(input.to_string(), &grass::Options::default().fs(&fs)).expect(input)
|
&grass::from_string(input.to_string(), &grass::Options::default().fs(&fs)).expect(input)
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
#[test]
|
||||||
|
fn imports_self() {
|
||||||
|
let mut fs = TestFs::new();
|
||||||
|
|
||||||
|
fs.add_file("input.scss", r#"@import "input";"#);
|
||||||
|
|
||||||
|
let input = r#"
|
||||||
|
@import "input";
|
||||||
|
"#;
|
||||||
|
|
||||||
|
assert_err!(
|
||||||
|
input,
|
||||||
|
"Error: This file is already being loaded.",
|
||||||
|
&grass::Options::default().fs(&fs)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn imports_explicit_file_extension() {
|
fn imports_explicit_file_extension() {
|
||||||
|
@ -845,6 +845,37 @@ fn import_module_using_same_builtin_module_has_styles() {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn use_member_global_variable_assignment_toplevel() {
|
||||||
|
let mut fs = TestFs::new();
|
||||||
|
|
||||||
|
fs.add_file(
|
||||||
|
"other.scss",
|
||||||
|
r#"
|
||||||
|
$member: value;
|
||||||
|
|
||||||
|
@function get-member() {
|
||||||
|
@return $member
|
||||||
|
}
|
||||||
|
"#,
|
||||||
|
);
|
||||||
|
|
||||||
|
let input = r#"
|
||||||
|
@use "other" as *;
|
||||||
|
|
||||||
|
$member: new value;
|
||||||
|
|
||||||
|
a {
|
||||||
|
b: get-member()
|
||||||
|
}
|
||||||
|
"#;
|
||||||
|
|
||||||
|
assert_eq!(
|
||||||
|
"a {\n b: new value;\n}\n",
|
||||||
|
&grass::from_string(input.to_string(), &grass::Options::default().fs(&fs)).expect(input)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
#[ignore = "we don't hermetically evaluate @extend"]
|
#[ignore = "we don't hermetically evaluate @extend"]
|
||||||
fn use_module_with_extend() {
|
fn use_module_with_extend() {
|
||||||
|
Loading…
x
Reference in New Issue
Block a user