123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123 |
- '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,
- },
- };
|