Repository: nodejs/node
Pull request: fix `require.resolve` not considering paths `.` and `..` relative

Fixes: https://github.com/nodejs/node/issues/47000

<!--
Before submitting a pull request, please read:

- the CONTRIBUTING guide at https://github.com/nodejs/node/blob/HEAD/CONTRIBUTING.md
- the commit message formatting guidelines at
  https://github.com/nodejs/node/blob/HEAD/doc/contributing/pull-requests.md#commit-message-guidelines

For code changes:
1. Include tests for any bug fixes or new features.
2. Update documentation if relevant.
3. Ensure that `make -j4 test` (UNIX), or `vcbuild test` (Windows) passes.

If you believe this PR should be highlighted in the Node.js CHANGELOG
please add the `notable-change` label.

Developer's Certificate of Origin 1.1

By making a contribution to this project, I certify that:

(a) The contribution was created in whole or in part by me and I
    have the right to submit it under the open source license
    indicated in the file; or

(b) The contribution is based upon previous work that, to the best
    of my knowledge, is covered under an appropriate open source
    license and I have the right under that license to submit that
    work with modifications, whether created in whole or in part
    by me, under the same open source license (unless I am
    permitted to submit under a different license), as indicated
    in the file; or

(c) The contribution was provided directly to me by some other
    person who certified (a), (b) or (c) and I have not modified
    it.

(d) I understand and agree that this project and the contribution
    are public and that a record of the contribution (including all
    personal information I submit with it, including my sign-off) is
    maintained indefinitely and may be redistributed consistent with
    this project or the open source license(s) involved.
-->


Changed files:
filename: lib/internal/modules/cjs/loader.js
patch: @@ -722,18 +722,8 @@ Module._findPath = function(request, paths, isMain, conditions = getCjsCondition
       )
     ));
 
-  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)))
-    );
   let insidePath = true;
-  if (isRelative) {
+  if (isRelative(request)) {
     const normalizedRequest = path.normalize(request);
     if (StringPrototypeStartsWith(normalizedRequest, '..')) {
       insidePath = false;
@@ -1328,12 +1318,7 @@ Module._resolveFilename = function(request, parent, isMain, options) {
 
   if (typeof options === 'object' && options !== null) {
     if (ArrayIsArray(options.paths)) {
-      const isRelative = StringPrototypeStartsWith(request, './') ||
-          StringPrototypeStartsWith(request, '../') ||
-          ((isWindows && StringPrototypeStartsWith(request, '.\\')) ||
-          StringPrototypeStartsWith(request, '..\\'));
-
-      if (isRelative) {
+      if (isRelative(request)) {
         paths = options.paths;
       } else {
         const fakeParent = new Module('', null);
@@ -1978,6 +1963,21 @@ function createRequire(filename) {
   return createRequireFromPath(filepath);
 }
 
+/**
+ * Checks if a path is relative
+ * @param {string} path the target path
+ * @returns {boolean} true if the path is relative, false otherwise
+ */
+function isRelative(path) {
+  if (StringPrototypeCharCodeAt(path, 0) !== CHAR_DOT) { return false; }
+
+  return path.length === 1 || path === '..' ||
+  StringPrototypeStartsWith(path, './') ||
+  StringPrototypeStartsWith(path, '../') ||
+  ((isWindows && StringPrototypeStartsWith(path, '.\\')) ||
+  StringPrototypeStartsWith(path, '..\\'));
+}
+
 Module.createRequire = createRequire;
 
 /**

filename: test/fixtures/module-require/relative/subdir/relative-subdir.js
patch: @@ -0,0 +1 @@
+exports.value = 'relative subdir';

filename: test/parallel/test-require-resolve-opts-paths-relative.js
patch: @@ -0,0 +1,43 @@
+'use strict';
+
+const common = require('../common');
+const assert = require('assert');
+const fixtures = require('../common/fixtures');
+
+if (!common.isMainThread)
+  common.skip('process.chdir is not available in Workers');
+
+const subdir = fixtures.path('module-require', 'relative', 'subdir');
+
+process.chdir(subdir);
+
+// Parent directory paths (`..`) work as intended
+{
+  assert(require.resolve('.', { paths: ['../'] }).endsWith('index.js'));
+  assert(require.resolve('./index.js', { paths: ['../'] }).endsWith('index.js'));
+
+  // paths: [".."] should resolve like paths: ["../"]
+  assert(require.resolve('.', { paths: ['..'] }).endsWith('index.js'));
+  assert(require.resolve('./index.js', { paths: ['..'] }).endsWith('index.js'));
+}
+
+process.chdir('..');
+
+// Current directory paths (`.`) work as intended
+{
+  assert(require.resolve('.', { paths: ['.'] }).endsWith('index.js'));
+  assert(require.resolve('./index.js', { paths: ['./'] }).endsWith('index.js'));
+
+  // paths: ["."] should resolve like paths: ["../"]
+  assert(require.resolve('.', { paths: ['.'] }).endsWith('index.js'));
+  assert(require.resolve('./index.js', { paths: ['.'] }).endsWith('index.js'));
+}
+
+// Sub directory paths work as intended
+{
+  // assert.deepStrictEqual(fs.readdirSync('./subdir'), [5]);
+  assert(require.resolve('./relative-subdir.js', { paths: ['./subdir'] }).endsWith('relative-subdir.js'));
+
+  // paths: ["subdir"] should resolve like paths: ["./subdir"]
+  assert(require.resolve('./relative-subdir.js', { paths: ['subdir'] }).endsWith('relative-subdir.js'));
+}