Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix: add global path in require.resolve.paths #13633

Merged
merged 10 commits into from Dec 2, 2022
2 changes: 2 additions & 0 deletions CHANGELOG.md
Expand Up @@ -4,6 +4,8 @@

### Fixes

- `[jest-resolve]` add global paths to `require.resolve.paths` ([#13633](https://github.com/facebook/jest/pull/13633))

### Chore & Maintenance

- `[@jest/fake-timers]` Update `@sinonjs/fake-timers` ([#13612](https://github.com/facebook/jest/pull/13612))
Expand Down
4 changes: 2 additions & 2 deletions e2e/__tests__/__snapshots__/moduleNameMapper.test.ts.snap
Expand Up @@ -41,7 +41,7 @@ exports[`moduleNameMapper wrong array configuration 1`] = `
12 | module.exports = () => 'test';
13 |

at createNoMappedModuleFoundError (../../packages/jest-resolve/build/resolver.js:752:17)
at createNoMappedModuleFoundError (../../packages/jest-resolve/build/resolver.js:758:17)
at Object.require (index.js:10:1)
at Object.require (__tests__/index.js:10:20)"
`;
Expand Down Expand Up @@ -71,7 +71,7 @@ exports[`moduleNameMapper wrong configuration 1`] = `
12 | module.exports = () => 'test';
13 |

at createNoMappedModuleFoundError (../../packages/jest-resolve/build/resolver.js:752:17)
at createNoMappedModuleFoundError (../../packages/jest-resolve/build/resolver.js:758:17)
at Object.require (index.js:10:1)
at Object.require (__tests__/index.js:10:20)"
`;
47 changes: 47 additions & 0 deletions packages/jest-resolve/src/__tests__/resolve.test.ts
Expand Up @@ -707,3 +707,50 @@ describe('Resolver.getModulePaths() -> nodeModulesPaths()', () => {
expect(dirs_actual).toEqual(expect.arrayContaining(dirs_expected));
});
});

describe('Resolver.getGlobalPaths()', () => {
const _path = path;
let moduleMap: IModuleMap;
beforeEach(() => {
moduleMap = ModuleMap.create('/');
});

it('return global paths with npm package', () => {
jest.doMock('path', () => _path.posix);
const resolver = new Resolver(moduleMap, {} as ResolverConfig);
const globalPaths = resolver.getGlobalPaths('jest');
globalPaths.forEach(globalPath =>
expect(require.resolve.paths('jest')).toContain(globalPath),
);
});

it('return empty array with builtin module', () => {
jest.doMock('path', () => _path.posix);
const resolver = new Resolver(moduleMap, {} as ResolverConfig);
const globalPaths = resolver.getGlobalPaths('fs');
expect(globalPaths).toStrictEqual([]);
});

it('return global paths with absolute path', () => {
jest.doMock('path', () => _path.posix);
const resolver = new Resolver(moduleMap, {} as ResolverConfig);
const globalPaths = resolver.getGlobalPaths('/');
globalPaths.forEach(globalPath =>
expect(require.resolve.paths('/')).toContain(globalPath),
);
});

it('return empty array with relative path', () => {
jest.doMock('path', () => _path.posix);
const resolver = new Resolver(moduleMap, {} as ResolverConfig);
const globalPaths = resolver.getGlobalPaths('./');
expect(globalPaths).toStrictEqual([]);
});

it('return empty array without module name', () => {
jest.doMock('path', () => _path.posix);
const resolver = new Resolver(moduleMap, {} as ResolverConfig);
const globalPaths = resolver.getGlobalPaths();
expect(globalPaths).toStrictEqual([]);
});
});
14 changes: 14 additions & 0 deletions packages/jest-resolve/src/nodeModulesPaths.ts
Expand Up @@ -70,3 +70,17 @@ export default function nodeModulesPaths(

return options.paths ? dirs.concat(options.paths) : dirs;
}

function findGlobalPaths(): Array<string> {
const {root} = path.parse(process.cwd());
const globalPath = path.join(root, 'node_modules');
const resolvePaths = require.resolve.paths('/');

if (resolvePaths) {
// the global paths start one after the root node_modules
const rootIndex = resolvePaths.indexOf(globalPath);
return rootIndex > -1 ? resolvePaths.slice(rootIndex + 1) : [];
}
return [];
}
export const GlobalPaths = findGlobalPaths();
10 changes: 9 additions & 1 deletion packages/jest-resolve/src/resolver.ts
Expand Up @@ -20,7 +20,7 @@ import defaultResolver, {
} from './defaultResolver';
import {clearFsCache} from './fileWalkers';
import isBuiltinModule from './isBuiltinModule';
import nodeModulesPaths from './nodeModulesPaths';
import nodeModulesPaths, {GlobalPaths} from './nodeModulesPaths';
import shouldLoadAsEsm, {clearCachedLookups} from './shouldLoadAsEsm';
import type {ResolverConfig} from './types';

Expand Down Expand Up @@ -526,6 +526,14 @@ export default class Resolver {
return paths;
}

getGlobalPaths(moduleName?: string): Array<string> {
if (!moduleName || moduleName[0] === '.' || this.isCoreModule(moduleName)) {
return [];
}

return GlobalPaths;
}

getModuleID(
virtualMocks: Map<string, boolean>,
from: string,
Expand Down
Expand Up @@ -132,8 +132,13 @@ describe('Runtime requireModule', () => {
'RegularModule',
);
expect(exports.paths.length).toBeGreaterThan(0);
exports.paths.forEach(path => {
expect(moduleDirectories.some(dir => path.endsWith(dir))).toBe(true);
const root = path.parse(process.cwd()).root;
const globalPath = path.join(root, 'node_modules');
const rootIndex = exports.paths.findIndex(path => path === globalPath);
exports.paths.forEach((path, index) => {
if (index <= rootIndex) {
expect(moduleDirectories.some(dir => path.endsWith(dir))).toBe(true);
}
});
});

Expand Down
20 changes: 16 additions & 4 deletions packages/jest-runtime/src/index.ts
Expand Up @@ -1066,7 +1066,13 @@ export default class Runtime {
} else {
// Only include the fromPath if a moduleName is given. Else treat as root.
const fromPath = moduleName ? from : null;
this._execModule(localModule, options, moduleRegistry, fromPath);
this._execModule(
localModule,
options,
moduleRegistry,
fromPath,
moduleName,
);
}
localModule.loaded = true;
}
Expand Down Expand Up @@ -1398,6 +1404,7 @@ export default class Runtime {
}

private _requireResolvePaths(from: string, moduleName?: string) {
const fromDir = path.resolve(from, '..');
if (moduleName == null) {
throw new Error(
'The first argument to require.resolve.paths must be a string. Received null or undefined.',
Expand All @@ -1410,19 +1417,22 @@ export default class Runtime {
}

if (moduleName[0] === '.') {
return [path.resolve(from, '..')];
return [fromDir];
}
if (this._resolver.isCoreModule(moduleName)) {
return null;
}
return this._resolver.getModulePaths(path.resolve(from, '..'));
const modulePaths = this._resolver.getModulePaths(fromDir);
const globalPaths = this._resolver.getGlobalPaths(moduleName);
return [...modulePaths, ...globalPaths];
}

private _execModule(
localModule: InitialModule,
options: InternalModuleOptions | undefined,
moduleRegistry: ModuleRegistry,
from: string | null,
moduleName?: string,
) {
if (this.isTornDown) {
this._logFormattedReferenceError(
Expand Down Expand Up @@ -1454,8 +1464,10 @@ export default class Runtime {
return moduleRegistry.get(key) || null;
},
});
const modulePaths = this._resolver.getModulePaths(module.path);
const globalPaths = this._resolver.getGlobalPaths(moduleName);
module.paths = [...modulePaths, ...globalPaths];

module.paths = this._resolver.getModulePaths(module.path);
Object.defineProperty(module, 'require', {
value: this._createRequireImplementation(module, options),
});
Expand Down