Skip to content

Commit 2ce1c1d

Browse files
authored
feat: add new package rule-tester (#6777)
1 parent 3e3b789 commit 2ce1c1d

File tree

77 files changed

+7501
-107
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

77 files changed

+7501
-107
lines changed

‎.cspell.json‎

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,14 @@
11
{
2-
"version": "0.1",
2+
"version": "0.2",
33
"language": "en",
4+
"enableFiletypes": [
5+
"markdown",
6+
"mdx",
7+
"typescript",
8+
"typescriptreact",
9+
"javascript",
10+
"javascriptreact"
11+
],
412
"ignorePaths": [
513
".cspell.json",
614
".github/workflows/**",

‎.eslintignore‎

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,3 +12,6 @@ packages/types/src/generated/**/*.ts
1212

1313
# Playground types downloaded from the web
1414
packages/website/src/vendor
15+
16+
# see the file header in eslint-base.test.js for more info
17+
packages/rule-tester/tests/eslint-base

‎.github/renovate.json5‎

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
{
22
enabledManagers: ['github-actions', 'npm'],
33
ignoreDeps: [
4+
// AJV is out-of-date, but it's intentionally synced with ESLint - https://github.com/eslint/eslint/blob/ad9dd6a933fd098a0d99c6a9aa059850535c23ee/package.json#L70
5+
'ajv',
46
// globby is ESM so we can't go any higher right now
57
'globby',
68
// this dep now uses package.json exports - we will be removing it next major

‎.prettierignore‎

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,3 +18,6 @@ CHANGELOG.md
1818

1919
packages/website/.docusaurus
2020
packages/website/build
21+
22+
# see the file header in eslint-base.test.js for more info
23+
packages/rule-tester/tests/eslint-base

‎.vscode/launch.json‎

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -105,6 +105,42 @@
105105
"${workspaceFolder}/packages/scope-manager/dist/index.js",
106106
],
107107
},
108+
{
109+
"type": "node",
110+
"request": "launch",
111+
"name": "Run currently opened rule-tester test",
112+
"cwd": "${workspaceFolder}/packages/rule-tester/",
113+
"program": "${workspaceFolder}/node_modules/jest/bin/jest.js",
114+
"args": [
115+
"--runInBand",
116+
"--no-cache",
117+
"--no-coverage",
118+
"${fileBasename}"
119+
],
120+
"sourceMaps": true,
121+
"console": "integratedTerminal",
122+
"internalConsoleOptions": "neverOpen",
123+
"skipFiles": [
124+
"${workspaceFolder}/packages/utils/src/index.ts",
125+
"${workspaceFolder}/packages/utils/dist/index.js",
126+
"${workspaceFolder}/packages/utils/src/ts-estree.ts",
127+
"${workspaceFolder}/packages/utils/dist/ts-estree.js",
128+
"${workspaceFolder}/packages/type-utils/src/ts-estree.ts",
129+
"${workspaceFolder}/packages/type-utils/dist/ts-estree.js",
130+
"${workspaceFolder}/packages/parser/src/index.ts",
131+
"${workspaceFolder}/packages/parser/dist/index.js",
132+
"${workspaceFolder}/packages/rule-tester/src/index.ts",
133+
"${workspaceFolder}/packages/rule-tester/dist/index.js",
134+
"${workspaceFolder}/packages/typescript-estree/src/index.ts",
135+
"${workspaceFolder}/packages/typescript-estree/dist/index.js",
136+
"${workspaceFolder}/packages/types/src/index.ts",
137+
"${workspaceFolder}/packages/types/dist/index.js",
138+
"${workspaceFolder}/packages/visitor-keys/src/index.ts",
139+
"${workspaceFolder}/packages/visitor-keys/dist/index.js",
140+
"${workspaceFolder}/packages/scope-manager/dist/index.js",
141+
"${workspaceFolder}/packages/scope-manager/dist/index.js",
142+
],
143+
},
108144
{
109145
"type": "node",
110146
"request": "launch",

‎docs/Architecture.mdx‎

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ They are:
1212
- [`@typescript-eslint/eslint-plugin`](./architecture/ESLint_Plugin.mdx): An ESLint plugin which provides lint rules for TypeScript codebases.
1313
- [`@typescript-eslint/eslint-plugin-tslint`](./architecture/ESLint_Plugin_TSLint.mdx): ESLint plugin that allows running TSLint rules within ESLint to help you migrate from TSLint to ESLint.
1414
- [`@typescript-eslint/parser`](./architecture/Parser.mdx): An ESLint parser which allows for ESLint to lint TypeScript source code.
15+
- [`@typescript-eslint/rule-tester`](./architecture/Rule_Tester.mdx): A utility for testing ESLint rules.
1516
- [`@typescript-eslint/scope-manager`](./architecture/Scope_Manager.mdx): A fork of [`eslint-scope`](https://github.com/eslint/eslint-scope), enhanced to support TypeScript functionality.
16-
- [`@typescript-eslint/typescript-estree`](./architecture/TypeScript-ESTree.mdx): The underlying code used by [`@typescript-eslint/parser`](./architecture/Parser.mdx) that converts TypeScript source code into an <a href="https://github.com/estree/estree">ESTree</a>-compatible form.
17+
- [`@typescript-eslint/typescript-estree`](./architecture/TypeScript_ESTree.mdx): The underlying code used by [`@typescript-eslint/parser`](./architecture/Parser.mdx) that converts TypeScript source code into an <a href="https://github.com/estree/estree">ESTree</a>-compatible form.
1718
- [`@typescript-eslint/utils`](./architecture/Utils.mdx): Utilities for working with TypeScript + ESLint together.

‎docs/Custom_Rules.mdx‎

Lines changed: 9 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -274,18 +274,20 @@ This can be necessary for TypeScript APIs not wrapped by the parser services.
274274

275275
## Testing
276276

277-
`@typescript-eslint/utils` exports a `RuleTester` with a similar API to the built-in [ESLint `RuleTester`](https://eslint.org/docs/developer-guide/nodejs-api#ruletester).
277+
`@typescript-eslint/rule-tester` exports a `RuleTester` with a similar API to the built-in ESLint `RuleTester`.
278278
It should be provided with the same `parser` and `parserOptions` you would use in your ESLint configuration.
279279

280+
Below is a quick-start guide. For more in-depth docs and examples [see the `@typescript-eslint/rule-tester` package documentation](./architecture/Rule_Tester.mdx).
281+
280282
### Testing Untyped Rules
281283

282284
For rules that don't need type information, passing just the `parser` will do:
283285

284286
```ts
285-
import { ESLintUtils } from '@typescript-eslint/utils';
287+
import { RuleTester } from '@typescript-eslint/rule-tester';
286288
import rule from './my-rule';
287289

288-
const ruleTester = new ESLintUtils.RuleTester({
290+
const ruleTester = new RuleTester({
289291
parser: '@typescript-eslint/parser',
290292
});
291293

@@ -305,10 +307,10 @@ For rules that do need type information, `parserOptions` must be passed in as we
305307
Tests must have at least an absolute `tsconfigRootDir` path provided as well as a relative `project` path from that directory:
306308

307309
```ts
308-
import { ESLintUtils } from '@typescript-eslint/utils';
310+
import { RuleTester } from '@typescript-eslint/rule-tester';
309311
import rule from './my-typed-rule';
310312

311-
const ruleTester = new ESLintUtils.RuleTester({
313+
const ruleTester = new RuleTester({
312314
parser: '@typescript-eslint/parser',
313315
parserOptions: {
314316
project: './tsconfig.json',
@@ -327,11 +329,11 @@ ruleTester.run('my-typed-rule', rule, {
327329
```
328330

329331
:::note
330-
For now, `ESLintUtils.RuleTester` requires the following physical files be present on disk for typed rules:
332+
For now, `RuleTester` requires the following physical files be present on disk for typed rules:
331333

332334
- `tsconfig.json`: tsconfig used as the test "project"
333335
- One of the following two files:
334336
- `file.ts`: blank test file used for normal TS tests
335-
- `file.tsx`: blank test file used for tests with `parserOptions: { ecmaFeatures: { jsx: true } }`
337+
- `react.tsx`: blank test file used for tests with `parserOptions: { ecmaFeatures: { jsx: true } }`
336338

337339
:::

‎docs/architecture/Rule_Tester.mdx‎

Lines changed: 218 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,218 @@
1+
---
2+
id: rule-tester
3+
sidebar_label: rule-tester
4+
---
5+
6+
import CodeBlock from '@theme/CodeBlock';
7+
8+
# `@typescript-eslint/rule-tester`
9+
10+
> A utility for testing ESLint rules
11+
12+
This is a fork of ESLint's built-in `RuleTester` to provide some better types and additional features for testing TypeScript rules.
13+
14+
## Usage
15+
16+
For non-type-aware rules you can test them as follows:
17+
18+
```ts
19+
import { RuleTester } from '@typescript-eslint/rule-tester';
20+
import rule from '../src/rules/my-rule.ts';
21+
22+
const ruleTester = new RuleTester({
23+
parser: '@typescript-eslint/parser',
24+
});
25+
26+
ruleTester.run('my-rule', rule, {
27+
valid: [
28+
// valid tests can be a raw string,
29+
'const x = 1;',
30+
// or they can be an object
31+
{
32+
code: 'const y = 2;',
33+
options: [{ ruleOption: true }],
34+
},
35+
36+
// you can enable JSX parsing by passing parserOptions.ecmaFeatures.jsx = true
37+
{
38+
code: 'const z = <div />;',
39+
parserOptions: {
40+
ecmaFeatures: {
41+
jsx: true,
42+
},
43+
},
44+
},
45+
],
46+
invalid: [
47+
// invalid tests must always be an object
48+
{
49+
code: 'const a = 1;',
50+
// invalid tests must always specify the expected errors
51+
errors: [
52+
{
53+
messageId: 'ruleMessage',
54+
// If applicable - it's recommended that you also assert the data in
55+
// addition to the messageId so that you can ensure the correct message
56+
// is generated
57+
data: {
58+
placeholder1: 'a',
59+
},
60+
},
61+
],
62+
},
63+
64+
// fixers can be tested using the output parameter
65+
{
66+
code: 'const b = 1;',
67+
output: 'const c = 1;',
68+
errors: [
69+
/* ... */
70+
],
71+
},
72+
// passing `output = null` will enforce the code is NOT changed
73+
{
74+
code: 'const c = 1;',
75+
output: null,
76+
errors: [
77+
/* ... */
78+
],
79+
},
80+
81+
// suggestions can be tested via errors
82+
{
83+
code: 'const d = 1;',
84+
output: null,
85+
errors: [
86+
{
87+
messageId: 'suggestionError',
88+
suggestions: [
89+
{
90+
messageId: 'suggestionOne',
91+
output: 'const e = 1;',
92+
},
93+
],
94+
},
95+
],
96+
},
97+
// passing `suggestions = null` will enforce there are NO suggestions
98+
{
99+
code: 'const d = 1;',
100+
output: null,
101+
errors: [
102+
{
103+
messageId: 'noSuggestionError',
104+
suggestions: null,
105+
},
106+
],
107+
},
108+
],
109+
});
110+
```
111+
112+
### Type-Aware Testing
113+
114+
Type-aware rules can be tested in almost exactly the same way, except you need to create some files on disk.
115+
We require files on disk due to a limitation with TypeScript in that it requires physical files on disk to initialize the project.
116+
We suggest creating a `fixture` folder nearby that contains three files:
117+
118+
1. `file.ts` - this should be an empty file.
119+
2. `react.tsx` - this should be an empty file.
120+
3. `tsconfig.json` - this should be the config to use for your test, for example:
121+
```json
122+
{
123+
"compilerOptions": {
124+
"strict": true
125+
},
126+
"include": ["file.ts", "react.tsx"]
127+
}
128+
```
129+
130+
:::caution
131+
It's important to note that both `file.ts` and `react.tsx` must both be empty files!
132+
The rule tester will automatically use the string content from your tests - the empty files are just there for initialization.
133+
:::
134+
135+
You can then test your rule by providing the type-aware config:
136+
137+
```ts
138+
const ruleTester = new RuleTester({
139+
parser: '@typescript-eslint/parser',
140+
// Added lines start
141+
parserOptions: {
142+
tsconfigRootDir: './path/to/your/folder/fixture',
143+
project: './tsconfig.json',
144+
},
145+
// Added lines end
146+
});
147+
```
148+
149+
With that config the parser will automatically run in type-aware mode and you can write tests just like before.
150+
151+
### Test Dependency Constraints
152+
153+
Sometimes it's desirable to test your rule against multiple versions of a dependency to ensure backwards and forwards compatibility.
154+
With backwards-compatibility testing there comes a complication in that some tests may not be compatible with an older version of a dependency.
155+
For example - if you're testing against an older version of TypeScript, certain features might cause a parser error!
156+
157+
import DependencyConstraint from '!!raw-loader!../../packages/rule-tester/src/types/DependencyConstraint.ts';
158+
159+
<CodeBlock language="ts">{DependencyConstraint}</CodeBlock>
160+
161+
The `RuleTester` allows you to apply dependency constraints at either an individual test or constructor level.
162+
163+
```ts
164+
const ruleTester = new RuleTester({
165+
parser: '@typescript-eslint/parser',
166+
// Added lines start
167+
dependencyConstraints: {
168+
// none of the tests will run unless `my-dependency` matches the semver range `>=1.2.3`
169+
'my-dependency': '1.2.3',
170+
// you can also provide granular semver ranges
171+
'my-granular-dep': {
172+
// none of the tests will run unless `my-granular-dep` matches the semver range `~3.2.1`
173+
range: '~3.2.1',
174+
},
175+
},
176+
// Added lines end
177+
});
178+
179+
ruleTester.run('my-rule', rule, {
180+
valid: [
181+
{
182+
code: 'const y = 2;',
183+
// Added lines start
184+
dependencyConstraints: {
185+
// this test won't run unless BOTH dependencies match the given ranges
186+
first: '1.2.3',
187+
second: '3.2.1',
188+
},
189+
// Added lines end
190+
},
191+
],
192+
invalid: [
193+
/* ... */
194+
],
195+
});
196+
```
197+
198+
All dependencies provided in the `dependencyConstraints` object must match their given ranges in order for a test to not be skipped.
199+
200+
## Options
201+
202+
### `RuleTester` constructor options
203+
204+
import RuleTesterConfig from '!!raw-loader!../../packages/rule-tester/src/types/RuleTesterConfig.ts';
205+
206+
<CodeBlock language="ts">{RuleTesterConfig}</CodeBlock>
207+
208+
### Valid test case options
209+
210+
import ValidTestCase from '!!raw-loader!../../packages/rule-tester/src/types/ValidTestCase.ts';
211+
212+
<CodeBlock language="ts">{ValidTestCase}</CodeBlock>
213+
214+
### Invalid test case options
215+
216+
import InvalidTestCase from '!!raw-loader!../../packages/rule-tester/src/types/InvalidTestCase.ts';
217+
218+
<CodeBlock language="ts">{InvalidTestCase}</CodeBlock>

‎packages/eslint-plugin-tslint/README.md‎

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,3 +8,5 @@
88
👉 See **https://typescript-eslint.io/architecture/eslint-plugin-tslint** for documentation on this package.
99

1010
> See https://typescript-eslint.io for general documentation on typescript-eslint, the tooling that allows you to run ESLint and Prettier on TypeScript code.
11+
12+
<!-- Local path for docs: docs/architecture/ESLint_Plugin_TSLint.mdx -->

0 commit comments

Comments
 (0)