UNPKG

@angular/cdk

Version:

Angular Material Component Development Kit

172 lines 9.1 kB
"use strict"; /** * @license * Copyright Google LLC All Rights Reserved. * * Use of this source code is governed by an MIT-style license that can be * found in the LICENSE file at https://angular.dev/license */ Object.defineProperty(exports, "__esModule", { value: true }); exports.UpdateProject = void 0; const ts = require("typescript"); const component_resource_collector_1 = require("./component-resource-collector"); const logger_1 = require("./logger"); const parse_tsconfig_1 = require("./utils/parse-tsconfig"); const virtual_host_1 = require("./utils/virtual-host"); /** * An update project that can be run against individual migrations. An update project * accepts a TypeScript program and a context that is provided to all migrations. The * context is usually not used by migrations, but in some cases migrations rely on * specifics from the tool that performs the update (e.g. the Angular CLI). In those cases, * the context can provide the necessary specifics to the migrations in a type-safe way. */ class UpdateProject { constructor( /** Context provided to all migrations. */ _context, /** TypeScript program using workspace paths. */ _program, /** File system used for reading, writing and editing files. */ _fileSystem, /** * Set of analyzed files. Used for avoiding multiple migration runs if * files overlap between targets. */ _analyzedFiles = new Set(), /** Logger used for printing messages. */ _logger = logger_1.defaultLogger) { this._context = _context; this._program = _program; this._fileSystem = _fileSystem; this._analyzedFiles = _analyzedFiles; this._logger = _logger; this._typeChecker = this._program.getTypeChecker(); } /** * Migrates the project to the specified target version. * @param migrationTypes Migrations that should be run. * @param target Version the project should be updated to. Can be `null` if the set of * specified migrations runs regardless of a target version. * @param data Upgrade data that is passed to all migration rules. * @param additionalStylesheetPaths Additional stylesheets that should be migrated, if not * referenced in an Angular component. This is helpful for global stylesheets in a project. * @param limitToDirectory If specified, changes will be limited to the given directory. */ migrate(migrationTypes, target, data, additionalStylesheetPaths, limitToDirectory) { limitToDirectory && (limitToDirectory = this._fileSystem.resolve(limitToDirectory)); // Create instances of the specified migrations. const migrations = this._createMigrations(migrationTypes, target, data); // Creates the component resource collector. The collector can visit arbitrary // TypeScript nodes and will find Angular component resources. Resources include // templates and stylesheets. It also captures inline stylesheets and templates. const resourceCollector = new component_resource_collector_1.ComponentResourceCollector(this._typeChecker, this._fileSystem); // Collect all of the TypeScript source files we want to migrate. We don't // migrate type definition files, or source files from external libraries. const sourceFiles = this._program.getSourceFiles().filter(f => { return (!f.isDeclarationFile && (limitToDirectory == null || this._fileSystem.resolve(f.fileName).startsWith(limitToDirectory)) && !this._program.isSourceFileFromExternalLibrary(f)); }); // Helper function that visits a given TypeScript node and collects all referenced // component resources (i.e. stylesheets or templates). Additionally, the helper // visits the node in each instantiated migration. const visitNodeAndCollectResources = (node) => { migrations.forEach(r => r.visitNode(node)); ts.forEachChild(node, visitNodeAndCollectResources); resourceCollector.visitNode(node); }; // Walk through all source file, if it has not been visited before, and // visit found nodes while collecting potential resources. sourceFiles.forEach(sourceFile => { const resolvedPath = this._fileSystem.resolve(sourceFile.fileName); // Do not visit source files which have been checked as part of a // previously migrated TypeScript project. if (!this._analyzedFiles.has(resolvedPath)) { visitNodeAndCollectResources(sourceFile); this._analyzedFiles.add(resolvedPath); } }); // Walk through all resolved templates and visit them in each instantiated // migration. Note that this can only happen after source files have been // visited because we find templates through the TypeScript source files. resourceCollector.resolvedTemplates.forEach(template => { // Do not visit the template if it has been checked before. Inline // templates cannot be referenced multiple times. if (template.inline || !this._analyzedFiles.has(template.filePath)) { migrations.forEach(m => m.visitTemplate(template)); this._analyzedFiles.add(template.filePath); } }); // Walk through all resolved stylesheets and visit them in each instantiated // migration. Note that this can only happen after source files have been // visited because we find stylesheets through the TypeScript source files. resourceCollector.resolvedStylesheets.forEach(stylesheet => { // Do not visit the stylesheet if it has been checked before. Inline // stylesheets cannot be referenced multiple times. if (stylesheet.inline || !this._analyzedFiles.has(stylesheet.filePath)) { migrations.forEach(r => r.visitStylesheet(stylesheet)); this._analyzedFiles.add(stylesheet.filePath); } }); // In some applications, developers will have global stylesheets which are not // specified in any Angular component. Therefore we allow for additional stylesheets // being specified. We visit them in each migration unless they have been already // discovered before as actual component resource. if (additionalStylesheetPaths) { additionalStylesheetPaths.forEach(filePath => { const resolvedPath = this._fileSystem.resolve(filePath); if (limitToDirectory == null || resolvedPath.startsWith(limitToDirectory)) { const stylesheet = resourceCollector.resolveExternalStylesheet(resolvedPath, null); // Do not visit stylesheets which have been referenced from a component. if (!this._analyzedFiles.has(resolvedPath) && stylesheet) { migrations.forEach(r => r.visitStylesheet(stylesheet)); this._analyzedFiles.add(resolvedPath); } } }); } // Call the "postAnalysis" method for each migration. migrations.forEach(r => r.postAnalysis()); // Collect all failures reported by individual migrations. const failures = migrations.reduce((res, m) => res.concat(m.failures), []); // In case there are failures, print these to the CLI logger as warnings. if (failures.length) { failures.forEach(({ filePath, message, position }) => { const lineAndCharacter = position ? `@${position.line + 1}:${position.character + 1}` : ''; this._logger.warn(`${filePath}${lineAndCharacter} - ${message}`); }); } return { hasFailures: !!failures.length, }; } /** * Creates instances of the given migrations with the specified target * version and data. */ _createMigrations(types, target, data) { const result = []; for (const ctor of types) { const instance = new ctor(this._program, this._typeChecker, target, this._context, data, this._fileSystem, this._logger); instance.init(); if (instance.enabled) { result.push(instance); } } return result; } /** * Creates a program from the specified tsconfig and patches the host * to read files and directories through the given file system. * * @throws {TsconfigParseError} If the tsconfig could not be parsed. */ static createProgramFromTsconfig(tsconfigPath, fs) { const parsed = (0, parse_tsconfig_1.parseTsconfigFile)(fs.resolve(tsconfigPath), fs); const host = (0, virtual_host_1.createFileSystemCompilerHost)(parsed.options, fs); return ts.createProgram(parsed.fileNames, parsed.options, host); } } exports.UpdateProject = UpdateProject; //# sourceMappingURL=index.js.map