new-for-builtins.js 2.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384
  1. 'use strict';
  2. const {ReferenceTracker} = require('eslint-utils');
  3. const builtins = require('./utils/builtins.js');
  4. const {
  5. switchCallExpressionToNewExpression,
  6. switchNewExpressionToCallExpression,
  7. } = require('./fix/index.js');
  8. const messages = {
  9. enforce: 'Use `new {{name}}()` instead of `{{name}}()`.',
  10. disallow: 'Use `{{name}}()` instead of `new {{name}}()`.',
  11. };
  12. function * enforceNewExpression({sourceCode, tracker}) {
  13. const traceMap = Object.fromEntries(
  14. builtins.enforceNew.map(name => [name, {[ReferenceTracker.CALL]: true}]),
  15. );
  16. for (const {node, path: [name]} of tracker.iterateGlobalReferences(traceMap)) {
  17. if (name === 'Object') {
  18. const {parent} = node;
  19. if (
  20. parent.type === 'BinaryExpression'
  21. && (parent.operator === '===' || parent.operator === '!==')
  22. && (parent.left === node || parent.right === node)
  23. ) {
  24. continue;
  25. }
  26. }
  27. yield {
  28. node,
  29. messageId: 'enforce',
  30. data: {name},
  31. fix: fixer => switchCallExpressionToNewExpression(node, sourceCode, fixer),
  32. };
  33. }
  34. }
  35. function * enforceCallExpression({sourceCode, tracker}) {
  36. const traceMap = Object.fromEntries(
  37. builtins.disallowNew.map(name => [name, {[ReferenceTracker.CONSTRUCT]: true}]),
  38. );
  39. for (const {node, path: [name]} of tracker.iterateGlobalReferences(traceMap)) {
  40. const problem = {
  41. node,
  42. messageId: 'disallow',
  43. data: {name},
  44. };
  45. if (name !== 'String' && name !== 'Boolean' && name !== 'Number') {
  46. problem.fix = function * (fixer) {
  47. yield * switchNewExpressionToCallExpression(node, sourceCode, fixer);
  48. };
  49. }
  50. yield problem;
  51. }
  52. }
  53. /** @param {import('eslint').Rule.RuleContext} context */
  54. const create = context => ({
  55. * 'Program:exit'() {
  56. const sourceCode = context.getSourceCode();
  57. const tracker = new ReferenceTracker(context.getScope());
  58. yield * enforceNewExpression({sourceCode, tracker});
  59. yield * enforceCallExpression({sourceCode, tracker});
  60. },
  61. });
  62. /** @type {import('eslint').Rule.RuleModule} */
  63. module.exports = {
  64. create,
  65. meta: {
  66. type: 'suggestion',
  67. docs: {
  68. description: 'Enforce the use of `new` for all builtins, except `String`, `Number`, `Boolean`, `Symbol` and `BigInt`.',
  69. },
  70. fixable: 'code',
  71. messages,
  72. },
  73. };