Skip to content

Commit 80df427

Browse files
authored
no-thenable: Use simple selector (#2126)
1 parent 6db779b commit 80df427

File tree

1 file changed

+112
-53
lines changed

1 file changed

+112
-53
lines changed

rules/no-thenable.js

Lines changed: 112 additions & 53 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
'use strict';
22
const {getStaticValue, getPropertyName} = require('@eslint-community/eslint-utils');
3-
const {methodCallSelector} = require('./selectors/index.js');
3+
const {isMethodCall} = require('./ast/index.js');
44

55
const MESSAGE_ID_OBJECT = 'no-thenable-object';
66
const MESSAGE_ID_EXPORT = 'no-thenable-export';
@@ -11,107 +11,166 @@ const messages = {
1111
[MESSAGE_ID_CLASS]: 'Do not add `then` to a class.',
1212
};
1313

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';
1616

1717
const cases = [
1818
// `{then() {}}`,
1919
// `{get then() {}}`,
2020
// `{[computedKey]() {}}`,
2121
// `{get [computedKey]() {}}`,
2222
{
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+
},
2534
messageId: MESSAGE_ID_OBJECT,
2635
},
2736
// `class Foo {then}`,
2837
// `class Foo {static then}`,
2938
// `class Foo {get then() {}}`,
3039
// `class Foo {static get then() {}}`,
3140
{
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+
},
3447
messageId: MESSAGE_ID_CLASS,
3548
},
3649
// `foo.then = …`
3750
// `foo[computedKey] = …`
3851
{
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+
},
4162
messageId: MESSAGE_ID_OBJECT,
4263
},
4364
// `Object.defineProperty(foo, 'then', …)`
4465
// `Reflect.defineProperty(foo, 'then', …)`
4566
{
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+
},
5687
messageId: MESSAGE_ID_OBJECT,
5788
},
58-
// `Object.fromEntries(['then', …])`
89+
// TODO[@fisker]: Bug, we are checking wrong pattern `Object.fromEntries(['then', …])`
90+
// `Object.fromEntries([['then', …]])`
5991
{
60-
selector: [
61-
methodCallSelector({
92+
selector: 'CallExpression',
93+
* getNodes(node, context) {
94+
if (!isMethodCall(node, {
6295
object: 'Object',
6396
method: 'fromEntries',
6497
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+
},
70114
messageId: MESSAGE_ID_OBJECT,
71115
},
72116
// `export {then}`
73117
{
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+
},
75128
messageId: MESSAGE_ID_EXPORT,
76129
},
77130
// `export function then() {}`,
78131
// `export class then {}`,
79132
{
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+
},
81145
messageId: MESSAGE_ID_EXPORT,
82146
},
83147
// `export const … = …`;
84148
{
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+
},
86161
messageId: MESSAGE_ID_EXPORT,
87-
getNodes: (node, sourceCode) => sourceCode.getDeclaredVariables(node).flatMap(({name, identifiers}) => name === 'then' ? identifiers : []),
88162
},
89163
];
90164

91165
/** @param {import('eslint').Rule.RuleContext} context */
92166
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+
}
115174
};
116175

117176
/** @type {import('eslint').Rule.RuleModule} */

0 commit comments

Comments
 (0)