support the @use ... with (...)
syntax
This commit is contained in:
parent
94becb4dcb
commit
074d679cbd
@ -27,6 +27,34 @@ pub(crate) struct Module(pub Scope);
|
|||||||
#[derive(Debug, Default)]
|
#[derive(Debug, Default)]
|
||||||
pub(crate) struct Modules(BTreeMap<Identifier, Module>);
|
pub(crate) struct Modules(BTreeMap<Identifier, Module>);
|
||||||
|
|
||||||
|
#[derive(Debug, Default)]
|
||||||
|
pub(crate) struct ModuleConfig(BTreeMap<Identifier, Value>);
|
||||||
|
|
||||||
|
impl ModuleConfig {
|
||||||
|
/// Removes and returns element with name
|
||||||
|
pub fn get(&mut self, name: Identifier) -> Option<Value> {
|
||||||
|
self.0.remove(&name)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// If this structure is not empty at the end of
|
||||||
|
/// an `@use`, we must throw an error
|
||||||
|
pub fn is_empty(&self) -> bool {
|
||||||
|
self.0.is_empty()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn insert(&mut self, name: Spanned<Identifier>, value: Spanned<Value>) -> SassResult<()> {
|
||||||
|
if self.0.insert(name.node, value.node).is_some() {
|
||||||
|
Err((
|
||||||
|
"The same variable may only be configured once.",
|
||||||
|
name.span.merge(value.span),
|
||||||
|
)
|
||||||
|
.into())
|
||||||
|
} else {
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl Modules {
|
impl Modules {
|
||||||
pub fn insert(&mut self, name: Identifier, module: Module, span: Span) -> SassResult<()> {
|
pub fn insert(&mut self, name: Identifier, module: Module, span: Span) -> SassResult<()> {
|
||||||
if self.0.contains_key(&name) {
|
if self.0.contains_key(&name) {
|
||||||
|
@ -95,7 +95,7 @@ use peekmore::PeekMore;
|
|||||||
pub use crate::error::{SassError as Error, SassResult as Result};
|
pub use crate::error::{SassError as Error, SassResult as Result};
|
||||||
pub(crate) use crate::token::Token;
|
pub(crate) use crate::token::Token;
|
||||||
use crate::{
|
use crate::{
|
||||||
builtin::modules::Modules,
|
builtin::modules::{ModuleConfig, Modules},
|
||||||
lexer::Lexer,
|
lexer::Lexer,
|
||||||
output::Css,
|
output::Css,
|
||||||
parse::{
|
parse::{
|
||||||
@ -295,6 +295,7 @@ pub fn from_path(p: &str, options: &Options) -> Result<String> {
|
|||||||
content_scopes: &mut Scopes::new(),
|
content_scopes: &mut Scopes::new(),
|
||||||
options,
|
options,
|
||||||
modules: &mut Modules::default(),
|
modules: &mut Modules::default(),
|
||||||
|
module_config: &mut ModuleConfig::default(),
|
||||||
}
|
}
|
||||||
.parse()
|
.parse()
|
||||||
.map_err(|e| raw_to_parse_error(&map, *e, options.unicode_error_messages))?;
|
.map_err(|e| raw_to_parse_error(&map, *e, options.unicode_error_messages))?;
|
||||||
@ -340,6 +341,7 @@ pub fn from_string(p: String, options: &Options) -> Result<String> {
|
|||||||
content_scopes: &mut Scopes::new(),
|
content_scopes: &mut Scopes::new(),
|
||||||
options,
|
options,
|
||||||
modules: &mut Modules::default(),
|
modules: &mut Modules::default(),
|
||||||
|
module_config: &mut ModuleConfig::default(),
|
||||||
}
|
}
|
||||||
.parse()
|
.parse()
|
||||||
.map_err(|e| raw_to_parse_error(&map, *e, options.unicode_error_messages))?;
|
.map_err(|e| raw_to_parse_error(&map, *e, options.unicode_error_messages))?;
|
||||||
@ -376,6 +378,7 @@ pub fn from_string(p: String) -> std::result::Result<String, JsValue> {
|
|||||||
content_scopes: &mut Scopes::new(),
|
content_scopes: &mut Scopes::new(),
|
||||||
options: &Options::default(),
|
options: &Options::default(),
|
||||||
modules: &mut Modules::default(),
|
modules: &mut Modules::default(),
|
||||||
|
module_config: &mut ModuleConfig::default(),
|
||||||
}
|
}
|
||||||
.parse()
|
.parse()
|
||||||
.map_err(|e| raw_to_parse_error(&map, *e, true).to_string())?;
|
.map_err(|e| raw_to_parse_error(&map, *e, true).to_string())?;
|
||||||
|
@ -54,6 +54,7 @@ impl<'a> Parser<'a> {
|
|||||||
content_scopes: self.content_scopes,
|
content_scopes: self.content_scopes,
|
||||||
options: self.options,
|
options: self.options,
|
||||||
modules: self.modules,
|
modules: self.modules,
|
||||||
|
module_config: self.module_config,
|
||||||
}
|
}
|
||||||
.parse_stmt()?;
|
.parse_stmt()?;
|
||||||
} else {
|
} else {
|
||||||
@ -114,6 +115,7 @@ impl<'a> Parser<'a> {
|
|||||||
content_scopes: self.content_scopes,
|
content_scopes: self.content_scopes,
|
||||||
options: self.options,
|
options: self.options,
|
||||||
modules: self.modules,
|
modules: self.modules,
|
||||||
|
module_config: self.module_config,
|
||||||
}
|
}
|
||||||
.parse_stmt()?;
|
.parse_stmt()?;
|
||||||
} else {
|
} else {
|
||||||
@ -143,6 +145,7 @@ impl<'a> Parser<'a> {
|
|||||||
content_scopes: self.content_scopes,
|
content_scopes: self.content_scopes,
|
||||||
options: self.options,
|
options: self.options,
|
||||||
modules: self.modules,
|
modules: self.modules,
|
||||||
|
module_config: self.module_config,
|
||||||
}
|
}
|
||||||
.parse_stmt();
|
.parse_stmt();
|
||||||
}
|
}
|
||||||
@ -323,6 +326,7 @@ impl<'a> Parser<'a> {
|
|||||||
content_scopes: self.content_scopes,
|
content_scopes: self.content_scopes,
|
||||||
options: self.options,
|
options: self.options,
|
||||||
modules: self.modules,
|
modules: self.modules,
|
||||||
|
module_config: self.module_config,
|
||||||
}
|
}
|
||||||
.parse_stmt()?;
|
.parse_stmt()?;
|
||||||
if !these_stmts.is_empty() {
|
if !these_stmts.is_empty() {
|
||||||
@ -346,6 +350,7 @@ impl<'a> Parser<'a> {
|
|||||||
content_scopes: self.content_scopes,
|
content_scopes: self.content_scopes,
|
||||||
options: self.options,
|
options: self.options,
|
||||||
modules: self.modules,
|
modules: self.modules,
|
||||||
|
module_config: self.module_config,
|
||||||
}
|
}
|
||||||
.parse_stmt()?,
|
.parse_stmt()?,
|
||||||
);
|
);
|
||||||
@ -397,6 +402,7 @@ impl<'a> Parser<'a> {
|
|||||||
content_scopes: self.content_scopes,
|
content_scopes: self.content_scopes,
|
||||||
options: self.options,
|
options: self.options,
|
||||||
modules: self.modules,
|
modules: self.modules,
|
||||||
|
module_config: self.module_config,
|
||||||
}
|
}
|
||||||
.parse_stmt()?;
|
.parse_stmt()?;
|
||||||
if !these_stmts.is_empty() {
|
if !these_stmts.is_empty() {
|
||||||
@ -420,6 +426,7 @@ impl<'a> Parser<'a> {
|
|||||||
content_scopes: self.content_scopes,
|
content_scopes: self.content_scopes,
|
||||||
options: self.options,
|
options: self.options,
|
||||||
modules: self.modules,
|
modules: self.modules,
|
||||||
|
module_config: self.module_config,
|
||||||
}
|
}
|
||||||
.parse_stmt()?,
|
.parse_stmt()?,
|
||||||
);
|
);
|
||||||
@ -512,6 +519,7 @@ impl<'a> Parser<'a> {
|
|||||||
content_scopes: self.content_scopes,
|
content_scopes: self.content_scopes,
|
||||||
options: self.options,
|
options: self.options,
|
||||||
modules: self.modules,
|
modules: self.modules,
|
||||||
|
module_config: self.module_config,
|
||||||
}
|
}
|
||||||
.parse_stmt()?;
|
.parse_stmt()?;
|
||||||
if !these_stmts.is_empty() {
|
if !these_stmts.is_empty() {
|
||||||
@ -535,6 +543,7 @@ impl<'a> Parser<'a> {
|
|||||||
content_scopes: self.content_scopes,
|
content_scopes: self.content_scopes,
|
||||||
options: self.options,
|
options: self.options,
|
||||||
modules: self.modules,
|
modules: self.modules,
|
||||||
|
module_config: self.module_config,
|
||||||
}
|
}
|
||||||
.parse_stmt()?,
|
.parse_stmt()?,
|
||||||
);
|
);
|
||||||
|
@ -121,6 +121,7 @@ impl<'a> Parser<'a> {
|
|||||||
content_scopes: self.content_scopes,
|
content_scopes: self.content_scopes,
|
||||||
options: self.options,
|
options: self.options,
|
||||||
modules: self.modules,
|
modules: self.modules,
|
||||||
|
module_config: self.module_config,
|
||||||
}
|
}
|
||||||
.parse_stmt()?;
|
.parse_stmt()?;
|
||||||
|
|
||||||
|
@ -108,6 +108,7 @@ impl<'a> Parser<'a> {
|
|||||||
content_scopes: self.content_scopes,
|
content_scopes: self.content_scopes,
|
||||||
options: self.options,
|
options: self.options,
|
||||||
modules: self.modules,
|
modules: self.modules,
|
||||||
|
module_config: self.module_config,
|
||||||
}
|
}
|
||||||
.parse();
|
.parse();
|
||||||
}
|
}
|
||||||
|
@ -174,6 +174,7 @@ impl<'a> Parser<'a> {
|
|||||||
content_scopes: self.content_scopes,
|
content_scopes: self.content_scopes,
|
||||||
options: self.options,
|
options: self.options,
|
||||||
modules: self.modules,
|
modules: self.modules,
|
||||||
|
module_config: self.module_config,
|
||||||
})
|
})
|
||||||
.parse_keyframes_selector()?;
|
.parse_keyframes_selector()?;
|
||||||
|
|
||||||
@ -210,6 +211,7 @@ impl<'a> Parser<'a> {
|
|||||||
content_scopes: self.content_scopes,
|
content_scopes: self.content_scopes,
|
||||||
options: self.options,
|
options: self.options,
|
||||||
modules: self.modules,
|
modules: self.modules,
|
||||||
|
module_config: self.module_config,
|
||||||
}
|
}
|
||||||
.parse_stmt()?;
|
.parse_stmt()?;
|
||||||
|
|
||||||
|
@ -25,16 +25,6 @@ impl<'a> Parser<'a> {
|
|||||||
Ok(false)
|
Ok(false)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn expect_char(&mut self, c: char) -> SassResult<()> {
|
|
||||||
if let Some(Token { kind, .. }) = self.toks.peek() {
|
|
||||||
if *kind == c {
|
|
||||||
self.toks.next();
|
|
||||||
return Ok(());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Err((format!("expected \"{}\".", c), self.span_before).into())
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn scan_char(&mut self, c: char) -> bool {
|
pub fn scan_char(&mut self, c: char) -> bool {
|
||||||
if let Some(Token { kind, .. }) = self.toks.peek() {
|
if let Some(Token { kind, .. }) = self.toks.peek() {
|
||||||
if *kind == c {
|
if *kind == c {
|
||||||
|
@ -183,6 +183,7 @@ impl<'a> Parser<'a> {
|
|||||||
content_scopes: self.content_scopes,
|
content_scopes: self.content_scopes,
|
||||||
options: self.options,
|
options: self.options,
|
||||||
modules: self.modules,
|
modules: self.modules,
|
||||||
|
module_config: self.module_config,
|
||||||
}
|
}
|
||||||
.parse_stmt()?;
|
.parse_stmt()?;
|
||||||
|
|
||||||
@ -245,6 +246,7 @@ impl<'a> Parser<'a> {
|
|||||||
content_scopes: self.scopes,
|
content_scopes: self.scopes,
|
||||||
options: self.options,
|
options: self.options,
|
||||||
modules: self.modules,
|
modules: self.modules,
|
||||||
|
module_config: self.module_config,
|
||||||
}
|
}
|
||||||
.parse_stmt()?
|
.parse_stmt()?
|
||||||
} else {
|
} else {
|
||||||
|
167
src/parse/mod.rs
167
src/parse/mod.rs
@ -12,7 +12,8 @@ use crate::{
|
|||||||
},
|
},
|
||||||
builtin::modules::{
|
builtin::modules::{
|
||||||
declare_module_color, declare_module_list, declare_module_map, declare_module_math,
|
declare_module_color, declare_module_list, declare_module_map, declare_module_math,
|
||||||
declare_module_meta, declare_module_selector, declare_module_string, Module, Modules,
|
declare_module_meta, declare_module_selector, declare_module_string, Module, ModuleConfig,
|
||||||
|
Modules,
|
||||||
},
|
},
|
||||||
error::SassResult,
|
error::SassResult,
|
||||||
lexer::Lexer,
|
lexer::Lexer,
|
||||||
@ -94,6 +95,7 @@ pub(crate) struct Parser<'a> {
|
|||||||
pub options: &'a Options<'a>,
|
pub options: &'a Options<'a>,
|
||||||
|
|
||||||
pub modules: &'a mut Modules,
|
pub modules: &'a mut Modules,
|
||||||
|
pub module_config: &'a mut ModuleConfig,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a> Parser<'a> {
|
impl<'a> Parser<'a> {
|
||||||
@ -113,6 +115,99 @@ impl<'a> Parser<'a> {
|
|||||||
Ok(stmts)
|
Ok(stmts)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn expect_char(&mut self, c: char) -> SassResult<()> {
|
||||||
|
match self.toks.peek() {
|
||||||
|
Some(Token { kind, pos }) if *kind == c => {
|
||||||
|
self.span_before = *pos;
|
||||||
|
self.toks.next();
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
Some(Token { pos, .. }) => Err((format!("expected \"{}\".", c), *pos).into()),
|
||||||
|
None => Err((format!("expected \"{}\".", c), self.span_before).into()),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn consume_char_if_exists(&mut self, c: char) {
|
||||||
|
if let Some(Token { kind, .. }) = self.toks.peek() {
|
||||||
|
if *kind == c {
|
||||||
|
self.toks.next();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn parse_module_alias(&mut self) -> SassResult<Option<String>> {
|
||||||
|
if let Some(Token { kind: 'a', .. }) | Some(Token { kind: 'A', .. }) = self.toks.peek() {
|
||||||
|
let mut ident = peek_ident_no_interpolation(self.toks, false, self.span_before)?;
|
||||||
|
ident.node.make_ascii_lowercase();
|
||||||
|
if ident.node != "as" {
|
||||||
|
return Err(("expected \";\".", ident.span).into());
|
||||||
|
}
|
||||||
|
|
||||||
|
self.whitespace_or_comment();
|
||||||
|
|
||||||
|
if let Some(Token { kind: '*', .. }) = self.toks.peek() {
|
||||||
|
self.toks.next();
|
||||||
|
return Ok(Some('*'.to_string()));
|
||||||
|
} else {
|
||||||
|
let name = self.parse_identifier_no_interpolation(false)?;
|
||||||
|
|
||||||
|
return Ok(Some(name.node));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(None)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn parse_module_config(&mut self) -> SassResult<ModuleConfig> {
|
||||||
|
let mut config = ModuleConfig::default();
|
||||||
|
|
||||||
|
if let Some(Token { kind: 'w', .. }) | Some(Token { kind: 'W', .. }) = self.toks.peek() {
|
||||||
|
let mut ident = peek_ident_no_interpolation(self.toks, false, self.span_before)?;
|
||||||
|
ident.node.make_ascii_lowercase();
|
||||||
|
if ident.node != "with" {
|
||||||
|
return Err(("expected \";\".", ident.span).into());
|
||||||
|
}
|
||||||
|
|
||||||
|
self.whitespace_or_comment();
|
||||||
|
|
||||||
|
self.span_before = ident.span;
|
||||||
|
|
||||||
|
self.expect_char('(')?;
|
||||||
|
|
||||||
|
loop {
|
||||||
|
self.whitespace_or_comment();
|
||||||
|
self.expect_char('$')?;
|
||||||
|
|
||||||
|
let name = self.parse_identifier_no_interpolation(false)?;
|
||||||
|
|
||||||
|
self.whitespace_or_comment();
|
||||||
|
self.expect_char(':')?;
|
||||||
|
self.whitespace_or_comment();
|
||||||
|
|
||||||
|
let value = self.parse_value(false, &|toks| match toks.peek() {
|
||||||
|
Some(Token { kind: ',', .. }) | Some(Token { kind: ')', .. }) => true,
|
||||||
|
_ => false,
|
||||||
|
})?;
|
||||||
|
|
||||||
|
config.insert(name.map_node(|n| n.into()), value)?;
|
||||||
|
|
||||||
|
match self.toks.next() {
|
||||||
|
Some(Token { kind: ',', .. }) => {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
Some(Token { kind: ')', .. }) => {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
Some(..) | None => {
|
||||||
|
return Err(("expected \")\".", self.span_before).into());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(config)
|
||||||
|
}
|
||||||
|
|
||||||
/// Returns any multiline comments that may have been found
|
/// Returns any multiline comments that may have been found
|
||||||
/// while loading modules
|
/// while loading modules
|
||||||
#[allow(clippy::eval_order_dependence)]
|
#[allow(clippy::eval_order_dependence)]
|
||||||
@ -156,44 +251,14 @@ impl<'a> Parser<'a> {
|
|||||||
|
|
||||||
self.whitespace_or_comment();
|
self.whitespace_or_comment();
|
||||||
|
|
||||||
let mut module_alias: Option<String> = None;
|
let module_alias = self.parse_module_alias()?;
|
||||||
|
|
||||||
match self.toks.peek() {
|
|
||||||
Some(Token { kind: ';', .. }) => {
|
|
||||||
self.toks.next();
|
|
||||||
}
|
|
||||||
Some(Token { kind: 'a', .. }) | Some(Token { kind: 'A', .. }) => {
|
|
||||||
let mut ident =
|
|
||||||
peek_ident_no_interpolation(self.toks, false, self.span_before)?;
|
|
||||||
ident.node.make_ascii_lowercase();
|
|
||||||
if ident.node != "as" {
|
|
||||||
return Err(("expected \";\".", ident.span).into());
|
|
||||||
}
|
|
||||||
|
|
||||||
self.whitespace_or_comment();
|
self.whitespace_or_comment();
|
||||||
|
|
||||||
let name_span;
|
let mut config = self.parse_module_config()?;
|
||||||
|
|
||||||
if let Some(Token { kind: '*', pos }) = self.toks.peek() {
|
self.whitespace_or_comment();
|
||||||
name_span = *pos;
|
self.expect_char(';')?;
|
||||||
self.toks.next();
|
|
||||||
module_alias = Some('*'.to_string());
|
|
||||||
} else {
|
|
||||||
let name = self.parse_identifier_no_interpolation(false)?;
|
|
||||||
|
|
||||||
module_alias = Some(name.node);
|
|
||||||
name_span = name.span;
|
|
||||||
}
|
|
||||||
|
|
||||||
if !matches!(self.toks.next(), Some(Token { kind: ';', .. })) {
|
|
||||||
return Err(("expected \";\".", name_span).into());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Some(Token { kind: 'w', .. }) | Some(Token { kind: 'W', .. }) => {
|
|
||||||
todo!("with")
|
|
||||||
}
|
|
||||||
Some(..) | None => return Err(("expected \";\".", span).into()),
|
|
||||||
}
|
|
||||||
|
|
||||||
let module = match module_name.as_ref() {
|
let module = match module_name.as_ref() {
|
||||||
"sass:color" => declare_module_color(),
|
"sass:color" => declare_module_color(),
|
||||||
@ -232,19 +297,28 @@ impl<'a> Parser<'a> {
|
|||||||
content_scopes: self.content_scopes,
|
content_scopes: self.content_scopes,
|
||||||
options: self.options,
|
options: self.options,
|
||||||
modules: self.modules,
|
modules: self.modules,
|
||||||
|
module_config: &mut config,
|
||||||
}
|
}
|
||||||
.parse()?,
|
.parse()?,
|
||||||
);
|
);
|
||||||
|
|
||||||
|
if !config.is_empty() {
|
||||||
|
return Err(("This variable was not declared with !default in the @used module.", span).into());
|
||||||
|
}
|
||||||
|
|
||||||
Module::new_from_scope(global_scope)
|
Module::new_from_scope(global_scope)
|
||||||
} else {
|
} else {
|
||||||
return Err(
|
return Err(("Can't find stylesheet to import.", span).into());
|
||||||
("Error: Can't find stylesheet to import.", span).into()
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// if the config isn't empty here, that means
|
||||||
|
// variables were passed to a builtin module
|
||||||
|
if !config.is_empty() {
|
||||||
|
return Err(("Built-in modules can't be configured.", span).into());
|
||||||
|
}
|
||||||
|
|
||||||
let module_name = match module_alias.as_deref() {
|
let module_name = match module_alias.as_deref() {
|
||||||
Some("*") => {
|
Some("*") => {
|
||||||
self.global_scope.merge_module(module);
|
self.global_scope.merge_module(module);
|
||||||
@ -599,6 +673,7 @@ impl<'a> Parser<'a> {
|
|||||||
content_scopes: self.content_scopes,
|
content_scopes: self.content_scopes,
|
||||||
options: self.options,
|
options: self.options,
|
||||||
modules: self.modules,
|
modules: self.modules,
|
||||||
|
module_config: self.module_config,
|
||||||
},
|
},
|
||||||
allows_parent,
|
allows_parent,
|
||||||
true,
|
true,
|
||||||
@ -657,10 +732,11 @@ impl<'a> Parser<'a> {
|
|||||||
|
|
||||||
pub fn parse_interpolation(&mut self) -> SassResult<Spanned<Value>> {
|
pub fn parse_interpolation(&mut self) -> SassResult<Spanned<Value>> {
|
||||||
let val = self.parse_value(true, &|_| false)?;
|
let val = self.parse_value(true, &|_| false)?;
|
||||||
match self.toks.next() {
|
|
||||||
Some(Token { kind: '}', .. }) => {}
|
self.span_before = val.span;
|
||||||
Some(..) | None => return Err(("expected \"}\".", val.span).into()),
|
|
||||||
}
|
self.expect_char('}')?;
|
||||||
|
|
||||||
Ok(val.map_node(Value::unquote))
|
Ok(val.map_node(Value::unquote))
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -899,6 +975,7 @@ impl<'a> Parser<'a> {
|
|||||||
content_scopes: self.content_scopes,
|
content_scopes: self.content_scopes,
|
||||||
options: self.options,
|
options: self.options,
|
||||||
modules: self.modules,
|
modules: self.modules,
|
||||||
|
module_config: self.module_config,
|
||||||
}
|
}
|
||||||
.parse_stmt()?
|
.parse_stmt()?
|
||||||
.into_iter()
|
.into_iter()
|
||||||
@ -944,14 +1021,14 @@ impl<'a> Parser<'a> {
|
|||||||
content_scopes: self.content_scopes,
|
content_scopes: self.content_scopes,
|
||||||
options: self.options,
|
options: self.options,
|
||||||
modules: self.modules,
|
modules: self.modules,
|
||||||
|
module_config: self.module_config,
|
||||||
}
|
}
|
||||||
.parse_selector(false, true, String::new())?;
|
.parse_selector(false, true, String::new())?;
|
||||||
|
|
||||||
|
// todo: this might be superfluous
|
||||||
self.whitespace();
|
self.whitespace();
|
||||||
|
|
||||||
if let Some(Token { kind: ';', .. }) = self.toks.peek() {
|
self.consume_char_if_exists(';');
|
||||||
self.toks.next();
|
|
||||||
}
|
|
||||||
|
|
||||||
let extend_rule = ExtendRule::new(value.clone(), is_optional, self.span_before);
|
let extend_rule = ExtendRule::new(value.clone(), is_optional, self.span_before);
|
||||||
|
|
||||||
|
@ -199,6 +199,7 @@ impl<'a> Parser<'a> {
|
|||||||
content_scopes: self.content_scopes,
|
content_scopes: self.content_scopes,
|
||||||
options: self.options,
|
options: self.options,
|
||||||
modules: self.modules,
|
modules: self.modules,
|
||||||
|
module_config: self.module_config,
|
||||||
}
|
}
|
||||||
.parse_value(in_paren, predicate)
|
.parse_value(in_paren, predicate)
|
||||||
}
|
}
|
||||||
@ -224,6 +225,7 @@ impl<'a> Parser<'a> {
|
|||||||
content_scopes: self.content_scopes,
|
content_scopes: self.content_scopes,
|
||||||
options: self.options,
|
options: self.options,
|
||||||
modules: self.modules,
|
modules: self.modules,
|
||||||
|
module_config: self.module_config,
|
||||||
}
|
}
|
||||||
.parse_value(in_paren, &|_| false)
|
.parse_value(in_paren, &|_| false)
|
||||||
}
|
}
|
||||||
|
@ -39,13 +39,24 @@ impl<'a> Parser<'a> {
|
|||||||
} = self.parse_variable_value()?;
|
} = self.parse_variable_value()?;
|
||||||
|
|
||||||
if default {
|
if default {
|
||||||
|
let config_val = self.module_config.get(ident);
|
||||||
if self.at_root && !self.flags.in_control_flow() {
|
if self.at_root && !self.flags.in_control_flow() {
|
||||||
if !self.global_scope.var_exists(ident) {
|
if !self.global_scope.var_exists(ident) {
|
||||||
let value = self.parse_value_from_vec(val_toks, true)?.node;
|
let value = if let Some(config_val) = config_val {
|
||||||
|
config_val
|
||||||
|
} else {
|
||||||
|
self.parse_value_from_vec(val_toks, true)?.node
|
||||||
|
};
|
||||||
|
|
||||||
self.global_scope.insert_var(ident, value);
|
self.global_scope.insert_var(ident, value);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
let value = self.parse_value_from_vec(val_toks, true)?.node;
|
let value = if let Some(config_val) = config_val {
|
||||||
|
config_val
|
||||||
|
} else {
|
||||||
|
self.parse_value_from_vec(val_toks, true)?.node
|
||||||
|
};
|
||||||
|
|
||||||
if global && !self.global_scope.var_exists(ident) {
|
if global && !self.global_scope.var_exists(ident) {
|
||||||
self.global_scope.insert_var(ident, value.clone());
|
self.global_scope.insert_var(ident, value.clone());
|
||||||
}
|
}
|
||||||
|
@ -543,6 +543,7 @@ impl Value {
|
|||||||
content_scopes: parser.content_scopes,
|
content_scopes: parser.content_scopes,
|
||||||
options: parser.options,
|
options: parser.options,
|
||||||
modules: parser.modules,
|
modules: parser.modules,
|
||||||
|
module_config: parser.module_config,
|
||||||
}
|
}
|
||||||
.parse_selector(allows_parent, true, String::new())?
|
.parse_selector(allows_parent, true, String::new())?
|
||||||
.0)
|
.0)
|
||||||
|
62
tests/use.rs
62
tests/use.rs
@ -191,3 +191,65 @@ fn use_idempotent_builtin() {
|
|||||||
input
|
input
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn use_with_simple() {
|
||||||
|
let input = "@use \"use_with_simple\" with ($a: red);\na {\n color: use_with_simple.$a;\n}";
|
||||||
|
tempfile!("use_with_simple.scss", "$a: green !default;");
|
||||||
|
assert_eq!(
|
||||||
|
"a {\n color: red;\n}\n",
|
||||||
|
&grass::from_string(input.to_string(), &grass::Options::default()).expect(input)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn use_as_with() {
|
||||||
|
let input = "@use \"use_as_with\" as module with ($a: red);\na {\n color: module.$a;\n}";
|
||||||
|
tempfile!("use_as_with.scss", "$a: green !default;");
|
||||||
|
assert_eq!(
|
||||||
|
"a {\n color: red;\n}\n",
|
||||||
|
&grass::from_string(input.to_string(), &grass::Options::default()).expect(input)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn use_whitespace_and_comments() {
|
||||||
|
let input = "@use /**/ \"use_whitespace_and_comments\" /**/ as /**/ foo /**/ with /**/ ( /**/ $a /**/ : /**/ red /**/ ) /**/ ;";
|
||||||
|
tempfile!(
|
||||||
|
"use_whitespace_and_comments.scss",
|
||||||
|
"$a: green !default; a { color: $a }"
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
"a {\n color: red;\n}\n",
|
||||||
|
&grass::from_string(input.to_string(), &grass::Options::default()).expect(input)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn use_with_builtin_module() {
|
||||||
|
let input = "@use \"sass:math\" with ($e: 2.7);";
|
||||||
|
|
||||||
|
assert_err!("Error: Built-in modules can't be configured.", input);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn use_with_variable_never_used() {
|
||||||
|
let input = "@use \"use_with_variable_never_used\" with ($a: red);";
|
||||||
|
tempfile!("use_with_variable_never_used.scss", "");
|
||||||
|
|
||||||
|
assert_err!(
|
||||||
|
"Error: This variable was not declared with !default in the @used module.",
|
||||||
|
input
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn use_with_same_variable_multiple_times() {
|
||||||
|
let input = "@use \"use_with_same_variable_multiple_times\" as foo with ($a: b, $a: c);";
|
||||||
|
tempfile!("use_with_same_variable_multiple_times.scss", "");
|
||||||
|
|
||||||
|
assert_err!(
|
||||||
|
"Error: The same variable may only be configured once.",
|
||||||
|
input
|
||||||
|
);
|
||||||
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user