emit @import
when importing a url or .css file
This commit is contained in:
parent
4edc324fcd
commit
e1e643d286
@ -32,13 +32,16 @@ enum Toplevel {
|
|||||||
Media { query: String, body: Vec<Stmt> },
|
Media { query: String, body: Vec<Stmt> },
|
||||||
Supports { params: String, body: Vec<Stmt> },
|
Supports { params: String, body: Vec<Stmt> },
|
||||||
Newline,
|
Newline,
|
||||||
|
// todo: do we actually need a toplevel style variant?
|
||||||
Style(Style),
|
Style(Style),
|
||||||
|
Import(String),
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
enum BlockEntry {
|
enum BlockEntry {
|
||||||
Style(Box<Style>),
|
Style(Style),
|
||||||
MultilineComment(String),
|
MultilineComment(String),
|
||||||
|
Import(String),
|
||||||
}
|
}
|
||||||
|
|
||||||
impl BlockEntry {
|
impl BlockEntry {
|
||||||
@ -46,6 +49,7 @@ impl BlockEntry {
|
|||||||
match self {
|
match self {
|
||||||
BlockEntry::Style(s) => s.to_string(),
|
BlockEntry::Style(s) => s.to_string(),
|
||||||
BlockEntry::MultilineComment(s) => Ok(format!("/*{}*/", s)),
|
BlockEntry::MultilineComment(s) => Ok(format!("/*{}*/", s)),
|
||||||
|
BlockEntry::Import(s) => Ok(format!("@import {};", s)),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -64,7 +68,7 @@ impl Toplevel {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if let Toplevel::RuleSet(_, entries) | Toplevel::KeyframesRuleSet(_, entries) = self {
|
if let Toplevel::RuleSet(_, entries) | Toplevel::KeyframesRuleSet(_, entries) = self {
|
||||||
entries.push(BlockEntry::Style(Box::new(s)));
|
entries.push(BlockEntry::Style(s));
|
||||||
} else {
|
} else {
|
||||||
panic!()
|
panic!()
|
||||||
}
|
}
|
||||||
@ -77,6 +81,14 @@ impl Toplevel {
|
|||||||
panic!()
|
panic!()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn push_import(&mut self, s: String) {
|
||||||
|
if let Toplevel::RuleSet(_, entries) | Toplevel::KeyframesRuleSet(_, entries) = self {
|
||||||
|
entries.push(BlockEntry::Import(s));
|
||||||
|
} else {
|
||||||
|
panic!()
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
@ -145,11 +157,13 @@ impl Css {
|
|||||||
k @ Stmt::KeyframesRuleSet(..) => {
|
k @ Stmt::KeyframesRuleSet(..) => {
|
||||||
unreachable!("@keyframes ruleset {:?}", k)
|
unreachable!("@keyframes ruleset {:?}", k)
|
||||||
}
|
}
|
||||||
|
Stmt::Import(s) => vals.get_mut(0).unwrap().push_import(s),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
vals
|
vals
|
||||||
}
|
}
|
||||||
Stmt::Comment(s) => vec![Toplevel::MultilineComment(s)],
|
Stmt::Comment(s) => vec![Toplevel::MultilineComment(s)],
|
||||||
|
Stmt::Import(s) => vec![Toplevel::Import(s)],
|
||||||
Stmt::Style(s) => vec![Toplevel::Style(s)],
|
Stmt::Style(s) => vec![Toplevel::Style(s)],
|
||||||
Stmt::Media(m) => {
|
Stmt::Media(m) => {
|
||||||
let MediaRule { query, body, .. } = *m;
|
let MediaRule { query, body, .. } = *m;
|
||||||
@ -271,6 +285,10 @@ impl Css {
|
|||||||
has_written = true;
|
has_written = true;
|
||||||
writeln!(buf, "{}/*{}*/", padding, s)?;
|
writeln!(buf, "{}/*{}*/", padding, s)?;
|
||||||
}
|
}
|
||||||
|
Toplevel::Import(s) => {
|
||||||
|
has_written = true;
|
||||||
|
writeln!(buf, "{}@import {};", padding, s)?;
|
||||||
|
}
|
||||||
Toplevel::UnknownAtRule(u) => {
|
Toplevel::UnknownAtRule(u) => {
|
||||||
let ToplevelUnknownAtRule { params, name, body } = *u;
|
let ToplevelUnknownAtRule { params, name, body } = *u;
|
||||||
if should_emit_newline {
|
if should_emit_newline {
|
||||||
|
@ -1,38 +1,45 @@
|
|||||||
use std::{ffi::OsStr, fs, path::Path};
|
use std::{ffi::OsStr, fs, path::Path};
|
||||||
|
|
||||||
|
use codemap::Spanned;
|
||||||
use peekmore::PeekMore;
|
use peekmore::PeekMore;
|
||||||
|
|
||||||
use crate::{error::SassResult, Token};
|
use crate::{common::QuoteKind, error::SassResult, lexer::Lexer, value::Value, Token};
|
||||||
|
|
||||||
use crate::lexer::Lexer;
|
|
||||||
|
|
||||||
use super::{Parser, Stmt};
|
use super::{Parser, Stmt};
|
||||||
|
|
||||||
impl<'a> Parser<'a> {
|
impl<'a> Parser<'a> {
|
||||||
pub(super) fn import(&mut self) -> SassResult<Vec<Stmt>> {
|
pub(super) fn import(&mut self) -> SassResult<Vec<Stmt>> {
|
||||||
self.whitespace();
|
self.whitespace();
|
||||||
let mut file_name = String::new();
|
|
||||||
let next = match self.toks.next() {
|
match self.toks.peek() {
|
||||||
Some(v) => v,
|
Some(Token { kind: '\'', .. })
|
||||||
|
| Some(Token { kind: '"', .. })
|
||||||
|
| Some(Token { kind: 'u', .. }) => {}
|
||||||
|
Some(Token { pos, .. }) => return Err(("Expected string.", *pos).into()),
|
||||||
None => return Err(("expected more input.", self.span_before).into()),
|
None => return Err(("expected more input.", self.span_before).into()),
|
||||||
};
|
};
|
||||||
match next.kind {
|
|
||||||
q @ '"' | q @ '\'' => {
|
let Spanned {
|
||||||
file_name.push_str(
|
node: file_name_as_value,
|
||||||
&self
|
span,
|
||||||
.parse_quoted_string(q)?
|
} = self.parse_value()?;
|
||||||
.node
|
let file_name = match file_name_as_value {
|
||||||
.unquote()
|
Value::String(s, QuoteKind::Quoted) => {
|
||||||
.to_css_string(self.span_before)?,
|
if s.ends_with(".css") || s.starts_with("http://") || s.starts_with("https://") {
|
||||||
);
|
return Ok(vec![Stmt::Import(format!("\"{}\"", s))]);
|
||||||
|
} else {
|
||||||
|
s
|
||||||
|
}
|
||||||
}
|
}
|
||||||
_ => return Err(("Expected string.", next.pos()).into()),
|
Value::String(s, QuoteKind::None) => {
|
||||||
}
|
if s.starts_with("url(") {
|
||||||
if let Some(t) = self.toks.peek() {
|
return Ok(vec![Stmt::Import(s)]);
|
||||||
if t.kind == ';' {
|
} else {
|
||||||
self.toks.next();
|
s
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
_ => return Err(("Expected string.", span).into()),
|
||||||
|
};
|
||||||
|
|
||||||
self.whitespace();
|
self.whitespace();
|
||||||
|
|
||||||
@ -47,12 +54,8 @@ impl<'a> Parser<'a> {
|
|||||||
.unwrap_or_else(|| Path::new(""))
|
.unwrap_or_else(|| Path::new(""))
|
||||||
.join(path)
|
.join(path)
|
||||||
};
|
};
|
||||||
// todo: will panic if path ended in `..`
|
|
||||||
let name = path_buf.file_name().unwrap();
|
let name = path_buf.file_name().unwrap_or_else(|| OsStr::new(".."));
|
||||||
if path_buf.extension() == Some(OsStr::new(".css")) {
|
|
||||||
// || name.starts_with("http://") || name.starts_with("https://") {
|
|
||||||
todo!("css imports")
|
|
||||||
}
|
|
||||||
|
|
||||||
let paths = [
|
let paths = [
|
||||||
path_buf.with_file_name(name).with_extension("scss"),
|
path_buf.with_file_name(name).with_extension("scss"),
|
||||||
@ -92,6 +95,6 @@ impl<'a> Parser<'a> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(Vec::new())
|
Err(("Can't find stylesheet to import.", span).into())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -64,6 +64,9 @@ pub(crate) enum Stmt {
|
|||||||
Return(Box<Value>),
|
Return(Box<Value>),
|
||||||
Keyframes(Box<Keyframes>),
|
Keyframes(Box<Keyframes>),
|
||||||
KeyframesRuleSet(Box<KeyframesRuleSet>),
|
KeyframesRuleSet(Box<KeyframesRuleSet>),
|
||||||
|
/// A plain import such as `@import "foo.css";` or
|
||||||
|
/// `@import url(https://fonts.google.com/foo?bar);`
|
||||||
|
Import(String),
|
||||||
}
|
}
|
||||||
|
|
||||||
/// We could use a generic for the toks, but it makes the API
|
/// We could use a generic for the toks, but it makes the API
|
||||||
|
@ -49,13 +49,10 @@ fn imports_variable() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
#[ignore = "we don't actually check if the semicolon exists"]
|
||||||
fn import_no_semicolon() {
|
fn import_no_semicolon() {
|
||||||
let input = "@import \"import_no_semicolon\"\na {\n color: $a;\n}";
|
let input = "@import \"import_no_semicolon\"\na {\n color: $a;\n}";
|
||||||
tempfile!("import_no_semicolon", "$a: red;");
|
tempfile!("import_no_semicolon", "$a: red;");
|
||||||
assert_eq!(
|
|
||||||
"a {\n color: red;\n}\n",
|
|
||||||
&grass::from_string(input.to_string()).expect(input)
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
@ -140,5 +137,44 @@ error!(
|
|||||||
missing_input_after_import,
|
missing_input_after_import,
|
||||||
"@import", "Error: expected more input."
|
"@import", "Error: expected more input."
|
||||||
);
|
);
|
||||||
|
error!(
|
||||||
|
import_unquoted_http,
|
||||||
|
"@import http://foo.com/;", "Error: Expected string."
|
||||||
|
);
|
||||||
|
error!(
|
||||||
|
import_file_doesnt_exist,
|
||||||
|
"@import \"idontexist\";", "Error: Can't find stylesheet to import."
|
||||||
|
);
|
||||||
|
error!(
|
||||||
|
file_name_is_two_periods,
|
||||||
|
"@import \"foo/..\";", "Error: Can't find stylesheet to import."
|
||||||
|
);
|
||||||
|
test!(
|
||||||
|
import_beginning_with_http,
|
||||||
|
"@import \"http://foo.com/\";",
|
||||||
|
"@import \"http://foo.com/\";\n"
|
||||||
|
);
|
||||||
|
test!(
|
||||||
|
import_beginning_with_http_no_ending_slash,
|
||||||
|
"@import \"http://foo.com\";",
|
||||||
|
"@import \"http://foo.com\";\n"
|
||||||
|
);
|
||||||
|
test!(
|
||||||
|
import_beginning_with_https,
|
||||||
|
"@import \"https://foo.com/\";",
|
||||||
|
"@import \"https://foo.com/\";\n"
|
||||||
|
);
|
||||||
|
test!(
|
||||||
|
import_ending_in_css,
|
||||||
|
"@import \"foo.css\";",
|
||||||
|
"@import \"foo.css\";\n"
|
||||||
|
);
|
||||||
|
test!(import_url, "@import url(foo..);", "@import url(foo..);\n");
|
||||||
|
test!(
|
||||||
|
import_url_interpolation,
|
||||||
|
"@import url(#{1+1}..);",
|
||||||
|
"@import url(2..);\n"
|
||||||
|
);
|
||||||
|
|
||||||
// todo: test for calling paths, e.g. `grass b\index.scss`
|
// todo: test for calling paths, e.g. `grass b\index.scss`
|
||||||
|
// todo: test for absolute paths (how?)
|
||||||
|
Loading…
x
Reference in New Issue
Block a user