prefer-top-level-await.js 2.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102
  1. 'use strict';
  2. const {findVariable, getFunctionHeadLocation} = require('eslint-utils');
  3. const {matches, memberExpressionSelector} = require('./selectors/index.js');
  4. const ERROR_PROMISE = 'promise';
  5. const ERROR_IIFE = 'iife';
  6. const ERROR_IDENTIFIER = 'identifier';
  7. const SUGGESTION_ADD_AWAIT = 'add-await';
  8. const messages = {
  9. [ERROR_PROMISE]: 'Prefer top-level await over using a promise chain.',
  10. [ERROR_IIFE]: 'Prefer top-level await over an async IIFE.',
  11. [ERROR_IDENTIFIER]: 'Prefer top-level await over an async function `{{name}}` call.',
  12. [SUGGESTION_ADD_AWAIT]: 'Insert `await`.',
  13. };
  14. const topLevelCallExpression = 'Program > ExpressionStatement > CallExpression[optional!=true].expression';
  15. const iife = [
  16. topLevelCallExpression,
  17. matches([
  18. '[callee.type="FunctionExpression"]',
  19. '[callee.type="ArrowFunctionExpression"]',
  20. ]),
  21. '[callee.async!=false]',
  22. '[callee.generator!=true]',
  23. ].join('');
  24. const promise = [
  25. topLevelCallExpression,
  26. memberExpressionSelector({
  27. path: 'callee',
  28. properties: ['then', 'catch', 'finally'],
  29. }),
  30. ].join('');
  31. const identifier = [
  32. topLevelCallExpression,
  33. '[callee.type="Identifier"]',
  34. ].join('');
  35. /** @param {import('eslint').Rule.RuleContext} context */
  36. function create(context) {
  37. return {
  38. [promise](node) {
  39. return {
  40. node: node.callee.property,
  41. messageId: ERROR_PROMISE,
  42. };
  43. },
  44. [iife](node) {
  45. return {
  46. node,
  47. loc: getFunctionHeadLocation(node.callee, context.getSourceCode()),
  48. messageId: ERROR_IIFE,
  49. };
  50. },
  51. [identifier](node) {
  52. const variable = findVariable(context.getScope(), node.callee);
  53. if (!variable || variable.defs.length !== 1) {
  54. return;
  55. }
  56. const [definition] = variable.defs;
  57. const value = definition.type === 'Variable' && definition.kind === 'const'
  58. ? definition.node.init
  59. : definition.node;
  60. if (
  61. !(
  62. (
  63. value.type === 'ArrowFunctionExpression'
  64. || value.type === 'FunctionExpression'
  65. || value.type === 'FunctionDeclaration'
  66. ) && !value.generator && value.async
  67. )
  68. ) {
  69. return;
  70. }
  71. return {
  72. node,
  73. messageId: ERROR_IDENTIFIER,
  74. data: {name: node.callee.name},
  75. suggest: [
  76. {
  77. messageId: SUGGESTION_ADD_AWAIT,
  78. fix: fixer => fixer.insertTextBefore(node, 'await '),
  79. },
  80. ],
  81. };
  82. },
  83. };
  84. }
  85. /** @type {import('eslint').Rule.RuleModule} */
  86. module.exports = {
  87. create,
  88. meta: {
  89. type: 'suggestion',
  90. docs: {
  91. description: 'Prefer top-level await over top-level promises and async function calls.',
  92. },
  93. hasSuggestions: true,
  94. messages,
  95. },
  96. };