securityos/node_modules/eslint-plugin-unicorn/rules/no-thenable.js

124 lines
3.4 KiB
JavaScript
Raw Normal View History

2024-09-06 15:32:35 +00:00
'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,
},
};