Skip to content

Commit f16ce0d

Browse files
jonkoopsljharb
authored andcommitted
[New] react-in-jsx-scope, jsx-uses-react: automatically disable when React >= 19 is detected
React 19 mandates the automatic JSX transform, so `React` no longer needs to be in scope for JSX. This makes the `react-in-jsx-scope` and `jsx-uses-react` rules no-ops when the configured React version is >= 19.0.0, following the same `return {}` early-exit pattern used by `no-unsafe`. Signed-off-by: Jon Koops <jonkoops@gmail.com>
1 parent 1fbff57 commit f16ce0d

8 files changed

Lines changed: 66 additions & 24 deletions

File tree

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ This change log adheres to standards from [Keep a CHANGELOG](https://keepachange
2626
* [`no-unused-prop-types`]: detect used props in nested components ([#3955][] @avaice)
2727

2828
### Changed
29+
* [`react-in-jsx-scope`], [`jsx-uses-react`]: automatically disable when React >= 19 is detected ([#XXXX][] @jonkoops)
2930
* [Docs] [`no-array-index-key`]: add template literal examples ([#3978][] @akahoshi1421)
3031

3132
[#3986]: https://github.com/jsx-eslint/eslint-plugin-react/pull/3986

docs/rules/jsx-uses-react.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -52,3 +52,5 @@ var Hello = <div>Hello {this.props.name}</div>;
5252
If you are not using JSX, if React is declared as global variable, or if you do not use the `no-unused-vars` rule.
5353

5454
If you are using the [new JSX transform from React 17](https://reactjs.org/blog/2020/09/22/introducing-the-new-jsx-transform.html#removing-unused-react-imports), you should disable this rule by extending [`react/jsx-runtime`](https://github.com/jsx-eslint/eslint-plugin-react/blob/HEAD/index.js#L163-L176) in your eslint config (add `"plugin:react/jsx-runtime"` to `"extends"`).
55+
56+
**Note**: When React >= 19.0.0 is detected, this rule is automatically disabled, since the automatic JSX transform is mandatory in React 19.

docs/rules/react-in-jsx-scope.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,3 +51,5 @@ var Hello = <div>Hello {this.props.name}</div>;
5151
If you are not using JSX, or if you are setting `React` as a global variable.
5252

5353
If you are using the [new JSX transform from React 17](https://reactjs.org/blog/2020/09/22/introducing-the-new-jsx-transform.html#removing-unused-react-imports), you should disable this rule by extending [`react/jsx-runtime`](https://github.com/jsx-eslint/eslint-plugin-react/blob/8cf47a8ac2242ee00ea36eac4b6ae51956ba4411/index.js#L165-L179) in your eslint config (add `"plugin:react/jsx-runtime"` to `"extends"`).
54+
55+
**Note**: When React >= 19.0.0 is detected, this rule is automatically disabled, since the automatic JSX transform is mandatory in React 19.

lib/rules/jsx-uses-react.js

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
const pragmaUtil = require('../util/pragma');
99
const docsUrl = require('../util/docsUrl');
1010
const markVariableAsUsed = require('../util/eslint').markVariableAsUsed;
11+
const testReactVersion = require('../util/version').testReactVersion;
1112

1213
// ------------------------------------------------------------------------------
1314
// Rule Definition
@@ -27,6 +28,10 @@ module.exports = {
2728
},
2829

2930
create(context) {
31+
if (testReactVersion(context, '>= 19.0.0')) {
32+
return {};
33+
}
34+
3035
const pragma = pragmaUtil.getFromContext(context);
3136
const fragment = pragmaUtil.getFragmentFromContext(context);
3237

lib/rules/react-in-jsx-scope.js

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ const variableUtil = require('../util/variable');
99
const pragmaUtil = require('../util/pragma');
1010
const docsUrl = require('../util/docsUrl');
1111
const report = require('../util/report');
12+
const testReactVersion = require('../util/version').testReactVersion;
1213

1314
// -----------------------------------------------------------------------------
1415
// Rule Definition
@@ -34,6 +35,10 @@ module.exports = {
3435
},
3536

3637
create(context) {
38+
if (testReactVersion(context, '>= 19.0.0')) {
39+
return {};
40+
}
41+
3742
const pragma = pragmaUtil.getFromContext(context);
3843

3944
function checkIfReactIsInScope(node) {

tests/flat-config.js

Lines changed: 11 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -46,16 +46,13 @@ describe('eslint-plugin-react in flat config', () => {
4646
return eslint.lintFiles(['test.jsx']).then((results) => {
4747
const result = results[0];
4848

49-
assert.strictEqual(result.messages.length, 3);
49+
assert.strictEqual(result.messages.length, 2);
5050
assert.strictEqual(result.messages[0].severity, 2);
51-
assert.strictEqual(result.messages[0].ruleId, 'react/react-in-jsx-scope');
52-
assert.strictEqual(result.messages[0].messageId, 'notInScope');
51+
assert.strictEqual(result.messages[0].ruleId, 'react/no-unknown-property');
52+
assert.strictEqual(result.messages[0].messageId, 'unknownProp');
5353
assert.strictEqual(result.messages[1].severity, 2);
54-
assert.strictEqual(result.messages[1].ruleId, 'react/no-unknown-property');
55-
assert.strictEqual(result.messages[1].messageId, 'unknownProp');
56-
assert.strictEqual(result.messages[2].severity, 2);
57-
assert.strictEqual(result.messages[2].ruleId, 'react/jsx-no-literals');
58-
assert.strictEqual(result.messages[2].messageId, 'literalNotInJSXExpression');
54+
assert.strictEqual(result.messages[1].ruleId, 'react/jsx-no-literals');
55+
assert.strictEqual(result.messages[1].messageId, 'literalNotInJSXExpression');
5956
});
6057
});
6158

@@ -68,13 +65,10 @@ describe('eslint-plugin-react in flat config', () => {
6865
return eslint.lintFiles(['test.jsx']).then((results) => {
6966
const result = results[0];
7067

71-
assert.strictEqual(result.messages.length, 2);
68+
assert.strictEqual(result.messages.length, 1);
7269
assert.strictEqual(result.messages[0].severity, 2);
73-
assert.strictEqual(result.messages[0].ruleId, 'react/react-in-jsx-scope');
74-
assert.strictEqual(result.messages[0].messageId, 'notInScope');
75-
assert.strictEqual(result.messages[1].severity, 2);
76-
assert.strictEqual(result.messages[1].ruleId, 'react/no-unknown-property');
77-
assert.strictEqual(result.messages[1].messageId, 'unknownProp');
70+
assert.strictEqual(result.messages[0].ruleId, 'react/no-unknown-property');
71+
assert.strictEqual(result.messages[0].messageId, 'unknownProp');
7872
});
7973
});
8074

@@ -104,13 +98,10 @@ describe('eslint-plugin-react in flat config', () => {
10498
return eslint.lintFiles(['test.jsx']).then((results) => {
10599
const result = results[0];
106100

107-
assert.strictEqual(result.messages.length, 2);
101+
assert.strictEqual(result.messages.length, 1);
108102
assert.strictEqual(result.messages[0].severity, 2);
109-
assert.strictEqual(result.messages[0].ruleId, 'react/react-in-jsx-scope');
110-
assert.strictEqual(result.messages[0].messageId, 'notInScope');
111-
assert.strictEqual(result.messages[1].severity, 2);
112-
assert.strictEqual(result.messages[1].ruleId, 'react/no-unknown-property');
113-
assert.strictEqual(result.messages[1].messageId, 'unknownProp');
103+
assert.strictEqual(result.messages[0].ruleId, 'react/no-unknown-property');
104+
assert.strictEqual(result.messages[0].messageId, 'unknownProp');
114105
});
115106
});
116107
});

tests/lib/rules/jsx-uses-react.js

Lines changed: 18 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -27,14 +27,18 @@ const parserOptions = {
2727
const settings = {
2828
react: {
2929
pragma: 'Foo',
30+
version: '18',
3031
},
3132
};
3233

3334
// -----------------------------------------------------------------------------
3435
// Tests
3536
// -----------------------------------------------------------------------------
3637

37-
const ruleTester = new RuleTester({ parserOptions });
38+
const ruleTester = new RuleTester({
39+
parserOptions,
40+
settings: { react: { version: '18' } },
41+
});
3842
const ruleDefiner = getRuleDefiner(ruleTester);
3943
ruleDefiner.defineRule('react/jsx-uses-react', require('../../../lib/rules/jsx-uses-react'));
4044

@@ -49,7 +53,7 @@ ruleTester.run('no-unused-vars', rule, {
4953
},
5054
{
5155
code: '/*eslint react/jsx-uses-react:1*/ var Frag; <></>;',
52-
settings: { react: { fragment: 'Frag' } },
56+
settings: { react: { fragment: 'Frag', version: '18' } },
5357
features: ['fragment'],
5458
},
5559
{
@@ -58,6 +62,17 @@ ruleTester.run('no-unused-vars', rule, {
5862
},
5963
].map(parsers.disableNewTS)),
6064
invalid: parsers.all([
65+
{
66+
code: '/*eslint react/jsx-uses-react:1*/ var React; <div />;',
67+
settings: { react: { version: '19.0.0' } },
68+
errors: [{
69+
message: '\'React\' is defined but never used.',
70+
suggestions: [{
71+
messageId: 'removeVar',
72+
output: '/*eslint react/jsx-uses-react:1*/ <div />;',
73+
}],
74+
}],
75+
},
6176
{
6277
code: '/*eslint react/jsx-uses-react:1*/ var React;',
6378
errors: [{
@@ -99,7 +114,7 @@ ruleTester.run('no-unused-vars', rule, {
99114
}],
100115
}],
101116
features: ['fragment'],
102-
settings: { react: { fragment: 'Fragment' } },
117+
settings: { react: { fragment: 'Fragment', version: '18' } },
103118
},
104119
{
105120
code: '/*eslint react/jsx-uses-react:1*/ var React; <></>;',

tests/lib/rules/react-in-jsx-scope.js

Lines changed: 22 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,14 +25,18 @@ const parserOptions = {
2525
const settings = {
2626
react: {
2727
pragma: 'Foo',
28+
version: '18',
2829
},
2930
};
3031

3132
// -----------------------------------------------------------------------------
3233
// Tests
3334
// -----------------------------------------------------------------------------
3435

35-
const ruleTester = new RuleTester({ parserOptions });
36+
const ruleTester = new RuleTester({
37+
parserOptions,
38+
settings: { react: { version: '18' } },
39+
});
3640
ruleTester.run('react-in-jsx-scope', rule, {
3741
valid: parsers.all([
3842
{ code: 'var React, App; <App />;' },
@@ -63,6 +67,23 @@ ruleTester.run('react-in-jsx-scope', rule, {
6367
code: 'var Foo, App; <App />;',
6468
settings,
6569
},
70+
{
71+
code: 'var App, a = <App />;',
72+
settings: { react: { version: '19.0.0' } },
73+
},
74+
{
75+
code: 'var a = <App />;',
76+
settings: { react: { version: '19.0.0' } },
77+
},
78+
{
79+
code: 'var a = <img />;',
80+
settings: { react: { version: '19.0.0' } },
81+
},
82+
{
83+
code: 'var a = <>fragment</>;',
84+
features: ['fragment'],
85+
settings: { react: { version: '19.0.0' } },
86+
},
6687
]),
6788
invalid: parsers.all([
6889
{

0 commit comments

Comments
 (0)