Skip to content

Commit f5adeee

Browse files
test(alias): guard that absolute path aliasing is not skipped (#553)
1 parent faa178f commit f5adeee

2 files changed

Lines changed: 109 additions & 1 deletion

File tree

lib/AliasUtils.js

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -132,11 +132,22 @@ function aliasResolveHandler(
132132
/** @type {boolean} */
133133
let shouldStop = false;
134134

135+
// For absolute-name aliases, accept the normalized
136+
// `absolutePath` form as well as the raw `nameWithSlash`.
137+
// `nameWithSlash` unconditionally appends `/`, so a raw
138+
// windows request with native backslashes
139+
// (e.g. `C:\\abs\\foo\\baz` against `name: "C:\\abs\\foo"`)
140+
// otherwise fails `startsWith("C:\\abs\\foo/")` and is
141+
// silently skipped. The `!hasRequestString` branch already
142+
// uses `absolutePath`; mirroring it here closes the gap
143+
// without changing any existing matches.
135144
const matchRequest =
136145
innerRequest === item.name ||
137146
(!item.onlyModule &&
138147
(hasRequestString
139-
? innerRequest.startsWith(item.nameWithSlash)
148+
? innerRequest.startsWith(item.nameWithSlash) ||
149+
(item.absolutePath !== null &&
150+
innerRequest.startsWith(item.absolutePath))
140151
: item.absolutePath !== null &&
141152
innerRequest.startsWith(item.absolutePath)));
142153

test/alias.test.js

Lines changed: 97 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -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

Comments
 (0)