code_block.js 3.7 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980
  1. "use strict";
  2. /**
  3. * @license
  4. * Copyright Google LLC All Rights Reserved.
  5. *
  6. * Use of this source code is governed by an MIT-style license that can be
  7. * found in the LICENSE file at https://angular.dev/license
  8. */
  9. var __importDefault = (this && this.__importDefault) || function (mod) {
  10. return (mod && mod.__esModule) ? mod : { "default": mod };
  11. };
  12. Object.defineProperty(exports, "__esModule", { value: true });
  13. exports.CodeBlock = void 0;
  14. const typescript_1 = __importDefault(require("../../third_party/github.com/Microsoft/TypeScript/lib/typescript"));
  15. const ast_utils_1 = require("../ast-utils");
  16. const change_1 = require("../change");
  17. /** Counter used to generate unique IDs. */
  18. let uniqueIdCounter = 0;
  19. /**
  20. * Utility class used to generate blocks of code that
  21. * can be inserted by the devkit into a user's app.
  22. */
  23. class CodeBlock {
  24. _imports = new Map();
  25. // Note: the methods here are defined as arrow function so that they can be destructured by
  26. // consumers without losing their context. This makes the API more concise.
  27. /** Function used to tag a code block in order to produce a `PendingCode` object. */
  28. code = (strings, ...params) => {
  29. return {
  30. expression: strings.map((part, index) => part + (params[index] || '')).join(''),
  31. imports: this._imports,
  32. };
  33. };
  34. /**
  35. * Used inside of a code block to mark external symbols and which module they should be imported
  36. * from. When the code is inserted, the required import statements will be produced automatically.
  37. * @param symbolName Name of the external symbol.
  38. * @param moduleName Module from which the symbol should be imported.
  39. */
  40. external = (symbolName, moduleName) => {
  41. if (!this._imports.has(moduleName)) {
  42. this._imports.set(moduleName, new Map());
  43. }
  44. const symbolsPerModule = this._imports.get(moduleName);
  45. if (!symbolsPerModule.has(symbolName)) {
  46. symbolsPerModule.set(symbolName, `@@__SCHEMATIC_PLACEHOLDER_${uniqueIdCounter++}__@@`);
  47. }
  48. return symbolsPerModule.get(symbolName);
  49. };
  50. /**
  51. * Produces the necessary rules to transform a `PendingCode` object into valid code.
  52. * @param initialCode Code pending transformed.
  53. * @param filePath Path of the file in which the code will be inserted.
  54. */
  55. static transformPendingCode(initialCode, filePath) {
  56. const code = { ...initialCode };
  57. const rules = [];
  58. code.imports.forEach((symbols, moduleName) => {
  59. symbols.forEach((placeholder, symbolName) => {
  60. rules.push((tree) => {
  61. const recorder = tree.beginUpdate(filePath);
  62. const sourceFile = typescript_1.default.createSourceFile(filePath, tree.readText(filePath), typescript_1.default.ScriptTarget.Latest, true);
  63. // Note that this could still technically clash if there's a top-level symbol called
  64. // `${symbolName}_alias`, however this is unlikely. We can revisit this if it becomes
  65. // a problem.
  66. const alias = (0, ast_utils_1.hasTopLevelIdentifier)(sourceFile, symbolName, moduleName)
  67. ? symbolName + '_alias'
  68. : undefined;
  69. code.expression = code.expression.replace(new RegExp(placeholder, 'g'), alias || symbolName);
  70. (0, change_1.applyToUpdateRecorder)(recorder, [
  71. (0, ast_utils_1.insertImport)(sourceFile, filePath, symbolName, moduleName, false, alias),
  72. ]);
  73. tree.commitUpdate(recorder);
  74. });
  75. });
  76. });
  77. return { code, rules };
  78. }
  79. }
  80. exports.CodeBlock = CodeBlock;