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

feat: add new package rule-tester #6777

Merged
merged 13 commits into from Apr 27, 2023
Merged
Show file tree
Hide file tree
Changes from 11 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
10 changes: 9 additions & 1 deletion .cspell.json
@@ -1,6 +1,14 @@
{
"version": "0.1",
"version": "0.2",
"language": "en",
"enableFiletypes": [
"markdown",
"mdx",
"typescript",
"typescriptreact",
"javascript",
"javascriptreact"
],
"ignorePaths": [
".cspell.json",
".github/workflows/**",
Expand Down
3 changes: 3 additions & 0 deletions .eslintignore
Expand Up @@ -12,3 +12,6 @@ packages/types/src/generated/**/*.ts

# Playground types downloaded from the web
packages/website/src/vendor

# see the file header in eslint-base.test.js for more info
packages/rule-tester/tests/eslint-base
3 changes: 3 additions & 0 deletions .prettierignore
Expand Up @@ -18,3 +18,6 @@ CHANGELOG.md

packages/website/.docusaurus
packages/website/build

# see the file header in eslint-base.test.js for more info
packages/rule-tester/tests/eslint-base
36 changes: 36 additions & 0 deletions .vscode/launch.json
Expand Up @@ -72,6 +72,42 @@
"${workspaceFolder}/packages/scope-manager/dist/index.js",
],
},
{
"type": "node",
"request": "launch",
"name": "Run currently opened rule-tester test",
"cwd": "${workspaceFolder}/packages/rule-tester/",
"program": "${workspaceFolder}/node_modules/jest/bin/jest.js",
"args": [
"--runInBand",
"--no-cache",
"--no-coverage",
"${fileBasename}"
],
"sourceMaps": true,
"console": "integratedTerminal",
"internalConsoleOptions": "neverOpen",
"skipFiles": [
"${workspaceFolder}/packages/utils/src/index.ts",
"${workspaceFolder}/packages/utils/dist/index.js",
"${workspaceFolder}/packages/utils/src/ts-estree.ts",
"${workspaceFolder}/packages/utils/dist/ts-estree.js",
"${workspaceFolder}/packages/type-utils/src/ts-estree.ts",
"${workspaceFolder}/packages/type-utils/dist/ts-estree.js",
"${workspaceFolder}/packages/parser/src/index.ts",
"${workspaceFolder}/packages/parser/dist/index.js",
"${workspaceFolder}/packages/rule-tester/src/index.ts",
"${workspaceFolder}/packages/rule-tester/dist/index.js",
"${workspaceFolder}/packages/typescript-estree/src/index.ts",
"${workspaceFolder}/packages/typescript-estree/dist/index.js",
"${workspaceFolder}/packages/types/src/index.ts",
"${workspaceFolder}/packages/types/dist/index.js",
"${workspaceFolder}/packages/visitor-keys/src/index.ts",
"${workspaceFolder}/packages/visitor-keys/dist/index.js",
"${workspaceFolder}/packages/scope-manager/dist/index.js",
"${workspaceFolder}/packages/scope-manager/dist/index.js",
],
},
{
"type": "node",
"request": "launch",
Expand Down
3 changes: 2 additions & 1 deletion docs/Architecture.mdx
Expand Up @@ -12,6 +12,7 @@ They are:
- [`@typescript-eslint/eslint-plugin`](./architecture/ESLint_Plugin.mdx): An ESLint plugin which provides lint rules for TypeScript codebases.
- [`@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.
- [`@typescript-eslint/parser`](./architecture/Parser.mdx): An ESLint parser which allows for ESLint to lint TypeScript source code.
- [`@typescript-eslint/rule-tester`](./architecture/Rule_Tester.mdx): A utility for testing ESLint rules.
- [`@typescript-eslint/scope-manager`](./architecture/Scope_Manager.mdx): A fork of [`eslint-scope`](https://github.com/eslint/eslint-scope), enhanced to support TypeScript functionality.
- [`@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.
- [`@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.
- [`@typescript-eslint/utils`](./architecture/Utils.mdx): Utilities for working with TypeScript + ESLint together.
216 changes: 216 additions & 0 deletions docs/architecture/Rule_Tester.mdx
bradzacher marked this conversation as resolved.
Show resolved Hide resolved
@@ -0,0 +1,216 @@
---
id: rule-tester
sidebar_label: rule-tester
---

import CodeBlock from '@theme/CodeBlock';

# `@typescript-eslint/rule-tester`
Copy link
Member

Choose a reason for hiding this comment

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

[Docs] Maybe we should explicitly call this out as beta/wip/similar, so folks don't see this and worry they should immediately switch to it?

Copy link
Member Author

Choose a reason for hiding this comment

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

As per our chats on discord - @armano2 and I think we should push to make this a breaking change replacement in v6 - people should switch to it cos it's better in many ways and it should mostly be a 1:1 replacement.

Also switching to the package means people get the only: boolean test prop for free, regardless of their ESLint version.


> A utility for testing ESLint rules

## Usage

For non-type-aware rules you can test them as follows:

```ts
import { RuleTester } from '@typescript-eslint/rule-tester';
import rule from '../src/rules/my-rule.ts';

const ruleTester = new RuleTester({
parser: '@typescript-eslint/parser',
});

ruleTester.run('my-rule', rule, {
valid: [
// valid tests can be a raw string,
'const x = 1;',
// or they can be an object
{
code: 'const y = 2;',
options: [{ ruleOption: true }],
},

// you can enable JSX parsing by passing parserOptions.ecmaFeatures.jsx = true
{
code: 'const z = <div />;',
parserOptions: {
ecmaFeatures: {
jsx: true,
},
},
},
],
invalid: [
// invalid tests must always be an object
{
code: 'const a = 1;',
// invalid tests must always specify the expected errors
errors: [
{
messageId: 'ruleMessage',
// If applicable - it's recommended that you also assert the data in
// addition to the messageId so that you can ensure the correct message
// is generated
data: {
placeholder1: 'a',
},
},
],
},

// fixers can be tested using the output parameter
{
code: 'const b = 1;',
output: 'const c = 1;',
errors: [
/* ... */
],
},
// passing `output = null` will enforce the code is NOT changed
{
code: 'const c = 1;',
output: null,
errors: [
/* ... */
],
},

// suggestions can be tested via errors
{
code: 'const d = 1;',
output: null,
errors: [
{
messageId: 'suggestionError',
suggestions: [
{
messageId: 'suggestionOne',
output: 'const e = 1;',
},
],
},
],
},
// passing `suggestions = null` will enforce there are NO suggestions
{
code: 'const d = 1;',
output: null,
errors: [
{
messageId: 'noSuggestionError',
suggestions: null,
},
],
},
],
});
```

### Type-Aware Testing

Type-aware rules can be tested in almost exactly the same way, except you need to create some files on disk.
We require files on disk due to a limitation with TypeScript in that it requires physical files on disk to initialize the project.
We suggest creating a `fixture` folder nearby that contains three files:

1. `file.ts` - this should be an empty file.
2. `react.tsx` - this should be an empty file.
3. `tsconfig.json` - this should be the config to use for your test, for example:
```json
{
"compilerOptions": {
"strict": true
},
"include": ["file.ts", "react.tsx"]
}
```

:::caution
It's important to note that both `file.ts` and `react.tsx` must both be empty files!
The rule tester will automatically use the string content from your tests - the empty files are just there for initialization.
:::

You can then test your rule by providing the type-aware config:

```ts
const ruleTester = new RuleTester({
parser: '@typescript-eslint/parser',
// Added lines start
parserOptions: {
tsconfigRootDir: './path/to/your/folder/fixture',
project: './tsconfig.json',
},
// Added lines end
});
```

With that config the parser will automatically run in type-aware mode and you can write tests just like before.

### Test Dependency Constraints

Sometimes it's desirable to test your rule against multiple versions of a dependency to ensure backwards and forwards compatibility.
With backwards-compatibility testing there comes a complication in that some tests may not be compatible with an older version of a dependency.
For example - if you're testing against an older version of TypeScript, certain features might cause a parser error!

import DependencyConstraint from '!!raw-loader!../../packages/rule-tester/src/types/DependencyConstraint.ts';
bradzacher marked this conversation as resolved.
Show resolved Hide resolved

<CodeBlock language="ts">{DependencyConstraint}</CodeBlock>

The `RuleTester` allows you to apply dependency constraints at either an individual test or constructor level.

```ts
const ruleTester = new RuleTester({
parser: '@typescript-eslint/parser',
// Added lines start
dependencyConstraints: {
// none of the tests will run unless `my-dependency` matches the semver range `>=1.2.3`
'my-dependency': '1.2.3',
// you can also provide granular semver ranges
'my-granular-dep': {
// none of the tests will run unless `my-granular-dep` matches the semver range `~3.2.1`
range: '~3.2.1',
},
},
// Added lines end
});

ruleTester.run('my-rule', rule, {
valid: [
{
code: 'const y = 2;',
// Added lines start
dependencyConstraints: {
// this test won't run unless BOTH dependencies match the given ranges
first: '1.2.3',
second: '3.2.1',
},
// Added lines end
},
],
invalid: [
/* ... */
],
});
```

All dependencies provided in the `dependencyConstraints` object must match their given ranges in order for a test to not be skipped.

## Options

### `RuleTester` constructor options

import RuleTesterConfig from '!!raw-loader!../../packages/rule-tester/src/types/RuleTesterConfig.ts';

<CodeBlock language="ts">{RuleTesterConfig}</CodeBlock>

### Valid test case options

import ValidTestCase from '!!raw-loader!../../packages/rule-tester/src/types/ValidTestCase.ts';

<CodeBlock language="ts">{ValidTestCase}</CodeBlock>

### Invalid test case options

import InvalidTestCase from '!!raw-loader!../../packages/rule-tester/src/types/InvalidTestCase.ts';

<CodeBlock language="ts">{InvalidTestCase}</CodeBlock>
2 changes: 2 additions & 0 deletions packages/eslint-plugin-tslint/README.md
Expand Up @@ -8,3 +8,5 @@
👉 See **https://typescript-eslint.io/architecture/eslint-plugin-tslint** for documentation on this package.

> See https://typescript-eslint.io for general documentation on typescript-eslint, the tooling that allows you to run ESLint and Prettier on TypeScript code.

<!-- Local path for docs: docs/architecture/ESLint_Plugin_TSLint.mdx -->
2 changes: 1 addition & 1 deletion packages/eslint-plugin-tslint/src/rules/config.ts
@@ -1,5 +1,5 @@
import { ESLintUtils } from '@typescript-eslint/utils';
import memoize from 'lodash/memoize';
import { memoize } from 'lodash';
import type { RuleSeverity } from 'tslint';
import { Configuration } from 'tslint';

Expand Down
2 changes: 2 additions & 0 deletions packages/eslint-plugin/README.md
Expand Up @@ -8,3 +8,5 @@ An ESLint plugin which provides lint rules for TypeScript codebases.
👉 See **https://typescript-eslint.io/getting-started** for our Getting Started docs.

> See https://typescript-eslint.io for general documentation on typescript-eslint, the tooling that allows you to run ESLint and Prettier on TypeScript code.

<!-- Local path for docs: docs/architecture/ESLint_Plugin.mdx -->
2 changes: 2 additions & 0 deletions packages/parser/README.md
Expand Up @@ -8,3 +8,5 @@
👉 See **https://typescript-eslint.io/architecture/parser** for documentation on this package.

> See https://typescript-eslint.io for general documentation on typescript-eslint, the tooling that allows you to run ESLint and Prettier on TypeScript code.

<!-- Local path for docs: docs/architecture/Parser.mdx -->
21 changes: 21 additions & 0 deletions packages/rule-tester/LICENSE
@@ -0,0 +1,21 @@
MIT License

Copyright (c) 2019 typescript-eslint and other contributors
bradzacher marked this conversation as resolved.
Show resolved Hide resolved

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
10 changes: 10 additions & 0 deletions packages/rule-tester/README.md
@@ -0,0 +1,10 @@
# `@typescript-eslint/rule-tester`

> Tooling to test ESLint rules

[![NPM Version](https://img.shields.io/npm/v/@typescript-eslint/rule-tester.svg?style=flat-square)](https://www.npmjs.com/package/@typescript-eslint/rule-tester)
[![NPM Downloads](https://img.shields.io/npm/dm/@typescript-eslint/rule-tester.svg?style=flat-square)](https://www.npmjs.com/package/@typescript-eslint/rule-tester)

👉 See **https://typescript-eslint.io/architecture/rule-tester** for documentation on this package.

<!-- Local path for docs: docs/architecture/Rule_Tester.mdx -->
7 changes: 7 additions & 0 deletions packages/rule-tester/jest.config.js
@@ -0,0 +1,7 @@
'use strict';

// @ts-check
/** @type {import('@jest/types').Config.InitialOptions} */
module.exports = {
...require('../../jest.config.base.js'),
};