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
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)"
`;
50 changes: 49 additions & 1 deletion packages/jest-resolve/src/__tests__/resolve.test.ts
Expand Up @@ -13,7 +13,7 @@ import {IModuleMap, ModuleMap} from 'jest-haste-map';
import userResolver from '../__mocks__/userResolver';
import userResolverAsync from '../__mocks__/userResolverAsync';
import defaultResolver, {PackageFilter} from '../defaultResolver';
import nodeModulesPaths from '../nodeModulesPaths';
import nodeModulesPaths, {findGlobalPaths} from '../nodeModulesPaths';
import Resolver from '../resolver';
import type {ResolverConfig} from '../types';

Expand Down Expand Up @@ -707,3 +707,51 @@ describe('Resolver.getModulePaths() -> nodeModulesPaths()', () => {
expect(dirs_actual).toEqual(expect.arrayContaining(dirs_expected));
});
});

describe('findGlobalPaths', () => {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

can be removed I think, it's kind of internal and we're testing it through Resolver.getGlobalPaths

const paths = findGlobalPaths();
expect(paths.length).toBeGreaterThanOrEqual(1);
});

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');
expect(globalPaths.length).toBeGreaterThan(0);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Probably we could test this more precisely with something like

Suggested change
expect(globalPaths.length).toBeGreaterThan(0);
globalPaths.forEach(globalPath => expect(require.resolve.paths('jest')).toContain(globalPath))

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks! I will update it

});

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 empty array with absolute path', () => {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
it('return empty array with absolute path', () => {
it('return global paths with absolute path', () => {

jest.doMock('path', () => _path.posix);
const resolver = new Resolver(moduleMap, {} as ResolverConfig);
const globalPaths = resolver.getGlobalPaths('/');
expect(globalPaths.length).toBeGreaterThan(0);
});

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([]);
});
});
18 changes: 18 additions & 0 deletions packages/jest-resolve/src/nodeModulesPaths.ts
Expand Up @@ -70,3 +70,21 @@ export default function nodeModulesPaths(

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

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

if (resolvePaths) {
// find the rootIndex and remain the global paths
const rootIndex = resolvePaths.findIndex(
resolvePath => resolvePath === 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