index.js 3.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125
  1. 'use strict';
  2. var detect = require('acorn-globals');
  3. var acorn = require('acorn');
  4. var walk = require('acorn/dist/walk');
  5. // polyfill for https://github.com/marijnh/acorn/pull/231
  6. walk.base.ExportNamedDeclaration = walk.base.ExportDefaultDeclaration = function (node, st, c) {
  7. return c(node.declaration, st);
  8. };
  9. walk.base.ImportDefaultSpecifier = walk.base.ImportNamespaceSpecifier = function () {};
  10. // hacky fix for https://github.com/marijnh/acorn/issues/227
  11. function reallyParse(source) {
  12. try {
  13. return acorn.parse(source, {
  14. ecmaVersion: 5,
  15. allowReturnOutsideFunction: true
  16. });
  17. } catch (ex) {
  18. if (ex.name !== 'SyntaxError') {
  19. throw ex;
  20. }
  21. return acorn.parse(source, {
  22. ecmaVersion: 6,
  23. allowReturnOutsideFunction: true
  24. });
  25. }
  26. }
  27. module.exports = addWith
  28. /**
  29. * Mimic `with` as far as possible but at compile time
  30. *
  31. * @param {String} obj The object part of a with expression
  32. * @param {String} src The body of the with expression
  33. * @param {Array.<String>} exclude A list of variable names to explicitly exclude
  34. */
  35. function addWith(obj, src, exclude) {
  36. obj = obj + ''
  37. src = src + ''
  38. exclude = exclude || []
  39. exclude = exclude.concat(detect(obj).map(function (global) { return global.name; }))
  40. var vars = detect(src).map(function (global) { return global.name; })
  41. .filter(function (v) {
  42. return exclude.indexOf(v) === -1
  43. })
  44. if (vars.length === 0) return src
  45. var declareLocal = ''
  46. var local = 'locals_for_with'
  47. var result = 'result_of_with'
  48. if (/^[a-zA-Z0-9$_]+$/.test(obj)) {
  49. local = obj
  50. } else {
  51. while (vars.indexOf(local) != -1 || exclude.indexOf(local) != -1) {
  52. local += '_'
  53. }
  54. declareLocal = 'var ' + local + ' = (' + obj + ')'
  55. }
  56. while (vars.indexOf(result) != -1 || exclude.indexOf(result) != -1) {
  57. result += '_'
  58. }
  59. var inputVars = vars.map(function (v) {
  60. return JSON.stringify(v) + ' in ' + local + '?' +
  61. local + '.' + v + ':' +
  62. 'typeof ' + v + '!=="undefined"?' + v + ':undefined'
  63. })
  64. src = '(function (' + vars.join(', ') + ') {' +
  65. src +
  66. '}.call(this' + inputVars.map(function (v) { return ',' + v; }).join('') + '))'
  67. return ';' + declareLocal + ';' + unwrapReturns(src, result) + ';'
  68. }
  69. /**
  70. * Take a self calling function, and unwrap it such that return inside the function
  71. * results in return outside the function
  72. *
  73. * @param {String} src Some JavaScript code representing a self-calling function
  74. * @param {String} result A temporary variable to store the result in
  75. */
  76. function unwrapReturns(src, result) {
  77. var originalSource = src
  78. var hasReturn = false
  79. var ast = reallyParse(src)
  80. var ref
  81. src = src.split('')
  82. // get a reference to the function that was inserted to add an inner context
  83. if ((ref = ast.body).length !== 1
  84. || (ref = ref[0]).type !== 'ExpressionStatement'
  85. || (ref = ref.expression).type !== 'CallExpression'
  86. || (ref = ref.callee).type !== 'MemberExpression' || ref.computed !== false || ref.property.name !== 'call'
  87. || (ref = ref.object).type !== 'FunctionExpression')
  88. throw new Error('AST does not seem to represent a self-calling function')
  89. var fn = ref
  90. walk.recursive(ast, null, {
  91. Function: function (node, st, c) {
  92. if (node === fn) {
  93. c(node.body, st, "ScopeBody");
  94. }
  95. },
  96. ReturnStatement: function (node) {
  97. hasReturn = true
  98. replace(node, 'return {value: ' + source(node.argument) + '};');
  99. }
  100. });
  101. function source(node) {
  102. return src.slice(node.start, node.end).join('')
  103. }
  104. function replace(node, str) {
  105. for (var i = node.start; i < node.end; i++) {
  106. src[i] = ''
  107. }
  108. src[node.start] = str
  109. }
  110. if (!hasReturn) return originalSource
  111. else return 'var ' + result + '=' + src.join('') + ';if (' + result + ') return ' + result + '.value'
  112. }