index.js 8.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172
  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. Object.defineProperty(exports, "__esModule", { value: true });
  10. exports.UpdateProject = void 0;
  11. const ts = require("typescript");
  12. const component_resource_collector_1 = require("./component-resource-collector");
  13. const logger_1 = require("./logger");
  14. const parse_tsconfig_1 = require("./utils/parse-tsconfig");
  15. const virtual_host_1 = require("./utils/virtual-host");
  16. /**
  17. * An update project that can be run against individual migrations. An update project
  18. * accepts a TypeScript program and a context that is provided to all migrations. The
  19. * context is usually not used by migrations, but in some cases migrations rely on
  20. * specifics from the tool that performs the update (e.g. the Angular CLI). In those cases,
  21. * the context can provide the necessary specifics to the migrations in a type-safe way.
  22. */
  23. class UpdateProject {
  24. constructor(
  25. /** Context provided to all migrations. */
  26. _context,
  27. /** TypeScript program using workspace paths. */
  28. _program,
  29. /** File system used for reading, writing and editing files. */
  30. _fileSystem,
  31. /**
  32. * Set of analyzed files. Used for avoiding multiple migration runs if
  33. * files overlap between targets.
  34. */
  35. _analyzedFiles = new Set(),
  36. /** Logger used for printing messages. */
  37. _logger = logger_1.defaultLogger) {
  38. this._context = _context;
  39. this._program = _program;
  40. this._fileSystem = _fileSystem;
  41. this._analyzedFiles = _analyzedFiles;
  42. this._logger = _logger;
  43. this._typeChecker = this._program.getTypeChecker();
  44. }
  45. /**
  46. * Migrates the project to the specified target version.
  47. * @param migrationTypes Migrations that should be run.
  48. * @param target Version the project should be updated to. Can be `null` if the set of
  49. * specified migrations runs regardless of a target version.
  50. * @param data Upgrade data that is passed to all migration rules.
  51. * @param additionalStylesheetPaths Additional stylesheets that should be migrated, if not
  52. * referenced in an Angular component. This is helpful for global stylesheets in a project.
  53. * @param limitToDirectory If specified, changes will be limited to the given directory.
  54. */
  55. migrate(migrationTypes, target, data, additionalStylesheetPaths, limitToDirectory) {
  56. limitToDirectory && (limitToDirectory = this._fileSystem.resolve(limitToDirectory));
  57. // Create instances of the specified migrations.
  58. const migrations = this._createMigrations(migrationTypes, target, data);
  59. // Creates the component resource collector. The collector can visit arbitrary
  60. // TypeScript nodes and will find Angular component resources. Resources include
  61. // templates and stylesheets. It also captures inline stylesheets and templates.
  62. const resourceCollector = new component_resource_collector_1.ComponentResourceCollector(this._typeChecker, this._fileSystem);
  63. // Collect all of the TypeScript source files we want to migrate. We don't
  64. // migrate type definition files, or source files from external libraries.
  65. const sourceFiles = this._program.getSourceFiles().filter(f => {
  66. return (!f.isDeclarationFile &&
  67. (limitToDirectory == null ||
  68. this._fileSystem.resolve(f.fileName).startsWith(limitToDirectory)) &&
  69. !this._program.isSourceFileFromExternalLibrary(f));
  70. });
  71. // Helper function that visits a given TypeScript node and collects all referenced
  72. // component resources (i.e. stylesheets or templates). Additionally, the helper
  73. // visits the node in each instantiated migration.
  74. const visitNodeAndCollectResources = (node) => {
  75. migrations.forEach(r => r.visitNode(node));
  76. ts.forEachChild(node, visitNodeAndCollectResources);
  77. resourceCollector.visitNode(node);
  78. };
  79. // Walk through all source file, if it has not been visited before, and
  80. // visit found nodes while collecting potential resources.
  81. sourceFiles.forEach(sourceFile => {
  82. const resolvedPath = this._fileSystem.resolve(sourceFile.fileName);
  83. // Do not visit source files which have been checked as part of a
  84. // previously migrated TypeScript project.
  85. if (!this._analyzedFiles.has(resolvedPath)) {
  86. visitNodeAndCollectResources(sourceFile);
  87. this._analyzedFiles.add(resolvedPath);
  88. }
  89. });
  90. // Walk through all resolved templates and visit them in each instantiated
  91. // migration. Note that this can only happen after source files have been
  92. // visited because we find templates through the TypeScript source files.
  93. resourceCollector.resolvedTemplates.forEach(template => {
  94. // Do not visit the template if it has been checked before. Inline
  95. // templates cannot be referenced multiple times.
  96. if (template.inline || !this._analyzedFiles.has(template.filePath)) {
  97. migrations.forEach(m => m.visitTemplate(template));
  98. this._analyzedFiles.add(template.filePath);
  99. }
  100. });
  101. // Walk through all resolved stylesheets and visit them in each instantiated
  102. // migration. Note that this can only happen after source files have been
  103. // visited because we find stylesheets through the TypeScript source files.
  104. resourceCollector.resolvedStylesheets.forEach(stylesheet => {
  105. // Do not visit the stylesheet if it has been checked before. Inline
  106. // stylesheets cannot be referenced multiple times.
  107. if (stylesheet.inline || !this._analyzedFiles.has(stylesheet.filePath)) {
  108. migrations.forEach(r => r.visitStylesheet(stylesheet));
  109. this._analyzedFiles.add(stylesheet.filePath);
  110. }
  111. });
  112. // In some applications, developers will have global stylesheets which are not
  113. // specified in any Angular component. Therefore we allow for additional stylesheets
  114. // being specified. We visit them in each migration unless they have been already
  115. // discovered before as actual component resource.
  116. if (additionalStylesheetPaths) {
  117. additionalStylesheetPaths.forEach(filePath => {
  118. const resolvedPath = this._fileSystem.resolve(filePath);
  119. if (limitToDirectory == null || resolvedPath.startsWith(limitToDirectory)) {
  120. const stylesheet = resourceCollector.resolveExternalStylesheet(resolvedPath, null);
  121. // Do not visit stylesheets which have been referenced from a component.
  122. if (!this._analyzedFiles.has(resolvedPath) && stylesheet) {
  123. migrations.forEach(r => r.visitStylesheet(stylesheet));
  124. this._analyzedFiles.add(resolvedPath);
  125. }
  126. }
  127. });
  128. }
  129. // Call the "postAnalysis" method for each migration.
  130. migrations.forEach(r => r.postAnalysis());
  131. // Collect all failures reported by individual migrations.
  132. const failures = migrations.reduce((res, m) => res.concat(m.failures), []);
  133. // In case there are failures, print these to the CLI logger as warnings.
  134. if (failures.length) {
  135. failures.forEach(({ filePath, message, position }) => {
  136. const lineAndCharacter = position ? `@${position.line + 1}:${position.character + 1}` : '';
  137. this._logger.warn(`${filePath}${lineAndCharacter} - ${message}`);
  138. });
  139. }
  140. return {
  141. hasFailures: !!failures.length,
  142. };
  143. }
  144. /**
  145. * Creates instances of the given migrations with the specified target
  146. * version and data.
  147. */
  148. _createMigrations(types, target, data) {
  149. const result = [];
  150. for (const ctor of types) {
  151. const instance = new ctor(this._program, this._typeChecker, target, this._context, data, this._fileSystem, this._logger);
  152. instance.init();
  153. if (instance.enabled) {
  154. result.push(instance);
  155. }
  156. }
  157. return result;
  158. }
  159. /**
  160. * Creates a program from the specified tsconfig and patches the host
  161. * to read files and directories through the given file system.
  162. *
  163. * @throws {TsconfigParseError} If the tsconfig could not be parsed.
  164. */
  165. static createProgramFromTsconfig(tsconfigPath, fs) {
  166. const parsed = (0, parse_tsconfig_1.parseTsconfigFile)(fs.resolve(tsconfigPath), fs);
  167. const host = (0, virtual_host_1.createFileSystemCompilerHost)(parsed.options, fs);
  168. return ts.createProgram(parsed.fileNames, parsed.options, host);
  169. }
  170. }
  171. exports.UpdateProject = UpdateProject;
  172. //# sourceMappingURL=index.js.map