Skip to content

Commit 4f872d6

Browse files
authored
[code-infra] Add "use client" directive to files with React APIs (#45036)
1 parent 57639f9 commit 4f872d6

File tree

20 files changed

+84
-0
lines changed

20 files changed

+84
-0
lines changed

.eslintrc.js

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -408,6 +408,21 @@ module.exports = /** @type {Config} */ ({
408408
'react/prop-types': 'off',
409409
},
410410
},
411+
{
412+
files: ['packages/*/src/*/*.?(c|m)[jt]s?(x)'],
413+
excludedFiles: [
414+
'*.spec.*',
415+
'*.test.*',
416+
// deprecated library
417+
'**/mui-base/**/*',
418+
'**/mui-joy/**/*',
419+
// used internally, not used on app router yet
420+
'**/mui-docs/**/*',
421+
],
422+
rules: {
423+
'material-ui/disallow-react-api-in-server-components': 'error',
424+
},
425+
},
411426
{
412427
files: ['packages/*/src/**/*.?(c|m)[jt]s?(x)'],
413428
excludedFiles: '*.spec.*',

packages/eslint-plugin-material-ui/src/index.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,4 +8,5 @@ module.exports.rules = {
88
'no-empty-box': require('./rules/no-empty-box'),
99
'no-styled-box': require('./rules/no-styled-box'),
1010
'straight-quotes': require('./rules/straight-quotes'),
11+
'disallow-react-api-in-server-components': require('./rules/disallow-react-api-in-server-components'),
1112
};
Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
module.exports = {
2+
create(context) {
3+
let hasUseClientDirective = false;
4+
const apis = new Set([
5+
'useState',
6+
'useEffect',
7+
'useLayoutEffect',
8+
'useReducer',
9+
'useTransition',
10+
'createContext',
11+
]);
12+
return {
13+
Program(node) {
14+
hasUseClientDirective = node.body.some(
15+
(statement) =>
16+
statement.type === 'ExpressionStatement' &&
17+
statement.expression.type === 'Literal' &&
18+
statement.expression.value === 'use client',
19+
);
20+
},
21+
CallExpression(node) {
22+
if (
23+
!hasUseClientDirective &&
24+
node.callee.type === 'MemberExpression' &&
25+
node.callee.object.name === 'React' &&
26+
apis.has(node.callee.property.name)
27+
) {
28+
context.report({
29+
node,
30+
message: `Using 'React.${node.callee.property.name}' is forbidden if the file doesn't have a 'use client' directive.`,
31+
fix(fixer) {
32+
const sourceCode = context.getSourceCode();
33+
if (
34+
sourceCode.text.includes('"use server"') ||
35+
sourceCode.text.includes("'use server'")
36+
) {
37+
return null;
38+
}
39+
40+
const firstToken = sourceCode.ast.body[0];
41+
return fixer.insertTextBefore(firstToken, "'use client';\n");
42+
},
43+
});
44+
}
45+
},
46+
};
47+
},
48+
meta: {
49+
fixable: 'code',
50+
},
51+
};

packages/mui-lab/src/Timeline/TimelineContext.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
'use client';
12
import * as React from 'react';
23

34
/**

packages/mui-material/src/ButtonGroup/ButtonGroupButtonContext.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
'use client';
12
import * as React from 'react';
23

34
type ButtonPositionClassName = string;

packages/mui-material/src/ButtonGroup/ButtonGroupContext.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
'use client';
12
import * as React from 'react';
23
import type { ButtonGroupProps } from './ButtonGroup';
34

packages/mui-material/src/Dialog/DialogContext.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
'use client';
12
import * as React from 'react';
23

34
interface DialogContextValue {

packages/mui-material/src/FormControl/FormControlContext.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
'use client';
12
import * as React from 'react';
23
import type { FormControlProps } from './FormControl';
34

packages/mui-material/src/Hidden/withWidth.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
'use client';
12
import * as React from 'react';
23
import PropTypes from 'prop-types';
34
import getDisplayName from '@mui/utils/getDisplayName';

packages/mui-material/src/RadioGroup/RadioGroupContext.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
'use client';
12
import * as React from 'react';
23

34
export interface RadioGroupContextValue {

0 commit comments

Comments
 (0)