Skip to content

Commit 2dd1638

Browse files
authored
feat(experimental-utils): expose our RuleTester extension (typescript-eslint#1948)
1 parent 383f931 commit 2dd1638

File tree

4 files changed

+120
-116
lines changed

4 files changed

+120
-116
lines changed
Lines changed: 7 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -1,22 +1,10 @@
1-
import { TSESLint, ESLintUtils } from '@typescript-eslint/experimental-utils';
1+
import { ESLintUtils } from '@typescript-eslint/experimental-utils';
2+
import path from 'path';
23

3-
const { batchedSingleLineTests } = ESLintUtils;
4-
5-
const parser = '@typescript-eslint/parser';
6-
7-
type RuleTesterConfig = Omit<TSESLint.RuleTesterConfig, 'parser'> & {
8-
parser: typeof parser;
9-
};
10-
class RuleTester extends TSESLint.RuleTester {
11-
// as of eslint 6 you have to provide an absolute path to the parser
12-
// but that's not as clean to type, this saves us trying to manually enforce
13-
// that contributors require.resolve everything
14-
constructor(options: RuleTesterConfig) {
15-
super({
16-
...options,
17-
parser: require.resolve(options.parser),
18-
});
19-
}
4+
function getFixturesRootDir(): string {
5+
return path.join(__dirname, 'fixtures');
206
}
217

22-
export { RuleTester, batchedSingleLineTests };
8+
const { batchedSingleLineTests, RuleTester } = ESLintUtils;
9+
10+
export { RuleTester, batchedSingleLineTests, getFixturesRootDir };
Lines changed: 3 additions & 97 deletions
Original file line numberDiff line numberDiff line change
@@ -1,104 +1,10 @@
1-
import { TSESLint, ESLintUtils } from '@typescript-eslint/experimental-utils';
2-
import { clearCaches } from '@typescript-eslint/parser';
1+
import { ESLintUtils } from '@typescript-eslint/experimental-utils';
32
import * as path from 'path';
43

5-
const parser = '@typescript-eslint/parser';
6-
7-
type RuleTesterConfig = Omit<TSESLint.RuleTesterConfig, 'parser'> & {
8-
parser: typeof parser;
9-
};
10-
class RuleTester extends TSESLint.RuleTester {
11-
// as of eslint 6 you have to provide an absolute path to the parser
12-
// but that's not as clean to type, this saves us trying to manually enforce
13-
// that contributors require.resolve everything
14-
constructor(private readonly options: RuleTesterConfig) {
15-
super({
16-
...options,
17-
parser: require.resolve(options.parser),
18-
});
19-
}
20-
private getFilename(options?: TSESLint.ParserOptions): string {
21-
if (options) {
22-
const filename = `file.ts${
23-
options.ecmaFeatures && options.ecmaFeatures.jsx ? 'x' : ''
24-
}`;
25-
if (options.project) {
26-
return path.join(getFixturesRootDir(), filename);
27-
}
28-
29-
return filename;
30-
} else if (this.options.parserOptions) {
31-
return this.getFilename(this.options.parserOptions);
32-
}
33-
34-
return 'file.ts';
35-
}
36-
37-
// as of eslint 6 you have to provide an absolute path to the parser
38-
// If you don't do that at the test level, the test will fail somewhat cryptically...
39-
// This is a lot more explicit
40-
run<TMessageIds extends string, TOptions extends Readonly<unknown[]>>(
41-
name: string,
42-
rule: TSESLint.RuleModule<TMessageIds, TOptions>,
43-
tests: TSESLint.RunTests<TMessageIds, TOptions>,
44-
): void {
45-
const errorMessage = `Do not set the parser at the test level unless you want to use a parser other than ${parser}`;
46-
47-
// standardize the valid tests as objects
48-
tests.valid = tests.valid.map(test => {
49-
if (typeof test === 'string') {
50-
return {
51-
code: test,
52-
};
53-
}
54-
return test;
55-
});
56-
57-
tests.valid.forEach(test => {
58-
if (typeof test !== 'string') {
59-
if (test.parser === parser) {
60-
throw new Error(errorMessage);
61-
}
62-
if (!test.filename) {
63-
test.filename = this.getFilename(test.parserOptions);
64-
}
65-
}
66-
});
67-
tests.invalid.forEach(test => {
68-
if (test.parser === parser) {
69-
throw new Error(errorMessage);
70-
}
71-
if (!test.filename) {
72-
test.filename = this.getFilename(test.parserOptions);
73-
}
74-
});
75-
76-
super.run(name, rule, tests);
77-
}
78-
}
79-
804
function getFixturesRootDir(): string {
81-
return path.join(process.cwd(), 'tests/fixtures/');
5+
return path.join(__dirname, 'fixtures');
826
}
837

84-
const { batchedSingleLineTests } = ESLintUtils;
85-
86-
// make sure that the parser doesn't hold onto file handles between tests
87-
// on linux (i.e. our CI env), there can be very a limited number of watch handles available
88-
afterAll(() => {
89-
clearCaches();
90-
});
91-
92-
/**
93-
* Simple no-op tag to mark code samples as "should not format with prettier"
94-
* for the internal/plugin-test-formatting lint rule
95-
*/
96-
function noFormat(strings: TemplateStringsArray, ...keys: string[]): string {
97-
const lastIndex = strings.length - 1;
98-
return (
99-
strings.slice(0, lastIndex).reduce((p, s, i) => p + s + keys[i], '') +
100-
strings[lastIndex]
101-
);
102-
}
8+
const { batchedSingleLineTests, RuleTester, noFormat } = ESLintUtils;
1039

10410
export { batchedSingleLineTests, getFixturesRootDir, noFormat, RuleTester };
Lines changed: 109 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,109 @@
1+
import * as TSESLint from '../ts-eslint';
2+
import * as path from 'path';
3+
4+
const parser = '@typescript-eslint/parser';
5+
6+
type RuleTesterConfig = Omit<TSESLint.RuleTesterConfig, 'parser'> & {
7+
parser: typeof parser;
8+
};
9+
10+
class RuleTester extends TSESLint.RuleTester {
11+
// as of eslint 6 you have to provide an absolute path to the parser
12+
// but that's not as clean to type, this saves us trying to manually enforce
13+
// that contributors require.resolve everything
14+
constructor(private readonly options: RuleTesterConfig) {
15+
super({
16+
...options,
17+
parser: require.resolve(options.parser),
18+
});
19+
20+
// make sure that the parser doesn't hold onto file handles between tests
21+
// on linux (i.e. our CI env), there can be very a limited number of watch handles available
22+
afterAll(() => {
23+
try {
24+
// instead of creating a hard dependency, just use a soft require
25+
// a bit weird, but if they're using this tooling, it'll be installed
26+
require(parser).clearCaches();
27+
} catch {
28+
// ignored
29+
}
30+
});
31+
}
32+
private getFilename(options?: TSESLint.ParserOptions): string {
33+
if (options) {
34+
const filename = `file.ts${
35+
options.ecmaFeatures && options.ecmaFeatures.jsx ? 'x' : ''
36+
}`;
37+
if (options.project) {
38+
return path.join(
39+
options.tsconfigRootDir != null
40+
? options.tsconfigRootDir
41+
: process.cwd(),
42+
filename,
43+
);
44+
}
45+
46+
return filename;
47+
} else if (this.options.parserOptions) {
48+
return this.getFilename(this.options.parserOptions);
49+
}
50+
51+
return 'file.ts';
52+
}
53+
54+
// as of eslint 6 you have to provide an absolute path to the parser
55+
// If you don't do that at the test level, the test will fail somewhat cryptically...
56+
// This is a lot more explicit
57+
run<TMessageIds extends string, TOptions extends Readonly<unknown[]>>(
58+
name: string,
59+
rule: TSESLint.RuleModule<TMessageIds, TOptions>,
60+
tests: TSESLint.RunTests<TMessageIds, TOptions>,
61+
): void {
62+
const errorMessage = `Do not set the parser at the test level unless you want to use a parser other than ${parser}`;
63+
64+
// standardize the valid tests as objects
65+
tests.valid = tests.valid.map(test => {
66+
if (typeof test === 'string') {
67+
return {
68+
code: test,
69+
};
70+
}
71+
return test;
72+
});
73+
74+
tests.valid.forEach(test => {
75+
if (typeof test !== 'string') {
76+
if (test.parser === parser) {
77+
throw new Error(errorMessage);
78+
}
79+
if (!test.filename) {
80+
test.filename = this.getFilename(test.parserOptions);
81+
}
82+
}
83+
});
84+
tests.invalid.forEach(test => {
85+
if (test.parser === parser) {
86+
throw new Error(errorMessage);
87+
}
88+
if (!test.filename) {
89+
test.filename = this.getFilename(test.parserOptions);
90+
}
91+
});
92+
93+
super.run(name, rule, tests);
94+
}
95+
}
96+
97+
/**
98+
* Simple no-op tag to mark code samples as "should not format with prettier"
99+
* for the internal/plugin-test-formatting lint rule
100+
*/
101+
function noFormat(strings: TemplateStringsArray, ...keys: string[]): string {
102+
const lastIndex = strings.length - 1;
103+
return (
104+
strings.slice(0, lastIndex).reduce((p, s, i) => p + s + keys[i], '') +
105+
strings[lastIndex]
106+
);
107+
}
108+
109+
export { noFormat, RuleTester };

packages/experimental-utils/src/eslint-utils/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,4 +2,5 @@ export * from './applyDefault';
22
export * from './batchedSingleLineTests';
33
export * from './getParserServices';
44
export * from './RuleCreator';
5+
export * from './RuleTester';
56
export * from './deepMerge';

0 commit comments

Comments
 (0)
pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy