initial implementation of private module members

This commit is contained in:
Connor Skees 2020-08-02 04:20:08 -04:00
parent a9e4d5cba5
commit 698339b8c7
6 changed files with 110 additions and 17 deletions

View File

@ -1,5 +1,7 @@
use super::{Builtin, GlobalFunctionMap, GLOBAL_FUNCTIONS};
use codemap::Spanned;
use crate::{
args::CallArgs,
common::{Identifier, QuoteKind},
@ -220,7 +222,10 @@ pub(crate) fn get_function(mut args: CallArgs, parser: &mut Parser<'_>) -> SassR
parser
.modules
.get(module_name.into(), args.span())?
.get_fn(name)
.get_fn(Spanned {
node: name,
span: args.span(),
})?
} else {
parser.scopes.get_fn(name, parser.global_scope)
} {

View File

@ -51,6 +51,14 @@ impl Modules {
impl Module {
pub fn get_var(&self, name: Spanned<Identifier>) -> SassResult<&Value> {
if name.node.as_str().starts_with('-') {
return Err((
"Private members can't be accessed from outside their modules.",
name.span,
)
.into());
}
match self.0.vars.get(&name.node) {
Some(v) => Ok(v),
None => Err(("Undefined variable.", name.span).into()),
@ -61,16 +69,24 @@ impl Module {
self.0.vars.insert(name.into(), value);
}
pub fn get_fn(&self, name: Identifier) -> Option<SassFunction> {
self.0.functions.get(&name).cloned()
pub fn get_fn(&self, name: Spanned<Identifier>) -> SassResult<Option<SassFunction>> {
if name.node.as_str().starts_with('-') {
return Err((
"Private members can't be accessed from outside their modules.",
name.span,
)
.into());
}
Ok(self.0.functions.get(&name.node).cloned())
}
pub fn var_exists(&self, name: Identifier) -> bool {
self.0.var_exists(name)
!name.as_str().starts_with('-') && self.0.var_exists(name)
}
pub fn mixin_exists(&self, name: Identifier) -> bool {
self.0.mixin_exists(name)
!name.as_str().starts_with('-') && self.0.mixin_exists(name)
}
pub fn insert_builtin(
@ -89,6 +105,7 @@ impl Module {
self.0
.functions
.iter()
.filter(|(key, _)| !key.as_str().starts_with('-'))
.map(|(key, value)| {
(
Value::String(key.to_string(), QuoteKind::Quoted),
@ -104,6 +121,7 @@ impl Module {
self.0
.vars
.iter()
.filter(|(key, _)| !key.as_str().starts_with('-'))
.map(|(key, value)| {
(
Value::String(key.to_string(), QuoteKind::Quoted),

View File

@ -271,7 +271,7 @@ impl<'a> Parser<'a> {
let function = self
.modules
.get(module.into(), module_span)?
.get_fn(fn_name.node)
.get_fn(fn_name)?
.ok_or(("Undefined function.", fn_name.span))?;
if !matches!(self.toks.next(), Some(Token { kind: '(', .. })) {

View File

@ -26,17 +26,8 @@ fn import_no_semicolon() {
fn import_no_quotes() {
let input = "@import import_no_quotes";
tempfile!("import_no_quotes", "$a: red;");
match grass::from_string(input.to_string(), &grass::Options::default()) {
Ok(..) => panic!("did not fail"),
Err(e) => assert_eq!(
"Error: Expected string.",
e.to_string()
.chars()
.take_while(|c| *c != '\n')
.collect::<String>()
.as_str()
),
}
assert_err!("Error: Expected string.", input);
}
#[test]

View File

@ -85,3 +85,20 @@ macro_rules! tempfile {
write!(f, "{}", $content).unwrap();
};
}
#[macro_export]
macro_rules! assert_err {
($err:literal, $input:expr) => {
match grass::from_string($input.to_string(), &grass::Options::default()) {
Ok(..) => panic!("did not fail"),
Err(e) => assert_eq!(
$err,
e.to_string()
.chars()
.take_while(|c| *c != '\n')
.collect::<String>()
.as_str()
),
}
};
}

View File

@ -74,6 +74,68 @@ fn use_user_defined_same_directory() {
);
}
#[test]
fn private_variable_begins_with_underscore() {
let input = "@use \"private_variable_begins_with_underscore\" as module;\na {\n color: module.$_foo;\n}";
tempfile!(
"private_variable_begins_with_underscore.scss",
"$_foo: red; a { color: $_foo; }"
);
assert_err!(
"Error: Private members can't be accessed from outside their modules.",
input
);
}
#[test]
fn private_variable_begins_with_hyphen() {
let input =
"@use \"private_variable_begins_with_hyphen\" as module;\na {\n color: module.$-foo;\n}";
tempfile!(
"private_variable_begins_with_hyphen.scss",
"$-foo: red; a { color: $-foo; }"
);
assert_err!(
"Error: Private members can't be accessed from outside their modules.",
input
);
}
#[test]
fn private_function() {
let input = "@use \"private_function\" as module;\na {\n color: module._foo(green);\n}";
tempfile!(
"private_function.scss",
"@function _foo($a) { @return $a; } a { color: _foo(red); }"
);
assert_err!(
"Error: Private members can't be accessed from outside their modules.",
input
);
}
#[test]
fn global_variable_exists_private() {
let input = r#"
@use "global_variable_exists_private" as module;
a {
color: global-variable-exists($name: foo, $module: module);
color: global-variable-exists($name: _foo, $module: module);
}"#;
tempfile!(
"global_variable_exists_private.scss",
"$foo: red;\n$_foo: red;\n"
);
assert_eq!(
"a {\n color: true;\n color: false;\n}\n",
&grass::from_string(input.to_string(), &grass::Options::default()).expect(input)
);
}
#[test]
fn use_user_defined_as() {
let input = "@use \"use_user_defined_as\" as module;\na {\n color: module.$a;\n}";