@@ -5,12 +5,13 @@ use std::{
55 sync:: Arc ,
66} ;
77
8+ use once_cell:: sync:: OnceCell ;
89use parking_lot:: Mutex ;
910use rustc_hash:: FxHashMap as HashMap ;
10- use substring:: Substring ;
1111
1212use crate :: {
1313 helpers:: { get_map, split_into_lines, GeneratedInfo , StreamChunks } ,
14+ with_indices:: WithIndices ,
1415 MapOptions , Mapping , OriginalLocation , Source , SourceMap ,
1516} ;
1617
@@ -36,22 +37,76 @@ use crate::{
3637#[ derive( Debug ) ]
3738pub struct ReplaceSource < T > {
3839 inner : Arc < T > ,
40+ inner_source_code : OnceCell < Box < str > > ,
3941 replacements : Mutex < Vec < Replacement > > ,
4042}
4143
42- #[ derive( Debug , Hash , Clone , PartialEq , Eq ) ]
44+ #[ derive( Debug , Clone , Eq ) ]
4345struct Replacement {
4446 start : u32 ,
4547 end : u32 ,
48+ char_start : OnceCell < u32 > ,
49+ char_end : OnceCell < u32 > ,
4650 content : String ,
4751 name : Option < String > ,
4852}
4953
54+ impl Hash for Replacement {
55+ fn hash < H : Hasher > ( & self , state : & mut H ) {
56+ self . start . hash ( state) ;
57+ self . end . hash ( state) ;
58+ self . content . hash ( state) ;
59+ self . name . hash ( state) ;
60+ }
61+ }
62+
63+ impl PartialEq for Replacement {
64+ fn eq ( & self , other : & Self ) -> bool {
65+ self . start == other. start
66+ && self . end == other. end
67+ && self . content == other. content
68+ && self . name == other. name
69+ }
70+ }
71+
72+ impl Replacement {
73+ pub fn new (
74+ start : u32 ,
75+ end : u32 ,
76+ content : String ,
77+ name : Option < String > ,
78+ ) -> Self {
79+ Self {
80+ start,
81+ end,
82+ char_start : OnceCell :: new ( ) ,
83+ char_end : OnceCell :: new ( ) ,
84+ content,
85+ name,
86+ }
87+ }
88+
89+ pub fn char_start ( & self , inner_source_code : & str ) -> u32 {
90+ * self . char_start . get_or_init ( || {
91+ str_indices:: chars:: from_byte_idx ( inner_source_code, self . start as usize )
92+ as u32
93+ } )
94+ }
95+
96+ pub fn char_end ( & self , inner_source_code : & str ) -> u32 {
97+ * self . char_end . get_or_init ( || {
98+ str_indices:: chars:: from_byte_idx ( inner_source_code, self . end as usize )
99+ as u32
100+ } )
101+ }
102+ }
103+
50104impl < T > ReplaceSource < T > {
51105 /// Create a [ReplaceSource].
52106 pub fn new ( source : T ) -> Self {
53107 Self {
54108 inner : Arc :: new ( source) ,
109+ inner_source_code : OnceCell :: new ( ) ,
55110 replacements : Mutex :: new ( Vec :: new ( ) ) ,
56111 }
57112 }
@@ -61,14 +116,24 @@ impl<T> ReplaceSource<T> {
61116 & self . inner
62117 }
63118
119+ fn sort_replacement ( & self ) {
120+ self
121+ . replacements
122+ . lock ( )
123+ . sort_by ( |a, b| ( a. start , a. end ) . cmp ( & ( b. start , b. end ) ) ) ;
124+ }
125+ }
126+
127+ impl < T : Source > ReplaceSource < T > {
128+ fn get_inner_source_code ( & self ) -> & str {
129+ self
130+ . inner_source_code
131+ . get_or_init ( || Box :: from ( self . inner . source ( ) ) )
132+ }
133+
64134 /// Insert a content at start.
65135 pub fn insert ( & mut self , start : u32 , content : & str , name : Option < & str > ) {
66- self . replacements . lock ( ) . push ( Replacement {
67- start,
68- end : start,
69- content : content. into ( ) ,
70- name : name. map ( |s| s. into ( ) ) ,
71- } ) ;
136+ self . replace ( start, start, content, name)
72137 }
73138
74139 /// Create a replacement with content at `[start, end)`.
@@ -79,48 +144,45 @@ impl<T> ReplaceSource<T> {
79144 content : & str ,
80145 name : Option < & str > ,
81146 ) {
82- self . replacements . lock ( ) . push ( Replacement {
147+ self . replacements . lock ( ) . push ( Replacement :: new (
83148 start,
84149 end,
85- content : content. into ( ) ,
86- name : name. map ( |s| s. into ( ) ) ,
87- } ) ;
88- }
89-
90- fn sort_replacement ( & self ) {
91- self
92- . replacements
93- . lock ( )
94- . sort_by ( |a, b| ( a. start , a. end ) . cmp ( & ( b. start , b. end ) ) ) ;
150+ content. into ( ) ,
151+ name. map ( |s| s. into ( ) ) ,
152+ ) ) ;
95153 }
96154}
97155
98156impl < T : Source + Hash + PartialEq + Eq + ' static > Source for ReplaceSource < T > {
99157 fn source ( & self ) -> Cow < str > {
100158 self . sort_replacement ( ) ;
101159
102- let inner_source_code = self . inner . source ( ) ;
160+ let inner_source_code = self . get_inner_source_code ( ) ;
161+ let inner_source_code_with_indices = WithIndices :: new ( inner_source_code) ;
103162
104163 // mut_string_push_str is faster that vec join
105164 // concatenate strings benchmark, see https://github.com/hoodie/concatenation_benchmarks-rs
106165 let mut source_code = String :: new ( ) ;
107166 let mut inner_pos = 0 ;
108167 for replacement in self . replacements . lock ( ) . iter ( ) {
109- if inner_pos < replacement. start {
110- let end_pos = ( replacement. start as usize ) . min ( inner_source_code. len ( ) ) ;
111- source_code
112- . push_str ( inner_source_code. substring ( inner_pos as usize , end_pos) ) ;
168+ if inner_pos < replacement. char_start ( inner_source_code) {
169+ let end_pos = ( replacement. char_start ( inner_source_code) as usize )
170+ . min ( inner_source_code. len ( ) ) ;
171+ source_code. push_str (
172+ inner_source_code_with_indices. substring ( inner_pos as usize , end_pos) ,
173+ ) ;
113174 }
114175 source_code. push_str ( & replacement. content ) ;
115176 #[ allow( clippy:: manual_clamp) ]
116177 {
117178 inner_pos = inner_pos
118- . max ( replacement. end )
179+ . max ( replacement. char_end ( inner_source_code ) )
119180 . min ( inner_source_code. len ( ) as u32 ) ;
120181 }
121182 }
122- source_code
123- . push_str ( inner_source_code. substring ( inner_pos as usize , usize:: MAX ) ) ;
183+ source_code. push_str (
184+ inner_source_code_with_indices. substring ( inner_pos as usize , usize:: MAX ) ,
185+ ) ;
124186
125187 source_code. into ( )
126188 }
@@ -166,7 +228,7 @@ impl<T: Source> StreamChunks for ReplaceSource<T> {
166228 let mut generated_line_offset: i64 = 0 ;
167229 let mut generated_column_offset: i64 = 0 ;
168230 let mut generated_column_offset_line = 0 ;
169- let source_content_lines: RefCell < Vec < Option < Vec < String > > > > =
231+ let source_content_lines: RefCell < Vec < Option < Vec < WithIndices < String > > > > > =
170232 RefCell :: new ( Vec :: new ( ) ) ;
171233 let name_mapping: RefCell < HashMap < String , u32 > > =
172234 RefCell :: new ( HashMap :: default ( ) ) ;
@@ -224,6 +286,7 @@ impl<T: Source> StreamChunks for ReplaceSource<T> {
224286 & mut |chunk, mut mapping| {
225287 // SAFETY: final_source is false in ReplaceSource
226288 let chunk = chunk. unwrap ( ) ;
289+ let chunk_with_indices = WithIndices :: new ( chunk) ;
227290 let mut chunk_pos = 0 ;
228291 let end_pos = pos + chunk. len ( ) as u32 ;
229292 // Skip over when it has been replaced
@@ -253,7 +316,7 @@ impl<T: Source> StreamChunks for ReplaceSource<T> {
253316 original. source_index ,
254317 original. original_line ,
255318 original. original_column ,
256- chunk . substring ( 0 , chunk_pos as usize ) ,
319+ chunk_with_indices . substring ( 0 , chunk_pos as usize ) ,
257320 ) {
258321 original. original_column += chunk_pos;
259322 }
@@ -274,7 +337,7 @@ impl<T: Source> StreamChunks for ReplaceSource<T> {
274337 if next_replacement_pos > pos {
275338 // Emit chunk until replacement
276339 let offset = next_replacement_pos - pos;
277- let chunk_slice = chunk . substring ( chunk_pos as usize , ( chunk_pos + offset) as usize ) ;
340+ let chunk_slice = chunk_with_indices . substring ( chunk_pos as usize , ( chunk_pos + offset) as usize ) ;
278341 on_chunk ( Some ( chunk_slice) , Mapping {
279342 generated_line : line as u32 ,
280343 generated_column : mapping. generated_column + if line == generated_column_offset_line { generated_column_offset} else { 0 } as u32 ,
@@ -367,7 +430,7 @@ impl<T: Source> StreamChunks for ReplaceSource<T> {
367430
368431 // Partially skip over chunk
369432 let line = mapping. generated_line as i64 + generated_line_offset;
370- if let Some ( original) = & mut mapping. original && check_original_content ( original. source_index , original. original_line , original. original_column , chunk . substring ( chunk_pos as usize , ( chunk_pos + offset as u32 ) as usize ) ) {
433+ if let Some ( original) = & mut mapping. original && check_original_content ( original. source_index , original. original_line , original. original_column , chunk_with_indices . substring ( chunk_pos as usize , ( chunk_pos + offset as u32 ) as usize ) ) {
371434 original. original_column += offset as u32 ;
372435 }
373436 chunk_pos += offset as u32 ;
@@ -384,7 +447,7 @@ impl<T: Source> StreamChunks for ReplaceSource<T> {
384447
385448 // Emit remaining chunk
386449 if ( chunk_pos as usize ) < chunk. len ( ) {
387- let chunk_slice = if chunk_pos == 0 { chunk} else { chunk . substring ( chunk_pos as usize , usize:: MAX ) } ;
450+ let chunk_slice = if chunk_pos == 0 { chunk} else { chunk_with_indices . substring ( chunk_pos as usize , usize:: MAX ) } ;
388451 let line = mapping. generated_line as i64 + generated_line_offset;
389452 on_chunk ( Some ( chunk_slice) , Mapping {
390453 generated_line : line as u32 ,
@@ -400,7 +463,7 @@ impl<T: Source> StreamChunks for ReplaceSource<T> {
400463 source_content_lines. push ( None ) ;
401464 }
402465 source_content_lines[ source_index as usize ] = source_content. map ( |source_content| {
403- split_into_lines ( source_content) . into_iter ( ) . map ( Into :: into) . collect ( )
466+ split_into_lines ( source_content) . into_iter ( ) . map ( |line| WithIndices :: new ( line . into ( ) ) ) . collect ( )
404467 } ) ;
405468 on_source ( source_index, source, source_content) ;
406469 } ,
@@ -473,6 +536,7 @@ impl<T: Source> Clone for ReplaceSource<T> {
473536 fn clone ( & self ) -> Self {
474537 Self {
475538 inner : self . inner . clone ( ) ,
539+ inner_source_code : self . inner_source_code . clone ( ) ,
476540 replacements : Mutex :: new ( self . replacements . lock ( ) . clone ( ) ) ,
477541 }
478542 }
@@ -900,4 +964,41 @@ return <div>{data.foo}</div>
900964 source. hash ( & mut hasher) ;
901965 assert_eq ! ( format!( "{:x}" , hasher. finish( ) ) , "ab891b4c45dc95b4" ) ;
902966 }
967+
968+ #[ test]
969+ fn should_replace_correctly_with_unicode ( ) {
970+ let content = r#"
971+ "abc"; url(__PUBLIC_PATH__logo.png);
972+ "ヒラギノ角ゴ"; url(__PUBLIC_PATH__logo.png);
973+ "游ゴシック体"; url(__PUBLIC_PATH__logo.png);
974+ "🤪"; url(__PUBLIC_PATH__logo.png);
975+ "👨👩👧👧"; url(__PUBLIC_PATH__logo.png);
976+ "# ;
977+ let mut source =
978+ ReplaceSource :: new ( OriginalSource :: new ( content, "file.css" ) . boxed ( ) ) ;
979+ for mat in regex:: Regex :: new ( "__PUBLIC_PATH__" )
980+ . unwrap ( )
981+ . find_iter ( content)
982+ {
983+ source. replace ( mat. start ( ) as u32 , mat. end ( ) as u32 , "../" , None ) ;
984+ }
985+ assert_eq ! (
986+ source. source( ) ,
987+ r#"
988+ "abc"; url(../logo.png);
989+ "ヒラギノ角ゴ"; url(../logo.png);
990+ "游ゴシック体"; url(../logo.png);
991+ "🤪"; url(../logo.png);
992+ "👨👩👧👧"; url(../logo.png);
993+ "#
994+ ) ;
995+ assert_eq ! (
996+ source
997+ . map( & MapOptions :: default ( ) )
998+ . unwrap( )
999+ . to_json( )
1000+ . unwrap( ) ,
1001+ r#"{"version":3,"sources":["file.css"],"sourcesContent":["\n\"abc\"; url(__PUBLIC_PATH__logo.png);\n\"ヒラギノ角ゴ\"; url(__PUBLIC_PATH__logo.png);\n\"游ゴシック体\"; url(__PUBLIC_PATH__logo.png);\n\"🤪\"; url(__PUBLIC_PATH__logo.png);\n\"👨👩👧👧\"; url(__PUBLIC_PATH__logo.png);\n"],"names":[],"mappings":";AACA,OAAO,IAAI,GAAe;AAC1B,sBAAsB;AACtB,sBAAsB;AACtB,QAAQ;AACR,6BAA6B"}"# ,
1002+ ) ;
1003+ }
9031004}
0 commit comments