fix(css/codegen): Fix sourcemap of multibyte characters (#5183)

This commit is contained in:
Alexander Akait 2022-07-12 06:12:32 +03:00 committed by GitHub
parent 78e288a5c3
commit 5b70233400
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
7 changed files with 141 additions and 80 deletions

1
Cargo.lock generated
View File

@ -3174,6 +3174,7 @@ version = "0.108.0"
dependencies = [
"auto_impl",
"bitflags",
"rustc-hash",
"serde",
"swc_atoms",
"swc_common",

View File

@ -15,16 +15,17 @@ bench = false
[dependencies]
auto_impl = "0.5.0"
bitflags = "1.3.2"
rustc-hash = "1.1.0"
serde = "1.0.127"
swc_atoms = {version = "0.2.7", path = "../swc_atoms"}
swc_common = { version = "0.23.0", path = "../swc_common"}
swc_css_ast = {version = "0.98.0", path = "../swc_css_ast"}
swc_css_codegen_macros = {version = "0.2.0", path = "../swc_css_codegen_macros"}
swc_atoms = { version = "0.2.7", path = "../swc_atoms" }
swc_common = { version = "0.23.0", path = "../swc_common" }
swc_css_ast = { version = "0.98.0", path = "../swc_css_ast" }
swc_css_codegen_macros = { version = "0.2.0", path = "../swc_css_codegen_macros" }
[dev-dependencies]
swc_common = { version = "0.23.0", path = "../swc_common", features = [
"sourcemap",
]}
swc_css_parser = {version = "0.107.0", path = "../swc_css_parser"}
swc_css_visit = {version = "0.97.0", path = "../swc_css_visit"}
testing = {version = "0.25.0", path = "../testing"}
] }
swc_css_parser = { version = "0.107.0", path = "../swc_css_parser" }
swc_css_visit = { version = "0.97.0", path = "../swc_css_visit" }
testing = { version = "0.25.0", path = "../testing" }

View File

@ -989,7 +989,8 @@ where
#[emitter]
fn emit_important_flag(&mut self, n: &ImportantFlag) -> Result {
let mut value = String::new();
// `!` + `important`
let mut value = String::with_capacity(10);
value.push('!');
@ -1133,7 +1134,7 @@ where
#[emitter]
fn emit_hex_color(&mut self, n: &HexColor) -> Result {
let mut hex_color = String::new();
let mut hex_color = String::with_capacity(5);
hex_color.push('#');
@ -1245,7 +1246,7 @@ where
match &n.token {
Token::AtKeyword { raw, .. } => {
let mut at_keyword = String::new();
let mut at_keyword = String::with_capacity(1 + raw.len());
at_keyword.push('@');
at_keyword.push_str(raw);
@ -1271,7 +1272,7 @@ where
write_raw!(self, span, raw);
}
Token::Percentage { raw, .. } => {
let mut percentage = String::new();
let mut percentage = String::with_capacity(raw.len() + 1);
percentage.push_str(raw);
percentage.push('%');
@ -1283,7 +1284,7 @@ where
raw_unit,
..
} => {
let mut dimension = String::new();
let mut dimension = String::with_capacity(raw_value.len() + raw_unit.len());
dimension.push_str(raw_value);
dimension.push_str(raw_unit);
@ -1294,7 +1295,7 @@ where
write_raw!(self, span, raw);
}
Token::Function { raw, .. } => {
let mut function = String::new();
let mut function = String::with_capacity(raw.len() + 1);
function.push_str(raw);
function.push('(');
@ -1314,7 +1315,9 @@ where
after,
..
} => {
let mut url = String::new();
let mut url = String::with_capacity(
raw_name.len() + before.len() + raw_value.len() + after.len() + 2,
);
url.push_str(raw_name);
url.push('(');
@ -1330,7 +1333,7 @@ where
raw_value,
..
} => {
let mut bad_url = String::new();
let mut bad_url = String::with_capacity(raw_name.len() + raw_value.len() + 2);
bad_url.push_str(raw_name);
bad_url.push('(');
@ -1355,7 +1358,7 @@ where
write_raw!(self, span, ":");
}
Token::Hash { raw, .. } => {
let mut hash = String::new();
let mut hash = String::with_capacity(raw.len() + 1);
hash.push('#');
hash.push_str(raw);
@ -1381,7 +1384,7 @@ where
match token {
Token::AtKeyword { raw, .. } => {
let mut at_keyword = String::new();
let mut at_keyword = String::with_capacity(raw.len() + 1);
at_keyword.push('@');
at_keyword.push_str(raw);
@ -1407,7 +1410,7 @@ where
write_raw!(self, span, raw);
}
Token::Percentage { raw, .. } => {
let mut percentage = String::new();
let mut percentage = String::with_capacity(raw.len() + 1);
percentage.push_str(raw);
percentage.push('%');
@ -1419,7 +1422,7 @@ where
raw_unit,
..
} => {
let mut dimension = String::new();
let mut dimension = String::with_capacity(raw_value.len() + raw_unit.len());
dimension.push_str(raw_value);
dimension.push_str(raw_unit);
@ -1430,7 +1433,7 @@ where
write_raw!(self, span, raw);
}
Token::Function { raw, .. } => {
let mut function = String::new();
let mut function = String::with_capacity(raw.len() + 1);
function.push_str(raw);
function.push('(');
@ -1450,7 +1453,9 @@ where
after,
..
} => {
let mut url = String::new();
let mut url = String::with_capacity(
raw_name.len() + before.len() + raw_value.len() + after.len() + 2,
);
url.push_str(raw_name);
url.push('(');
@ -1466,7 +1471,7 @@ where
raw_value,
..
} => {
let mut bad_url = String::new();
let mut bad_url = String::with_capacity(raw_name.len() + raw_value.len() + 2);
bad_url.push_str(raw_name);
bad_url.push('(');
@ -1491,7 +1496,7 @@ where
write_raw!(self, span, ":");
}
Token::Hash { raw, .. } => {
let mut hash = String::new();
let mut hash = String::with_capacity(raw.len() + 1);
hash.push('#');
hash.push_str(raw);
@ -1543,23 +1548,21 @@ where
#[emitter]
fn emit_url_value_raw(&mut self, n: &UrlValueRaw) -> Result {
let mut url = String::new();
if !self.config.minify {
url.push_str(&n.before);
}
if self.config.minify {
let mut url = String::with_capacity(n.value.len());
url.push_str(&n.value);
write_str!(self, n.span, &url);
} else {
let mut url = String::with_capacity(n.before.len() + n.raw.len() + n.after.len());
url.push_str(&n.before);
url.push_str(&n.raw);
}
if !self.config.minify {
url.push_str(&n.after);
}
write_str!(self, n.span, &url);
write_str!(self, n.span, &url);
}
}
#[emitter]
@ -1572,7 +1575,15 @@ where
#[emitter]
fn emit_unicode_range(&mut self, n: &UnicodeRange) -> Result {
let mut value = String::new();
let mut value = String::with_capacity(
n.start.len()
+ if let Some(end) = &n.end {
end.len() + 1
} else {
0
}
+ 2,
);
value.push(n.prefix);
value.push('+');
@ -1785,7 +1796,7 @@ where
#[emitter]
fn emit_an_plus_b_notation(&mut self, n: &AnPlusBNotation) -> Result {
if self.config.minify {
let mut an_plus_b_minified = String::new();
let mut an_plus_b_minified = String::with_capacity(4);
if let Some(a) = &n.a {
if *a == -1 {
@ -1807,7 +1818,7 @@ where
write_raw!(self, n.span, &an_plus_b_minified);
} else {
let mut an_plus_b = String::new();
let mut an_plus_b = String::with_capacity(4);
if let Some(a_raw) = &n.a_raw {
an_plus_b.push_str(a_raw);
@ -2021,7 +2032,7 @@ fn minify_hex_color(value: &str) -> String {
if chars[0] == chars[1] && chars[2] == chars[3] && chars[4] == chars[5] {
// 6 -> 3 or 8 -> 3
if length == 6 || chars[6] == b'f' && chars[7] == b'f' {
let mut minified = String::new();
let mut minified = String::with_capacity(3);
minified.push((chars[0] as char).to_ascii_lowercase());
minified.push((chars[2] as char).to_ascii_lowercase());
@ -2031,7 +2042,7 @@ fn minify_hex_color(value: &str) -> String {
}
// 8 -> 4
else if length == 8 && chars[6] == chars[7] {
let mut minified = String::new();
let mut minified = String::with_capacity(4);
minified.push((chars[0] as char).to_ascii_lowercase());
minified.push((chars[2] as char).to_ascii_lowercase());
@ -2047,7 +2058,7 @@ fn minify_hex_color(value: &str) -> String {
}
fn minify_string(value: &str) -> String {
let mut minified = String::new();
let mut minified = String::with_capacity(value.len());
let mut dq = 0;
let mut sq = 0;

View File

@ -1,5 +1,6 @@
use std::fmt::{Result, Write};
use rustc_hash::FxHashSet;
use swc_common::{BytePos, LineCol, Span};
use super::CssWriter;
@ -55,6 +56,9 @@ where
linefeed: &'a str,
srcmap: Option<&'a mut Vec<(BytePos, LineCol)>>,
srcmap_done: FxHashSet<(BytePos, u32, u32)>,
/// Used to avoid including whitespaces created by indention.
pending_srcmap: Option<BytePos>,
config: BasicCssWriterConfig,
@ -92,14 +96,35 @@ where
srcmap,
w: writer,
pending_srcmap: Default::default(),
srcmap_done: Default::default(),
}
}
fn write_indent_string(&mut self) -> Result {
for _ in 0..(self.config.indent_width * self.indent_level as i32) {
self.raw_write(self.indent_type)?;
}
Ok(())
}
fn raw_write(&mut self, data: &str) -> Result {
self.w.write_str(data)?;
self.col += data.chars().count();
Ok(())
}
fn write(&mut self, span: Option<Span>, data: &str) -> Result {
if !data.is_empty() {
if self.line_start {
self.write_indent_string()?;
self.line_start = false;
if let Some(pending) = self.pending_srcmap.take() {
self.srcmap(pending);
}
}
if let Some(span) = span {
@ -120,30 +145,23 @@ where
Ok(())
}
fn write_indent_string(&mut self) -> Result {
for _ in 0..(self.config.indent_width * self.indent_level as i32) {
self.raw_write(self.indent_type)?;
fn srcmap(&mut self, byte_pos: BytePos) {
if byte_pos.is_dummy() {
return;
}
Ok(())
}
fn raw_write(&mut self, data: &str) -> Result {
self.w.write_str(data)?;
self.col += data.chars().count();
Ok(())
}
fn srcmap(&mut self, byte_pos: BytePos) {
if let Some(ref mut srcmap) = self.srcmap {
srcmap.push((
byte_pos,
LineCol {
if self
.srcmap_done
.insert((byte_pos, self.line as _, self.col as _))
{
let loc = LineCol {
line: self.line as _,
col: self.col as _,
},
))
};
srcmap.push((byte_pos, loc));
}
}
}
}
@ -157,11 +175,17 @@ where
}
fn write_newline(&mut self) -> Result {
let pending = self.pending_srcmap.take();
if !self.line_start {
self.raw_write(self.linefeed)?;
self.line += 1;
self.col = 0;
self.line_start = true;
if let Some(pending) = pending {
self.srcmap(pending)
}
}
Ok(())
@ -181,27 +205,24 @@ where
fn write_str(&mut self, span: Span, s: &str) -> Result {
if !s.is_empty() {
let mut lines = s.split('\n').peekable();
let mut lo_byte_pos = span.lo();
if !span.is_dummy() {
self.srcmap(span.lo())
}
while let Some(line) = lines.next() {
if !span.is_dummy() {
self.srcmap(lo_byte_pos)
}
self.write(None, s)?;
self.raw_write(line)?;
let line_start_of_s = compute_line_starts(s);
if lines.peek().is_some() {
self.raw_write("\n")?;
self.line += 1;
self.col = 0;
if line_start_of_s.len() > 1 {
self.line = self.line + line_start_of_s.len() - 1;
if !span.is_dummy() {
lo_byte_pos = lo_byte_pos + BytePos((line.len() + 1) as u32);
}
} else if !span.is_dummy() {
self.srcmap(span.hi());
}
let last_line_byte_index = line_start_of_s.last().cloned().unwrap_or(0);
self.col = s[last_line_byte_index..].chars().count();
}
if !span.is_dummy() {
self.srcmap(span.hi())
}
}
@ -221,3 +242,30 @@ where
self.indent_level -= 1;
}
}
fn compute_line_starts(s: &str) -> Vec<usize> {
let mut res = vec![];
let mut line_start = 0;
let mut chars = s.char_indices().peekable();
while let Some((pos, c)) = chars.next() {
match c {
'\r' => {
if let Some(&(_, '\n')) = chars.peek() {
let _ = chars.next();
}
}
'\n' => {
res.push(line_start);
line_start = pos + 1;
}
_ => {}
}
}
// Last line.
res.push(line_start);
res
}

View File

@ -1,5 +1,5 @@
div {
content: '⬇';
content: '⬇';
content: "😀";
content: "\'test\'";
content: "\\'test\'";

View File

@ -1,5 +1,5 @@
div {
content: '⬇';
content: '⬇';
content: "😀";
content: "\'test\'";
content: "\\'test\'";

View File

@ -1 +1 @@
div{content:"⬇";content:"😀";content:"'test'";content:"\\'test'";content:"\\\\'test'";content:'"string" is string';content:"'string' is string";content:'"string" is string';content:"'string' is string";content:'"string" is string';content:"'string' is string";content:"'string' is \"string\"";content:"'string' is \"string\"";content:"'test' 'test'";content:'"test" "test"';content:"'test'";content:"'test'";content:'"test"';content:"'test'";content:"'test'";content:'"test"';content:'"string"';content:"'string'";content:"This string has a \a line break in it.";content:"This string has a \a line break in it.";content:"This string has a \a line break in it.";content:"This string has a \a line break in it.";content:'"test" "test" "test" \'test\'';content:'"test" "test" "test" \'test\'';content:"'test' 'test' 'test' \"test\"";content:'"test" "test" "test" \'test\'';content:"'test' 'test' 'test' \"test\"";content:"'test' 'test' 'test' \"test\"";content:"\\'test\\' \\'test\\' \\'test\\' \\\"test\\\"";background:url("http://example.com/foo'bar.jpg");background:url('http://example.com/foo"bar.jpg');background:url("http://example.com/foo'bar.jpg");background:url('http://example.com/foo"bar.jpg')}div{grid-template:[header-top]"a a a"[header-bottom][main-top]"b b b"1fr[main-bottom]/auto 1fr auto}.other{grid-template:[header-left]"head head"30px[header-right][main-left]"nav main"1fr[main-right][footer-left]"nav foot"30px[footer-right]/120px 1fr}.prop{prop:name"test"}.foo{content:"string is string"}.foo{content:"string is string"}
div{content:"⬇";content:"😀";content:"'test'";content:"\\'test'";content:"\\\\'test'";content:'"string" is string';content:"'string' is string";content:'"string" is string';content:"'string' is string";content:'"string" is string';content:"'string' is string";content:"'string' is \"string\"";content:"'string' is \"string\"";content:"'test' 'test'";content:'"test" "test"';content:"'test'";content:"'test'";content:'"test"';content:"'test'";content:"'test'";content:'"test"';content:'"string"';content:"'string'";content:"This string has a \a line break in it.";content:"This string has a \a line break in it.";content:"This string has a \a line break in it.";content:"This string has a \a line break in it.";content:'"test" "test" "test" \'test\'';content:'"test" "test" "test" \'test\'';content:"'test' 'test' 'test' \"test\"";content:'"test" "test" "test" \'test\'';content:"'test' 'test' 'test' \"test\"";content:"'test' 'test' 'test' \"test\"";content:"\\'test\\' \\'test\\' \\'test\\' \\\"test\\\"";background:url("http://example.com/foo'bar.jpg");background:url('http://example.com/foo"bar.jpg');background:url("http://example.com/foo'bar.jpg");background:url('http://example.com/foo"bar.jpg')}div{grid-template:[header-top]"a a a"[header-bottom][main-top]"b b b"1fr[main-bottom]/auto 1fr auto}.other{grid-template:[header-left]"head head"30px[header-right][main-left]"nav main"1fr[main-right][footer-left]"nav foot"30px[footer-right]/120px 1fr}.prop{prop:name"test"}.foo{content:"string is string"}.foo{content:"string is string"}