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

View File

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

View File

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

View File

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

View File

@ -85,3 +85,20 @@ macro_rules! tempfile {
write!(f, "{}", $content).unwrap(); 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] #[test]
fn use_user_defined_as() { fn use_user_defined_as() {
let input = "@use \"use_user_defined_as\" as module;\na {\n color: module.$a;\n}"; let input = "@use \"use_user_defined_as\" as module;\na {\n color: module.$a;\n}";