@@ -5,12 +5,13 @@ use std::{
5
5
sync:: Arc ,
6
6
} ;
7
7
8
+ use once_cell:: sync:: OnceCell ;
8
9
use parking_lot:: Mutex ;
9
10
use rustc_hash:: FxHashMap as HashMap ;
10
- use substring:: Substring ;
11
11
12
12
use crate :: {
13
13
helpers:: { get_map, split_into_lines, GeneratedInfo , StreamChunks } ,
14
+ with_indices:: WithIndices ,
14
15
MapOptions , Mapping , OriginalLocation , Source , SourceMap ,
15
16
} ;
16
17
@@ -36,22 +37,76 @@ use crate::{
36
37
#[ derive( Debug ) ]
37
38
pub struct ReplaceSource < T > {
38
39
inner : Arc < T > ,
40
+ inner_source_code : OnceCell < Box < str > > ,
39
41
replacements : Mutex < Vec < Replacement > > ,
40
42
}
41
43
42
- #[ derive( Debug , Hash , Clone , PartialEq , Eq ) ]
44
+ #[ derive( Debug , Clone , Eq ) ]
43
45
struct Replacement {
44
46
start : u32 ,
45
47
end : u32 ,
48
+ char_start : OnceCell < u32 > ,
49
+ char_end : OnceCell < u32 > ,
46
50
content : String ,
47
51
name : Option < String > ,
48
52
}
49
53
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
+
50
104
impl < T > ReplaceSource < T > {
51
105
/// Create a [ReplaceSource].
52
106
pub fn new ( source : T ) -> Self {
53
107
Self {
54
108
inner : Arc :: new ( source) ,
109
+ inner_source_code : OnceCell :: new ( ) ,
55
110
replacements : Mutex :: new ( Vec :: new ( ) ) ,
56
111
}
57
112
}
@@ -61,14 +116,24 @@ impl<T> ReplaceSource<T> {
61
116
& self . inner
62
117
}
63
118
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
+
64
134
/// Insert a content at start.
65
135
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)
72
137
}
73
138
74
139
/// Create a replacement with content at `[start, end)`.
@@ -79,48 +144,45 @@ impl<T> ReplaceSource<T> {
79
144
content : & str ,
80
145
name : Option < & str > ,
81
146
) {
82
- self . replacements . lock ( ) . push ( Replacement {
147
+ self . replacements . lock ( ) . push ( Replacement :: new (
83
148
start,
84
149
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
+ ) ) ;
95
153
}
96
154
}
97
155
98
156
impl < T : Source + Hash + PartialEq + Eq + ' static > Source for ReplaceSource < T > {
99
157
fn source ( & self ) -> Cow < str > {
100
158
self . sort_replacement ( ) ;
101
159
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) ;
103
162
104
163
// mut_string_push_str is faster that vec join
105
164
// concatenate strings benchmark, see https://github.com/hoodie/concatenation_benchmarks-rs
106
165
let mut source_code = String :: new ( ) ;
107
166
let mut inner_pos = 0 ;
108
167
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
+ ) ;
113
174
}
114
175
source_code. push_str ( & replacement. content ) ;
115
176
#[ allow( clippy:: manual_clamp) ]
116
177
{
117
178
inner_pos = inner_pos
118
- . max ( replacement. end )
179
+ . max ( replacement. char_end ( inner_source_code ) )
119
180
. min ( inner_source_code. len ( ) as u32 ) ;
120
181
}
121
182
}
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
+ ) ;
124
186
125
187
source_code. into ( )
126
188
}
@@ -166,7 +228,7 @@ impl<T: Source> StreamChunks for ReplaceSource<T> {
166
228
let mut generated_line_offset: i64 = 0 ;
167
229
let mut generated_column_offset: i64 = 0 ;
168
230
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 > > > > > =
170
232
RefCell :: new ( Vec :: new ( ) ) ;
171
233
let name_mapping: RefCell < HashMap < String , u32 > > =
172
234
RefCell :: new ( HashMap :: default ( ) ) ;
@@ -224,6 +286,7 @@ impl<T: Source> StreamChunks for ReplaceSource<T> {
224
286
& mut |chunk, mut mapping| {
225
287
// SAFETY: final_source is false in ReplaceSource
226
288
let chunk = chunk. unwrap ( ) ;
289
+ let chunk_with_indices = WithIndices :: new ( chunk) ;
227
290
let mut chunk_pos = 0 ;
228
291
let end_pos = pos + chunk. len ( ) as u32 ;
229
292
// Skip over when it has been replaced
@@ -253,7 +316,7 @@ impl<T: Source> StreamChunks for ReplaceSource<T> {
253
316
original. source_index ,
254
317
original. original_line ,
255
318
original. original_column ,
256
- chunk . substring ( 0 , chunk_pos as usize ) ,
319
+ chunk_with_indices . substring ( 0 , chunk_pos as usize ) ,
257
320
) {
258
321
original. original_column += chunk_pos;
259
322
}
@@ -274,7 +337,7 @@ impl<T: Source> StreamChunks for ReplaceSource<T> {
274
337
if next_replacement_pos > pos {
275
338
// Emit chunk until replacement
276
339
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 ) ;
278
341
on_chunk ( Some ( chunk_slice) , Mapping {
279
342
generated_line : line as u32 ,
280
343
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> {
367
430
368
431
// Partially skip over chunk
369
432
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 ) ) {
371
434
original. original_column += offset as u32 ;
372
435
}
373
436
chunk_pos += offset as u32 ;
@@ -384,7 +447,7 @@ impl<T: Source> StreamChunks for ReplaceSource<T> {
384
447
385
448
// Emit remaining chunk
386
449
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 ) } ;
388
451
let line = mapping. generated_line as i64 + generated_line_offset;
389
452
on_chunk ( Some ( chunk_slice) , Mapping {
390
453
generated_line : line as u32 ,
@@ -400,7 +463,7 @@ impl<T: Source> StreamChunks for ReplaceSource<T> {
400
463
source_content_lines. push ( None ) ;
401
464
}
402
465
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 ( )
404
467
} ) ;
405
468
on_source ( source_index, source, source_content) ;
406
469
} ,
@@ -473,6 +536,7 @@ impl<T: Source> Clone for ReplaceSource<T> {
473
536
fn clone ( & self ) -> Self {
474
537
Self {
475
538
inner : self . inner . clone ( ) ,
539
+ inner_source_code : self . inner_source_code . clone ( ) ,
476
540
replacements : Mutex :: new ( self . replacements . lock ( ) . clone ( ) ) ,
477
541
}
478
542
}
@@ -900,4 +964,41 @@ return <div>{data.foo}</div>
900
964
source. hash ( & mut hasher) ;
901
965
assert_eq ! ( format!( "{:x}" , hasher. finish( ) ) , "ab891b4c45dc95b4" ) ;
902
966
}
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
+ }
903
1004
}
1 commit comments
github-actions[bot] commentedon May 10, 2023
Benchmark
benchmark_concat_generate_base64
30364
ns/iter (± 4689
)28132
ns/iter (± 2351
)1.08
benchmark_concat_generate_base64_with_cache
20069
ns/iter (± 3000
)18107
ns/iter (± 389
)1.11
benchmark_concat_generate_string
11303
ns/iter (± 2149
)14078
ns/iter (± 709
)0.80
benchmark_concat_generate_string_with_cache
3567
ns/iter (± 680
)4018
ns/iter (± 194
)0.89
This comment was automatically generated by workflow using github-action-benchmark.