2
2
* @fileoverview Enforce components are written as function components
3
3
*/
4
4
5
- // TODO: improve typing
6
- /* eslint-disable @typescript-eslint/no-explicit-any, @typescript-eslint/no-unsafe-member-access, @typescript-eslint/no-unsafe-return, @typescript-eslint/no-unsafe-assignment, @typescript-eslint/no-unsafe-call */
7
-
8
5
import type { Rule } from "eslint" ;
9
- // Using eslint-plugin-react internals for upstream consideration
10
- // https://github.com/yannickcr/eslint-plugin-react/issues/2860#issuecomment-819784530
11
- import Components from "eslint-plugin-react/lib/util/Components" ;
12
- import {
13
- getComponentProperties ,
14
- getPropertyName ,
15
- } from "eslint-plugin-react/lib/util/ast" ;
16
6
7
+ // TODO:
8
+ // .eslintrc shared settings (http://eslint.org/docs/user-guide/configuring#adding-shared-settings)
9
+ // https://github.com/yannickcr/eslint-plugin-react/blob/master/lib/util/pragma.js
10
+ const pragma = "React" ;
11
+ const createClass = "createReactClass" ;
17
12
export const COMPONENT_SHOULD_BE_FUNCTION = "componentShouldBeFunction" ;
18
13
export const ALLOW_COMPONENT_DID_CATCH = "allowComponentDidCatch" ;
19
14
const COMPONENT_DID_CATCH = "componentDidCatch" ;
20
15
// https://eslint.org/docs/developer-guide/working-with-rules
21
16
const PROGRAM_EXIT = "Program:exit" ;
22
17
18
+ // TODO: Type definitions
19
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
20
+ type Node = any ;
21
+
22
+ /* eslint-disable @typescript-eslint/no-unsafe-member-access, @typescript-eslint/no-unsafe-return, @typescript-eslint/no-unsafe-assignment */
23
+
24
+ function getComponentProperties ( node : Node ) : Node [ ] {
25
+ switch ( node . type ) {
26
+ case "ClassDeclaration" :
27
+ case "ClassExpression" :
28
+ return node . body . body ;
29
+ case "ObjectExpression" :
30
+ return node . properties ;
31
+ default :
32
+ return [ ] ;
33
+ }
34
+ }
35
+
36
+ function getPropertyNameNode ( node : Node ) : Node | undefined {
37
+ // eslint-disable-next-line @typescript-eslint/no-unsafe-argument
38
+ if ( node . key || [ "MethodDefinition" , "Property" ] . indexOf ( node . type ) !== - 1 ) {
39
+ return node . key ;
40
+ }
41
+ if ( node . type === "MemberExpression" ) {
42
+ return node . property ;
43
+ }
44
+ return undefined ;
45
+ }
46
+
47
+ function getPropertyName ( node : Node ) : string {
48
+ const nameNode = getPropertyNameNode ( node ) ;
49
+ return nameNode ? nameNode . name : "" ;
50
+ }
51
+
23
52
// https://eslint.org/docs/developer-guide/working-with-rules
24
53
const rule : Rule . RuleModule = {
25
54
meta : {
@@ -28,8 +57,7 @@ const rule: Rule.RuleModule = {
28
57
category : "Stylistic Issues" ,
29
58
recommended : false ,
30
59
suggestion : false ,
31
- url :
32
- "https://github.com/tatethurston/eslint-plugin-react-prefer-function-component#rule-details" ,
60
+ url : "https://github.com/tatethurston/eslint-plugin-react-prefer-function-component#rule-details" ,
33
61
} ,
34
62
type : "problem" ,
35
63
messages : {
@@ -50,11 +78,34 @@ const rule: Rule.RuleModule = {
50
78
] ,
51
79
} ,
52
80
53
- create : Components . detect ( ( context : any , components : any , utils : any ) => {
81
+ create ( context : Rule . RuleContext ) {
54
82
const allowComponentDidCatch =
55
83
context . options [ 0 ] ?. allowComponentDidCatch ?? true ;
84
+ const sourceCode = context . getSourceCode ( ) ;
85
+
86
+ function isES5Component ( node : Node ) : boolean {
87
+ if ( ! node . parent ) {
88
+ return false ;
89
+ }
56
90
57
- function shouldPreferFunction ( node : any ) : boolean {
91
+ return new RegExp ( `^(${ pragma } \\.)?${ createClass } $` ) . test (
92
+ // eslint-disable-next-line @typescript-eslint/no-unsafe-argument
93
+ sourceCode . getText ( node . parent . callee )
94
+ ) ;
95
+ }
96
+
97
+ function isES6Component ( node : Node ) : boolean {
98
+ if ( ! node . superClass ) {
99
+ return false ;
100
+ }
101
+
102
+ return new RegExp ( `^(${ pragma } \\.)?(Pure)?Component$` ) . test (
103
+ // eslint-disable-next-line @typescript-eslint/no-unsafe-argument
104
+ sourceCode . getText ( node . superClass )
105
+ ) ;
106
+ }
107
+
108
+ function shouldPreferFunction ( node : Node ) : boolean {
58
109
if ( ! allowComponentDidCatch ) {
59
110
return true ;
60
111
}
@@ -63,32 +114,29 @@ const rule: Rule.RuleModule = {
63
114
return ! properties . includes ( COMPONENT_DID_CATCH ) ;
64
115
}
65
116
66
- const detect = ( guard : ( node : any ) => boolean ) => ( node : any ) => {
117
+ const components = new Set < Node > ( ) ;
118
+
119
+ const detect = ( guard : ( node : Node ) => boolean ) => ( node : Node ) => {
67
120
if ( guard ( node ) && shouldPreferFunction ( node ) ) {
68
- components . set ( node , {
69
- [ COMPONENT_SHOULD_BE_FUNCTION ] : true ,
70
- } ) ;
121
+ components . add ( node ) ;
71
122
}
72
123
} ;
73
124
74
125
return {
75
- ObjectExpression : detect ( utils . isES5Component ) ,
76
- ClassDeclaration : detect ( utils . isES6Component ) ,
77
- ClassExpression : detect ( utils . isES6Component ) ,
126
+ ObjectExpression : detect ( isES5Component ) ,
127
+ ClassDeclaration : detect ( isES6Component ) ,
128
+ ClassExpression : detect ( isES6Component ) ,
78
129
79
130
[ PROGRAM_EXIT ] ( ) {
80
- const list = components . list ( ) ;
81
- Object . values ( list ) . forEach ( ( component : any ) => {
82
- if ( component [ COMPONENT_SHOULD_BE_FUNCTION ] ) {
83
- context . report ( {
84
- node : component . node ,
85
- messageId : COMPONENT_SHOULD_BE_FUNCTION ,
86
- } ) ;
87
- }
131
+ components . forEach ( ( node ) => {
132
+ context . report ( {
133
+ node,
134
+ messageId : COMPONENT_SHOULD_BE_FUNCTION ,
135
+ } ) ;
88
136
} ) ;
89
137
} ,
90
138
} ;
91
- } ) ,
139
+ } ,
92
140
} ;
93
141
94
142
export default rule ;
0 commit comments