Skip to content

Commit 13645b7

Browse files
chore: v7 (#901)
BREAKING CHANGE: Requires Node@^18.18.0 || ^20.9.0 || >=21.1.0 BREAKING CHANGE: Requires ESLint `^8.57.0`, or `^9.0.0` Co-authored-by: Michaël De Boey <[email protected]>
1 parent 3615223 commit 13645b7

30 files changed

+665
-587
lines changed

.eslintrc.js

+2-13
Original file line numberDiff line numberDiff line change
@@ -14,17 +14,6 @@ module.exports = {
1414
rules: {
1515
// Base
1616
'max-lines-per-function': 'off',
17-
'no-restricted-imports': [
18-
'error',
19-
{
20-
patterns: [
21-
{
22-
group: ['@typescript-eslint/utils/dist/*'],
23-
message: 'Import from `@typescript-eslint/utils` instead.',
24-
},
25-
],
26-
},
27-
],
2817

2918
// Import
3019
'import/order': [
@@ -51,12 +40,12 @@ module.exports = {
5140
files: ['**/*.ts?(x)'],
5241
parser: '@typescript-eslint/parser',
5342
parserOptions: {
43+
project: './tsconfig.eslint.json',
5444
tsconfigRootDir: __dirname,
55-
project: ['./tsconfig.eslint.json'],
5645
},
5746
extends: [
5847
'plugin:@typescript-eslint/recommended',
59-
'plugin:@typescript-eslint/recommended-requiring-type-checking',
48+
'plugin:@typescript-eslint/recommended-type-checked',
6049
'plugin:import/typescript',
6150
],
6251
rules: {

.github/workflows/verifications.yml

+2-16
Original file line numberDiff line numberDiff line change
@@ -35,22 +35,8 @@ jobs:
3535
strategy:
3636
fail-fast: false
3737
matrix:
38-
node: [12.22.0, 12, 14.17.0, 14, 16, 17, 18, 19, 20, 22]
39-
eslint: [7.5, 7, 8, 9]
40-
exclude:
41-
# eslint@9 doesn't support < Node v18
42-
- node: 17
43-
eslint: 9
44-
- node: 16
45-
eslint: 9
46-
- node: 14
47-
eslint: 9
48-
- node: 14.17.0
49-
eslint: 9
50-
- node: 12
51-
eslint: 9
52-
- node: 12.22.0
53-
eslint: 9
38+
node: [18.18.0, 18, 20.9.0, 20, 21.1.0, 21, 22, 23]
39+
eslint: [8.57.0, 8, 9]
5440
steps:
5541
- name: Checkout
5642
uses: actions/checkout@v4

README.md

+8-7
Original file line numberDiff line numberDiff line change
@@ -22,19 +22,19 @@
2222
[![PRs Welcome][pr-badge]][pr-url]
2323
[![All Contributors][all-contributors-badge]](#contributors-)
2424

25-
## Installation
25+
## Prerequisites
2626

27-
You'll first need to install [ESLint](https://eslint.org):
27+
To use this plugin, you must have [Node.js](https://nodejs.org/en/) (`^18.18.0`, `^20.9.0`, or `>=21.1.0`) installed.
2828

29-
```shell
30-
$ npm install --save-dev eslint
31-
# or
32-
$ yarn add --dev eslint
33-
```
29+
## Installation
30+
31+
You'll first need to [install ESLint](https://eslint.org/docs/latest/use/getting-started).
3432

3533
Next, install `eslint-plugin-testing-library`:
3634

3735
```shell
36+
$ pnpm add --save-dev eslint-plugin-testing-library
37+
# or
3838
$ npm install --save-dev eslint-plugin-testing-library
3939
# or
4040
$ yarn add --dev eslint-plugin-testing-library
@@ -49,6 +49,7 @@ You can find detailed guides for migrating `eslint-plugin-testing-library` in th
4949
- [Migration guide for v4](docs/migration-guides/v4.md)
5050
- [Migration guide for v5](docs/migration-guides/v5.md)
5151
- [Migration guide for v6](docs/migration-guides/v6.md)
52+
- [Migration guide for v7](docs/migration-guides/v7.md)
5253

5354
## Usage
5455

docs/migration-guides/v7.md

+14
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
# Guide: migrating to v7
2+
3+
If you are not on v6 yet, we recommend first following the [v6 migration guide](docs/migration-guides/v6.md).
4+
5+
## Overview
6+
7+
- **(Breaking)** Supported versions of Node.js have been updated to `^18.18.0`, `^20.9.0`, or `>=21.1.0`, matching ESLint.
8+
- **(Breaking)** Supported versions of ESLint have been updated to `^8.57.0`, or `^9.0.0`.
9+
- Full support for ESLint v9 (v8 still compatible) and typescript-eslint v8
10+
11+
## Steps to upgrade
12+
13+
1. Make sure you are using a supported version of Node.js, and upgrade if not.
14+
2. Make sure you are using a supported version of ESLint, and upgrade if not.

lib/configs/index.ts

+3-5
Original file line numberDiff line numberDiff line change
@@ -1,24 +1,22 @@
11
import { join } from 'path';
22

3-
import { type TSESLint } from '@typescript-eslint/utils';
3+
import type { TSESLint } from '@typescript-eslint/utils';
44

55
import {
66
importDefault,
77
SUPPORTED_TESTING_FRAMEWORKS,
88
SupportedTestingFramework,
99
} from '../utils';
1010

11-
export type LinterConfigRules = Pick<Required<TSESLint.Linter.Config>, 'rules'>;
12-
1311
const configsDir = __dirname;
1412

1513
const getConfigForFramework = (framework: SupportedTestingFramework) =>
16-
importDefault<LinterConfigRules>(join(configsDir, framework));
14+
importDefault<TSESLint.SharedConfig.RulesRecord>(join(configsDir, framework));
1715

1816
export default SUPPORTED_TESTING_FRAMEWORKS.reduce(
1917
(allConfigs, framework) => ({
2018
...allConfigs,
2119
[framework]: getConfigForFramework(framework),
2220
}),
2321
{}
24-
) as Record<SupportedTestingFramework, LinterConfigRules>;
22+
) as Record<SupportedTestingFramework, TSESLint.SharedConfig.RulesRecord>;

lib/create-testing-library-rule/detect-testing-library-utils.ts

+12-11
Original file line numberDiff line numberDiff line change
@@ -25,32 +25,33 @@ import {
2525
PRESENCE_MATCHERS,
2626
} from '../utils';
2727

28-
const SETTING_OPTION_OFF = 'off' as const;
28+
const SETTING_OPTION_OFF = 'off';
2929

3030
export type TestingLibrarySettings = {
31-
'testing-library/utils-module'?: string | typeof SETTING_OPTION_OFF;
31+
'testing-library/utils-module'?:
32+
| typeof SETTING_OPTION_OFF
33+
| (string & NonNullable<unknown>);
3234
'testing-library/custom-renders'?: string[] | typeof SETTING_OPTION_OFF;
3335
'testing-library/custom-queries'?: string[] | typeof SETTING_OPTION_OFF;
3436
};
3537

3638
export type TestingLibraryContext<
37-
TOptions extends readonly unknown[],
3839
TMessageIds extends string,
40+
TOptions extends readonly unknown[],
3941
> = Readonly<
4042
TSESLint.RuleContext<TMessageIds, TOptions> & {
4143
settings: TestingLibrarySettings;
4244
}
4345
>;
4446

4547
export type EnhancedRuleCreate<
46-
TOptions extends readonly unknown[],
4748
TMessageIds extends string,
48-
TRuleListener extends TSESLint.RuleListener = TSESLint.RuleListener,
49+
TOptions extends readonly unknown[],
4950
> = (
50-
context: TestingLibraryContext<TOptions, TMessageIds>,
51+
context: TestingLibraryContext<TMessageIds, TOptions>,
5152
optionsWithDefault: Readonly<TOptions>,
5253
detectionHelpers: Readonly<DetectionHelpers>
53-
) => TRuleListener;
54+
) => TSESLint.RuleListener;
5455

5556
// Helpers methods
5657
type GetTestingLibraryImportNodeFn = () => ImportModuleNode | null;
@@ -154,15 +155,14 @@ export type DetectionOptions = {
154155
* Enhances a given rule `create` with helpers to detect Testing Library utils.
155156
*/
156157
export function detectTestingLibraryUtils<
157-
TOptions extends readonly unknown[],
158158
TMessageIds extends string,
159-
TRuleListener extends TSESLint.RuleListener = TSESLint.RuleListener,
159+
TOptions extends readonly unknown[],
160160
>(
161-
ruleCreate: EnhancedRuleCreate<TOptions, TMessageIds, TRuleListener>,
161+
ruleCreate: EnhancedRuleCreate<TMessageIds, TOptions>,
162162
{ skipRuleReportingCheck = false }: Partial<DetectionOptions> = {}
163163
) {
164164
return (
165-
context: TestingLibraryContext<TOptions, TMessageIds>,
165+
context: TestingLibraryContext<TMessageIds, TOptions>,
166166
optionsWithDefault: Readonly<TOptions>
167167
): TSESLint.RuleListener => {
168168
const importedTestingLibraryNodes: ImportModuleNode[] = [];
@@ -212,6 +212,7 @@ export function detectTestingLibraryUtils<
212212

213213
const originalNodeName =
214214
isImportSpecifier(importedUtilSpecifier) &&
215+
ASTUtils.isIdentifier(importedUtilSpecifier.imported) &&
215216
importedUtilSpecifier.local.name !== importedUtilSpecifier.imported.name
216217
? importedUtilSpecifier.imported.name
217218
: undefined;
+18-25
Original file line numberDiff line numberDiff line change
@@ -1,44 +1,37 @@
1-
import { ESLintUtils, TSESLint } from '@typescript-eslint/utils';
1+
import { ESLintUtils } from '@typescript-eslint/utils';
22

3-
import { getDocsUrl, TestingLibraryRuleMeta } from '../utils';
3+
import { getDocsUrl, TestingLibraryPluginDocs } from '../utils';
44

55
import {
66
DetectionOptions,
77
detectTestingLibraryUtils,
88
EnhancedRuleCreate,
99
} from './detect-testing-library-utils';
1010

11-
export function createTestingLibraryRule<
11+
export const createTestingLibraryRule = <
1212
TOptions extends readonly unknown[],
1313
TMessageIds extends string,
14-
TRuleListener extends TSESLint.RuleListener = TSESLint.RuleListener,
1514
>({
1615
create,
1716
detectionOptions = {},
18-
meta,
1917
...remainingConfig
20-
}: Readonly<{
21-
name: string;
22-
meta: TestingLibraryRuleMeta<TMessageIds, TOptions>;
23-
defaultOptions: Readonly<TOptions>;
24-
detectionOptions?: Partial<DetectionOptions>;
25-
create: EnhancedRuleCreate<TOptions, TMessageIds, TRuleListener>;
26-
}>): TSESLint.RuleModule<TMessageIds, TOptions> {
27-
// eslint-disable-next-line new-cap
28-
return ESLintUtils.RuleCreator(getDocsUrl)({
18+
}: Readonly<
19+
Omit<
20+
ESLintUtils.RuleWithMetaAndName<
21+
TOptions,
22+
TMessageIds,
23+
TestingLibraryPluginDocs<TOptions>
24+
>,
25+
'create'
26+
> & {
27+
create: EnhancedRuleCreate<TMessageIds, TOptions>;
28+
detectionOptions?: Partial<DetectionOptions>;
29+
}
30+
>) =>
31+
ESLintUtils.RuleCreator<TestingLibraryPluginDocs<TOptions>>(getDocsUrl)({
2932
...remainingConfig,
30-
create: detectTestingLibraryUtils<TOptions, TMessageIds, TRuleListener>(
33+
create: detectTestingLibraryUtils<TMessageIds, TOptions>(
3134
create,
3235
detectionOptions
3336
),
34-
meta: {
35-
...meta,
36-
docs: {
37-
...meta.docs,
38-
// We're using our own recommendedConfig meta to tell our build tools
39-
// if the rule is recommended on a config basis
40-
recommended: false,
41-
},
42-
},
4337
});
44-
}

lib/index.ts

+4-4
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ import { SupportedTestingFramework } from './utils';
88
const {
99
name: packageName,
1010
version: packageVersion,
11-
// eslint-disable-next-line @typescript-eslint/no-var-requires
11+
// eslint-disable-next-line @typescript-eslint/no-require-imports
1212
} = require('../package.json') as { name: string; version: string };
1313

1414
const plugin = {
@@ -20,7 +20,7 @@ const plugin = {
2020
// we don't have types for flat config yet
2121
configs: {} as Record<
2222
SupportedTestingFramework | `flat/${SupportedTestingFramework}`,
23-
Pick<Required<TSESLint.Linter.Config>, 'rules'>
23+
TSESLint.SharedConfig.RulesRecord
2424
>,
2525
rules,
2626
};
@@ -35,9 +35,9 @@ plugin.configs = {
3535
rules: config.rules,
3636
},
3737
])
38-
) as Record<
38+
) as unknown as Record<
3939
`flat/${SupportedTestingFramework}`,
40-
Pick<Required<TSESLint.Linter.Config>, 'rules'> & { plugins: unknown }
40+
TSESLint.SharedConfig.RulesRecord & { plugins: unknown }
4141
>),
4242
};
4343

lib/node-utils/index.ts

+5-4
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
1+
import { FunctionScope, ScopeType } from '@typescript-eslint/scope-manager';
12
import {
23
AST_NODE_TYPES,
34
ASTUtils,
45
TSESLint,
5-
TSESLintScope,
66
TSESTree,
77
} from '@typescript-eslint/utils';
88

@@ -188,7 +188,7 @@ export function isPromiseAllSettled(node: TSESTree.CallExpression): boolean {
188188
}
189189

190190
/**
191-
* Determines whether a given node belongs to handled Promise.all or Promise.allSettled
191+
* Determines whether a given node belongs to handled `Promise.all` or `Promise.allSettled`
192192
* array expression.
193193
*/
194194
export function isPromisesArrayResolved(node: TSESTree.Node): boolean {
@@ -295,7 +295,7 @@ export function getVariableReferences(
295295
return [];
296296
}
297297

298-
interface InnermostFunctionScope extends TSESLintScope.FunctionScope {
298+
interface InnermostFunctionScope extends FunctionScope {
299299
block:
300300
| TSESTree.ArrowFunctionExpression
301301
| TSESTree.FunctionDeclaration
@@ -312,7 +312,7 @@ export function getInnermostFunctionScope(
312312
);
313313

314314
if (
315-
innermostScope.type === 'function' &&
315+
innermostScope.type === ScopeType.function &&
316316
ASTUtils.isFunction(innermostScope.block)
317317
) {
318318
return innermostScope as unknown as InnermostFunctionScope;
@@ -665,6 +665,7 @@ export function findImportSpecifier(
665665
const namedExport = node.specifiers.find((n) => {
666666
return (
667667
isImportSpecifier(n) &&
668+
ASTUtils.isIdentifier(n.imported) &&
668669
[n.imported.name, n.local.name].includes(specifierName)
669670
);
670671
});

lib/rules/await-async-events.ts

+3-3
Original file line numberDiff line numberDiff line change
@@ -55,15 +55,15 @@ export default createTestingLibraryRule<Options, MessageIds>({
5555
default: USER_EVENT_NAME,
5656
oneOf: [
5757
{
58+
enum: EVENTS_SIMULATORS.concat(),
5859
type: 'string',
59-
enum: EVENTS_SIMULATORS,
6060
},
6161
{
62-
type: 'array',
6362
items: {
6463
type: 'string',
65-
enum: EVENTS_SIMULATORS,
64+
enum: EVENTS_SIMULATORS.concat(),
6665
},
66+
type: 'array',
6767
},
6868
],
6969
},

lib/rules/index.ts

+5-11
Original file line numberDiff line numberDiff line change
@@ -1,26 +1,20 @@
11
import { readdirSync } from 'fs';
22
import { join, parse } from 'path';
33

4-
import { TSESLint } from '@typescript-eslint/utils';
5-
6-
import { importDefault, TestingLibraryRuleMeta } from '../utils';
7-
8-
type RuleModule = TSESLint.RuleModule<string, unknown[]> & {
9-
meta: TestingLibraryRuleMeta<string, unknown[]> & {
10-
recommended: false;
11-
};
12-
};
4+
import { importDefault, TestingLibraryPluginRuleModule } from '../utils';
135

146
const rulesDir = __dirname;
157
const excludedFiles = ['index'];
168

179
export default readdirSync(rulesDir)
1810
.map((rule) => parse(rule).name)
1911
.filter((ruleName) => !excludedFiles.includes(ruleName))
20-
.reduce<Record<string, RuleModule>>(
12+
.reduce<Record<string, TestingLibraryPluginRuleModule<string, unknown[]>>>(
2113
(allRules, ruleName) => ({
2214
...allRules,
23-
[ruleName]: importDefault<RuleModule>(join(rulesDir, ruleName)),
15+
[ruleName]: importDefault<
16+
TestingLibraryPluginRuleModule<string, unknown[]>
17+
>(join(rulesDir, ruleName)),
2418
}),
2519
{}
2620
);

0 commit comments

Comments
 (0)