allow redeclaration of module variables

This commit is contained in:
Connor Skees 2020-08-07 02:01:04 -04:00
parent bb0b352af2
commit 438abe52be
8 changed files with 232 additions and 58 deletions

View File

@ -22,7 +22,13 @@ mod selector;
mod string; mod string;
#[derive(Debug, Default)] #[derive(Debug, Default)]
pub(crate) struct Module(pub Scope); pub(crate) struct Module {
pub scope: Scope,
/// Whether or not this module is builtin
/// e.g. `"sass:math"`
is_builtin: bool,
}
#[derive(Debug, Default)] #[derive(Debug, Default)]
pub(crate) struct Modules(BTreeMap<Identifier, Module>); pub(crate) struct Modules(BTreeMap<Identifier, Module>);
@ -83,9 +89,27 @@ impl Modules {
.into()), .into()),
} }
} }
pub fn get_mut(&mut self, name: Identifier, span: Span) -> SassResult<&mut Module> {
match self.0.get_mut(&name) {
Some(v) => Ok(v),
None => Err((
format!(
"There is no module with the namespace \"{}\".",
name.as_str()
),
span,
)
.into()),
}
}
} }
impl Module { impl Module {
pub fn new_builtin() -> Self {
Module { scope: Scope::default(), is_builtin: true }
}
pub fn get_var(&self, name: Spanned<Identifier>) -> SassResult<&Value> { pub fn get_var(&self, name: Spanned<Identifier>) -> SassResult<&Value> {
if name.node.as_str().starts_with('-') { if name.node.as_str().starts_with('-') {
return Err(( return Err((
@ -95,12 +119,36 @@ impl Module {
.into()); .into());
} }
match self.0.vars.get(&name.node) { match self.scope.vars.get(&name.node) {
Some(v) => Ok(v), Some(v) => Ok(v),
None => Err(("Undefined variable.", name.span).into()), None => Err(("Undefined variable.", name.span).into()),
} }
} }
pub fn update_var(&mut self, name: Spanned<Identifier>, value: Value) -> SassResult<()> {
if self.is_builtin {
return Err((
"Cannot modify built-in variable.",
name.span,
)
.into());
}
if name.node.as_str().starts_with('-') {
return Err((
"Private members can't be accessed from outside their modules.",
name.span,
)
.into());
}
if self.scope.insert_var(name.node, value).is_some() {
Ok(())
} else {
Err(("Undefined variable.", name.span).into())
}
}
pub fn get_mixin(&self, name: Spanned<Identifier>) -> SassResult<Mixin> { pub fn get_mixin(&self, name: Spanned<Identifier>) -> SassResult<Mixin> {
if name.node.as_str().starts_with('-') { if name.node.as_str().starts_with('-') {
return Err(( return Err((
@ -110,18 +158,18 @@ impl Module {
.into()); .into());
} }
match self.0.mixins.get(&name.node) { match self.scope.mixins.get(&name.node) {
Some(v) => Ok(v.clone()), Some(v) => Ok(v.clone()),
None => Err(("Undefined mixin.", name.span).into()), None => Err(("Undefined mixin.", name.span).into()),
} }
} }
pub fn insert_builtin_mixin(&mut self, name: &'static str, mixin: BuiltinMixin) { pub fn insert_builtin_mixin(&mut self, name: &'static str, mixin: BuiltinMixin) {
self.0.mixins.insert(name.into(), Mixin::Builtin(mixin)); self.scope.mixins.insert(name.into(), Mixin::Builtin(mixin));
} }
pub fn insert_builtin_var(&mut self, name: &'static str, value: Value) { pub fn insert_builtin_var(&mut self, name: &'static str, value: Value) {
self.0.vars.insert(name.into(), value); self.scope.vars.insert(name.into(), value);
} }
pub fn get_fn(&self, name: Spanned<Identifier>) -> SassResult<Option<SassFunction>> { pub fn get_fn(&self, name: Spanned<Identifier>) -> SassResult<Option<SassFunction>> {
@ -133,15 +181,15 @@ impl Module {
.into()); .into());
} }
Ok(self.0.functions.get(&name.node).cloned()) Ok(self.scope.functions.get(&name.node).cloned())
} }
pub fn var_exists(&self, name: Identifier) -> bool { pub fn var_exists(&self, name: Identifier) -> bool {
!name.as_str().starts_with('-') && self.0.var_exists(name) !name.as_str().starts_with('-') && self.scope.var_exists(name)
} }
pub fn mixin_exists(&self, name: Identifier) -> bool { pub fn mixin_exists(&self, name: Identifier) -> bool {
!name.as_str().starts_with('-') && self.0.mixin_exists(name) !name.as_str().starts_with('-') && self.scope.mixin_exists(name)
} }
pub fn insert_builtin( pub fn insert_builtin(
@ -150,14 +198,14 @@ impl Module {
function: fn(CallArgs, &mut Parser<'_>) -> SassResult<Value>, function: fn(CallArgs, &mut Parser<'_>) -> SassResult<Value>,
) { ) {
let ident = name.into(); let ident = name.into();
self.0 self.scope
.functions .functions
.insert(ident, SassFunction::Builtin(Builtin::new(function), ident)); .insert(ident, SassFunction::Builtin(Builtin::new(function), ident));
} }
pub fn functions(&self) -> SassMap { pub fn functions(&self) -> SassMap {
SassMap::new_with( SassMap::new_with(
self.0 self.scope
.functions .functions
.iter() .iter()
.filter(|(key, _)| !key.as_str().starts_with('-')) .filter(|(key, _)| !key.as_str().starts_with('-'))
@ -173,7 +221,7 @@ impl Module {
pub fn variables(&self) -> SassMap { pub fn variables(&self) -> SassMap {
SassMap::new_with( SassMap::new_with(
self.0 self.scope
.vars .vars
.iter() .iter()
.filter(|(key, _)| !key.as_str().starts_with('-')) .filter(|(key, _)| !key.as_str().starts_with('-'))
@ -187,49 +235,49 @@ impl Module {
) )
} }
pub const fn new_from_scope(scope: Scope) -> Self { pub const fn new_from_scope(scope: Scope, is_builtin: bool) -> Self {
Module(scope) Module { scope, is_builtin }
} }
} }
pub(crate) fn declare_module_color() -> Module { pub(crate) fn declare_module_color() -> Module {
let mut module = Module::default(); let mut module = Module::new_builtin();
color::declare(&mut module); color::declare(&mut module);
module module
} }
pub(crate) fn declare_module_list() -> Module { pub(crate) fn declare_module_list() -> Module {
let mut module = Module::default(); let mut module = Module::new_builtin();
list::declare(&mut module); list::declare(&mut module);
module module
} }
pub(crate) fn declare_module_map() -> Module { pub(crate) fn declare_module_map() -> Module {
let mut module = Module::default(); let mut module = Module::new_builtin();
map::declare(&mut module); map::declare(&mut module);
module module
} }
pub(crate) fn declare_module_math() -> Module { pub(crate) fn declare_module_math() -> Module {
let mut module = Module::default(); let mut module = Module::new_builtin();
math::declare(&mut module); math::declare(&mut module);
module module
} }
pub(crate) fn declare_module_meta() -> Module { pub(crate) fn declare_module_meta() -> Module {
let mut module = Module::default(); let mut module = Module::new_builtin();
meta::declare(&mut module); meta::declare(&mut module);
module module
} }
pub(crate) fn declare_module_selector() -> Module { pub(crate) fn declare_module_selector() -> Module {
let mut module = Module::default(); let mut module = Module::new_builtin();
selector::declare(&mut module); selector::declare(&mut module);
module module
} }
pub(crate) fn declare_module_string() -> Module { pub(crate) fn declare_module_string() -> Module {
let mut module = Module::default(); let mut module = Module::new_builtin();
string::declare(&mut module); string::declare(&mut module);
module module
} }

View File

@ -2,7 +2,7 @@ use std::ops::{BitAnd, BitOr};
use codemap::Spanned; use codemap::Spanned;
use crate::{interner::InternedString, value::Value}; use crate::{common::Identifier, interner::InternedString, value::Value};
#[derive(Debug, Clone)] #[derive(Debug, Clone)]
pub(crate) struct NeverEmptyVec<T> { pub(crate) struct NeverEmptyVec<T> {
@ -42,6 +42,7 @@ impl<T> NeverEmptyVec<T> {
pub(super) enum SelectorOrStyle { pub(super) enum SelectorOrStyle {
Selector(String), Selector(String),
Style(InternedString, Option<Box<Spanned<Value>>>), Style(InternedString, Option<Box<Spanned<Value>>>),
ModuleVariableRedeclaration(Identifier),
} }
#[derive(Debug, Copy, Clone)] #[derive(Debug, Copy, Clone)]

View File

@ -11,6 +11,7 @@ use crate::{
AtRuleKind, SupportsRule, UnknownAtRule, AtRuleKind, SupportsRule, UnknownAtRule,
}, },
builtin::modules::{ModuleConfig, Modules}, builtin::modules::{ModuleConfig, Modules},
common::Identifier,
error::SassResult, error::SassResult,
scope::{Scope, Scopes}, scope::{Scope, Scopes},
selector::{ selector::{
@ -27,6 +28,7 @@ use crate::{
use common::{Comment, ContextFlags, NeverEmptyVec, SelectorOrStyle}; use common::{Comment, ContextFlags, NeverEmptyVec, SelectorOrStyle};
pub(crate) use value::{HigherIntermediateValue, ValueVisitor}; pub(crate) use value::{HigherIntermediateValue, ValueVisitor};
use variable::VariableValue;
mod args; mod args;
pub mod common; pub mod common;
@ -131,6 +133,41 @@ impl<'a> Parser<'a> {
} }
} }
fn parse_module_variable_redeclaration(&mut self, module: Identifier) -> SassResult<()> {
let variable = self
.parse_identifier_no_interpolation(false)?
.map_node(|n| n.into());
self.whitespace_or_comment();
self.expect_char(':')?;
let VariableValue {
val_toks,
global,
default,
} = self.parse_variable_value()?;
if global {
return Err((
"!global isn't allowed for variables in other modules.",
variable.span,
)
.into());
}
if default {
return Ok(());
}
let value = self.parse_value_from_vec(val_toks, true)?;
self.modules
.get_mut(module, variable.span)?
.update_var(variable, value.node)?;
Ok(())
}
fn parse_stmt(&mut self) -> SassResult<Vec<Stmt>> { fn parse_stmt(&mut self) -> SassResult<Vec<Stmt>> {
let mut stmts = Vec::new(); let mut stmts = Vec::new();
while let Some(Token { kind, pos }) = self.toks.peek() { while let Some(Token { kind, pos }) = self.toks.peek() {
@ -294,6 +331,9 @@ impl<'a> Parser<'a> {
} }
if self.flags.in_keyframes() { if self.flags.in_keyframes() {
match self.is_selector_or_style()? { match self.is_selector_or_style()? {
SelectorOrStyle::ModuleVariableRedeclaration(module) => {
self.parse_module_variable_redeclaration(module)?
}
SelectorOrStyle::Style(property, value) => { SelectorOrStyle::Style(property, value) => {
if let Some(value) = value { if let Some(value) = value {
stmts.push(Stmt::Style(Style { property, value })); stmts.push(Stmt::Style(Style { property, value }));
@ -321,6 +361,9 @@ impl<'a> Parser<'a> {
} }
match self.is_selector_or_style()? { match self.is_selector_or_style()? {
SelectorOrStyle::ModuleVariableRedeclaration(module) => {
self.parse_module_variable_redeclaration(module)?
}
SelectorOrStyle::Style(property, value) => { SelectorOrStyle::Style(property, value) => {
if let Some(value) = value { if let Some(value) = value {
stmts.push(Stmt::Style(Style { property, value })); stmts.push(Stmt::Style(Style { property, value }));

View File

@ -143,7 +143,7 @@ impl<'a> Parser<'a> {
.into()); .into());
} }
(Module::new_from_scope(global_scope), stmts) (Module::new_from_scope(global_scope, false), stmts)
} else { } else {
return Err(("Can't find stylesheet to import.", self.span_before).into()); return Err(("Can't find stylesheet to import.", self.span_before).into());
} }

View File

@ -111,43 +111,62 @@ impl<'a> Parser<'a> {
let mut property = self.parse_identifier()?.node; let mut property = self.parse_identifier()?.node;
let whitespace_after_property = self.whitespace(); let whitespace_after_property = self.whitespace();
if let Some(Token { kind: ':', .. }) = self.toks.peek() { match self.toks.peek() {
self.toks.next(); Some(Token { kind: ':', .. }) => {
if let Some(Token { kind, .. }) = self.toks.peek() { self.toks.next();
return Ok(match kind { if let Some(Token { kind, .. }) = self.toks.peek() {
':' => { return Ok(match kind {
if whitespace_after_property { ':' => {
property.push(' '); if whitespace_after_property {
} property.push(' ');
property.push(':');
SelectorOrStyle::Selector(property)
}
c if is_name(*c) => {
if let Some(toks) = self.parse_style_value_when_no_space_after_semicolon() {
let len = toks.len();
if let Ok(val) = self.parse_value_from_vec(toks, false) {
self.toks.take(len).for_each(drop);
return Ok(SelectorOrStyle::Style(
InternedString::get_or_intern(property),
Some(Box::new(val)),
));
} }
property.push(':');
SelectorOrStyle::Selector(property)
} }
c if is_name(*c) => {
if let Some(toks) =
self.parse_style_value_when_no_space_after_semicolon()
{
let len = toks.len();
if let Ok(val) = self.parse_value_from_vec(toks, false) {
self.toks.take(len).for_each(drop);
return Ok(SelectorOrStyle::Style(
InternedString::get_or_intern(property),
Some(Box::new(val)),
));
}
}
if whitespace_after_property { if whitespace_after_property {
property.push(' '); property.push(' ');
}
property.push(':');
return Ok(SelectorOrStyle::Selector(property));
} }
property.push(':'); _ => SelectorOrStyle::Style(InternedString::get_or_intern(property), None),
return Ok(SelectorOrStyle::Selector(property)); });
}
}
Some(Token { kind: '.', .. }) => {
if matches!(self.toks.peek_next(), Some(Token { kind: '$', .. })) {
self.toks.next();
self.toks.next();
return Ok(SelectorOrStyle::ModuleVariableRedeclaration(
property.into(),
));
} else {
if whitespace_after_property {
property.push(' ');
} }
_ => SelectorOrStyle::Style(InternedString::get_or_intern(property), None), return Ok(SelectorOrStyle::Selector(property));
}); }
} }
} else { _ => {
if whitespace_after_property { if whitespace_after_property {
property.push(' '); property.push(' ');
}
return Ok(SelectorOrStyle::Selector(property));
} }
return Ok(SelectorOrStyle::Selector(property));
} }
Err(("expected \"{\".", self.span_before).into()) Err(("expected \"{\".", self.span_before).into())
} }

View File

@ -8,10 +8,10 @@ use crate::{
use super::Parser; use super::Parser;
#[derive(Debug)] #[derive(Debug)]
struct VariableValue { pub(crate) struct VariableValue {
val_toks: Vec<Token>, pub val_toks: Vec<Token>,
global: bool, pub global: bool,
default: bool, pub default: bool,
} }
impl VariableValue { impl VariableValue {
@ -88,7 +88,7 @@ impl<'a> Parser<'a> {
Ok(()) Ok(())
} }
fn parse_variable_value(&mut self) -> SassResult<VariableValue> { pub(super) fn parse_variable_value(&mut self) -> SassResult<VariableValue> {
let mut default = false; let mut default = false;
let mut global = false; let mut global = false;

View File

@ -81,7 +81,7 @@ impl Scope {
} }
pub fn merge_module(&mut self, other: Module) { pub fn merge_module(&mut self, other: Module) {
self.merge(other.0); self.merge(other.scope);
} }
} }

View File

@ -253,3 +253,66 @@ fn use_with_same_variable_multiple_times() {
input input
); );
} }
#[test]
fn use_variable_redeclaration_var_dne() {
let input = "@use \"use_variable_redeclaration_var_dne\" as mod;\nmod.$a: red;";
tempfile!("use_variable_redeclaration_var_dne.scss", "");
assert_err!("Error: Undefined variable.", input);
}
#[test]
fn use_variable_redeclaration_global() {
let input = "@use \"use_variable_redeclaration_global\" as mod;\nmod.$a: red !global;";
tempfile!("use_variable_redeclaration_global.scss", "$a: green;");
assert_err!(
"Error: !global isn't allowed for variables in other modules.",
input
);
}
#[test]
fn use_variable_redeclaration_simple() {
let input =
"@use \"use_variable_redeclaration_simple\" as mod;\nmod.$a: red; a { color: mod.$a; }";
tempfile!("use_variable_redeclaration_simple.scss", "$a: green;");
assert_eq!(
"a {\n color: red;\n}\n",
&grass::from_string(input.to_string(), &grass::Options::default()).expect(input)
);
}
#[test]
fn use_variable_redeclaration_default() {
let input = "@use \"use_variable_redeclaration_default\" as mod;\nmod.$a: 1 % red !default; a { color: mod.$a; }";
tempfile!("use_variable_redeclaration_default.scss", "$a: green;");
assert_eq!(
"a {\n color: green;\n}\n",
&grass::from_string(input.to_string(), &grass::Options::default()).expect(input)
);
}
#[test]
fn use_variable_redeclaration_private() {
let input = "@use \"use_variable_redeclaration_private\" as mod;\nmod.$-a: red;";
tempfile!("use_variable_redeclaration_private.scss", "$a: green;");
assert_err!(
"Error: Private members can't be accessed from outside their modules.",
input
);
}
#[test]
fn use_variable_redeclaration_builtin() {
let input = "@use \"sass:math\";\nmath.$e: red;";
assert_err!(
"Error: Cannot modify built-in variable.",
input
);
}