_shift-to-espree-safe.js 5.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283
  1. const { default: reduceUsing, CloneReducer } = require('shift-reducer');
  2. const { default: analyzeScope, ScopeLookup } = require('shift-scope');
  3. const { check: isReservedWord } = require('reserved-words');
  4. class RegexReducer extends CloneReducer {
  5. reduceLiteralRegExpExpression(node) {
  6. return {
  7. ...node,
  8. unicode: false,
  9. };
  10. }
  11. }
  12. class NoReservedKeywordsReducer extends CloneReducer {
  13. isSafeName(name) {
  14. return !isReservedWord(name, 3)
  15. && !isReservedWord(name, 6, true)
  16. && name !== 'arguments'
  17. && name !== 'eval';
  18. }
  19. reduceLabeledStatement(node, rest) {
  20. if (this.isSafeName(node.label)) {
  21. return super.reduceLabeledStatement(node, rest);
  22. }
  23. return {
  24. ...node,
  25. ...rest,
  26. label: node.label + '_avoid_reserved_word',
  27. };
  28. }
  29. reduceBindingIdentifier(node) {
  30. if (this.isSafeName(node.name)) {
  31. return node;
  32. }
  33. return {
  34. ...node,
  35. name: node.name + '_avoid_reserved_word',
  36. };
  37. }
  38. reduceAssignmentTargetIdentifier(...args) {
  39. return this.reduceBindingIdentifier(...args);
  40. }
  41. reduceIdentifierExpression(...args) {
  42. return this.reduceBindingIdentifier(...args);
  43. }
  44. }
  45. class NoIdentifierDuplicatingAnImportReducer extends CloneReducer {
  46. reduceImportNamespace(node, { namespaceBinding, ...rest }) {
  47. return {
  48. ...node,
  49. ...rest,
  50. namespaceBinding: namespaceBinding && {
  51. ...namespaceBinding,
  52. name: namespaceBinding.name + '_avoid_capture',
  53. },
  54. };
  55. }
  56. }
  57. class ScopeLookupCloneReducer extends CloneReducer {
  58. constructor(scopeLookup) {
  59. super();
  60. this.scopeLookup = scopeLookup;
  61. }
  62. }
  63. class NoUndeclaredExportReducer extends ScopeLookupCloneReducer {
  64. reduceExportLocals(node, { namedExports, ...rest }) {
  65. return {
  66. ...node,
  67. ...rest,
  68. namedExports: namedExports.filter(export_ => !export_._isUndeclaredVariable),
  69. };
  70. }
  71. reduceExportLocalSpecifier(node, { name: { _isUndeclaredVariable, ...name }, ...rest }) {
  72. return {
  73. ...node,
  74. ...rest,
  75. name,
  76. _isUndeclaredVariable,
  77. };
  78. }
  79. reduceIdentifierExpression(node) {
  80. const [ variable ] = this.scopeLookup.lookup(node);
  81. return {
  82. ...node,
  83. _isUndeclaredVariable: variable && variable.declarations.length === 0,
  84. };
  85. }
  86. }
  87. const removingHOR = Reducer => class extends Reducer {
  88. constructor(...args) {
  89. super(...args);
  90. this.nodesToRemove = new Set();
  91. this.shouldKeep = statement => !this.nodesToRemove.has(statement);
  92. }
  93. markForRemoval(node) {
  94. this.nodesToRemove.add(node);
  95. }
  96. propagateRemoval(from, to) {
  97. if (this.nodesToRemove.has(from)) {
  98. this.nodesToRemove.add(to);
  99. }
  100. }
  101. reduceSwitchCase(node, { consequent, ...rest }) {
  102. return {
  103. ...node,
  104. ...rest,
  105. consequent: consequent.filter(this.shouldKeep),
  106. };
  107. }
  108. reduceSwitchDefault(node, { consequent, ...rest }) {
  109. return {
  110. ...node,
  111. ...rest,
  112. consequent: consequent.filter(this.shouldKeep),
  113. };
  114. }
  115. reduceScript(node, { statements, ...rest }) {
  116. return {
  117. ...node,
  118. ...rest,
  119. statements: statements.filter(this.shouldKeep),
  120. };
  121. }
  122. reduceFunctionBody(node, { statements, ...rest }) {
  123. return {
  124. ...node,
  125. ...rest,
  126. statements: statements.filter(this.shouldKeep),
  127. };
  128. }
  129. reduceBlock(node, { statements, ...rest }) {
  130. return {
  131. ...node,
  132. ...rest,
  133. statements: statements.filter(this.shouldKeep),
  134. };
  135. }
  136. reduceVariableDeclaration(node, { declarators, ...rest }) {
  137. declarators = declarators.filter(this.shouldKeep);
  138. node = {
  139. ...node,
  140. ...rest,
  141. declarators,
  142. };
  143. if (declarators.length === 0) {
  144. this.markForRemoval(node);
  145. }
  146. return node;
  147. }
  148. reduceVariableDeclarationStatement(node, { declaration }) {
  149. this.propagateRemoval(declaration, node);
  150. return node;
  151. }
  152. reduceForStatement(node, { body }) {
  153. this.propagateRemoval(body, node);
  154. return node;
  155. }
  156. reduceWhileStatement(node, { body }) {
  157. this.propagateRemoval(body, node);
  158. return node;
  159. }
  160. reduceLabeledStatement(node, { body }) {
  161. this.propagateRemoval(body, node);
  162. return node;
  163. }
  164. reduceForInStatement(node, { body }) {
  165. this.propagateRemoval(body, node);
  166. return node;
  167. }
  168. reduceIfStatement(node, { consequent, alternate }) {
  169. this.propagateRemoval(consequent, node);
  170. this.propagateRemoval(alternate, node);
  171. return node;
  172. }
  173. reduceDoWhileStatement(node, { body }) {
  174. this.propagateRemoval(body, node);
  175. return node;
  176. }
  177. };
  178. class NoNonStrictFeaturesReducer extends removingHOR(CloneReducer) {
  179. reduceWithStatement(node) {
  180. this.markForRemoval(node);
  181. return node;
  182. }
  183. reduceUnaryExpression(node) {
  184. if (node.operator === 'delete' && node.operand.type === 'IdentifierExpression') {
  185. return node.operand;
  186. }
  187. return node;
  188. }
  189. }
  190. class NoLabeledFunctionDeclarationReducer extends CloneReducer {
  191. reduceLabeledStatement(node, { body }) {
  192. if (body.type === 'FunctionDeclaration') {
  193. return body;
  194. }
  195. return node;
  196. }
  197. }
  198. class NoLabeledBreakContinueReducer extends CloneReducer {
  199. reduceBreakStatement(node) {
  200. return {
  201. ...node,
  202. label: null,
  203. };
  204. }
  205. }
  206. class UniqueBindingIdentifiersReducer extends CloneReducer {
  207. constructor() {
  208. super();
  209. this.boundNames = new Set();
  210. }
  211. reduceBindingIdentifier(node) {
  212. if (!this.boundNames.has(node.name)) {
  213. this.boundNames.add(node.name);
  214. return node;
  215. }
  216. return {
  217. ...node,
  218. name: node.name + Math.random().toString(16).slice(2),
  219. };
  220. }
  221. }
  222. module.exports = shiftAST => {
  223. [
  224. NoNonStrictFeaturesReducer,
  225. RegexReducer,
  226. NoReservedKeywordsReducer,
  227. NoIdentifierDuplicatingAnImportReducer,
  228. NoUndeclaredExportReducer,
  229. NoLabeledFunctionDeclarationReducer,
  230. UniqueBindingIdentifiersReducer,
  231. NoLabeledBreakContinueReducer,
  232. ].forEach(Reducer => {
  233. const scope = analyzeScope(shiftAST);
  234. const scopeLookup = new ScopeLookup(scope);
  235. shiftAST = reduceUsing(new Reducer(scopeLookup), shiftAST);
  236. });
  237. return shiftAST;
  238. };