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.
- package/README.md +1 -1
- package/dist/plugins/react-coding-style/rules/ban-use-imperative-handle-hook.mjs +4 -4
- package/dist/plugins/react-coding-style/rules/ban-use-imperative-handle-hook.mjs.map +1 -1
- package/dist/plugins/react-coding-style/rules/component-name.d.mts.map +1 -1
- package/dist/plugins/react-coding-style/rules/component-name.mjs +5 -3
- package/dist/plugins/react-coding-style/rules/component-name.mjs.map +1 -1
- package/dist/plugins/react-coding-style/rules/display-name.d.mts +14 -0
- package/dist/plugins/react-coding-style/rules/display-name.d.mts.map +1 -0
- package/dist/plugins/react-coding-style/rules/display-name.mjs +86 -0
- package/dist/plugins/react-coding-style/rules/display-name.mjs.map +1 -0
- package/dist/plugins/react-coding-style/rules/import-style.d.mts +8 -2
- package/dist/plugins/react-coding-style/rules/import-style.d.mts.map +1 -1
- package/dist/plugins/react-coding-style/rules/import-style.mjs +63 -26
- package/dist/plugins/react-coding-style/rules/import-style.mjs.map +1 -1
- package/dist/plugins/react-coding-style/rules/props-type-annotation-style.mjs +2 -2
- package/dist/plugins/react-coding-style/rules/props-type-annotation-style.mjs.map +1 -1
- package/dist/plugins/react-coding-style/rules/react-memo-props-argument-name.mjs +2 -2
- package/dist/plugins/react-coding-style/rules/react-memo-props-argument-name.mjs.map +1 -1
- package/dist/plugins/react-coding-style/rules/react-memo-type-parameter.mjs +2 -2
- package/dist/plugins/react-coding-style/rules/react-memo-type-parameter.mjs.map +1 -1
- package/dist/plugins/react-coding-style/rules/rules.d.mts +6 -1
- package/dist/plugins/react-coding-style/rules/rules.d.mts.map +1 -1
- package/dist/plugins/react-coding-style/rules/rules.mjs +2 -0
- package/dist/plugins/react-coding-style/rules/rules.mjs.map +1 -1
- package/dist/plugins/react-coding-style/rules/shared.d.mts +7 -3
- package/dist/plugins/react-coding-style/rules/shared.d.mts.map +1 -1
- package/dist/plugins/react-coding-style/rules/shared.mjs +61 -3
- package/dist/plugins/react-coding-style/rules/shared.mjs.map +1 -1
- package/dist/plugins/react-coding-style/rules/use-memo-hooks-style.d.mts.map +1 -1
- package/dist/plugins/react-coding-style/rules/use-memo-hooks-style.mjs +45 -3
- package/dist/plugins/react-coding-style/rules/use-memo-hooks-style.mjs.map +1 -1
- package/dist/plugins/ts-restrictions/rules/check-destructuring-completeness.d.mts.map +1 -1
- package/dist/plugins/ts-restrictions/rules/check-destructuring-completeness.mjs +46 -46
- package/dist/plugins/ts-restrictions/rules/check-destructuring-completeness.mjs.map +1 -1
- package/dist/rules/eslint-import-rules.d.mts +1 -3
- package/dist/rules/eslint-import-rules.d.mts.map +1 -1
- package/dist/rules/eslint-import-rules.mjs +3 -1
- package/dist/rules/eslint-import-rules.mjs.map +1 -1
- package/dist/rules/eslint-react-coding-style-rules.d.mts +6 -1
- package/dist/rules/eslint-react-coding-style-rules.d.mts.map +1 -1
- package/dist/rules/eslint-react-coding-style-rules.mjs +3 -2
- package/dist/rules/eslint-react-coding-style-rules.mjs.map +1 -1
- package/dist/types/rules/eslint-react-coding-style-rules.d.mts +69 -2
- package/dist/types/rules/eslint-react-coding-style-rules.d.mts.map +1 -1
- package/package.json +1 -1
- package/src/plugins/react-coding-style/README.md +22 -0
- package/src/plugins/react-coding-style/rules/ban-use-imperative-handle-hook.mts +4 -4
- package/src/plugins/react-coding-style/rules/ban-use-imperative-handle-hook.test.mts +50 -18
- package/src/plugins/react-coding-style/rules/component-name.mts +6 -3
- package/src/plugins/react-coding-style/rules/display-name.mts +117 -0
- package/src/plugins/react-coding-style/rules/display-name.test.mts +87 -0
- package/src/plugins/react-coding-style/rules/import-style.mts +92 -34
- package/src/plugins/react-coding-style/rules/import-style.test.mts +81 -34
- package/src/plugins/react-coding-style/rules/props-type-annotation-style.mts +2 -2
- package/src/plugins/react-coding-style/rules/react-memo-props-argument-name.mts +2 -2
- package/src/plugins/react-coding-style/rules/react-memo-type-parameter.mts +2 -2
- package/src/plugins/react-coding-style/rules/rules.mts +2 -0
- package/src/plugins/react-coding-style/rules/shared.mts +92 -7
- package/src/plugins/react-coding-style/rules/use-memo-hooks-style-named.test.mts +162 -0
- package/src/plugins/react-coding-style/rules/use-memo-hooks-style-namespace.test.mts +162 -0
- package/src/plugins/react-coding-style/rules/use-memo-hooks-style.mts +68 -3
- package/src/plugins/ts-restrictions/rules/check-destructuring-completeness.mts +59 -59
- package/src/rules/eslint-import-rules.mts +4 -1
- package/src/rules/eslint-react-coding-style-rules.mts +3 -2
- package/src/types/rules/eslint-react-coding-style-rules.mts +78 -2
- 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 {
|
|
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 (!
|
|
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
|
-
|
|
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
|
-
//
|
|
5
|
-
'react-coding-style/import-style': '
|
|
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
|
|
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
|
-
|
|
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
|
};
|