diff --git a/src/cached_source.rs b/src/cached_source.rs index 7c758f22..1dc70775 100644 --- a/src/cached_source.rs +++ b/src/cached_source.rs @@ -1,5 +1,6 @@ use std::{ borrow::Cow, + cell::OnceCell, hash::{Hash, Hasher}, sync::{Arc, OnceLock}, }; @@ -166,7 +167,8 @@ impl Source for CachedSource { } struct CachedSourceChunks<'source> { - chunks: Box, + inner: &'source BoxSource, + chunks: OnceCell>, cache: Arc, source: Cow<'source, str>, } @@ -176,7 +178,8 @@ impl<'a> CachedSourceChunks<'a> { let source = cache_source.source().into_string_lossy(); Self { - chunks: cache_source.inner.stream_chunks(), + inner: &cache_source.inner, + chunks: OnceCell::new(), cache: cache_source.cache.clone(), source, } @@ -220,10 +223,11 @@ impl Chunks for CachedSourceChunks<'_> { } } None => { + let chunks = self.chunks.get_or_init(|| self.inner.stream_chunks()); let (generated_info, map) = stream_and_get_source_and_map( options, object_pool, - self.chunks.as_ref(), + chunks.as_ref(), on_chunk, on_source, on_name, diff --git a/src/concat_source.rs b/src/concat_source.rs index 4e77729a..cac71e04 100644 --- a/src/concat_source.rs +++ b/src/concat_source.rs @@ -154,6 +154,9 @@ impl ConcatSource { children.extend(other_children.iter().cloned()); return; } + + children.push(box_source.clone()); + return; } // Check if the source itself is a ConcatSource diff --git a/src/encoder.rs b/src/encoder.rs index e435c135..b92cecec 100644 --- a/src/encoder.rs +++ b/src/encoder.rs @@ -5,12 +5,22 @@ const B64_CHARS: &[u8] = #[inline(always)] pub fn encode_vlq(out: &mut Vec, a: u32, b: u32) { + if a == b { + out.push(b'A'); + return; + } + let mut num = if a >= b { (a - b) << 1 } else { ((b - a) << 1) + 1 }; + if num < 0b100000 { + out.push(B64_CHARS[num as usize]); + return; + } + loop { let mut digit = num & 0b11111; num >>= 5; @@ -68,6 +78,14 @@ pub(crate) struct FullMappingsEncoder { mappings: Vec, } +#[derive(Clone, Copy)] +pub(crate) struct OriginalLocationInfo { + pub source_index: u32, + pub original_line: u32, + pub original_column: u32, + pub name_index: Option, +} + impl FullMappingsEncoder { pub fn new() -> Self { Self { @@ -86,10 +104,73 @@ impl FullMappingsEncoder { } impl FullMappingsEncoder { - fn encode(&mut self, mapping: &Mapping) { - if self.active_mapping && self.current_line == mapping.generated_line { + #[inline(always)] + pub(crate) fn encode_original_no_name( + &mut self, + generated_line: u32, + generated_column: u32, + original_line: u32, + original_column: u32, + ) { + if self.active_mapping + && self.current_line == generated_line + && self.current_source_index == 0 + && self.current_original_line == original_line + && self.current_original_column == original_column + && !self.active_name + { + return; + } + + if self.current_line < generated_line { + let count = (generated_line - self.current_line) as usize; + self.mappings.extend(std::iter::repeat_n(b';', count)); + self.current_line = generated_line; + self.current_column = 0; + self.initial = false; + } else if self.initial { + self.initial = false; + } else { + self.mappings.push(b','); + } + + encode_vlq(&mut self.mappings, generated_column, self.current_column); + self.current_column = generated_column; + self.active_mapping = true; + self.active_name = false; + if self.current_source_index == 0 { + self.mappings.push(b'A'); + } else { + encode_vlq(&mut self.mappings, 0, self.current_source_index); + self.current_source_index = 0; + } + encode_vlq( + &mut self.mappings, + original_line, + self.current_original_line, + ); + self.current_original_line = original_line; + if original_column == self.current_original_column { + self.mappings.push(b'A'); + } else { + encode_vlq( + &mut self.mappings, + original_column, + self.current_original_column, + ); + self.current_original_column = original_column; + } + } + + pub(crate) fn encode_raw( + &mut self, + generated_line: u32, + generated_column: u32, + original: Option, + ) { + if self.active_mapping && self.current_line == generated_line { // A mapping is still active - if mapping.original.as_ref().is_some_and(|original| { + if original.is_some_and(|original| { original.source_index == self.current_source_index && original.original_line == self.current_original_line && original.original_column == self.current_original_column @@ -101,16 +182,16 @@ impl FullMappingsEncoder { } } else { // No mapping is active - if mapping.original.is_none() { + if original.is_none() { // avoid writing unnecessary generated mappings return; } } - if self.current_line < mapping.generated_line { - let count = (mapping.generated_line - self.current_line) as usize; + if self.current_line < generated_line { + let count = (generated_line - self.current_line) as usize; self.mappings.extend(std::iter::repeat_n(b';', count)); - self.current_line = mapping.generated_line; + self.current_line = generated_line; self.current_column = 0; self.initial = false; } else if self.initial { @@ -119,13 +200,9 @@ impl FullMappingsEncoder { self.mappings.push(b','); } - encode_vlq( - &mut self.mappings, - mapping.generated_column, - self.current_column, - ); - self.current_column = mapping.generated_column; - if let Some(original) = &mapping.original { + encode_vlq(&mut self.mappings, generated_column, self.current_column); + self.current_column = generated_column; + if let Some(original) = original { self.active_mapping = true; if original.source_index == self.current_source_index { self.mappings.push(b'A'); @@ -165,8 +242,24 @@ impl FullMappingsEncoder { } } + fn encode(&mut self, mapping: &Mapping) { + self.encode_raw( + mapping.generated_line, + mapping.generated_column, + mapping + .original + .as_ref() + .map(|original| OriginalLocationInfo { + source_index: original.source_index, + original_line: original.original_line, + original_column: original.original_column, + name_index: original.name_index, + }), + ); + } + #[allow(unsafe_code)] - fn drain(&mut self) -> String { + pub(crate) fn drain(&mut self) -> String { unsafe { // SAFETY: The `mappings` field in the source map consists solely of ASCII characters. String::from_utf8_unchecked(std::mem::take(&mut self.mappings)) diff --git a/src/helpers.rs b/src/helpers.rs index 4e57cd68..739d9821 100644 --- a/src/helpers.rs +++ b/src/helpers.rs @@ -161,6 +161,15 @@ pub fn utf16_len(s: &str) -> usize { simd_utf16_len::utf16_len(s) } +#[inline] +pub(crate) fn utf16_len_or_len(s: &str, is_ascii: bool) -> usize { + if is_ascii { + s.len() + } else { + utf16_len(s) + } +} + pub struct PotentialTokens<'a> { text: &'a str, } diff --git a/src/original_source.rs b/src/original_source.rs index 64506388..9613730e 100644 --- a/src/original_source.rs +++ b/src/original_source.rs @@ -7,8 +7,8 @@ use std::{ use crate::{ helpers::{ get_generated_source_info, get_map, split_into_lines, - split_into_potential_tokens, utf16_len, Chunks, GeneratedInfo, - StreamChunks, + split_into_potential_tokens, utf16_len, utf16_len_or_len, Chunks, + GeneratedInfo, StreamChunks, }, object_pool::ObjectPool, source::{Mapping, OriginalLocation}, @@ -142,6 +142,7 @@ impl Chunks for OriginalSourceChunks<'_> { on_source(0, Cow::Borrowed(&self.0.name), Some(&self.0.value)); if options.columns { // With column info we need to read all lines and split them + let is_ascii = self.0.value.is_ascii(); let mut line = 1; let mut column = 0; for token in split_into_potential_tokens(self.0.value.as_ref()) { @@ -176,7 +177,7 @@ impl Chunks for OriginalSourceChunks<'_> { line += 1; column = 0; } else { - column += utf16_len(token) as u32; + column += utf16_len_or_len(token, is_ascii) as u32; } } GeneratedInfo { diff --git a/src/replace_source.rs b/src/replace_source.rs index e39f8100..420c4123 100644 --- a/src/replace_source.rs +++ b/src/replace_source.rs @@ -8,14 +8,16 @@ use std::{ use rustc_hash::FxHashMap as HashMap; use crate::{ + encoder::{FullMappingsEncoder, OriginalLocationInfo}, helpers::{ - get_map, split_into_lines, utf16_len, Chunks, GeneratedInfo, StreamChunks, + get_map, split_into_lines, split_into_potential_tokens, utf16_len_or_len, + Chunks, GeneratedInfo, StreamChunks, }, linear_map::LinearMap, object_pool::ObjectPool, source_content_lines::SourceContentLines, - BoxSource, MapOptions, Mapping, OriginalLocation, OriginalSource, Source, - SourceExt, SourceMap, SourceValue, + BoxSource, MapOptions, Mapping, OriginalLocation, OriginalSource, + RawStringSource, Source, SourceExt, SourceMap, SourceMapSource, SourceValue, }; /// Decorates a Source with replacements and insertions of source code, @@ -341,6 +343,473 @@ impl ReplaceSource { self.replacements.push(replacement); } } + + fn compute_size(&self) -> usize { + self.compute_size_with_inner_size(self.inner.size()) + } + + fn compute_size_with_inner_size(&self, inner_source_size: usize) -> usize { + if self.replacements.is_empty() { + return inner_source_size; + } + + // Simulate the replacement process to calculate accurate size. + let mut size = inner_source_size; + let mut inner_pos = 0u32; + + for replacement in self.replacements.iter() { + if replacement.start as usize >= inner_source_size { + size += replacement.content.len(); + continue; + } + + let original_length = replacement + .end + .saturating_sub(replacement.start.max(inner_pos)) + as usize; + let replacement_length = replacement.content.len(); + + size = size + .saturating_sub(original_length) + .saturating_add(replacement_length); + + inner_pos = inner_pos.max(replacement.end); + } + + size + } + + fn borrowed_inner_source(&self) -> Option<&str> { + if let Some(source) = self + .inner + .as_ref() + .as_any() + .downcast_ref::() + { + return Some(source.value()); + } + + if let Some(source) = self + .inner + .as_ref() + .as_any() + .downcast_ref::() + { + return Some(source.value()); + } + + if let Some(source) = self + .inner + .as_ref() + .as_any() + .downcast_ref::() + { + if let SourceValue::String(Cow::Borrowed(inner_source)) = source.source() + { + return Some(inner_source); + } + } + + None + } + + #[allow(unsafe_code)] + fn source_from_replacements(&self, inner_source: &str) -> String { + let mut string = String::with_capacity( + self.compute_size_with_inner_size(inner_source.len()), + ); + let mut source_pos = 0usize; + let source_len = inner_source.len(); + let mut replacement_idx = 0usize; + + while let Some(replacement) = self.replacements.get(replacement_idx) { + let start = replacement.start as usize; + if start >= source_len { + break; + } + + if start > source_pos { + string + .push_str(unsafe { inner_source.get_unchecked(source_pos..start) }); + } + + string.push_str(&replacement.content); + source_pos = source_pos.max((replacement.end as usize).min(source_len)); + replacement_idx += 1; + } + + if source_pos < source_len { + string.push_str(unsafe { inner_source.get_unchecked(source_pos..) }); + } + + for replacement in &self.replacements[replacement_idx..] { + string.push_str(&replacement.content); + } + + string + } + + fn map_original_source_columns( + &self, + original_source: &OriginalSource, + ) -> Option { + let source = original_source.value().as_ref(); + let source_is_ascii = source.is_ascii(); + let is_ascii = source_is_ascii + && self + .replacements + .iter() + .all(|replacement| replacement.content.is_ascii()); + + let mut encoder = FullMappingsEncoder::new(); + let mut names: Vec = Vec::new(); + let mut name_mapping: HashMap<&str, u32> = HashMap::default(); + let repls = &self.replacements; + let mut pos: u32 = 0; + let mut i: usize = 0; + let mut replacement_end: Option = None; + let mut next_replacement = (i < repls.len()).then(|| repls[i].start); + let mut generated_line_offset: i64 = 0; + let mut generated_column_offset: i64 = 0; + let mut generated_column_offset_line = 0; + let result: GeneratedInfo; + + { + macro_rules! encode_mapping { + ($generated_line:expr, $generated_column:expr, $original:expr $(,)?) => {{ + match $original { + Some(original) + if original.source_index == 0 + && original.name_index.is_none() => + { + encoder.encode_original_no_name( + $generated_line, + $generated_column, + original.original_line, + original.original_column, + ); + } + Some(original) => { + encoder.encode_raw( + $generated_line, + $generated_column, + Some(original), + ); + } + None => { + encoder.encode_raw($generated_line, $generated_column, None); + } + } + }}; + } + + let mut process_chunk = |chunk: &str, + mapping_generated_line: u32, + mut mapping_generated_column: u32, + mut mapping_original: Option< + OriginalLocationInfo, + >| { + let mut chunk_pos = 0; + let end_pos = pos + chunk.len() as u32; + + if let Some(replacement_end_value) = + replacement_end.filter(|replacement_end| *replacement_end > pos) + { + if replacement_end_value >= end_pos { + let line = mapping_generated_line as i64 + generated_line_offset; + if chunk.ends_with('\n') { + generated_line_offset -= 1; + if generated_column_offset_line == line { + generated_column_offset += mapping_generated_column as i64; + } + } else if generated_column_offset_line == line { + generated_column_offset -= + utf16_len_or_len(chunk, is_ascii) as i64; + } else { + generated_column_offset = + -(utf16_len_or_len(chunk, is_ascii) as i64); + generated_column_offset_line = line; + } + pos = end_pos; + return; + } + + chunk_pos = replacement_end_value - pos; + if let Some(original) = mapping_original.as_mut() { + original.original_column += chunk_pos; + } + pos += chunk_pos; + let chunk_utf16_pos = + utf16_len_or_len(&chunk[..chunk_pos as usize], is_ascii); + let line = mapping_generated_line as i64 + generated_line_offset; + if generated_column_offset_line == line { + generated_column_offset -= chunk_utf16_pos as i64; + } else { + generated_column_offset = -(chunk_utf16_pos as i64); + generated_column_offset_line = line; + } + mapping_generated_column += chunk_utf16_pos as u32; + } + + while let Some(next_replacement_pos) = next_replacement + .filter(|next_replacement_pos| *next_replacement_pos < end_pos) + { + let mut line = mapping_generated_line as i64 + generated_line_offset; + if next_replacement_pos > pos { + let offset = next_replacement_pos - pos; + let chunk_slice = + &chunk[chunk_pos as usize..(chunk_pos + offset) as usize]; + let chunk_slice_utf16_offset = + utf16_len_or_len(chunk_slice, is_ascii) as u32; + encode_mapping!( + line as u32, + ((mapping_generated_column as i64) + + if line == generated_column_offset_line { + generated_column_offset + } else { + 0 + }) as u32, + mapping_original, + ); + mapping_generated_column += chunk_slice_utf16_offset; + chunk_pos += offset; + pos = next_replacement_pos; + if let Some(original) = mapping_original.as_mut() { + original.original_column += chunk_slice_utf16_offset; + } + } + + let repl = &repls[i]; + let mut replacement_name_index = + mapping_original.and_then(|original| original.name_index); + if let Some(name) = + repl.name.as_ref().filter(|_| mapping_original.is_some()) + { + let name = name.as_ref(); + replacement_name_index = + Some(*name_mapping.entry(name).or_insert_with(|| { + let len = names.len() as u32; + names.push(name.to_string()); + len + })); + } + + let content = repl.content.as_ref(); + if !content.is_empty() && !content.as_bytes().contains(&b'\n') { + encode_mapping!( + line as u32, + ((mapping_generated_column as i64) + + if line == generated_column_offset_line { + generated_column_offset + } else { + 0 + }) as u32, + mapping_original.map(|mut original| { + original.name_index = replacement_name_index; + original + }), + ); + + if generated_column_offset_line == line { + generated_column_offset += + utf16_len_or_len(content, is_ascii) as i64; + } else { + generated_column_offset = + utf16_len_or_len(content, is_ascii) as i64; + generated_column_offset_line = line; + } + } else { + let mut lines = split_into_lines(content).peekable(); + while let Some(content_line) = lines.next() { + let is_last_line = lines.peek().is_none(); + encode_mapping!( + line as u32, + ((mapping_generated_column as i64) + + if line == generated_column_offset_line { + generated_column_offset + } else { + 0 + }) as u32, + mapping_original.map(|mut original| { + original.name_index = replacement_name_index; + original + }), + ); + replacement_name_index = None; + + if is_last_line && !content_line.ends_with('\n') { + if generated_column_offset_line == line { + generated_column_offset += + utf16_len_or_len(content_line, is_ascii) as i64; + } else { + generated_column_offset = + utf16_len_or_len(content_line, is_ascii) as i64; + generated_column_offset_line = line; + } + } else { + generated_line_offset += 1; + line += 1; + generated_column_offset = -(mapping_generated_column as i64); + generated_column_offset_line = line; + } + } + } + + replacement_end = if let Some(replacement_end) = replacement_end { + Some(replacement_end.max(repl.end)) + } else { + Some(repl.end) + }; + + i += 1; + next_replacement = if i < repls.len() { + Some(repls[i].start) + } else { + None + }; + + let offset = chunk.len() as i64 - end_pos as i64 + + replacement_end.unwrap() as i64 + - chunk_pos as i64; + if offset > 0 { + if replacement_end + .is_some_and(|replacement_end| replacement_end >= end_pos) + { + let line = mapping_generated_line as i64 + generated_line_offset; + if chunk.ends_with('\n') { + generated_line_offset -= 1; + if generated_column_offset_line == line { + generated_column_offset += mapping_generated_column as i64; + } + } else if generated_column_offset_line == line { + let remaining_chunk_utf16_len = + utf16_len_or_len(&chunk[chunk_pos as usize..], is_ascii) + as i64; + generated_column_offset -= remaining_chunk_utf16_len; + } else { + generated_column_offset = + -(utf16_len_or_len(&chunk[chunk_pos as usize..], is_ascii) + as i64); + generated_column_offset_line = line; + } + pos = end_pos; + return; + } + + let line = mapping_generated_line as i64 + generated_line_offset; + if let Some(original) = mapping_original.as_mut() { + original.original_column += offset as u32; + } + + let utf16_offset = utf16_len_or_len( + &chunk[chunk_pos as usize..(chunk_pos + offset as u32) as usize], + is_ascii, + ) as i64; + chunk_pos += offset as u32; + pos += offset as u32; + + if generated_column_offset_line == line { + generated_column_offset -= utf16_offset; + } else { + generated_column_offset = -utf16_offset; + generated_column_offset_line = line; + } + mapping_generated_column += utf16_offset as u32; + } + } + + if (chunk_pos as usize) < chunk.len() { + let line = mapping_generated_line as i64 + generated_line_offset; + encode_mapping!( + line as u32, + ((mapping_generated_column as i64) + + if line == generated_column_offset_line { + generated_column_offset + } else { + 0 + }) as u32, + mapping_original, + ); + } + pos = end_pos; + }; + + let mut line = 1; + let mut column = 0; + for token in split_into_potential_tokens(source) { + let is_end_of_line = token.ends_with('\n'); + let original = if is_end_of_line && token.len() == 1 { + None + } else { + Some(OriginalLocationInfo { + source_index: 0, + original_line: line, + original_column: column, + name_index: None, + }) + }; + + process_chunk(token, line, column, original); + + if is_end_of_line { + line += 1; + column = 0; + } else { + column += utf16_len_or_len(token, source_is_ascii) as u32; + } + } + + result = GeneratedInfo { + generated_line: line, + generated_column: column, + }; + } + + let mut line = result.generated_line as i64 + generated_line_offset; + while i < repls.len() { + let content = &repls[i].content; + + for content_line in split_into_lines(content) { + encoder.encode_raw( + line as u32, + ((result.generated_column as i64) + + if line == generated_column_offset_line { + generated_column_offset + } else { + 0 + }) as u32, + None, + ); + + if !content_line.ends_with('\n') { + let content_utf16_len = + utf16_len_or_len(content_line, is_ascii) as i64; + if generated_column_offset_line == line { + generated_column_offset += content_utf16_len; + } else { + generated_column_offset = content_utf16_len; + generated_column_offset_line = line; + } + } else { + line += 1; + generated_column_offset = -(result.generated_column as i64); + generated_column_offset_line = line; + } + } + + i += 1; + } + + let mappings = encoder.drain(); + (!mappings.is_empty()).then(|| { + SourceMap::new( + mappings, + vec![original_source.name().to_string()], + vec![original_source.value().clone()], + names, + ) + }) + } } impl Source for ReplaceSource { @@ -349,6 +818,12 @@ impl Source for ReplaceSource { return self.inner.source(); } + if let Some(inner_source) = self.borrowed_inner_source() { + return SourceValue::String(Cow::Owned( + self.source_from_replacements(inner_source), + )); + } + let mut string = String::with_capacity(self.size()); self.rope(&mut |chunk| { string.push_str(chunk); @@ -461,43 +936,11 @@ impl Source for ReplaceSource { } fn size(&self) -> usize { - let inner_source_size = self.inner.size(); - if self.replacements.is_empty() { - return inner_source_size; - } - - // Simulate the replacement process to calculate accurate size - let mut size = inner_source_size; - let mut inner_pos = 0u32; - - for replacement in self.replacements.iter() { - // Add original content before replacement - if inner_pos < replacement.start { - // This content is already counted in inner_source_size, so no change needed - } - if replacement.start as usize >= inner_source_size { - size += replacement.content.len(); - continue; - } - - // Handle the replacement itself - let original_length = replacement - .end - .saturating_sub(replacement.start.max(inner_pos)) - as usize; - let replacement_length = replacement.content.len(); - - // Subtract original content length and add replacement content length - size = size - .saturating_sub(original_length) - .saturating_add(replacement_length); - - // Move position forward, handling overlaps - inner_pos = inner_pos.max(replacement.end); + return self.inner.size(); } - size + self.compute_size() } fn map( @@ -509,6 +952,16 @@ impl Source for ReplaceSource { if replacements.is_empty() { return self.inner.map(&ObjectPool::default(), options); } + if options.columns { + if let Some(original_source) = self + .inner + .as_ref() + .as_any() + .downcast_ref::() + { + return self.map_original_source_columns(original_source); + } + } let chunks = self.stream_chunks(); get_map(&ObjectPool::default(), chunks.as_ref(), options) } @@ -589,6 +1042,8 @@ fn check_content_at_position( struct ReplaceSourceChunks<'a> { is_original_source: bool, + inner_source: Option<&'a str>, + replacements_are_ascii: bool, chunks: Box, replacements: &'a [Replacement], } @@ -597,8 +1052,15 @@ impl<'a> ReplaceSourceChunks<'a> { pub fn new(source: &'a ReplaceSource) -> Self { let is_original_source = source.inner.as_ref().as_any().is::(); + let inner_source = source.borrowed_inner_source(); + let replacements_are_ascii = source + .replacements + .iter() + .all(|replacement| replacement.content.is_ascii()); Self { is_original_source, + inner_source, + replacements_are_ascii, chunks: source.inner.stream_chunks(), replacements: &source.replacements, } @@ -629,6 +1091,8 @@ impl Chunks for ReplaceSourceChunks<'_> { RefCell::new(HashMap::default()); let name_index_mapping: RefCell> = RefCell::new(LinearMap::default()); + let is_ascii = self.replacements_are_ascii + && self.inner_source.is_some_and(str::is_ascii); // check if source_content[line][col] is equal to expect // Why this is needed? @@ -710,9 +1174,11 @@ impl Chunks for ReplaceSourceChunks<'_> { generated_column_offset += mapping.generated_column as i64; } } else if generated_column_offset_line == line { - generated_column_offset -= utf16_len(chunk) as i64; + generated_column_offset -= + utf16_len_or_len(chunk, is_ascii) as i64; } else { - generated_column_offset = -(utf16_len(chunk) as i64); + generated_column_offset = + -(utf16_len_or_len(chunk, is_ascii) as i64); generated_column_offset_line = line; } pos = end_pos; @@ -731,7 +1197,8 @@ impl Chunks for ReplaceSourceChunks<'_> { original.original_column += chunk_pos; } pos += chunk_pos; - let chunk_utf16_pos = utf16_len(&chunk[..chunk_pos as usize]); + let chunk_utf16_pos = + utf16_len_or_len(&chunk[..chunk_pos as usize], is_ascii); let line = mapping.generated_line as i64 + generated_line_offset; if generated_column_offset_line == line { generated_column_offset -= chunk_utf16_pos as i64; @@ -752,7 +1219,8 @@ impl Chunks for ReplaceSourceChunks<'_> { let offset = next_replacement_pos - pos; let chunk_slice = &chunk[chunk_pos as usize..(chunk_pos + offset) as usize]; - let chunk_slice_utf16_offset = utf16_len(chunk_slice) as u32; + let chunk_slice_utf16_offset = + utf16_len_or_len(chunk_slice, is_ascii) as u32; on_chunk( Some(chunk_slice), Mapping { @@ -813,11 +1281,11 @@ impl Chunks for ReplaceSourceChunks<'_> { } replacement_name_index = global_index; } - let mut lines = split_into_lines(repl.content.as_ref()).peekable(); - while let Some(content_line) = lines.next() { - let is_last_line = lines.peek().is_none(); + + let content = repl.content.as_ref(); + if !content.is_empty() && !content.as_bytes().contains(&b'\n') { on_chunk( - Some(content_line), + Some(content), Mapping { generated_line: line as u32, generated_column: ((mapping.generated_column as i64) @@ -836,21 +1304,57 @@ impl Chunks for ReplaceSourceChunks<'_> { }), }, ); - // Only the first chunk has name assigned - replacement_name_index = None; - if is_last_line && !content_line.ends_with('\n') { - if generated_column_offset_line == line { - generated_column_offset += utf16_len(content_line) as i64; + if generated_column_offset_line == line { + generated_column_offset += + utf16_len_or_len(content, is_ascii) as i64; + } else { + generated_column_offset = + utf16_len_or_len(content, is_ascii) as i64; + generated_column_offset_line = line; + } + } else { + let mut lines = split_into_lines(content).peekable(); + while let Some(content_line) = lines.next() { + let is_last_line = lines.peek().is_none(); + on_chunk( + Some(content_line), + Mapping { + generated_line: line as u32, + generated_column: ((mapping.generated_column as i64) + + if line == generated_column_offset_line { + generated_column_offset + } else { + 0 + }) as u32, + original: mapping.original.as_ref().map(|original| { + OriginalLocation { + source_index: original.source_index, + original_line: original.original_line, + original_column: original.original_column, + name_index: replacement_name_index, + } + }), + }, + ); + // Only the first chunk has name assigned + replacement_name_index = None; + + if is_last_line && !content_line.ends_with('\n') { + if generated_column_offset_line == line { + generated_column_offset += + utf16_len_or_len(content_line, is_ascii) as i64; + } else { + generated_column_offset = + utf16_len_or_len(content_line, is_ascii) as i64; + generated_column_offset_line = line; + } } else { - generated_column_offset = utf16_len(content_line) as i64; + generated_line_offset += 1; + line += 1; + generated_column_offset = -(mapping.generated_column as i64); generated_column_offset_line = line; } - } else { - generated_line_offset += 1; - line += 1; - generated_column_offset = -(mapping.generated_column as i64); - generated_column_offset_line = line; } } @@ -887,11 +1391,13 @@ impl Chunks for ReplaceSourceChunks<'_> { } } else if generated_column_offset_line == line { let remaining_chunk_utf16_len = - utf16_len(&chunk[chunk_pos as usize..]) as i64; + utf16_len_or_len(&chunk[chunk_pos as usize..], is_ascii) + as i64; generated_column_offset -= remaining_chunk_utf16_len; } else { generated_column_offset = - -(utf16_len(&chunk[chunk_pos as usize..]) as i64); + -(utf16_len_or_len(&chunk[chunk_pos as usize..], is_ascii) + as i64); generated_column_offset_line = line; } pos = end_pos; @@ -914,8 +1420,9 @@ impl Chunks for ReplaceSourceChunks<'_> { original.original_column += offset as u32; } - let utf16_offset = utf16_len( + let utf16_offset = utf16_len_or_len( &chunk[chunk_pos as usize..(chunk_pos + offset as u32) as usize], + is_ascii, ) as i64; chunk_pos += offset as u32; pos += offset as u32; @@ -1007,7 +1514,8 @@ impl Chunks for ReplaceSourceChunks<'_> { // Handle line and column offset updates if !content_line.ends_with('\n') { - let content_utf16_len = utf16_len(content_line) as i64; + let content_utf16_len = + utf16_len_or_len(content_line, is_ascii) as i64; // Last line of current replacement doesn't end with newline if generated_column_offset_line == line { generated_column_offset += content_utf16_len;