index.js 3.4 KB

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