Skip to content

Commit 55ffbaa

Browse files
committed
Emit JS errors for CSS syntax errors
1 parent 8d974af commit 55ffbaa

File tree

5 files changed

+110
-27
lines changed

5 files changed

+110
-27
lines changed

Cargo.lock

Lines changed: 2 additions & 2 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ parcel_sourcemap = "2.0.0"
2424
jemallocator = { version = "0.3.2", features = ["disable_initial_exec_tls"] }
2525

2626
[target.'cfg(not(target_arch = "wasm32"))'.dependencies]
27-
napi = { version = "1", features = ["serde-json"] }
27+
napi = { version = "1.7.10", features = ["serde-json"] }
2828
napi-derive = "1"
2929

3030
[target.'cfg(target_arch = "wasm32")'.dependencies]

src/lib.rs

Lines changed: 99 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,8 @@ use wasm_bindgen::prelude::*;
3030
#[wasm_bindgen]
3131
pub fn transform(config_val: JsValue) -> Result<JsValue, JsValue> {
3232
let config: Config = from_value(config_val).map_err(JsValue::from)?;
33-
let res = compile(config).unwrap();
33+
let code = unsafe { std::str::from_utf8_unchecked(&config.code) };
34+
let res = compile(code, &config).unwrap();
3435
let serializer = Serializer::new().serialize_maps_as_objects(true);
3536
res.serialize(&serializer).map_err(JsValue::from)
3637
}
@@ -65,8 +66,38 @@ struct TransformResult {
6566
fn transform(ctx: CallContext) -> napi::Result<JsUnknown> {
6667
let opts = ctx.get::<JsObject>(0)?;
6768
let config: Config = ctx.env.from_js_value(opts)?;
68-
let res = compile(config).unwrap();
69-
ctx.env.to_js_value(&res)
69+
let code = unsafe { std::str::from_utf8_unchecked(&config.code) };
70+
let res = {
71+
compile(code, &config)
72+
};
73+
74+
match res {
75+
Ok(res) => ctx.env.to_js_value(&res),
76+
Err(err) => {
77+
match &err {
78+
CompileError::ParseError(e) => {
79+
// Generate an error with location information.
80+
let syntax_error = ctx.env.get_global()?
81+
.get_named_property::<napi::JsFunction>("SyntaxError")?;
82+
let reason = ctx.env.create_string_from_std(err.reason())?;
83+
let line = ctx.env.create_int32((e.location.line + 1) as i32)?;
84+
let col = ctx.env.create_int32(e.location.column as i32)?;
85+
let mut obj = syntax_error.new(&[reason])?;
86+
let filename = ctx.env.create_string_from_std(config.filename)?;
87+
obj.set_named_property("fileName", filename)?;
88+
let source = ctx.env.create_string(code)?;
89+
obj.set_named_property("source", source)?;
90+
let mut loc = ctx.env.create_object()?;
91+
loc.set_named_property("line", line)?;
92+
loc.set_named_property("column", col)?;
93+
obj.set_named_property("loc", loc)?;
94+
ctx.env.throw(obj)?;
95+
Ok(ctx.env.get_undefined()?.into_unknown())
96+
}
97+
_ => Err(err.into())
98+
}
99+
}
100+
}
70101
}
71102

72103
#[cfg(not(target_arch = "wasm32"))]
@@ -89,16 +120,15 @@ struct Config {
89120
pub source_map: Option<bool>
90121
}
91122

92-
fn compile(config: Config) -> Result<TransformResult, ()> {
93-
let code = unsafe { std::str::from_utf8_unchecked(&config.code) };
94-
let mut stylesheet = StyleSheet::parse(config.filename, code);
123+
fn compile<'i>(code: &'i str, config: &Config) -> Result<TransformResult, CompileError<'i>> {
124+
let mut stylesheet = StyleSheet::parse(config.filename.clone(), &code)?;
95125
stylesheet.minify(config.targets); // TODO: should this be conditional?
96-
let (res, source_map) = stylesheet.to_css(config.minify.unwrap_or(false), config.source_map.unwrap_or(false)).map_err(|_| ())?;
126+
let (res, source_map) = stylesheet.to_css(config.minify.unwrap_or(false), config.source_map.unwrap_or(false))?;
97127

98128
let map = if let Some(mut source_map) = source_map {
99-
source_map.set_source_content(0, code).map_err(|_| ())?;
129+
source_map.set_source_content(0, code)?;
100130
let mut vlq_output: Vec<u8> = Vec::new();
101-
source_map.write_vlq(&mut vlq_output).map_err(|_| ())?;
131+
source_map.write_vlq(&mut vlq_output)?;
102132

103133
let sm = SourceMapJson {
104134
version: 3,
@@ -119,27 +149,84 @@ fn compile(config: Config) -> Result<TransformResult, ()> {
119149
})
120150
}
121151

152+
enum CompileError<'i> {
153+
ParseError(cssparser::ParseError<'i, ()>),
154+
PrinterError,
155+
SourceMapError(parcel_sourcemap::SourceMapError)
156+
}
157+
158+
impl<'i> CompileError<'i> {
159+
fn reason(&self) -> String {
160+
match self {
161+
CompileError::ParseError(e) => {
162+
match &e.kind {
163+
cssparser::ParseErrorKind::Basic(b) => {
164+
use cssparser::BasicParseErrorKind::*;
165+
match b {
166+
AtRuleBodyInvalid => "Invalid at rule body".into(),
167+
EndOfInput => "Unexpected end of input".into(),
168+
AtRuleInvalid(name) => format!("Unknown at rule: @{}", name),
169+
QualifiedRuleInvalid => "Invalid qualified rule".into(),
170+
UnexpectedToken(token) => format!("Unexpected token {:?}", token)
171+
}
172+
},
173+
_ => "Unknown error".into()
174+
}
175+
}
176+
CompileError::PrinterError => "Printer error".into(),
177+
_ => "Unknown error".into()
178+
}
179+
}
180+
}
181+
182+
impl<'i> From<cssparser::ParseError<'i, ()>> for CompileError<'i> {
183+
fn from(e: cssparser::ParseError<'i, ()>) -> CompileError<'i> {
184+
CompileError::ParseError(e)
185+
}
186+
}
187+
188+
impl<'i> From<std::fmt::Error> for CompileError<'i> {
189+
fn from(_: std::fmt::Error) -> CompileError<'i> {
190+
CompileError::PrinterError
191+
}
192+
}
193+
194+
impl<'i> From<parcel_sourcemap::SourceMapError> for CompileError<'i> {
195+
fn from(e: parcel_sourcemap::SourceMapError) -> CompileError<'i> {
196+
CompileError::SourceMapError(e)
197+
}
198+
}
199+
200+
impl<'i> From<CompileError<'i>> for napi::Error {
201+
fn from(e: CompileError) -> napi::Error {
202+
match e {
203+
CompileError::SourceMapError(e) => e.into(),
204+
_ => napi::Error::new(napi::Status::GenericFailure, e.reason())
205+
}
206+
}
207+
}
208+
122209
#[cfg(test)]
123210
mod tests {
124211
use super::*;
125212
use indoc::indoc;
126213

127214
fn test(source: &str, expected: &str) {
128-
let mut stylesheet = StyleSheet::parse("test.css".into(), source);
215+
let mut stylesheet = StyleSheet::parse("test.css".into(), source).unwrap();
129216
stylesheet.minify(None);
130217
let (res, _) = stylesheet.to_css(false, false).unwrap();
131218
assert_eq!(res, expected);
132219
}
133220

134221
fn minify_test(source: &str, expected: &str) {
135-
let mut stylesheet = StyleSheet::parse("test.css".into(), source);
222+
let mut stylesheet = StyleSheet::parse("test.css".into(), source).unwrap();
136223
stylesheet.minify(None);
137224
let (res, _) = stylesheet.to_css(true, false).unwrap();
138225
assert_eq!(res, expected);
139226
}
140227

141228
fn prefix_test(source: &str, expected: &str, targets: Browsers) {
142-
let mut stylesheet = StyleSheet::parse("test.css".into(), source);
229+
let mut stylesheet = StyleSheet::parse("test.css".into(), source).unwrap();
143230
stylesheet.minify(Some(targets));
144231
let (res, _) = stylesheet.to_css(false, false).unwrap();
145232
assert_eq!(res, expected);

src/parser.rs

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -315,10 +315,7 @@ impl<'a, 'b, 'i> AtRuleParser<'i> for NestedRuleParser {
315315
// Ok(AtRuleType::WithBlock(AtRuleBlockPrelude::Document(cond)))
316316
// },
317317
// _ => Err(input.new_custom_error(StyleParseErrorKind::UnsupportedAtRule(name.clone())))
318-
_ => {
319-
print!("UNKNOWN AT RULE {}", name);
320-
Ok(AtRuleType::WithBlock(AtRulePrelude::FontFeatureValues))
321-
}
318+
_ => Err(input.new_error(BasicParseErrorKind::AtRuleInvalid(name)))
322319
}
323320
}
324321

src/stylesheet.rs

Lines changed: 7 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
use cssparser::{Parser, ParserInput, RuleListParser};
1+
use cssparser::{Parser, ParserInput, RuleListParser, ParseError};
22
use parcel_sourcemap::SourceMap;
33
use crate::rules::CssRuleList;
44
use crate::parser::TopLevelRuleParser;
@@ -13,26 +13,25 @@ pub struct StyleSheet {
1313
}
1414

1515
impl StyleSheet {
16-
pub fn parse<'i>(filename: String, code: &str) -> StyleSheet {
16+
pub fn parse<'i>(filename: String, code: &'i str) -> Result<StyleSheet, ParseError<'i, ()>> {
1717
let mut input = ParserInput::new(&code);
1818
let mut parser = Parser::new(&mut input);
1919
let rule_list_parser = RuleListParser::new_for_stylesheet(&mut parser, TopLevelRuleParser {});
2020

2121
let mut rules = vec![];
2222
for rule in rule_list_parser {
23-
let rule = if let Ok((_, rule)) = rule {
24-
rule
25-
} else {
26-
continue
23+
let rule = match rule {
24+
Ok((_, rule)) => rule,
25+
Err((e, _)) => return Err(e)
2726
};
2827

2928
rules.push(rule)
3029
}
3130

32-
StyleSheet {
31+
Ok(StyleSheet {
3332
filename,
3433
rules: CssRuleList(rules)
35-
}
34+
})
3635
}
3736

3837
pub fn minify(&mut self, targets: Option<Browsers>) {

0 commit comments

Comments
 (0)