mirror of
https://github.com/swc-project/swc.git
synced 2024-12-24 06:05:02 +03:00
fix(css/codegen): Fix sourcemap of multibyte characters (#5183)
This commit is contained in:
parent
78e288a5c3
commit
5b70233400
1
Cargo.lock
generated
1
Cargo.lock
generated
@ -3174,6 +3174,7 @@ version = "0.108.0"
|
||||
dependencies = [
|
||||
"auto_impl",
|
||||
"bitflags",
|
||||
"rustc-hash",
|
||||
"serde",
|
||||
"swc_atoms",
|
||||
"swc_common",
|
||||
|
@ -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" }
|
||||
|
@ -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;
|
||||
|
@ -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
|
||||
}
|
||||
|
@ -1,5 +1,5 @@
|
||||
div {
|
||||
content: '⬇️';
|
||||
content: '⬇';
|
||||
content: "😀";
|
||||
content: "\'test\'";
|
||||
content: "\\'test\'";
|
||||
|
@ -1,5 +1,5 @@
|
||||
div {
|
||||
content: '⬇️';
|
||||
content: '⬇';
|
||||
content: "😀";
|
||||
content: "\'test\'";
|
||||
content: "\\'test\'";
|
||||
|
@ -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"}
|
||||
|
Loading…
Reference in New Issue
Block a user