avoid-capture.js 3.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146
  1. 'use strict';
  2. const {
  3. isIdentifierName,
  4. isStrictReservedWord,
  5. isKeyword,
  6. } = require('@babel/helper-validator-identifier');
  7. const resolveVariableName = require('./resolve-variable-name.js');
  8. const getReferences = require('./get-references.js');
  9. // https://github.com/microsoft/TypeScript/issues/2536#issuecomment-87194347
  10. const typescriptReservedWords = new Set([
  11. 'break',
  12. 'case',
  13. 'catch',
  14. 'class',
  15. 'const',
  16. 'continue',
  17. 'debugger',
  18. 'default',
  19. 'delete',
  20. 'do',
  21. 'else',
  22. 'enum',
  23. 'export',
  24. 'extends',
  25. 'false',
  26. 'finally',
  27. 'for',
  28. 'function',
  29. 'if',
  30. 'import',
  31. 'in',
  32. 'instanceof',
  33. 'new',
  34. 'null',
  35. 'return',
  36. 'super',
  37. 'switch',
  38. 'this',
  39. 'throw',
  40. 'true',
  41. 'try',
  42. 'typeof',
  43. 'var',
  44. 'void',
  45. 'while',
  46. 'with',
  47. 'as',
  48. 'implements',
  49. 'interface',
  50. 'let',
  51. 'package',
  52. 'private',
  53. 'protected',
  54. 'public',
  55. 'static',
  56. 'yield',
  57. 'any',
  58. 'boolean',
  59. 'constructor',
  60. 'declare',
  61. 'get',
  62. 'module',
  63. 'require',
  64. 'number',
  65. 'set',
  66. 'string',
  67. 'symbol',
  68. 'type',
  69. 'from',
  70. 'of',
  71. ]);
  72. // Copied from https://github.com/babel/babel/blob/fce35af69101c6b316557e28abf60bdbf77d6a36/packages/babel-types/src/validators/isValidIdentifier.ts#L7
  73. // Use this function instead of `require('@babel/types').isIdentifier`, since `@babel/helper-validator-identifier` package is much smaller
  74. const isValidIdentifier = name =>
  75. typeof name === 'string'
  76. && !isKeyword(name)
  77. && !isStrictReservedWord(name, true)
  78. && isIdentifierName(name)
  79. && name !== 'arguments'
  80. && !typescriptReservedWords.has(name);
  81. /*
  82. Unresolved reference is probably from the global scope. We should avoid using that name.
  83. For example, like `foo` and `bar` below.
  84. ```
  85. function unicorn() {
  86. return foo;
  87. }
  88. function unicorn() {
  89. return function() {
  90. return bar;
  91. };
  92. }
  93. ```
  94. */
  95. const isUnresolvedName = (name, scope) =>
  96. getReferences(scope).some(({identifier, resolved}) => identifier?.name === name && !resolved);
  97. const isSafeName = (name, scopes) =>
  98. !scopes.some(scope => resolveVariableName(name, scope) || isUnresolvedName(name, scope));
  99. const alwaysTrue = () => true;
  100. /**
  101. Rule-specific name check function.
  102. @callback isSafe
  103. @param {string} name - The generated candidate name.
  104. @param {Scope[]} scopes - The same list of scopes you pass to `avoidCapture`.
  105. @returns {boolean} - `true` if the `name` is ok.
  106. */
  107. /**
  108. Generates a unique name prefixed with `name` such that:
  109. - it is not defined in any of the `scopes`,
  110. - it is not a reserved word,
  111. - it is not `arguments` in strict scopes (where `arguments` is not allowed),
  112. - it does not collide with the actual `arguments` (which is always defined in function scopes).
  113. Useful when you want to rename a variable (or create a new variable) while being sure not to shadow any other variables in the code.
  114. @param {string} name - The desired name for a new variable.
  115. @param {Scope[]} scopes - The list of scopes the new variable will be referenced in.
  116. @param {isSafe} [isSafe] - Rule-specific name check function.
  117. @returns {string} - Either `name` as is, or a string like `${name}_` suffixed with underscores to make the name unique.
  118. */
  119. module.exports = (name, scopes, isSafe = alwaysTrue) => {
  120. if (!isValidIdentifier(name)) {
  121. name += '_';
  122. if (!isValidIdentifier(name)) {
  123. return;
  124. }
  125. }
  126. while (!isSafeName(name, scopes) || !isSafe(name, scopes)) {
  127. name += '_';
  128. }
  129. return name;
  130. };