124 lines
3.4 KiB
JavaScript
124 lines
3.4 KiB
JavaScript
'use strict';
|
|
const {getStaticValue, getPropertyName} = require('@eslint-community/eslint-utils');
|
|
const {methodCallSelector} = require('./selectors/index.js');
|
|
|
|
const MESSAGE_ID_OBJECT = 'no-thenable-object';
|
|
const MESSAGE_ID_EXPORT = 'no-thenable-export';
|
|
const MESSAGE_ID_CLASS = 'no-thenable-class';
|
|
const messages = {
|
|
[MESSAGE_ID_OBJECT]: 'Do not add `then` to an object.',
|
|
[MESSAGE_ID_EXPORT]: 'Do not export `then`.',
|
|
[MESSAGE_ID_CLASS]: 'Do not add `then` to a class.',
|
|
};
|
|
|
|
const isStringThen = (node, context) =>
|
|
getStaticValue(node, context.getScope())?.value === 'then';
|
|
|
|
const cases = [
|
|
// `{then() {}}`,
|
|
// `{get then() {}}`,
|
|
// `{[computedKey]() {}}`,
|
|
// `{get [computedKey]() {}}`,
|
|
{
|
|
selector: 'ObjectExpression > Property.properties > .key',
|
|
test: (node, context) => getPropertyName(node.parent, context.getScope()) === 'then',
|
|
messageId: MESSAGE_ID_OBJECT,
|
|
},
|
|
// `class Foo {then}`,
|
|
// `class Foo {static then}`,
|
|
// `class Foo {get then() {}}`,
|
|
// `class Foo {static get then() {}}`,
|
|
{
|
|
selector: ':matches(PropertyDefinition, MethodDefinition) > .key',
|
|
test: (node, context) => getPropertyName(node.parent, context.getScope()) === 'then',
|
|
messageId: MESSAGE_ID_CLASS,
|
|
},
|
|
// `foo.then = …`
|
|
// `foo[computedKey] = …`
|
|
{
|
|
selector: 'AssignmentExpression > MemberExpression.left > .property',
|
|
test: (node, context) => getPropertyName(node.parent, context.getScope()) === 'then',
|
|
messageId: MESSAGE_ID_OBJECT,
|
|
},
|
|
// `Object.defineProperty(foo, 'then', …)`
|
|
// `Reflect.defineProperty(foo, 'then', …)`
|
|
{
|
|
selector: [
|
|
methodCallSelector({
|
|
objects: ['Object', 'Reflect'],
|
|
method: 'defineProperty',
|
|
minimumArguments: 3,
|
|
}),
|
|
'[arguments.0.type!="SpreadElement"]',
|
|
' > .arguments:nth-child(2)',
|
|
].join(''),
|
|
test: isStringThen,
|
|
messageId: MESSAGE_ID_OBJECT,
|
|
},
|
|
// `Object.fromEntries(['then', …])`
|
|
{
|
|
selector: [
|
|
methodCallSelector({
|
|
object: 'Object',
|
|
method: 'fromEntries',
|
|
argumentsLength: 1,
|
|
}),
|
|
' > ArrayExpression.arguments:nth-child(1)',
|
|
' > .elements:nth-child(1)',
|
|
].join(''),
|
|
test: isStringThen,
|
|
messageId: MESSAGE_ID_OBJECT,
|
|
},
|
|
// `export {then}`
|
|
{
|
|
selector: 'ExportSpecifier.specifiers > Identifier.exported[name="then"]',
|
|
messageId: MESSAGE_ID_EXPORT,
|
|
},
|
|
// `export function then() {}`,
|
|
// `export class then {}`,
|
|
{
|
|
selector: 'ExportNamedDeclaration > :matches(FunctionDeclaration, ClassDeclaration).declaration > Identifier[name="then"].id',
|
|
messageId: MESSAGE_ID_EXPORT,
|
|
},
|
|
// `export const … = …`;
|
|
{
|
|
selector: 'ExportNamedDeclaration > VariableDeclaration.declaration',
|
|
messageId: MESSAGE_ID_EXPORT,
|
|
getNodes: (node, context) => context.getDeclaredVariables(node).flatMap(({name, identifiers}) => name === 'then' ? identifiers : []),
|
|
},
|
|
];
|
|
|
|
/** @param {import('eslint').Rule.RuleContext} context */
|
|
const create = context => Object.fromEntries(
|
|
cases.map(({selector, test, messageId, getNodes}) => [
|
|
selector,
|
|
function * (node) {
|
|
if (getNodes) {
|
|
for (const problematicNode of getNodes(node, context)) {
|
|
yield {node: problematicNode, messageId};
|
|
}
|
|
|
|
return;
|
|
}
|
|
|
|
if (test && !test(node, context)) {
|
|
return;
|
|
}
|
|
|
|
yield {node, messageId};
|
|
},
|
|
]),
|
|
);
|
|
|
|
/** @type {import('eslint').Rule.RuleModule} */
|
|
module.exports = {
|
|
create,
|
|
meta: {
|
|
type: 'problem',
|
|
docs: {
|
|
description: 'Disallow `then` property.',
|
|
},
|
|
messages,
|
|
},
|
|
};
|