"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.io/license */ Object.defineProperty(exports, "__esModule", { value: true }); const core_1 = require("@angular-devkit/core"); const glob_1 = require("glob"); const path_1 = require("path"); const ts = require("typescript"); const component_resource_collector_1 = require("./component-resource-collector"); const parse_tsconfig_1 = require("./utils/parse-tsconfig"); function runMigrationRules(tree, logger, tsconfigPath, targetVersion, ruleTypes, upgradeData, analyzedFiles) { // The CLI uses the working directory as the base directory for the // virtual file system tree. const basePath = process.cwd(); const parsed = parse_tsconfig_1.parseTsconfigFile(tsconfigPath, path_1.dirname(tsconfigPath)); const host = ts.createCompilerHost(parsed.options, true); // We need to overwrite the host "readFile" method, as we want the TypeScript // program to be based on the file contents in the virtual file tree. host.readFile = fileName => { const buffer = tree.read(getProjectRelativePath(fileName)); // Strip BOM as otherwise TSC methods (e.g. "getWidth") will return an offset which // which breaks the CLI UpdateRecorder. https://github.com/angular/angular/pull/30719 return buffer ? buffer.toString().replace(/^\uFEFF/, '') : undefined; }; const program = ts.createProgram(parsed.fileNames, parsed.options, host); const typeChecker = program.getTypeChecker(); const rules = []; // Create instances of all specified migration rules. for (const ruleCtor of ruleTypes) { const rule = new ruleCtor(program, typeChecker, targetVersion, upgradeData); rule.getUpdateRecorder = getUpdateRecorder; rule.init(); if (rule.ruleEnabled) { rules.push(rule); } } const sourceFiles = program.getSourceFiles().filter(f => !f.isDeclarationFile && !program.isSourceFileFromExternalLibrary(f)); const resourceCollector = new component_resource_collector_1.ComponentResourceCollector(typeChecker); const updateRecorderCache = new Map(); sourceFiles.forEach(sourceFile => { const relativePath = getProjectRelativePath(sourceFile.fileName); // Do not visit source files which have been checked as part of a // previously migrated TypeScript project. if (!analyzedFiles.has(relativePath)) { _visitTypeScriptNode(sourceFile); analyzedFiles.add(relativePath); } }); resourceCollector.resolvedTemplates.forEach(template => { const relativePath = getProjectRelativePath(template.filePath); // Do not visit the template if it has been checked before. Inline // templates cannot be referenced multiple times. if (template.inline || !analyzedFiles.has(relativePath)) { rules.forEach(r => r.visitTemplate(template)); analyzedFiles.add(relativePath); } }); resourceCollector.resolvedStylesheets.forEach(stylesheet => { const relativePath = getProjectRelativePath(stylesheet.filePath); // Do not visit the stylesheet if it has been checked before. Inline // stylesheets cannot be referenced multiple times. if (stylesheet.inline || !analyzedFiles.has(relativePath)) { rules.forEach(r => r.visitStylesheet(stylesheet)); analyzedFiles.add(relativePath); } }); // In some applications, developers will have global stylesheets which are not specified in any // Angular component. Therefore we glob up all CSS and SCSS files outside of node_modules and // dist. The files will be read by the individual stylesheet rules and checked. // TODO(devversion): double-check if we can solve this in a more elegant way. glob_1.sync('!(node_modules|dist)/**/*.+(css|scss)', { absolute: true, cwd: basePath }) .filter(filePath => !resourceCollector.resolvedStylesheets.some(s => s.filePath === filePath)) .forEach(filePath => { const stylesheet = resourceCollector.resolveExternalStylesheet(filePath, null); const relativePath = getProjectRelativePath(filePath); // do not visit stylesheets which have been referenced from a component. if (!analyzedFiles.has(relativePath)) { rules.forEach(r => r.visitStylesheet(stylesheet)); } }); // Commit all recorded updates in the update recorder. We need to perform the // replacements per source file in order to ensure that offsets in the TypeScript // program are not incorrectly shifted. updateRecorderCache.forEach(recorder => tree.commitUpdate(recorder)); // Collect all failures reported by individual migration rules. const ruleFailures = rules.reduce((res, rule) => res.concat(rule.failures), []); // In case there are rule failures, print these to the CLI logger as warnings. if (ruleFailures.length) { ruleFailures.forEach(({ filePath, message, position }) => { const normalizedFilePath = core_1.normalize(getProjectRelativePath(filePath)); const lineAndCharacter = `${position.line + 1}:${position.character + 1}`; logger.warn(`${normalizedFilePath}@${lineAndCharacter} - ${message}`); }); } return !!ruleFailures.length; function getUpdateRecorder(filePath) { const treeFilePath = getProjectRelativePath(filePath); if (updateRecorderCache.has(treeFilePath)) { return updateRecorderCache.get(treeFilePath); } const treeRecorder = tree.beginUpdate(treeFilePath); updateRecorderCache.set(treeFilePath, treeRecorder); return treeRecorder; } function _visitTypeScriptNode(node) { rules.forEach(r => r.visitNode(node)); ts.forEachChild(node, _visitTypeScriptNode); resourceCollector.visitNode(node); } /** Gets the specified path relative to the project root in POSIX format. */ function getProjectRelativePath(filePath) { return path_1.relative(basePath, filePath).replace(/\\/g, '/'); } } exports.runMigrationRules = runMigrationRules; //# sourceMappingURL=index.js.map