1
1
'use strict' ;
2
2
const { getStaticValue, getPropertyName} = require ( '@eslint-community/eslint-utils' ) ;
3
- const { methodCallSelector } = require ( './selectors /index.js' ) ;
3
+ const { isMethodCall } = require ( './ast /index.js' ) ;
4
4
5
5
const MESSAGE_ID_OBJECT = 'no-thenable-object' ;
6
6
const MESSAGE_ID_EXPORT = 'no-thenable-export' ;
@@ -11,107 +11,166 @@ const messages = {
11
11
[ MESSAGE_ID_CLASS ] : 'Do not add `then` to a class.' ,
12
12
} ;
13
13
14
- const isStringThen = ( node , sourceCode ) =>
15
- getStaticValue ( node , sourceCode . getScope ( node ) ) ?. value === 'then' ;
14
+ const isStringThen = ( node , context ) =>
15
+ getStaticValue ( node , context . sourceCode . getScope ( node ) ) ?. value === 'then' ;
16
16
17
17
const cases = [
18
18
// `{then() {}}`,
19
19
// `{get then() {}}`,
20
20
// `{[computedKey]() {}}`,
21
21
// `{get [computedKey]() {}}`,
22
22
{
23
- selector : 'ObjectExpression > Property.properties > .key' ,
24
- test : ( node , sourceCode ) => getPropertyName ( node . parent , sourceCode . getScope ( node . parent ) ) === 'then' ,
23
+ selector : 'ObjectExpression' ,
24
+ * getNodes ( node , context ) {
25
+ for ( const property of node . properties ) {
26
+ if (
27
+ property . type === 'Property'
28
+ && getPropertyName ( property , context . sourceCode . getScope ( property ) ) === 'then'
29
+ ) {
30
+ yield property . key ;
31
+ }
32
+ }
33
+ } ,
25
34
messageId : MESSAGE_ID_OBJECT ,
26
35
} ,
27
36
// `class Foo {then}`,
28
37
// `class Foo {static then}`,
29
38
// `class Foo {get then() {}}`,
30
39
// `class Foo {static get then() {}}`,
31
40
{
32
- selector : ':matches(PropertyDefinition, MethodDefinition) > .key' ,
33
- test : ( node , sourceCode ) => getPropertyName ( node . parent , sourceCode . getScope ( node . parent ) ) === 'then' ,
41
+ selectors : [ 'PropertyDefinition' , 'MethodDefinition' ] ,
42
+ * getNodes ( node , context ) {
43
+ if ( getPropertyName ( node , context . sourceCode . getScope ( node ) ) === 'then' ) {
44
+ yield node . key ;
45
+ }
46
+ } ,
34
47
messageId : MESSAGE_ID_CLASS ,
35
48
} ,
36
49
// `foo.then = …`
37
50
// `foo[computedKey] = …`
38
51
{
39
- selector : 'AssignmentExpression > MemberExpression.left > .property' ,
40
- test : ( node , sourceCode ) => getPropertyName ( node . parent , sourceCode . getScope ( node . parent ) ) === 'then' ,
52
+ selector : 'MemberExpression' ,
53
+ * getNodes ( node , context ) {
54
+ if ( ! ( node . parent . type === 'AssignmentExpression' && node . parent . left === node ) ) {
55
+ return ;
56
+ }
57
+
58
+ if ( getPropertyName ( node , context . sourceCode . getScope ( node ) ) === 'then' ) {
59
+ yield node . property ;
60
+ }
61
+ } ,
41
62
messageId : MESSAGE_ID_OBJECT ,
42
63
} ,
43
64
// `Object.defineProperty(foo, 'then', …)`
44
65
// `Reflect.defineProperty(foo, 'then', …)`
45
66
{
46
- selector : [
47
- methodCallSelector ( {
48
- objects : [ 'Object' , 'Reflect' ] ,
49
- method : 'defineProperty' ,
50
- minimumArguments : 3 ,
51
- } ) ,
52
- '[arguments.0.type!="SpreadElement"]' ,
53
- ' > .arguments:nth-child(2)' ,
54
- ] . join ( '' ) ,
55
- test : isStringThen ,
67
+ selector : 'CallExpression' ,
68
+ * getNodes ( node , context ) {
69
+ if ( ! (
70
+ isMethodCall ( node , {
71
+ objects : [ 'Object' , 'Reflect' ] ,
72
+ method : 'defineProperty' ,
73
+ minimumArguments : 3 ,
74
+ optionalCall : false ,
75
+ optionalMember : false ,
76
+ } )
77
+ && node . arguments [ 0 ] . type !== 'SpreadElement'
78
+ ) ) {
79
+ return ;
80
+ }
81
+
82
+ const [ , secondArgument ] = node . arguments ;
83
+ if ( isStringThen ( secondArgument , context ) ) {
84
+ yield secondArgument ;
85
+ }
86
+ } ,
56
87
messageId : MESSAGE_ID_OBJECT ,
57
88
} ,
58
- // `Object.fromEntries(['then', …])`
89
+ // TODO[@fisker ]: Bug, we are checking wrong pattern `Object.fromEntries(['then', …])`
90
+ // `Object.fromEntries([['then', …]])`
59
91
{
60
- selector : [
61
- methodCallSelector ( {
92
+ selector : 'CallExpression' ,
93
+ * getNodes ( node , context ) {
94
+ if ( ! isMethodCall ( node , {
62
95
object : 'Object' ,
63
96
method : 'fromEntries' ,
64
97
argumentsLength : 1 ,
65
- } ) ,
66
- ' > ArrayExpression.arguments:nth-child(1)' ,
67
- ' > .elements:nth-child(1)' ,
68
- ] . join ( '' ) ,
69
- test : isStringThen ,
98
+ optionalCall : false ,
99
+ optionalMember : false ,
100
+ } ) ) {
101
+ return ;
102
+ }
103
+
104
+ const [ firstArgument ] = node . arguments ;
105
+ if ( firstArgument . type !== 'ArrayExpression' ) {
106
+ return ;
107
+ }
108
+
109
+ const [ firstElement ] = firstArgument . elements ;
110
+ if ( isStringThen ( firstElement , context ) ) {
111
+ yield firstElement ;
112
+ }
113
+ } ,
70
114
messageId : MESSAGE_ID_OBJECT ,
71
115
} ,
72
116
// `export {then}`
73
117
{
74
- selector : 'ExportSpecifier.specifiers > Identifier.exported[name="then"]' ,
118
+ selector : 'Identifier' ,
119
+ * getNodes ( node ) {
120
+ if (
121
+ node . name === 'then'
122
+ && node . parent . type === 'ExportSpecifier'
123
+ && node . parent . exported === node
124
+ ) {
125
+ yield node ;
126
+ }
127
+ } ,
75
128
messageId : MESSAGE_ID_EXPORT ,
76
129
} ,
77
130
// `export function then() {}`,
78
131
// `export class then {}`,
79
132
{
80
- selector : 'ExportNamedDeclaration > :matches(FunctionDeclaration, ClassDeclaration).declaration > Identifier[name="then"].id' ,
133
+ selector : 'Identifier' ,
134
+ * getNodes ( node ) {
135
+ if (
136
+ node . name === 'then'
137
+ && ( node . parent . type === 'FunctionDeclaration' || node . parent . type === 'ClassDeclaration' )
138
+ && node . parent . id === node
139
+ && node . parent . parent . type === 'ExportNamedDeclaration'
140
+ && node . parent . parent . declaration === node . parent
141
+ ) {
142
+ yield node ;
143
+ }
144
+ } ,
81
145
messageId : MESSAGE_ID_EXPORT ,
82
146
} ,
83
147
// `export const … = …`;
84
148
{
85
- selector : 'ExportNamedDeclaration > VariableDeclaration.declaration' ,
149
+ selector : 'VariableDeclaration' ,
150
+ * getNodes ( node , context ) {
151
+ if ( ! ( node . parent . type === 'ExportNamedDeclaration' && node . parent . declaration === node ) ) {
152
+ return ;
153
+ }
154
+
155
+ for ( const variable of context . sourceCode . getDeclaredVariables ( node ) ) {
156
+ if ( variable . name === 'then' ) {
157
+ yield * variable . identifiers ;
158
+ }
159
+ }
160
+ } ,
86
161
messageId : MESSAGE_ID_EXPORT ,
87
- getNodes : ( node , sourceCode ) => sourceCode . getDeclaredVariables ( node ) . flatMap ( ( { name, identifiers} ) => name === 'then' ? identifiers : [ ] ) ,
88
162
} ,
89
163
] ;
90
164
91
165
/** @param {import('eslint').Rule.RuleContext } context */
92
166
const create = context => {
93
- const { sourceCode} = context ;
94
-
95
- return Object . fromEntries (
96
- cases . map ( ( { selector, test, messageId, getNodes} ) => [
97
- selector ,
98
- function * ( node ) {
99
- if ( getNodes ) {
100
- for ( const problematicNode of getNodes ( node , sourceCode ) ) {
101
- yield { node : problematicNode , messageId} ;
102
- }
103
-
104
- return ;
105
- }
106
-
107
- if ( test && ! test ( node , sourceCode ) ) {
108
- return ;
109
- }
110
-
111
- yield { node, messageId} ;
112
- } ,
113
- ] ) ,
114
- ) ;
167
+ for ( const { selector, selectors, messageId, getNodes} of cases ) {
168
+ context . on ( selector ?? selectors , function * ( node ) {
169
+ for ( const problematicNode of getNodes ( node , context ) ) {
170
+ yield { node : problematicNode , messageId} ;
171
+ }
172
+ } ) ;
173
+ }
115
174
} ;
116
175
117
176
/** @type {import('eslint').Rule.RuleModule } */
0 commit comments