| "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); |
| 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(path_1.relative(basePath, filePath)); |
| const lineAndCharacter = `${position.line + 1}:${position.character + 1}`; |
| logger.warn(`${normalizedFilePath}@${lineAndCharacter} - ${message}`); |
| }); |
| } |
| return !!ruleFailures.length; |
| function getUpdateRecorder(filePath) { |
| const treeFilePath = path_1.relative(basePath, 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. */ |
| function getProjectRelativePath(filePath) { |
| return path_1.relative(basePath, filePath); |
| } |
| } |
| exports.runMigrationRules = runMigrationRules; |
| //# sourceMappingURL=index.js.map |