template.js 4.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148
  1. /*
  2. * Copyright (c) 2015-present, Facebook, Inc.
  3. * All rights reserved.
  4. *
  5. * This source code is licensed under the BSD-style license found in the
  6. * LICENSE file in the root directory of this source tree. An additional grant
  7. * of patent rights can be found in the PATENTS file in the same directory.
  8. *
  9. */
  10. 'use strict';
  11. const recast = require('recast');
  12. const builders = recast.types.builders;
  13. const types = recast.types.namedTypes;
  14. function splice(arr, element, replacement) {
  15. arr.splice.apply(arr, [arr.indexOf(element), 1].concat(replacement));
  16. }
  17. function cleanLocation(node) {
  18. delete node.start;
  19. delete node.end;
  20. delete node.loc;
  21. return node;
  22. }
  23. function ensureStatement(node) {
  24. return types.Statement.check(node) ?
  25. // Removing the location information seems to ensure that the node is
  26. // correctly reprinted with a trailing semicolon
  27. cleanLocation(node) :
  28. builders.expressionStatement(node);
  29. }
  30. function getVistor(varNames, nodes) {
  31. return {
  32. visitIdentifier: function(path) {
  33. this.traverse(path);
  34. const node = path.node;
  35. const parent = path.parent.node;
  36. // If this identifier is not one of our generated ones, do nothing
  37. const varIndex = varNames.indexOf(node.name);
  38. if (varIndex === -1) {
  39. return;
  40. }
  41. let replacement = nodes[varIndex];
  42. nodes[varIndex] = null;
  43. // If the replacement is an array, we need to explode the nodes in context
  44. if (Array.isArray(replacement)) {
  45. if (types.Function.check(parent) &&
  46. parent.params.indexOf(node) > -1) {
  47. // Function parameters: function foo(${bar}) {}
  48. splice(parent.params, node, replacement);
  49. } else if (types.VariableDeclarator.check(parent)) {
  50. // Variable declarations: var foo = ${bar}, baz = 42;
  51. splice(
  52. path.parent.parent.node.declarations,
  53. parent,
  54. replacement
  55. );
  56. } else if (types.ArrayExpression.check(parent)) {
  57. // Arrays: var foo = [${bar}, baz];
  58. splice(parent.elements, node, replacement);
  59. } else if (types.Property.check(parent) && parent.shorthand) {
  60. // Objects: var foo = {${bar}, baz: 42};
  61. splice(
  62. path.parent.parent.node.properties,
  63. parent,
  64. replacement
  65. );
  66. } else if (types.CallExpression.check(parent) &&
  67. parent.arguments.indexOf(node) > -1) {
  68. // Function call arguments: foo(${bar}, baz)
  69. splice(parent.arguments, node, replacement);
  70. } else if (types.ExpressionStatement.check(parent)) {
  71. // Generic sequence of statements: { ${foo}; bar; }
  72. path.parent.replace.apply(
  73. path.parent,
  74. replacement.map(ensureStatement)
  75. );
  76. } else {
  77. // Every else, let recast take care of it
  78. path.replace.apply(path, replacement);
  79. }
  80. } else if (types.ExpressionStatement.check(parent)) {
  81. path.parent.replace(ensureStatement(replacement));
  82. } else {
  83. path.replace(replacement);
  84. }
  85. }
  86. };
  87. }
  88. function replaceNodes(src, varNames, nodes, parser) {
  89. const ast = recast.parse(src, {parser});
  90. recast.visit(ast, getVistor(varNames, nodes));
  91. return ast;
  92. }
  93. let varNameCounter = 0;
  94. function getUniqueVarName() {
  95. return `$jscodeshift${varNameCounter++}$`;
  96. }
  97. module.exports = function withParser(parser) {
  98. function statements(template/*, ...nodes*/) {
  99. template = Array.from(template);
  100. const nodes = Array.from(arguments).slice(1);
  101. const varNames = nodes.map(() => getUniqueVarName());
  102. const src = template.reduce(
  103. (result, elem, i) => result + varNames[i - 1] + elem
  104. );
  105. return replaceNodes(
  106. src,
  107. varNames,
  108. nodes,
  109. parser
  110. ).program.body;
  111. }
  112. function statement(/*template, ...nodes*/) {
  113. return statements.apply(null, arguments)[0];
  114. }
  115. function expression(template/*, ...nodes*/) {
  116. // wrap code in `(...)` to force evaluation as expression
  117. template = Array.from(template);
  118. if (template.length > 0) {
  119. template[0] = '(' + template[0];
  120. template[template.length - 1] += ')';
  121. }
  122. return statement.apply(
  123. null,
  124. [template].concat(Array.from(arguments).slice(1))
  125. ).expression;
  126. }
  127. return {statements, statement, expression};
  128. }