error-message.js 2.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596
  1. 'use strict';
  2. const {getStaticValue} = require('eslint-utils');
  3. const isShadowed = require('./utils/is-shadowed.js');
  4. const {callOrNewExpressionSelector} = require('./selectors/index.js');
  5. const MESSAGE_ID_MISSING_MESSAGE = 'missing-message';
  6. const MESSAGE_ID_EMPTY_MESSAGE = 'message-is-empty-string';
  7. const MESSAGE_ID_NOT_STRING = 'message-is-not-a-string';
  8. const messages = {
  9. [MESSAGE_ID_MISSING_MESSAGE]: 'Pass a message to the `{{constructorName}}` constructor.',
  10. [MESSAGE_ID_EMPTY_MESSAGE]: 'Error message should not be an empty string.',
  11. [MESSAGE_ID_NOT_STRING]: 'Error message should be a string.',
  12. };
  13. const selector = callOrNewExpressionSelector([
  14. // https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Error
  15. 'Error',
  16. 'EvalError',
  17. 'RangeError',
  18. 'ReferenceError',
  19. 'SyntaxError',
  20. 'TypeError',
  21. 'URIError',
  22. 'InternalError',
  23. 'AggregateError',
  24. ]);
  25. /** @param {import('eslint').Rule.RuleContext} context */
  26. const create = context => ({
  27. [selector](expression) {
  28. if (isShadowed(context.getScope(), expression.callee)) {
  29. return;
  30. }
  31. const constructorName = expression.callee.name;
  32. const messageArgumentIndex = constructorName === 'AggregateError' ? 1 : 0;
  33. const callArguments = expression.arguments;
  34. // If message is `SpreadElement` or there is `SpreadElement` before message
  35. if (callArguments.some((node, index) => index <= messageArgumentIndex && node.type === 'SpreadElement')) {
  36. return;
  37. }
  38. const node = callArguments[messageArgumentIndex];
  39. if (!node) {
  40. return {
  41. node: expression,
  42. messageId: MESSAGE_ID_MISSING_MESSAGE,
  43. data: {constructorName},
  44. };
  45. }
  46. // These types can't be string, and `getStaticValue` may don't know the value
  47. // Add more types, if issue reported
  48. if (node.type === 'ArrayExpression' || node.type === 'ObjectExpression') {
  49. return {
  50. node,
  51. messageId: MESSAGE_ID_NOT_STRING,
  52. };
  53. }
  54. const staticResult = getStaticValue(node, context.getScope());
  55. // We don't know the value of `message`
  56. if (!staticResult) {
  57. return;
  58. }
  59. const {value} = staticResult;
  60. if (typeof value !== 'string') {
  61. return {
  62. node,
  63. messageId: MESSAGE_ID_NOT_STRING,
  64. };
  65. }
  66. if (value === '') {
  67. return {
  68. node,
  69. messageId: MESSAGE_ID_EMPTY_MESSAGE,
  70. };
  71. }
  72. },
  73. });
  74. /** @type {import('eslint').Rule.RuleModule} */
  75. module.exports = {
  76. create,
  77. meta: {
  78. type: 'problem',
  79. docs: {
  80. description: 'Enforce passing a `message` value when creating a built-in error.',
  81. },
  82. messages,
  83. },
  84. };