no-thenable.js 3.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123
  1. 'use strict';
  2. const {getStaticValue, getPropertyName} = require('@eslint-community/eslint-utils');
  3. const {methodCallSelector} = require('./selectors/index.js');
  4. const MESSAGE_ID_OBJECT = 'no-thenable-object';
  5. const MESSAGE_ID_EXPORT = 'no-thenable-export';
  6. const MESSAGE_ID_CLASS = 'no-thenable-class';
  7. const messages = {
  8. [MESSAGE_ID_OBJECT]: 'Do not add `then` to an object.',
  9. [MESSAGE_ID_EXPORT]: 'Do not export `then`.',
  10. [MESSAGE_ID_CLASS]: 'Do not add `then` to a class.',
  11. };
  12. const isStringThen = (node, context) =>
  13. getStaticValue(node, context.getScope())?.value === 'then';
  14. const cases = [
  15. // `{then() {}}`,
  16. // `{get then() {}}`,
  17. // `{[computedKey]() {}}`,
  18. // `{get [computedKey]() {}}`,
  19. {
  20. selector: 'ObjectExpression > Property.properties > .key',
  21. test: (node, context) => getPropertyName(node.parent, context.getScope()) === 'then',
  22. messageId: MESSAGE_ID_OBJECT,
  23. },
  24. // `class Foo {then}`,
  25. // `class Foo {static then}`,
  26. // `class Foo {get then() {}}`,
  27. // `class Foo {static get then() {}}`,
  28. {
  29. selector: ':matches(PropertyDefinition, MethodDefinition) > .key',
  30. test: (node, context) => getPropertyName(node.parent, context.getScope()) === 'then',
  31. messageId: MESSAGE_ID_CLASS,
  32. },
  33. // `foo.then = …`
  34. // `foo[computedKey] = …`
  35. {
  36. selector: 'AssignmentExpression > MemberExpression.left > .property',
  37. test: (node, context) => getPropertyName(node.parent, context.getScope()) === 'then',
  38. messageId: MESSAGE_ID_OBJECT,
  39. },
  40. // `Object.defineProperty(foo, 'then', …)`
  41. // `Reflect.defineProperty(foo, 'then', …)`
  42. {
  43. selector: [
  44. methodCallSelector({
  45. objects: ['Object', 'Reflect'],
  46. method: 'defineProperty',
  47. minimumArguments: 3,
  48. }),
  49. '[arguments.0.type!="SpreadElement"]',
  50. ' > .arguments:nth-child(2)',
  51. ].join(''),
  52. test: isStringThen,
  53. messageId: MESSAGE_ID_OBJECT,
  54. },
  55. // `Object.fromEntries(['then', …])`
  56. {
  57. selector: [
  58. methodCallSelector({
  59. object: 'Object',
  60. method: 'fromEntries',
  61. argumentsLength: 1,
  62. }),
  63. ' > ArrayExpression.arguments:nth-child(1)',
  64. ' > .elements:nth-child(1)',
  65. ].join(''),
  66. test: isStringThen,
  67. messageId: MESSAGE_ID_OBJECT,
  68. },
  69. // `export {then}`
  70. {
  71. selector: 'ExportSpecifier.specifiers > Identifier.exported[name="then"]',
  72. messageId: MESSAGE_ID_EXPORT,
  73. },
  74. // `export function then() {}`,
  75. // `export class then {}`,
  76. {
  77. selector: 'ExportNamedDeclaration > :matches(FunctionDeclaration, ClassDeclaration).declaration > Identifier[name="then"].id',
  78. messageId: MESSAGE_ID_EXPORT,
  79. },
  80. // `export const … = …`;
  81. {
  82. selector: 'ExportNamedDeclaration > VariableDeclaration.declaration',
  83. messageId: MESSAGE_ID_EXPORT,
  84. getNodes: (node, context) => context.getDeclaredVariables(node).flatMap(({name, identifiers}) => name === 'then' ? identifiers : []),
  85. },
  86. ];
  87. /** @param {import('eslint').Rule.RuleContext} context */
  88. const create = context => Object.fromEntries(
  89. cases.map(({selector, test, messageId, getNodes}) => [
  90. selector,
  91. function * (node) {
  92. if (getNodes) {
  93. for (const problematicNode of getNodes(node, context)) {
  94. yield {node: problematicNode, messageId};
  95. }
  96. return;
  97. }
  98. if (test && !test(node, context)) {
  99. return;
  100. }
  101. yield {node, messageId};
  102. },
  103. ]),
  104. );
  105. /** @type {import('eslint').Rule.RuleModule} */
  106. module.exports = {
  107. create,
  108. meta: {
  109. type: 'problem',
  110. docs: {
  111. description: 'Disallow `then` property.',
  112. },
  113. messages,
  114. },
  115. };