eslint-config-typed 3.8.1 → 3.10.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (66) hide show
  1. package/README.md +1 -1
  2. package/dist/plugins/react-coding-style/rules/ban-use-imperative-handle-hook.mjs +4 -4
  3. package/dist/plugins/react-coding-style/rules/ban-use-imperative-handle-hook.mjs.map +1 -1
  4. package/dist/plugins/react-coding-style/rules/component-name.d.mts.map +1 -1
  5. package/dist/plugins/react-coding-style/rules/component-name.mjs +5 -3
  6. package/dist/plugins/react-coding-style/rules/component-name.mjs.map +1 -1
  7. package/dist/plugins/react-coding-style/rules/display-name.d.mts +14 -0
  8. package/dist/plugins/react-coding-style/rules/display-name.d.mts.map +1 -0
  9. package/dist/plugins/react-coding-style/rules/display-name.mjs +86 -0
  10. package/dist/plugins/react-coding-style/rules/display-name.mjs.map +1 -0
  11. package/dist/plugins/react-coding-style/rules/import-style.d.mts +8 -2
  12. package/dist/plugins/react-coding-style/rules/import-style.d.mts.map +1 -1
  13. package/dist/plugins/react-coding-style/rules/import-style.mjs +63 -26
  14. package/dist/plugins/react-coding-style/rules/import-style.mjs.map +1 -1
  15. package/dist/plugins/react-coding-style/rules/props-type-annotation-style.mjs +2 -2
  16. package/dist/plugins/react-coding-style/rules/props-type-annotation-style.mjs.map +1 -1
  17. package/dist/plugins/react-coding-style/rules/react-memo-props-argument-name.mjs +2 -2
  18. package/dist/plugins/react-coding-style/rules/react-memo-props-argument-name.mjs.map +1 -1
  19. package/dist/plugins/react-coding-style/rules/react-memo-type-parameter.mjs +2 -2
  20. package/dist/plugins/react-coding-style/rules/react-memo-type-parameter.mjs.map +1 -1
  21. package/dist/plugins/react-coding-style/rules/rules.d.mts +6 -1
  22. package/dist/plugins/react-coding-style/rules/rules.d.mts.map +1 -1
  23. package/dist/plugins/react-coding-style/rules/rules.mjs +2 -0
  24. package/dist/plugins/react-coding-style/rules/rules.mjs.map +1 -1
  25. package/dist/plugins/react-coding-style/rules/shared.d.mts +7 -3
  26. package/dist/plugins/react-coding-style/rules/shared.d.mts.map +1 -1
  27. package/dist/plugins/react-coding-style/rules/shared.mjs +61 -3
  28. package/dist/plugins/react-coding-style/rules/shared.mjs.map +1 -1
  29. package/dist/plugins/react-coding-style/rules/use-memo-hooks-style.d.mts.map +1 -1
  30. package/dist/plugins/react-coding-style/rules/use-memo-hooks-style.mjs +45 -3
  31. package/dist/plugins/react-coding-style/rules/use-memo-hooks-style.mjs.map +1 -1
  32. package/dist/plugins/ts-restrictions/rules/check-destructuring-completeness.d.mts.map +1 -1
  33. package/dist/plugins/ts-restrictions/rules/check-destructuring-completeness.mjs +46 -46
  34. package/dist/plugins/ts-restrictions/rules/check-destructuring-completeness.mjs.map +1 -1
  35. package/dist/rules/eslint-import-rules.d.mts +1 -3
  36. package/dist/rules/eslint-import-rules.d.mts.map +1 -1
  37. package/dist/rules/eslint-import-rules.mjs +3 -1
  38. package/dist/rules/eslint-import-rules.mjs.map +1 -1
  39. package/dist/rules/eslint-react-coding-style-rules.d.mts +6 -1
  40. package/dist/rules/eslint-react-coding-style-rules.d.mts.map +1 -1
  41. package/dist/rules/eslint-react-coding-style-rules.mjs +3 -2
  42. package/dist/rules/eslint-react-coding-style-rules.mjs.map +1 -1
  43. package/dist/types/rules/eslint-react-coding-style-rules.d.mts +69 -2
  44. package/dist/types/rules/eslint-react-coding-style-rules.d.mts.map +1 -1
  45. package/package.json +1 -1
  46. package/src/plugins/react-coding-style/README.md +22 -0
  47. package/src/plugins/react-coding-style/rules/ban-use-imperative-handle-hook.mts +4 -4
  48. package/src/plugins/react-coding-style/rules/ban-use-imperative-handle-hook.test.mts +50 -18
  49. package/src/plugins/react-coding-style/rules/component-name.mts +6 -3
  50. package/src/plugins/react-coding-style/rules/display-name.mts +117 -0
  51. package/src/plugins/react-coding-style/rules/display-name.test.mts +87 -0
  52. package/src/plugins/react-coding-style/rules/import-style.mts +92 -34
  53. package/src/plugins/react-coding-style/rules/import-style.test.mts +81 -34
  54. package/src/plugins/react-coding-style/rules/props-type-annotation-style.mts +2 -2
  55. package/src/plugins/react-coding-style/rules/react-memo-props-argument-name.mts +2 -2
  56. package/src/plugins/react-coding-style/rules/react-memo-type-parameter.mts +2 -2
  57. package/src/plugins/react-coding-style/rules/rules.mts +2 -0
  58. package/src/plugins/react-coding-style/rules/shared.mts +92 -7
  59. package/src/plugins/react-coding-style/rules/use-memo-hooks-style-named.test.mts +162 -0
  60. package/src/plugins/react-coding-style/rules/use-memo-hooks-style-namespace.test.mts +162 -0
  61. package/src/plugins/react-coding-style/rules/use-memo-hooks-style.mts +68 -3
  62. package/src/plugins/ts-restrictions/rules/check-destructuring-completeness.mts +59 -59
  63. package/src/rules/eslint-import-rules.mts +4 -1
  64. package/src/rules/eslint-react-coding-style-rules.mts +3 -2
  65. package/src/types/rules/eslint-react-coding-style-rules.mts +78 -2
  66. package/src/plugins/react-coding-style/rules/use-memo-hooks-style.test.mts +0 -72
@@ -0,0 +1,162 @@
1
+ import parser from '@typescript-eslint/parser';
2
+ import { RuleTester } from '@typescript-eslint/rule-tester';
3
+ import dedent from 'dedent';
4
+ import { useMemoHooksStyleRule } from './use-memo-hooks-style.mjs';
5
+
6
+ const ruleName = 'use-memo-hook-style';
7
+
8
+ const tester = new RuleTester({
9
+ languageOptions: {
10
+ parser,
11
+ parserOptions: {
12
+ ecmaVersion: 2020,
13
+ sourceType: 'module',
14
+ ecmaFeatures: {
15
+ jsx: true,
16
+ },
17
+ jsxPragma: null, // for @typescript/eslint-parser
18
+ },
19
+ },
20
+ });
21
+
22
+ describe('use-memo-hooks-style', () => {
23
+ describe('named import (useMemo)', () => {
24
+ tester.run(ruleName, useMemoHooksStyleRule, {
25
+ valid: [
26
+ {
27
+ code: dedent`
28
+ import { memo, useMemo } from 'react';
29
+
30
+ type Props = Readonly<{
31
+ readonly value: number;
32
+ }>;
33
+
34
+ const Component = memo<Props>((props) => {
35
+ const memoized = useMemo<number>(() => props.value, [props.value]);
36
+ const typed: number = useMemo(() => props.value, [props.value]);
37
+ return <div />;
38
+ });
39
+ `,
40
+ },
41
+ {
42
+ name: 'Should not trigger for non-React useMemo (locally defined)',
43
+ code: dedent`
44
+ const useMemo = <T,>(fn: () => T): T => fn();
45
+
46
+ const value = useMemo(() => 42) as number;
47
+ `,
48
+ },
49
+ {
50
+ name: 'Should not trigger for non-React useMemo (imported from other-library)',
51
+ code: dedent`
52
+ import { useMemo } from 'other-library';
53
+
54
+ const value = useMemo(() => 42) as number;
55
+ `,
56
+ },
57
+ ],
58
+ invalid: [
59
+ {
60
+ name: 'Disallow return type annotation',
61
+ code: dedent`
62
+ import { memo, useMemo } from 'react';
63
+
64
+ type Props = Readonly<{
65
+ readonly value: number;
66
+ }>;
67
+
68
+ const Component = memo<Props>((props) => {
69
+ const value = useMemo((): number => props.value, [props.value]);
70
+ return value;
71
+ });
72
+ `,
73
+ errors: [
74
+ {
75
+ messageId: 'disallowUseMemoTypeAnnotation',
76
+ },
77
+ ],
78
+ },
79
+ {
80
+ name: 'Disallow type assertion',
81
+ code: dedent`
82
+ import { memo, useMemo } from 'react';
83
+
84
+ type Props = Readonly<{
85
+ readonly value: number;
86
+ }>;
87
+
88
+ const Component = memo<Props>((props) => {
89
+ const value = useMemo(() => props.value, [props.value]) as number;
90
+ return value;
91
+ });
92
+ `,
93
+ errors: [
94
+ {
95
+ messageId: 'disallowUseMemoTypeAnnotation',
96
+ },
97
+ ],
98
+ },
99
+ {
100
+ name: 'Disallow type assertion (inner)',
101
+ code: dedent`
102
+ import { memo, useMemo } from 'react';
103
+
104
+ type Props = Readonly<{
105
+ readonly value: number;
106
+ }>;
107
+
108
+ const Component = memo<Props>((props) => {
109
+ const value = useMemo(() => props.value as number, [props.value]) ;
110
+ return value;
111
+ });
112
+ `,
113
+ errors: [
114
+ {
115
+ messageId: 'disallowUseMemoTypeAnnotation',
116
+ },
117
+ ],
118
+ },
119
+ {
120
+ name: 'Disallow satisfies expression',
121
+ code: dedent`
122
+ import { memo, useMemo } from 'react';
123
+
124
+ type Props = Readonly<{
125
+ readonly value: number;
126
+ }>;
127
+
128
+ const Component = memo<Props>((props) => {
129
+ const value = useMemo(() => props.value, [props.value]) satisfies number;
130
+ return value;
131
+ });
132
+ `,
133
+ errors: [
134
+ {
135
+ messageId: 'disallowUseMemoTypeAnnotation',
136
+ },
137
+ ],
138
+ },
139
+ {
140
+ name: 'Disallow satisfies expression (inner)',
141
+ code: dedent`
142
+ import { memo, useMemo } from 'react';
143
+
144
+ type Props = Readonly<{
145
+ readonly value: number;
146
+ }>;
147
+
148
+ const Component = memo<Props>((props) => {
149
+ const value = useMemo(() => props.value satisfies number, [props.value]);
150
+ return value;
151
+ });
152
+ `,
153
+ errors: [
154
+ {
155
+ messageId: 'disallowUseMemoTypeAnnotation',
156
+ },
157
+ ],
158
+ },
159
+ ],
160
+ });
161
+ });
162
+ });
@@ -0,0 +1,162 @@
1
+ import parser from '@typescript-eslint/parser';
2
+ import { RuleTester } from '@typescript-eslint/rule-tester';
3
+ import dedent from 'dedent';
4
+ import { useMemoHooksStyleRule } from './use-memo-hooks-style.mjs';
5
+
6
+ const ruleName = 'use-memo-hook-style';
7
+
8
+ const tester = new RuleTester({
9
+ languageOptions: {
10
+ parser,
11
+ parserOptions: {
12
+ ecmaVersion: 2020,
13
+ sourceType: 'module',
14
+ ecmaFeatures: {
15
+ jsx: true,
16
+ },
17
+ jsxPragma: null, // for @typescript/eslint-parser
18
+ },
19
+ },
20
+ });
21
+
22
+ describe('use-memo-hooks-style', () => {
23
+ describe('namespace import (React.useMemo)', () => {
24
+ tester.run(ruleName, useMemoHooksStyleRule, {
25
+ valid: [
26
+ {
27
+ code: dedent`
28
+ import * as React from 'react';
29
+
30
+ type Props = Readonly<{
31
+ readonly value: number;
32
+ }>;
33
+
34
+ const Component = React.memo<Props>((props) => {
35
+ const memoized = React.useMemo<number>(() => props.value, [props.value]);
36
+ const typed: number = React.useMemo(() => props.value, [props.value]);
37
+ return <div />;
38
+ });
39
+ `,
40
+ },
41
+ {
42
+ name: 'Should not trigger for non-React useMemo (locally defined)',
43
+ code: dedent`
44
+ const useMemo = <T,>(fn: () => T): T => fn();
45
+
46
+ const value = useMemo(() => 42) as number;
47
+ `,
48
+ },
49
+ {
50
+ name: 'Should not trigger for non-React useMemo (imported from other-library)',
51
+ code: dedent`
52
+ import { useMemo } from 'other-library';
53
+
54
+ const value = useMemo(() => 42) as number;
55
+ `,
56
+ },
57
+ ],
58
+ invalid: [
59
+ {
60
+ name: 'Disallow return type annotation',
61
+ code: dedent`
62
+ import * as React from 'react';
63
+
64
+ type Props = Readonly<{
65
+ readonly value: number;
66
+ }>;
67
+
68
+ const Component = React.memo<Props>((props) => {
69
+ const value = React.useMemo((): number => props.value, [props.value]);
70
+ return value;
71
+ });
72
+ `,
73
+ errors: [
74
+ {
75
+ messageId: 'disallowUseMemoTypeAnnotation',
76
+ },
77
+ ],
78
+ },
79
+ {
80
+ name: 'Disallow type assertion',
81
+ code: dedent`
82
+ import * as React from 'react';
83
+
84
+ type Props = Readonly<{
85
+ readonly value: number;
86
+ }>;
87
+
88
+ const Component = React.memo<Props>((props) => {
89
+ const value = React.useMemo(() => props.value, [props.value]) as number;
90
+ return value;
91
+ });
92
+ `,
93
+ errors: [
94
+ {
95
+ messageId: 'disallowUseMemoTypeAnnotation',
96
+ },
97
+ ],
98
+ },
99
+ {
100
+ name: 'Disallow type assertion (inner)',
101
+ code: dedent`
102
+ import * as React from 'react';
103
+
104
+ type Props = Readonly<{
105
+ readonly value: number;
106
+ }>;
107
+
108
+ const Component = React.memo<Props>((props) => {
109
+ const value = React.useMemo(() => props.value as number, [props.value]) ;
110
+ return value;
111
+ });
112
+ `,
113
+ errors: [
114
+ {
115
+ messageId: 'disallowUseMemoTypeAnnotation',
116
+ },
117
+ ],
118
+ },
119
+ {
120
+ name: 'Disallow satisfies expression',
121
+ code: dedent`
122
+ import * as React from 'react';
123
+
124
+ type Props = Readonly<{
125
+ readonly value: number;
126
+ }>;
127
+
128
+ const Component = React.memo<Props>((props) => {
129
+ const value = React.useMemo(() => props.value, [props.value]) satisfies number;
130
+ return value;
131
+ });
132
+ `,
133
+ errors: [
134
+ {
135
+ messageId: 'disallowUseMemoTypeAnnotation',
136
+ },
137
+ ],
138
+ },
139
+ {
140
+ name: 'Disallow satisfies expression (inner)',
141
+ code: dedent`
142
+ import * as React from 'react';
143
+
144
+ type Props = Readonly<{
145
+ readonly value: number;
146
+ }>;
147
+
148
+ const Component = React.memo<Props>((props) => {
149
+ const value = React.useMemo(() => props.value satisfies number, [props.value]);
150
+ return value;
151
+ });
152
+ `,
153
+ errors: [
154
+ {
155
+ messageId: 'disallowUseMemoTypeAnnotation',
156
+ },
157
+ ],
158
+ },
159
+ ],
160
+ });
161
+ });
162
+ });
@@ -3,8 +3,8 @@ import {
3
3
  type TSESLint,
4
4
  type TSESTree,
5
5
  } from '@typescript-eslint/utils';
6
- import { castDeepMutable } from 'ts-data-forge';
7
- import { isReactCallExpression } from './shared.mjs';
6
+ import { castDeepMutable, hasKey } from 'ts-data-forge';
7
+ import { isReactApiCall } from './shared.mjs';
8
8
 
9
9
  type MessageIds = 'disallowUseMemoTypeAnnotation';
10
10
 
@@ -23,7 +23,7 @@ export const useMemoHooksStyleRule: TSESLint.RuleModule<MessageIds> = {
23
23
  },
24
24
  create: (context) => ({
25
25
  CallExpression: (node: DeepReadonly<TSESTree.CallExpression>) => {
26
- if (!isReactCallExpression(node, 'useMemo')) {
26
+ if (!isReactApiCall(context, node, 'useMemo')) {
27
27
  return;
28
28
  }
29
29
 
@@ -46,7 +46,72 @@ export const useMemoHooksStyleRule: TSESLint.RuleModule<MessageIds> = {
46
46
  messageId: 'disallowUseMemoTypeAnnotation',
47
47
  });
48
48
  }
49
+
50
+ const [firstArg] = node.arguments;
51
+
52
+ if (firstArg?.type === AST_NODE_TYPES.ArrowFunctionExpression) {
53
+ const { returnType, body } = firstArg;
54
+
55
+ if (returnType !== undefined) {
56
+ context.report({
57
+ node: castDeepMutable(returnType),
58
+ messageId: 'disallowUseMemoTypeAnnotation',
59
+ });
60
+ }
61
+
62
+ checkNodeForTypeAnnotations(context, body);
63
+ }
49
64
  },
50
65
  }),
51
66
  defaultOptions: [],
52
67
  };
68
+
69
+ const checkNodeForTypeAnnotations = (
70
+ context: DeepReadonly<TSESLint.RuleContext<MessageIds, readonly []>>,
71
+ node: DeepReadonly<TSESTree.Node>,
72
+ ): void => {
73
+ if (
74
+ node.type === AST_NODE_TYPES.TSAsExpression ||
75
+ node.type === AST_NODE_TYPES.TSTypeAssertion ||
76
+ node.type === AST_NODE_TYPES.TSSatisfiesExpression
77
+ ) {
78
+ context.report({
79
+ node: castDeepMutable(node),
80
+ messageId: 'disallowUseMemoTypeAnnotation',
81
+ });
82
+
83
+ return;
84
+ }
85
+
86
+ if (hasKey(node, 'body')) {
87
+ const nodeWithBody = node;
88
+
89
+ if (nodeWithBody.body !== undefined && nodeWithBody.body !== null) {
90
+ checkNodeForTypeAnnotations(
91
+ context,
92
+ // eslint-disable-next-line total-functions/no-unsafe-type-assertion
93
+ nodeWithBody.body as DeepReadonly<TSESTree.Node>,
94
+ );
95
+ }
96
+ }
97
+
98
+ if (hasKey(node, 'expression')) {
99
+ const nodeWithExpression = node;
100
+
101
+ checkNodeForTypeAnnotations(
102
+ context,
103
+ // eslint-disable-next-line total-functions/no-unsafe-type-assertion
104
+ nodeWithExpression.expression as DeepReadonly<TSESTree.Node>,
105
+ );
106
+ }
107
+
108
+ if (hasKey(node, 'argument')) {
109
+ const nodeWithArgument = node;
110
+
111
+ checkNodeForTypeAnnotations(
112
+ context,
113
+ // eslint-disable-next-line total-functions/no-unsafe-type-assertion
114
+ nodeWithArgument.argument as DeepReadonly<TSESTree.Node>,
115
+ );
116
+ }
117
+ };
@@ -16,64 +16,6 @@ type MessageIds = 'incompleteDestructuring';
16
16
 
17
17
  const DEFAULT_DIRECTIVE_KEYWORD = '@check-destructuring-completeness';
18
18
 
19
- // eslint-disable-next-line @typescript-eslint/prefer-readonly-parameter-types
20
- const getObjectTypeProperties = (type: ts.Type): readonly string[] => {
21
- try {
22
- const properties = type.getProperties();
23
-
24
- // Limit to reasonable number of properties to avoid hangs
25
- if (properties.length > 1000) {
26
- return [];
27
- }
28
-
29
- return properties
30
- .map((prop) => prop.name)
31
- .filter(
32
- (name) =>
33
- // Filter out symbol properties and internal properties
34
- !name.startsWith('__') &&
35
- // Only include string property names
36
- typeof name === 'string' &&
37
- name.length > 0,
38
- );
39
- } catch {
40
- // If there's any error getting properties, return empty array
41
- return [];
42
- }
43
- };
44
-
45
- const isReactComponentFunction = (
46
- node: DeepReadonly<TSESTree.Node> | undefined | null,
47
- ): boolean => {
48
- if (node === undefined || node === null) return false;
49
-
50
- // Arrow function component
51
- if (node.type === AST_NODE_TYPES.ArrowFunctionExpression) {
52
- const { body } = node;
53
-
54
- if (body.type === AST_NODE_TYPES.BlockStatement) {
55
- return body.body.some((statement) => {
56
- if (statement.type !== AST_NODE_TYPES.ReturnStatement) return false;
57
-
58
- const { argument } = statement;
59
-
60
- // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
61
- if (argument === null || argument === undefined) return false;
62
-
63
- const argType = (argument as { type?: string }).type;
64
-
65
- return argType === 'JSXElement' || argType === 'JSXFragment';
66
- });
67
- }
68
-
69
- const bodyType = (body as { type?: string }).type;
70
-
71
- return bodyType === 'JSXElement' || bodyType === 'JSXFragment';
72
- }
73
-
74
- return false;
75
- };
76
-
77
19
  export const checkDestructuringCompleteness: TSESLint.RuleModule<
78
20
  MessageIds,
79
21
  Options
@@ -302,5 +244,63 @@ export const checkDestructuringCompleteness: TSESLint.RuleModule<
302
244
  },
303
245
  };
304
246
  },
305
- defaultOptions: [],
247
+ defaultOptions: [{ alwaysCheckReactComponentProps: true }],
248
+ };
249
+
250
+ // eslint-disable-next-line @typescript-eslint/prefer-readonly-parameter-types
251
+ const getObjectTypeProperties = (type: ts.Type): readonly string[] => {
252
+ try {
253
+ const properties = type.getProperties();
254
+
255
+ // Limit to reasonable number of properties to avoid hangs
256
+ if (properties.length > 1000) {
257
+ return [];
258
+ }
259
+
260
+ return properties
261
+ .map((prop) => prop.name)
262
+ .filter(
263
+ (name) =>
264
+ // Filter out symbol properties and internal properties
265
+ !name.startsWith('__') &&
266
+ // Only include string property names
267
+ typeof name === 'string' &&
268
+ name.length > 0,
269
+ );
270
+ } catch {
271
+ // If there's any error getting properties, return empty array
272
+ return [];
273
+ }
274
+ };
275
+
276
+ const isReactComponentFunction = (
277
+ node: DeepReadonly<TSESTree.Node> | undefined | null,
278
+ ): boolean => {
279
+ if (node === undefined || node === null) return false;
280
+
281
+ // Arrow function component
282
+ if (node.type === AST_NODE_TYPES.ArrowFunctionExpression) {
283
+ const { body } = node;
284
+
285
+ if (body.type === AST_NODE_TYPES.BlockStatement) {
286
+ return body.body.some((statement) => {
287
+ if (statement.type !== AST_NODE_TYPES.ReturnStatement) return false;
288
+
289
+ const { argument } = statement;
290
+
291
+ // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
292
+ if (argument === null || argument === undefined) return false;
293
+
294
+ const argType = (argument as { type?: string }).type;
295
+
296
+ return argType === 'JSXElement' || argType === 'JSXFragment';
297
+ });
298
+ }
299
+
300
+ const bodyType = (body as { type?: string }).type;
301
+
302
+ return bodyType === 'JSXElement' || bodyType === 'JSXFragment';
303
+ }
304
+
305
+ return false;
306
306
  };
@@ -132,7 +132,10 @@ export const eslintImportsRules = {
132
132
  // 'import/enforce-node-protocol-usage': ['error', 'always'],
133
133
 
134
134
  'import-x/no-rename-default': withDefaultOption('error'),
135
- 'import-x/prefer-namespace-import': ['error', { patterns: ['react'] }],
135
+
136
+ // Covered by react-coding-style/import-style
137
+ 'import-x/prefer-namespace-import': 'off',
138
+ // 'import-x/prefer-namespace-import': ['error', { patterns: ['react'] }],
136
139
 
137
140
  // deprecated rules
138
141
  'import-x/imports-first': 0,
@@ -1,8 +1,8 @@
1
1
  import { type EslintReactCodingStyleRules } from '../types/index.mjs';
2
2
 
3
3
  export const eslintReactCodingStyleRules = {
4
- // Covered by import-x/prefer-namespace-import
5
- 'react-coding-style/import-style': 'off',
4
+ // import-x/prefer-namespace-import checks similar things, but this rule enforces more strict style.
5
+ 'react-coding-style/import-style': ['error', { importStyle: 'namespace' }],
6
6
 
7
7
  'react-coding-style/component-name': ['error', { maxLength: 42 }],
8
8
  'react-coding-style/component-var-type-annotation': 'error',
@@ -11,4 +11,5 @@ export const eslintReactCodingStyleRules = {
11
11
  'react-coding-style/react-memo-type-parameter': 'error',
12
12
  'react-coding-style/ban-use-imperative-handle-hook': 'error',
13
13
  'react-coding-style/use-memo-hook-style': 'error',
14
+ 'react-coding-style/display-name': ['error', { ignoreTranspilerName: false }],
14
15
  } as const satisfies EslintReactCodingStyleRules;
@@ -66,7 +66,7 @@ namespace ComponentVarTypeAnnotation {
66
66
  }
67
67
 
68
68
  /**
69
- * Enforces importing React with a single namespace import named 'React'.
69
+ * Enforces importing React with a specific style (namespace or named imports).
70
70
  *
71
71
  * ```md
72
72
  * | key | value |
@@ -76,7 +76,40 @@ namespace ComponentVarTypeAnnotation {
76
76
  * ```
77
77
  */
78
78
  namespace ImportStyle {
79
- export type RuleEntry = Linter.StringSeverity;
79
+ /**
80
+ * ### schema
81
+ *
82
+ * ```json
83
+ * [
84
+ * {
85
+ * "type": "object",
86
+ * "properties": {
87
+ * "importStyle": {
88
+ * "type": "string",
89
+ * "enum": [
90
+ * "namespace",
91
+ * "named"
92
+ * ],
93
+ * "description": "Import style to enforce: \"namespace\" for `import * as React` or \"named\" for `import { ... }`"
94
+ * }
95
+ * },
96
+ * "additionalProperties": false
97
+ * }
98
+ * ]
99
+ * ```
100
+ */
101
+ export type Options = {
102
+ /**
103
+ * Import style to enforce: "namespace" for `import * as React` or "named"
104
+ * for `import { ... }`
105
+ */
106
+ readonly importStyle?: 'named' | 'namespace';
107
+ };
108
+
109
+ export type RuleEntry =
110
+ | Linter.Severity
111
+ | SpreadOptionsIfIsArray<readonly [Linter.StringSeverity, Options]>
112
+ | 'off';
80
113
  }
81
114
 
82
115
  /**
@@ -150,6 +183,46 @@ namespace BanUseImperativeHandleHook {
150
183
  export type RuleEntry = Linter.StringSeverity;
151
184
  }
152
185
 
186
+ /**
187
+ * Require displayName property for React components created with React.memo
188
+ *
189
+ * ```md
190
+ * | key | value |
191
+ * | :--------- | :--------- |
192
+ * | type | suggestion |
193
+ * | deprecated | false |
194
+ * ```
195
+ */
196
+ namespace DisplayName {
197
+ /**
198
+ * ### schema
199
+ *
200
+ * ```json
201
+ * [
202
+ * {
203
+ * "type": "object",
204
+ * "properties": {
205
+ * "ignoreTranspilerName": {
206
+ * "type": "boolean",
207
+ * "description": "When true, ignores components that get displayName from variable name"
208
+ * }
209
+ * },
210
+ * "additionalProperties": false
211
+ * }
212
+ * ]
213
+ * ```
214
+ */
215
+ export type Options = {
216
+ /** When true, ignores components that get displayName from variable name */
217
+ readonly ignoreTranspilerName?: boolean;
218
+ };
219
+
220
+ export type RuleEntry =
221
+ | Linter.Severity
222
+ | SpreadOptionsIfIsArray<readonly [Linter.StringSeverity, Options]>
223
+ | 'off';
224
+ }
225
+
153
226
  export type EslintReactCodingStyleRules = {
154
227
  readonly 'react-coding-style/component-name': ComponentName.RuleEntry;
155
228
  readonly 'react-coding-style/component-var-type-annotation': ComponentVarTypeAnnotation.RuleEntry;
@@ -159,8 +232,11 @@ export type EslintReactCodingStyleRules = {
159
232
  readonly 'react-coding-style/react-memo-type-parameter': ReactMemoTypeParameter.RuleEntry;
160
233
  readonly 'react-coding-style/use-memo-hook-style': UseMemoHookStyle.RuleEntry;
161
234
  readonly 'react-coding-style/ban-use-imperative-handle-hook': BanUseImperativeHandleHook.RuleEntry;
235
+ readonly 'react-coding-style/display-name': DisplayName.RuleEntry;
162
236
  };
163
237
 
164
238
  export type EslintReactCodingStyleRulesOption = {
165
239
  readonly 'react-coding-style/component-name': ComponentName.Options;
240
+ readonly 'react-coding-style/import-style': ImportStyle.Options;
241
+ readonly 'react-coding-style/display-name': DisplayName.Options;
166
242
  };