template.js 4.2 KB

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