Skip to content

Regression: require.resolve(".", { paths }) behaves differently from require.resolve("./", { paths })Β #47000

Description

@tolmasky

Version

18.14.2

Platform

22.3.0 Darwin Kernel Version 22.3.0

Subsystem

loader

What steps will reproduce the bug?

home:/$ docker run --rm -it node:18.14.2-slim bash
node:/# echo "['./', '.'].map(request => console.log(require.resolve(request, { paths: ['..'] })))" > index.js
node:/# mkdir test && cd test
node:/# node ../index.js

This produces:

> /index.js
> node:internal/modules/cjs/loader:1078
  throw err;
  ^

Error: Cannot find module '.'
Require stack:
- /index.js
    at Module._resolveFilename (node:internal/modules/cjs/loader:1075:15)
    at Function.resolve (node:internal/modules/cjs/helpers:116:19)
    at /index.js:1:48
    at Array.map (<anonymous>)
    at Object.<anonymous> (/index.js:1:13)
    at Module._compile (node:internal/modules/cjs/loader:1254:14)
    at Module._extensions..js (node:internal/modules/cjs/loader:1308:10)
    at Module.load (node:internal/modules/cjs/loader:1117:32)
    at Module._load (node:internal/modules/cjs/loader:958:12)
    at Function.executeUserEntryPoint [as runMain] (node:internal/modules/run_main:81:12) {
  code: 'MODULE_NOT_FOUND',
  requireStack: [ '/index.js' ]
}
node:/#

The same is true of .. and ../. I leave that as an exercise to the reader. If you rerun the above in node 10, you get the same log for both, as expected:

home:/$ docker run --rm -it node:10.24.1-slim bash
node:/# echo "['./', '.'].map(request => console.log(require.resolve(request, { paths: ['..'] })))" > index.js
node:/# mkdir test && cd test
node:/# node ../index.js
/index.js
/index.js
node:/#

If you want to get more clever, you can put a index.js inside of the test directory, and then it'll just give you different results without crashing:

home:/$ docker run --rm -it node:18.14.2-slim bash
node:/# echo "['./', '.'].map(request => console.log(require.resolve(request, { paths: ['..'] })))" > index.js
node:/# mkdir test && cd test
node:/# echo "['./', '.'].map(request => console.log(require.resolve(request, { paths: ['..'] })))" > index.js
node:/# node ../index.js
/index.js
/test/index.js
node:/#

How often does it reproduce? Is there a required condition?

Every time.

What is the expected behavior?

The same way it worked in node 10:

home:/$ docker run --rm -it node:10.24.1-slim bash
node:/# echo "['./', '.'].map(request => console.log(require.resolve(request, { paths: ['..'] })))" > index.js
node:/# mkdir test && cd test
node:/# node ../index.js
/index.js
/index.js
node:/#

What do you see instead?

Different file paths, crash, etc.

Additional information

This seems to be a result of this pull request: #27598

The code in Module._resolveFilename only checks for "./" and "../" to determine if the path is a relative path:

const isRelative = StringPrototypeStartsWith(request, './') ||
StringPrototypeStartsWith(request, '../') ||
((isWindows && StringPrototypeStartsWith(request, '.\\')) ||
StringPrototypeStartsWith(request, '..\\'));

Adding === "." and === ".." should fix the problem. Or, given that earlier in the file in Module._findPath the same thing is calculated slightly differently:

const isRelative = StringPrototypeCharCodeAt(request, 0) === CHAR_DOT &&
(
request.length === 1 ||
StringPrototypeCharCodeAt(request, 1) === CHAR_FORWARD_SLASH ||
(isWindows && StringPrototypeCharCodeAt(request, 1) === CHAR_BACKWARD_SLASH) ||
(StringPrototypeCharCodeAt(request, 1) === CHAR_DOT && ((
request.length === 2 ||
StringPrototypeCharCodeAt(request, 2) === CHAR_FORWARD_SLASH) ||
(isWindows && StringPrototypeCharCodeAt(request, 2) === CHAR_BACKWARD_SLASH)))

Perhaps this should be factored out into a isRelativePath function since it seems to be tricky enough to get right to not rely on separate inlined implementations in various different places.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions