emit @import when importing a url or .css file

This commit is contained in:
Connor Skees 2020-07-06 19:47:12 -04:00
parent 4edc324fcd
commit e1e643d286
4 changed files with 94 additions and 34 deletions

View File

@ -32,13 +32,16 @@ enum Toplevel {
Media { query: String, body: Vec<Stmt> },
Supports { params: String, body: Vec<Stmt> },
Newline,
// todo: do we actually need a toplevel style variant?
Style(Style),
Import(String),
}
#[derive(Debug, Clone)]
enum BlockEntry {
Style(Box<Style>),
Style(Style),
MultilineComment(String),
Import(String),
}
impl BlockEntry {
@ -46,6 +49,7 @@ impl BlockEntry {
match self {
BlockEntry::Style(s) => s.to_string(),
BlockEntry::MultilineComment(s) => Ok(format!("/*{}*/", s)),
BlockEntry::Import(s) => Ok(format!("@import {};", s)),
}
}
}
@ -64,7 +68,7 @@ impl Toplevel {
return;
}
if let Toplevel::RuleSet(_, entries) | Toplevel::KeyframesRuleSet(_, entries) = self {
entries.push(BlockEntry::Style(Box::new(s)));
entries.push(BlockEntry::Style(s));
} else {
panic!()
}
@ -77,6 +81,14 @@ impl Toplevel {
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)]
@ -145,11 +157,13 @@ impl Css {
k @ Stmt::KeyframesRuleSet(..) => {
unreachable!("@keyframes ruleset {:?}", k)
}
Stmt::Import(s) => vals.get_mut(0).unwrap().push_import(s),
};
}
vals
}
Stmt::Comment(s) => vec![Toplevel::MultilineComment(s)],
Stmt::Import(s) => vec![Toplevel::Import(s)],
Stmt::Style(s) => vec![Toplevel::Style(s)],
Stmt::Media(m) => {
let MediaRule { query, body, .. } = *m;
@ -271,6 +285,10 @@ impl Css {
has_written = true;
writeln!(buf, "{}/*{}*/", padding, s)?;
}
Toplevel::Import(s) => {
has_written = true;
writeln!(buf, "{}@import {};", padding, s)?;
}
Toplevel::UnknownAtRule(u) => {
let ToplevelUnknownAtRule { params, name, body } = *u;
if should_emit_newline {

View File

@ -1,38 +1,45 @@
use std::{ffi::OsStr, fs, path::Path};
use codemap::Spanned;
use peekmore::PeekMore;
use crate::{error::SassResult, Token};
use crate::lexer::Lexer;
use crate::{common::QuoteKind, error::SassResult, lexer::Lexer, value::Value, Token};
use super::{Parser, Stmt};
impl<'a> Parser<'a> {
pub(super) fn import(&mut self) -> SassResult<Vec<Stmt>> {
self.whitespace();
let mut file_name = String::new();
let next = match self.toks.next() {
Some(v) => v,
match self.toks.peek() {
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()),
};
match next.kind {
q @ '"' | q @ '\'' => {
file_name.push_str(
&self
.parse_quoted_string(q)?
.node
.unquote()
.to_css_string(self.span_before)?,
);
let Spanned {
node: file_name_as_value,
span,
} = self.parse_value()?;
let file_name = match file_name_as_value {
Value::String(s, QuoteKind::Quoted) => {
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()),
}
if let Some(t) = self.toks.peek() {
if t.kind == ';' {
self.toks.next();
Value::String(s, QuoteKind::None) => {
if s.starts_with("url(") {
return Ok(vec![Stmt::Import(s)]);
} else {
s
}
}
}
_ => return Err(("Expected string.", span).into()),
};
self.whitespace();
@ -47,12 +54,8 @@ impl<'a> Parser<'a> {
.unwrap_or_else(|| Path::new(""))
.join(path)
};
// todo: will panic if path ended in `..`
let name = path_buf.file_name().unwrap();
if path_buf.extension() == Some(OsStr::new(".css")) {
// || name.starts_with("http://") || name.starts_with("https://") {
todo!("css imports")
}
let name = path_buf.file_name().unwrap_or_else(|| OsStr::new(".."));
let paths = [
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())
}
}

View File

@ -64,6 +64,9 @@ pub(crate) enum Stmt {
Return(Box<Value>),
Keyframes(Box<Keyframes>),
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

View File

@ -49,13 +49,10 @@ fn imports_variable() {
}
#[test]
#[ignore = "we don't actually check if the semicolon exists"]
fn import_no_semicolon() {
let input = "@import \"import_no_semicolon\"\na {\n color: $a;\n}";
tempfile!("import_no_semicolon", "$a: red;");
assert_eq!(
"a {\n color: red;\n}\n",
&grass::from_string(input.to_string()).expect(input)
);
}
#[test]
@ -140,5 +137,44 @@ error!(
missing_input_after_import,
"@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 absolute paths (how?)