no-thenable.js 3.4 KB

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