Skip to content

Commit d32c0cc

Browse files
authored
fix: replace_source_hash_is_order_independent (#195)
1 parent c4f7e16 commit d32c0cc

File tree

2 files changed

+31
-3
lines changed

2 files changed

+31
-3
lines changed

src/replace_source.rs

Lines changed: 30 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -766,8 +766,13 @@ impl Clone for ReplaceSource {
766766
impl Hash for ReplaceSource {
767767
fn hash<H: Hasher>(&self, state: &mut H) {
768768
"ReplaceSource".hash(state);
769+
// replacements are ordered, so when hashing,
770+
// skip fields (enforce and insertion_order) that are only used
769771
for repl in &self.replacements {
770-
repl.hash(state);
772+
repl.start.hash(state);
773+
repl.end.hash(state);
774+
repl.content.hash(state);
775+
repl.name.hash(state);
771776
}
772777
self.inner.hash(state);
773778
}
@@ -784,6 +789,8 @@ impl Eq for ReplaceSource {}
784789

785790
#[cfg(test)]
786791
mod tests {
792+
use rustc_hash::FxHasher;
793+
787794
use crate::{
788795
source_map_source::WithoutOriginalOptions, OriginalSource, RawStringSource,
789796
ReplacementEnforce, SourceExt, SourceMapSource,
@@ -1185,7 +1192,7 @@ return <div>{data.foo}</div>
11851192
assert_eq!(source.map(&MapOptions::default()), None);
11861193
let mut hasher = twox_hash::XxHash64::default();
11871194
source.hash(&mut hasher);
1188-
assert_eq!(format!("{:x}", hasher.finish()), "15e48cdf294935ab");
1195+
assert_eq!(format!("{:x}", hasher.finish()), "96abdb94c6fd5aba");
11891196
}
11901197

11911198
#[test]
@@ -1360,4 +1367,25 @@ return <div>{data.foo}</div>
13601367

13611368
assert_eq!(source.size(), source.source().into_string_lossy().len());
13621369
}
1370+
1371+
#[test]
1372+
fn replace_source_hash_is_order_independent() {
1373+
let mut source1 =
1374+
ReplaceSource::new(RawStringSource::from_static("hello, world!").boxed());
1375+
source1.replace(0, 5, "你好", None);
1376+
source1.replace(6, 11, "世界", None);
1377+
1378+
let mut source2 =
1379+
ReplaceSource::new(RawStringSource::from_static("hello, world!").boxed());
1380+
source2.replace(6, 11, "世界", None);
1381+
source2.replace(0, 5, "你好", None);
1382+
1383+
assert_eq!(source1.source(), source2.source());
1384+
1385+
let mut hasher1 = FxHasher::default();
1386+
source1.hash(&mut hasher1);
1387+
let mut hasher2 = FxHasher::default();
1388+
source2.hash(&mut hasher2);
1389+
assert_eq!(hasher1.finish(), hasher2.finish());
1390+
}
13631391
}

src/source.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ pub type BoxSource = Arc<dyn Source>;
2424
/// `SourceValue` provides a flexible way to handle source content regardless of whether
2525
/// it's originally stored as a string or raw bytes. This is particularly useful for
2626
/// build tools and bundlers that need to process various types of source files.
27-
#[derive(Debug)]
27+
#[derive(Debug, PartialEq, Eq)]
2828
pub enum SourceValue<'a> {
2929
/// Text content stored as a UTF-8 string.
3030
String(Cow<'a, str>),

0 commit comments

Comments
 (0)