Skip to content

Commit 6940488

Browse files
authored
fix(no-deprecated-functions): remove process.cwd from resolve paths (#889)
1 parent ffc9392 commit 6940488

File tree

7 files changed

+347
-180
lines changed

7 files changed

+347
-180
lines changed

README.md

Lines changed: 27 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -59,23 +59,43 @@ doing:
5959
This is included in all configs shared by this plugin, so can be omitted if
6060
extending them.
6161

62-
The behaviour of some rules (specifically `no-deprecated-functions`) change
63-
depending on the version of `jest` being used.
62+
### Jest `version` setting
6463

65-
This setting is detected automatically based off the version of the `jest`
66-
package installed in `node_modules`, but it can also be provided explicitly if
67-
desired:
64+
The behaviour of some rules (specifically [`no-deprecated-functions`][]) change
65+
depending on the version of Jest being used.
66+
67+
By default, this plugin will attempt to determine to locate Jest using
68+
`require.resolve`, meaning it will start looking in the closest `node_modules`
69+
folder to the file being linted and work its way up.
70+
71+
Since we cache the automatically determined version, if you're linting
72+
sub-folders that have different versions of Jest, you may find that the wrong
73+
version of Jest is considered when linting. You can work around this by
74+
providing the Jest version explicitly in nested ESLint configs:
6875

6976
```json
7077
{
7178
"settings": {
7279
"jest": {
73-
"version": 26
80+
"version": 27
7481
}
7582
}
7683
}
7784
```
7885

86+
To avoid hard-coding a number, you can also fetch it from the installed version
87+
of Jest if you use a JavaScript config file such as `.eslintrc.js`:
88+
89+
```js
90+
module.exports = {
91+
settings: {
92+
jest: {
93+
version: require('jest/package.json').version,
94+
},
95+
},
96+
};
97+
```
98+
7999
## Shareable configurations
80100

81101
### Recommended
@@ -226,3 +246,4 @@ https://github.com/istanbuljs/eslint-plugin-istanbul
226246
[suggest]: https://img.shields.io/badge/-suggest-yellow.svg
227247
[fixable]: https://img.shields.io/badge/-fixable-green.svg
228248
[style]: https://img.shields.io/badge/-style-blue.svg
249+
[`no-deprecated-functions`]: docs/rules/no-deprecated-functions.md

docs/rules/no-deprecated-functions.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,11 @@ either been renamed for clarity, or replaced with more powerful APIs.
66
While typically these deprecated functions are kept in the codebase for a number
77
of majors, eventually they are removed completely.
88

9+
This rule requires knowing which version of Jest you're using - see
10+
[this section of the readme](../../README.md#jest-version-setting) for details
11+
on how that is obtained automatically and how you can explicitly provide a
12+
version if needed.
13+
914
## Rule details
1015

1116
This rule warns about calls to deprecated functions, and provides details on

src/index.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@ const importDefault = (moduleName: string) =>
2626
interopRequireDefault(require(moduleName)).default;
2727

2828
const rulesDir = join(__dirname, 'rules');
29-
const excludedFiles = ['__tests__', 'utils'];
29+
const excludedFiles = ['__tests__', 'detectJestVersion', 'utils'];
3030

3131
const rules = readdirSync(rulesDir)
3232
.map(rule => parse(rule).name)
Lines changed: 227 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,227 @@
1+
import { spawnSync } from 'child_process';
2+
import * as fs from 'fs';
3+
import * as os from 'os';
4+
import * as path from 'path';
5+
import { JSONSchemaForNPMPackageJsonFiles } from '@schemastore/package';
6+
import { create } from 'ts-node';
7+
import { detectJestVersion } from '../detectJestVersion';
8+
9+
const compileFnCode = (pathToFn: string) => {
10+
const fnContents = fs.readFileSync(pathToFn, 'utf-8');
11+
12+
return create({
13+
transpileOnly: true,
14+
compilerOptions: { sourceMap: false },
15+
}).compile(fnContents, pathToFn);
16+
};
17+
const compiledFn = compileFnCode(require.resolve('../detectJestVersion.ts'));
18+
const relativePathToFn = 'eslint-plugin-jest/lib/rules/detectJestVersion.js';
19+
20+
const runNodeScript = (cwd: string, script: string) => {
21+
return spawnSync('node', ['-e', script.split('\n').join(' ')], {
22+
cwd,
23+
encoding: 'utf-8',
24+
});
25+
};
26+
27+
const runDetectJestVersion = (cwd: string) => {
28+
return runNodeScript(
29+
cwd,
30+
`
31+
try {
32+
console.log(require('${relativePathToFn}').detectJestVersion());
33+
} catch (error) {
34+
console.error(error.message);
35+
}
36+
`,
37+
);
38+
};
39+
40+
/**
41+
* Makes a new temp directory, prefixed with `eslint-plugin-jest-`
42+
*
43+
* @return {Promise<string>}
44+
*/
45+
const makeTempDir = () =>
46+
fs.mkdtempSync(path.join(os.tmpdir(), 'eslint-plugin-jest-'));
47+
48+
interface ProjectStructure {
49+
[key: `${string}/package.json`]: JSONSchemaForNPMPackageJsonFiles;
50+
[key: `${string}/${typeof relativePathToFn}`]: string;
51+
[key: `${string}/`]: null;
52+
'package.json'?: JSONSchemaForNPMPackageJsonFiles;
53+
}
54+
55+
const setupFakeProject = (structure: ProjectStructure): string => {
56+
const tempDir = makeTempDir();
57+
58+
for (const [filePath, contents] of Object.entries(structure)) {
59+
if (contents === null) {
60+
fs.mkdirSync(path.join(tempDir, filePath), { recursive: true });
61+
62+
continue;
63+
}
64+
65+
const folderPath = path.dirname(filePath);
66+
67+
// make the directory (recursively)
68+
fs.mkdirSync(path.join(tempDir, folderPath), { recursive: true });
69+
70+
const finalContents =
71+
typeof contents === 'string' ? contents : JSON.stringify(contents);
72+
73+
fs.writeFileSync(path.join(tempDir, filePath), finalContents);
74+
}
75+
76+
return tempDir;
77+
};
78+
79+
describe('detectJestVersion', () => {
80+
describe('basic tests', () => {
81+
const packageJsonFactory = jest.fn<JSONSchemaForNPMPackageJsonFiles, []>();
82+
83+
beforeEach(() => {
84+
jest.resetModules();
85+
jest.doMock(require.resolve('jest/package.json'), packageJsonFactory);
86+
});
87+
88+
describe('when the package.json is missing the version property', () => {
89+
it('throws an error', () => {
90+
packageJsonFactory.mockReturnValue({});
91+
92+
expect(() => detectJestVersion()).toThrow(
93+
/Unable to detect Jest version/iu,
94+
);
95+
});
96+
});
97+
98+
it('caches versions', () => {
99+
packageJsonFactory.mockReturnValue({ version: '1.2.3' });
100+
101+
const version = detectJestVersion();
102+
103+
jest.resetModules();
104+
105+
expect(detectJestVersion).not.toThrow();
106+
expect(detectJestVersion()).toBe(version);
107+
});
108+
});
109+
110+
describe('when in a simple project', () => {
111+
it('finds the correct version', () => {
112+
const projectDir = setupFakeProject({
113+
'package.json': { name: 'simple-project' },
114+
[`node_modules/${relativePathToFn}` as const]: compiledFn,
115+
'node_modules/jest/package.json': {
116+
name: 'jest',
117+
version: '21.0.0',
118+
},
119+
});
120+
121+
const { stdout, stderr } = runDetectJestVersion(projectDir);
122+
123+
expect(stdout.trim()).toBe('21');
124+
expect(stderr.trim()).toBe('');
125+
});
126+
});
127+
128+
describe('when in a hoisted mono-repo', () => {
129+
it('finds the correct version', () => {
130+
const projectDir = setupFakeProject({
131+
'package.json': { name: 'mono-repo' },
132+
[`node_modules/${relativePathToFn}` as const]: compiledFn,
133+
'node_modules/jest/package.json': {
134+
name: 'jest',
135+
version: '19.0.0',
136+
},
137+
'packages/a/package.json': { name: 'package-a' },
138+
'packages/b/package.json': { name: 'package-b' },
139+
});
140+
141+
const { stdout, stderr } = runDetectJestVersion(projectDir);
142+
143+
expect(stdout.trim()).toBe('19');
144+
expect(stderr.trim()).toBe('');
145+
});
146+
});
147+
148+
describe('when in a subproject', () => {
149+
it('finds the correct versions', () => {
150+
const projectDir = setupFakeProject({
151+
'backend/package.json': { name: 'package-a' },
152+
[`backend/node_modules/${relativePathToFn}` as const]: compiledFn,
153+
'backend/node_modules/jest/package.json': {
154+
name: 'jest',
155+
version: '24.0.0',
156+
},
157+
'frontend/package.json': { name: 'package-b' },
158+
[`frontend/node_modules/${relativePathToFn}` as const]: compiledFn,
159+
'frontend/node_modules/jest/package.json': {
160+
name: 'jest',
161+
version: '15.0.0',
162+
},
163+
});
164+
165+
const { stdout: stdoutBackend, stderr: stderrBackend } =
166+
runDetectJestVersion(path.join(projectDir, 'backend'));
167+
168+
expect(stdoutBackend.trim()).toBe('24');
169+
expect(stderrBackend.trim()).toBe('');
170+
171+
const { stdout: stdoutFrontend, stderr: stderrFrontend } =
172+
runDetectJestVersion(path.join(projectDir, 'frontend'));
173+
174+
expect(stdoutFrontend.trim()).toBe('15');
175+
expect(stderrFrontend.trim()).toBe('');
176+
});
177+
});
178+
179+
describe('when jest is not installed', () => {
180+
it('throws an error', () => {
181+
const projectDir = setupFakeProject({
182+
'package.json': { name: 'no-jest' },
183+
[`node_modules/${relativePathToFn}` as const]: compiledFn,
184+
'node_modules/pack/package.json': { name: 'pack' },
185+
});
186+
187+
const { stdout, stderr } = runDetectJestVersion(projectDir);
188+
189+
expect(stdout.trim()).toBe('');
190+
expect(stderr.trim()).toContain('Unable to detect Jest version');
191+
});
192+
});
193+
194+
describe('when jest is changed on disk', () => {
195+
it('uses the cached version', () => {
196+
const projectDir = setupFakeProject({
197+
'package.json': { name: 'no-jest' },
198+
[`node_modules/${relativePathToFn}` as const]: compiledFn,
199+
'node_modules/jest/package.json': { name: 'jest', version: '26.0.0' },
200+
});
201+
202+
const { stdout, stderr } = runNodeScript(
203+
projectDir,
204+
`
205+
const { detectJestVersion } = require('${relativePathToFn}');
206+
const fs = require('fs');
207+
208+
console.log(detectJestVersion());
209+
fs.writeFileSync(
210+
'node_modules/jest/package.json',
211+
JSON.stringify({
212+
name: 'jest',
213+
version: '25.0.0',
214+
}),
215+
);
216+
console.log(detectJestVersion());
217+
`,
218+
);
219+
220+
const [firstCall, secondCall] = stdout.split('\n');
221+
222+
expect(firstCall).toBe('26');
223+
expect(secondCall).toBe('26');
224+
expect(stderr.trim()).toBe('');
225+
});
226+
});
227+
});

0 commit comments

Comments
 (0)