@@ -179,10 +179,107 @@ describe("alias", () => {
179179 } ) ;
180180 } ) ;
181181
182+ // Regression guard for the `firstCharCode` screen added in
183+ // AliasUtils. Absolute-path aliases must keep matching both when the
184+ // request is a raw absolute-path string (`request.request`, hits the
185+ // `nameWithSlash` branch at the `raw-resolve` hook) and after
186+ // `JoinRequestPlugin` has turned it into a joined `request.path`
187+ // (hits the `absolutePath` branch at the `file` hook). If the
188+ // char-code screen ever diverges from the startsWith comparison
189+ // these resolves silently fall through to the original target.
190+ it ( "should not skip absolute path aliasing" , ( ) => {
191+ expect ( resolver . resolveSync ( { } , "/" , "/d/dir" ) ) . toBe ( "/c/dir/index" ) ;
192+ expect ( resolver . resolveSync ( { } , "/" , "/d/dir/index" ) ) . toBe ( "/c/dir/index" ) ;
193+ expect ( resolver . resolveSync ( { } , "/" , "d/dir/index" ) ) . toBe ( "/c/dir/index" ) ;
194+ expect ( resolver . resolveSync ( { } , "/" , "d" ) ) . toBe ( "/c/index" ) ;
195+ } ) ;
196+
182197 it ( "should resolve a wildcard alias with multiple targets correctly" , ( ) => {
183198 expect ( resolver . resolveSync ( { } , "/" , "shared/b" ) ) . toBe ( "/src/components/b" ) ;
184199 } ) ;
185200
201+ // Absolute-path aliasing — OS-native (posix on Linux CI, backslash
202+ // on Windows CI).
203+ //
204+ // These cases drive the full `resolver.resolve` pipeline against the
205+ // real `test/fixtures/` tree with `CachedInputFileSystem` — no
206+ // filesystem or matcher mocking. Paths are built through the Node
207+ // `path` module, so on Linux the alias name uses forward slashes
208+ // and on Windows the same test code exercises native backslashes.
209+ // That covers the regression the previous commit fixed
210+ // (`nameWithSlash` hardcodes `/`, which silently skipped native
211+ // backslash windows subpaths at the `raw-resolve` hook).
212+ describe ( "absolute path aliasing (OS-native)" , ( ) => {
213+ const fixturesDir = path . resolve ( __dirname , "fixtures" ) ;
214+ // `imaginary-foo` deliberately does NOT exist on disk so the
215+ // only way the resolver can succeed for requests under it is
216+ // through the alias.
217+ const aliasName = path . resolve ( fixturesDir , "imaginary-foo" ) ;
218+ const aliasTarget = path . resolve ( fixturesDir , "foo" ) ;
219+ const expectedIndex = path . resolve ( aliasTarget , "index.js" ) ;
220+
221+ const absResolver = ResolverFactory . createResolver ( {
222+ extensions : [ ".js" ] ,
223+ alias : { [ aliasName ] : aliasTarget } ,
224+ fileSystem : nodeFileSystem ,
225+ } ) ;
226+
227+ it ( "aliases a raw absolute subpath request (raw-resolve hook)" , ( done ) => {
228+ // path.join uses the OS-native separator, so on windows this
229+ // is `...\\imaginary-foo\\index` and exercises the backslash
230+ // fallback; on linux it is `.../imaginary-foo/index`.
231+ absResolver . resolve (
232+ { } ,
233+ fixturesDir ,
234+ path . join ( aliasName , "index" ) ,
235+ { } ,
236+ ( err , result ) => {
237+ if ( err ) return done ( err ) ;
238+ expect ( result ) . toBe ( expectedIndex ) ;
239+ done ( ) ;
240+ } ,
241+ ) ;
242+ } ) ;
243+
244+ it ( "aliases a request that equals the alias name (exact equality)" , ( done ) => {
245+ absResolver . resolve ( { } , fixturesDir , aliasName , { } , ( err , result ) => {
246+ if ( err ) return done ( err ) ;
247+ expect ( result ) . toBe ( expectedIndex ) ;
248+ done ( ) ;
249+ } ) ;
250+ } ) ;
251+
252+ it ( "aliases after JoinRequestPlugin normalizes the path (file hook)" , ( done ) => {
253+ // A relative request hits `JoinRequestPlugin` first, which
254+ // turns it into `request.path` with `request.request` set to
255+ // `undefined`. That hits the `absolutePath`-only branch of
256+ // the matcher.
257+ absResolver . resolve (
258+ { } ,
259+ fixturesDir ,
260+ `./${ path . basename ( aliasName ) } /index` ,
261+ { } ,
262+ ( err , result ) => {
263+ if ( err ) return done ( err ) ;
264+ expect ( result ) . toBe ( expectedIndex ) ;
265+ done ( ) ;
266+ } ,
267+ ) ;
268+ } ) ;
269+
270+ it ( "does not fire for a different absolute prefix sharing the same head" , ( done ) => {
271+ // `imaginary-food` shares `imaginary-foo` as a prefix but
272+ // not `imaginary-foo<sep>`. The alias must not fire, and
273+ // since `imaginary-food` does not exist, the resolve fails.
274+ const requestPath = path . join ( fixturesDir , "imaginary-food" , "index" ) ;
275+ absResolver . resolve ( { } , fixturesDir , requestPath , { } , ( err , result ) => {
276+ expect ( err ) . toBeInstanceOf ( Error ) ;
277+ expect ( result ) . toBeFalsy ( ) ;
278+ done ( ) ;
279+ } ) ;
280+ } ) ;
281+ } ) ;
282+
186283 // Regression tests for the watch-mode fallback described in
187284 // https://github.com/webpack/enhanced-resolve/issues/395 and
188285 // https://github.com/webpack/enhanced-resolve/issues/250.
0 commit comments