Skip to content

Commit 650a863

Browse files
refactor: extract case-sensitive check to plugin (#12532)
* refactor: extract case-sensitive check to plugin - Rename WarnCaseSensitiveModulesPlugin to CaseSensitivePlugin - Move case-sensitive asset check from compiler to plugin - Plugin now handles both module and asset case-sensitive checks - Update test cases to use new plugin * fix clippy * fix clippy * docs * docs * docs * Apply suggestions from code review * Apply suggestions from code review --------- Co-authored-by: neverland <[email protected]>
1 parent 769e045 commit 650a863

File tree

23 files changed

+151
-99
lines changed

23 files changed

+151
-99
lines changed

Cargo.lock

Lines changed: 15 additions & 14 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -193,6 +193,7 @@ rspack_napi_macros = { version = "=0.7.0-beta.0", path = "cr
193193
rspack_paths = { version = "=0.7.0-beta.0", path = "crates/rspack_paths", default-features = false }
194194
rspack_plugin_asset = { version = "=0.7.0-beta.0", path = "crates/rspack_plugin_asset", default-features = false }
195195
rspack_plugin_banner = { version = "=0.7.0-beta.0", path = "crates/rspack_plugin_banner", default-features = false }
196+
rspack_plugin_case_sensitive = { version = "=0.7.0-beta.0", path = "crates/rspack_plugin_case_sensitive", default-features = false }
196197
rspack_plugin_circular_dependencies = { version = "=0.7.0-beta.0", path = "crates/rspack_plugin_circular_dependencies", default-features = false }
197198
rspack_plugin_copy = { version = "=0.7.0-beta.0", path = "crates/rspack_plugin_copy", default-features = false }
198199
rspack_plugin_css = { version = "=0.7.0-beta.0", path = "crates/rspack_plugin_css", default-features = false }
@@ -233,7 +234,6 @@ rspack_plugin_size_limits = { version = "=0.7.0-beta.0", path = "cr
233234
rspack_plugin_split_chunks = { version = "=0.7.0-beta.0", path = "crates/rspack_plugin_split_chunks", default-features = false }
234235
rspack_plugin_sri = { version = "=0.7.0-beta.0", path = "crates/rspack_plugin_sri", default-features = false }
235236
rspack_plugin_swc_js_minimizer = { version = "=0.7.0-beta.0", path = "crates/rspack_plugin_swc_js_minimizer", default-features = false }
236-
rspack_plugin_warn_sensitive_module = { version = "=0.7.0-beta.0", path = "crates/rspack_plugin_warn_sensitive_module", default-features = false }
237237
rspack_plugin_wasm = { version = "=0.7.0-beta.0", path = "crates/rspack_plugin_wasm", default-features = false }
238238
rspack_plugin_web_worker_template = { version = "=0.7.0-beta.0", path = "crates/rspack_plugin_web_worker_template", default-features = false }
239239
rspack_plugin_worker = { version = "=0.7.0-beta.0", path = "crates/rspack_plugin_worker", default-features = false }

crates/node_binding/napi-binding.d.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -556,7 +556,7 @@ export declare enum BuiltinPluginName {
556556
RealContentHashPlugin = 'RealContentHashPlugin',
557557
RemoveEmptyChunksPlugin = 'RemoveEmptyChunksPlugin',
558558
EnsureChunkConditionsPlugin = 'EnsureChunkConditionsPlugin',
559-
WarnCaseSensitiveModulesPlugin = 'WarnCaseSensitiveModulesPlugin',
559+
CaseSensitivePlugin = 'CaseSensitivePlugin',
560560
DataUriPlugin = 'DataUriPlugin',
561561
FileUriPlugin = 'FileUriPlugin',
562562
RuntimePlugin = 'RuntimePlugin',

crates/rspack_binding_api/Cargo.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -82,6 +82,7 @@ rspack_loader_testing = { workspace = true }
8282
rspack_napi_macros = { workspace = true }
8383
rspack_plugin_asset = { workspace = true }
8484
rspack_plugin_banner = { workspace = true }
85+
rspack_plugin_case_sensitive = { workspace = true }
8586
rspack_plugin_circular_dependencies = { workspace = true }
8687
rspack_plugin_copy = { workspace = true }
8788
rspack_plugin_css = { workspace = true }
@@ -115,7 +116,6 @@ rspack_plugin_size_limits = { workspace = true }
115116
rspack_plugin_split_chunks = { workspace = true }
116117
rspack_plugin_sri = { workspace = true }
117118
rspack_plugin_swc_js_minimizer = { workspace = true }
118-
rspack_plugin_warn_sensitive_module = { workspace = true }
119119
rspack_plugin_wasm = { workspace = true }
120120
rspack_plugin_web_worker_template = { workspace = true }
121121
rspack_plugin_worker = { workspace = true }

crates/rspack_binding_api/src/raw_options/raw_builtins/mod.rs

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,7 @@ use rspack_ids::{
4242
};
4343
use rspack_plugin_asset::AssetPlugin;
4444
use rspack_plugin_banner::BannerPlugin;
45+
use rspack_plugin_case_sensitive::CaseSensitivePlugin;
4546
use rspack_plugin_circular_dependencies::CircularDependencyRspackPlugin;
4647
use rspack_plugin_copy::{CopyRspackPlugin, CopyRspackPluginOptions};
4748
use rspack_plugin_css::CssPlugin;
@@ -94,7 +95,6 @@ use rspack_plugin_schemes::{DataUriPlugin, FileUriPlugin};
9495
use rspack_plugin_size_limits::SizeLimitsPlugin;
9596
use rspack_plugin_sri::{SubresourceIntegrityPlugin, SubresourceIntegrityPluginOptions};
9697
use rspack_plugin_swc_js_minimizer::SwcJsMinimizerRspackPlugin;
97-
use rspack_plugin_warn_sensitive_module::WarnCaseSensitiveModulesPlugin;
9898
use rspack_plugin_wasm::{
9999
AsyncWasmPlugin, FetchCompileAsyncWasmPlugin, enable_wasm_loading_plugin,
100100
};
@@ -183,7 +183,7 @@ pub enum BuiltinPluginName {
183183
RealContentHashPlugin,
184184
RemoveEmptyChunksPlugin,
185185
EnsureChunkConditionsPlugin,
186-
WarnCaseSensitiveModulesPlugin,
186+
CaseSensitivePlugin,
187187
DataUriPlugin,
188188
FileUriPlugin,
189189
RuntimePlugin,
@@ -545,8 +545,8 @@ impl<'a> BuiltinPlugin<'a> {
545545
BuiltinPluginName::EnsureChunkConditionsPlugin => {
546546
plugins.push(EnsureChunkConditionsPlugin::default().boxed())
547547
}
548-
BuiltinPluginName::WarnCaseSensitiveModulesPlugin => {
549-
plugins.push(WarnCaseSensitiveModulesPlugin::default().boxed())
548+
BuiltinPluginName::CaseSensitivePlugin => {
549+
plugins.push(CaseSensitivePlugin::default().boxed())
550550
}
551551
BuiltinPluginName::DataUriPlugin => plugins.push(DataUriPlugin::default().boxed()),
552552
BuiltinPluginName::FileUriPlugin => plugins.push(FileUriPlugin::default().boxed()),

crates/rspack_core/src/compiler/mod.rs

Lines changed: 1 addition & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -2,15 +2,14 @@ mod rebuild;
22
use std::sync::{Arc, atomic::AtomicU32};
33

44
use futures::future::join_all;
5-
use itertools::Itertools;
65
use rspack_error::Result;
76
use rspack_fs::{IntermediateFileSystem, NativeFileSystem, ReadableFileSystem, WritableFileSystem};
87
use rspack_hook::define_hook;
98
use rspack_paths::{Utf8Path, Utf8PathBuf};
109
use rspack_sources::BoxSource;
1110
use rspack_tasks::{CompilerContext, within_compiler_context};
1211
use rspack_util::{node_path::NodePath, tracing_preset::TRACING_BENCH_TARGET};
13-
use rustc_hash::{FxHashMap as HashMap, FxHashSet as HashSet};
12+
use rustc_hash::FxHashMap as HashMap;
1413
use tracing::instrument;
1514

1615
pub use self::rebuild::CompilationRecords;
@@ -386,34 +385,6 @@ impl Compiler {
386385
.call(&mut self.compilation)
387386
.await?;
388387

389-
// Check for case-sensitive conflicts before emitting assets
390-
// Only check for filenames that differ in casing (not query strings)
391-
// Only report conflict if filenames have same lowercase but different casing
392-
let mut case_map: HashMap<String, HashSet<String>> = HashMap::default();
393-
for filename in self.compilation.assets().keys() {
394-
let (target_file, _query) = filename.split_once('?').unwrap_or((filename, ""));
395-
let lower_key = cow_utils::CowUtils::cow_to_lowercase(target_file);
396-
case_map
397-
.entry(lower_key.to_string())
398-
.or_default()
399-
.insert(target_file.to_string());
400-
}
401-
402-
// Found conflict: multiple filenames with same lowercase representation but different casing
403-
for (_lower_key, filenames) in case_map.iter() {
404-
// Only report conflict if there are multiple unique filenames (different casing)
405-
if filenames.len() > 1 {
406-
let filenames_str = filenames.iter().map(|f| format!(" - {f}")).join("\n");
407-
self.compilation.push_diagnostic(
408-
rspack_error::error!(
409-
"Prevent writing to file that only differs in casing or query string from already written file.\nThis will lead to a race-condition and corrupted files on case-insensitive file systems.\n{}",
410-
filenames_str
411-
)
412-
.into(),
413-
);
414-
}
415-
}
416-
417388
let mut new_emitted_asset_versions = HashMap::default();
418389

419390
rspack_futures::scope(|token| {

crates/rspack_plugin_warn_sensitive_module/Cargo.toml renamed to crates/rspack_plugin_case_sensitive/Cargo.toml

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,14 @@
11
[package]
2-
description = "rspack sensitive module plugin"
2+
description = "rspack case sensitive plugin"
33
edition.workspace = true
44
license = "MIT"
5-
name = "rspack_plugin_warn_sensitive_module"
5+
name = "rspack_plugin_case_sensitive"
66
repository = "https://github.com/web-infra-dev/rspack"
77
version.workspace = true
88

99
[dependencies]
1010
cow-utils = { workspace = true }
11+
itertools = { workspace = true }
1112
rspack_collections = { workspace = true }
1213
rspack_core = { workspace = true }
1314
rspack_error = { workspace = true }

crates/rspack_plugin_warn_sensitive_module/src/lib.rs renamed to crates/rspack_plugin_case_sensitive/src/lib.rs

Lines changed: 54 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,18 @@
11
// https://github.com/webpack/webpack/blob/main/lib/WarnCaseSensitiveModulesPlugin.js
22

3-
use std::collections::HashMap;
4-
53
use cow_utils::CowUtils;
4+
use itertools::Itertools;
65
use rspack_collections::{Identifier, IdentifierSet};
7-
use rspack_core::{Compilation, CompilationSeal, Logger, ModuleGraph, Plugin};
6+
use rspack_core::{Compilation, CompilationSeal, CompilerEmit, Logger, ModuleGraph, Plugin};
87
use rspack_error::{Diagnostic, Result};
98
use rspack_hook::{plugin, plugin_hook};
10-
use rustc_hash::{FxBuildHasher, FxHashMap};
9+
use rustc_hash::{FxBuildHasher, FxHashMap as HashMap, FxHashSet as HashSet};
1110

1211
#[plugin]
1312
#[derive(Debug, Default)]
14-
pub struct WarnCaseSensitiveModulesPlugin;
13+
pub struct CaseSensitivePlugin;
1514

16-
impl WarnCaseSensitiveModulesPlugin {
15+
impl CaseSensitivePlugin {
1716
pub fn create_sensitive_modules_warning(
1817
&self,
1918
modules: Vec<Identifier>,
@@ -41,18 +40,28 @@ impl WarnCaseSensitiveModulesPlugin {
4140

4241
message
4342
}
43+
44+
pub fn create_sensitive_assets_warning(&self, filenames: &HashSet<String>) -> String {
45+
let filenames_str = filenames.iter().map(|f| format!(" - {f}")).join("\n");
46+
format!(
47+
r#"Prevent writing to file that only differs in casing or query string from already written file.
48+
This will lead to a race-condition and corrupted files on case-insensitive file systems.
49+
{}"#,
50+
filenames_str
51+
)
52+
}
4453
}
4554

46-
#[plugin_hook(CompilationSeal for WarnCaseSensitiveModulesPlugin)]
55+
#[plugin_hook(CompilationSeal for CaseSensitivePlugin)]
4756
async fn seal(&self, compilation: &mut Compilation) -> Result<()> {
4857
let logger = compilation.get_logger(self.name());
4958
let start = logger.time("check case sensitive modules");
5059
let mut diagnostics: Vec<Diagnostic> = vec![];
5160
let module_graph = compilation.get_module_graph();
5261
let all_modules = module_graph.modules();
53-
let mut not_conflect: FxHashMap<String, Identifier> =
62+
let mut not_conflect: HashMap<String, Identifier> =
5463
HashMap::with_capacity_and_hasher(all_modules.len(), FxBuildHasher);
55-
let mut conflict: FxHashMap<String, IdentifierSet> = FxHashMap::default();
64+
let mut conflict: HashMap<String, IdentifierSet> = HashMap::default();
5665

5766
for module in all_modules.values() {
5867
// Ignore `data:` URLs, because it's not a real path
@@ -87,7 +96,7 @@ async fn seal(&self, compilation: &mut Compilation) -> Result<()> {
8796
let mut case_modules = set.iter().copied().collect::<Vec<_>>();
8897
case_modules.sort_unstable();
8998
diagnostics.push(Diagnostic::warn(
90-
"Sensitive Modules Warn".to_string(),
99+
"Sensitive Warn".to_string(),
91100
self.create_sensitive_modules_warning(case_modules, &compilation.get_module_graph()),
92101
));
93102
}
@@ -97,16 +106,49 @@ async fn seal(&self, compilation: &mut Compilation) -> Result<()> {
97106
Ok(())
98107
}
99108

109+
#[plugin_hook(CompilerEmit for CaseSensitivePlugin)]
110+
async fn emit(&self, compilation: &mut Compilation) -> Result<()> {
111+
let mut diagnostics: Vec<Diagnostic> = vec![];
112+
113+
// Check for case-sensitive conflicts before emitting assets
114+
// Only check for filenames that differ in casing (not query strings)
115+
// Only report conflict if filenames have same lowercase but different casing
116+
let mut case_map: HashMap<String, HashSet<String>> = HashMap::default();
117+
for filename in compilation.assets().keys() {
118+
let (target_file, _query) = filename.split_once('?').unwrap_or((filename, ""));
119+
let lower_key = cow_utils::CowUtils::cow_to_lowercase(target_file);
120+
case_map
121+
.entry(lower_key.to_string())
122+
.or_default()
123+
.insert(target_file.to_string());
124+
}
125+
126+
// Found conflict: multiple filenames with same lowercase representation but different casing
127+
for (_lower_key, filenames) in case_map.iter() {
128+
// Only report conflict if there are multiple unique filenames (different casing)
129+
if filenames.len() > 1 {
130+
diagnostics.push(Diagnostic::warn(
131+
"Sensitive Warn".to_string(),
132+
self.create_sensitive_assets_warning(filenames),
133+
));
134+
}
135+
}
136+
137+
compilation.extend_diagnostics(diagnostics);
138+
Ok(())
139+
}
140+
100141
// This Plugin warns when there are case sensitive modules in the compilation
101142
// which can cause unexpected behavior when deployed on a case-insensitive environment
102143
// it is executed in hook `compilation.seal`
103-
impl Plugin for WarnCaseSensitiveModulesPlugin {
144+
impl Plugin for CaseSensitivePlugin {
104145
fn name(&self) -> &'static str {
105-
"rspack.WarnCaseSensitiveModulesPlugin"
146+
"rspack.CaseSensitivePlugin"
106147
}
107148

108149
fn apply(&self, ctx: &mut rspack_core::ApplyContext<'_>) -> Result<()> {
109150
ctx.compilation_hooks.seal.tap(seal::new(self));
151+
ctx.compiler_hooks.emit.tap(emit::new(self));
110152
Ok(())
111153
}
112154
}

packages/rspack/etc/core.api.md

Lines changed: 15 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -617,6 +617,19 @@ interface CallExpression extends ExpressionBase {
617617
// @public (undocumented)
618618
type CallFn = (...args: any[]) => any;
619619

620+
// @public (undocumented)
621+
const CaseSensitivePlugin: {
622+
new (): {
623+
name: string;
624+
_args: [];
625+
affectedHooks: keyof CompilerHooks | undefined;
626+
raw(compiler: Compiler): BuiltinPlugin;
627+
apply(compiler: Compiler): void;
628+
};
629+
};
630+
export { CaseSensitivePlugin }
631+
export { CaseSensitivePlugin as WarnCaseSensitiveModulesPlugin }
632+
620633
// @public (undocumented)
621634
interface CatchClause extends Node_4, HasSpan {
622635
// (undocumented)
@@ -6584,6 +6597,8 @@ declare namespace rspackExports {
65846597
ProgressPluginArgument,
65856598
ProvidePluginOptions,
65866599
BannerPlugin,
6600+
CaseSensitivePlugin,
6601+
CaseSensitivePlugin as WarnCaseSensitiveModulesPlugin,
65876602
DefinePlugin,
65886603
DynamicEntryPlugin,
65896604
EntryPlugin,
@@ -6595,7 +6610,6 @@ declare namespace rspackExports {
65956610
ProgressPlugin,
65966611
ProvidePlugin,
65976612
RuntimePlugin,
6598-
WarnCaseSensitiveModulesPlugin,
65996613
DllPlugin,
66006614
DllPluginOptions,
66016615
DllReferencePlugin,
@@ -9148,17 +9162,6 @@ class VirtualModulesPlugin {
91489162
writeModule(filePath: string, contents: string): void;
91499163
}
91509164

9151-
// @public (undocumented)
9152-
export const WarnCaseSensitiveModulesPlugin: {
9153-
new (): {
9154-
name: string;
9155-
_args: [];
9156-
affectedHooks: keyof CompilerHooks | undefined;
9157-
raw(compiler: Compiler): BuiltinPlugin;
9158-
apply(compiler: Compiler): void;
9159-
};
9160-
};
9161-
91629165
// @public (undocumented)
91639166
interface Wasm {
91649167
// (undocumented)

packages/rspack/scripts/check-documentation-coverage.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -111,7 +111,8 @@ function checkPluginsDocumentationCoverage() {
111111
"RuntimePlugin", // This plugin only provides hooks, should not be used separately
112112
"RsdoctorPlugin", // This plugin is not stable yet
113113
"RstestPlugin", // This plugin is not stable yet
114-
"RslibPlugin" // This plugin is not stable yet
114+
"RslibPlugin", // This plugin is not stable yet
115+
"WarnCaseSensitiveModulesPlugin" // This plugin is deprecated and will be replaced with CaseSensitivePlugin
115116
];
116117

117118
const undocumentedPlugins = Array.from(implementedPlugins).filter(

0 commit comments

Comments
 (0)