UNPKG

@angular/core

Version:

Angular - the core framework

179 lines (174 loc) • 7.29 kB
'use strict'; /** * @license Angular v19.2.7 * (c) 2010-2025 Google LLC. https://angular.io/ * License: MIT */ 'use strict'; var schematics = require('@angular-devkit/schematics'); var p = require('path'); var project_tsconfig_paths = require('./project_tsconfig_paths-CDVxT6Ov.js'); var compiler_host = require('./compiler_host-BafHjBMK.js'); var ts = require('typescript'); var imports = require('./imports-CIX-JgAN.js'); require('@angular-devkit/core'); require('./checker-BNmiXJIJ.js'); require('os'); require('fs'); require('module'); require('url'); const CORE = '@angular/core'; const DIRECTIVE = 'Directive'; const COMPONENT = 'Component'; const PIPE = 'Pipe'; function migrateFile(sourceFile, rewriteFn) { const changeTracker = new compiler_host.ChangeTracker(ts.createPrinter()); // Check if there are any imports of the `AfterRenderPhase` enum. const coreImports = imports.getNamedImports(sourceFile, CORE); if (!coreImports) { return; } const directive = imports.getImportSpecifier(sourceFile, CORE, DIRECTIVE); const component = imports.getImportSpecifier(sourceFile, CORE, COMPONENT); const pipe = imports.getImportSpecifier(sourceFile, CORE, PIPE); if (!directive && !component && !pipe) { return; } ts.forEachChild(sourceFile, function visit(node) { ts.forEachChild(node, visit); // First we need to check for class declarations // Decorators will come after if (!ts.isClassDeclaration(node)) { return; } ts.getDecorators(node)?.forEach((decorator) => { if (!ts.isDecorator(decorator)) { return; } const callExpression = decorator.expression; if (!ts.isCallExpression(callExpression)) { return; } const decoratorIdentifier = callExpression.expression; if (!ts.isIdentifier(decoratorIdentifier)) { return; } // Checking the identifier of the decorator by comparing to the import specifier switch (decoratorIdentifier.text) { case directive?.name.text: case component?.name.text: case pipe?.name.text: break; default: // It's not a decorator to migrate return; } const [decoratorArgument] = callExpression.arguments; if (!decoratorArgument || !ts.isObjectLiteralExpression(decoratorArgument)) { return; } const properties = decoratorArgument.properties; const standaloneProp = getStandaloneProperty(properties); const hasImports = decoratorHasImports(decoratorArgument); // We'll use the presence of imports to keep the migration idempotent // We need to take care of 3 cases // - standalone: true => remove the property if we have imports // - standalone: false => nothing // - No standalone property => add a standalone: false property if there are no imports let newProperties; if (!standaloneProp) { if (!hasImports) { const standaloneFalseProperty = ts.factory.createPropertyAssignment('standalone', ts.factory.createFalse()); newProperties = [...properties, standaloneFalseProperty]; } } else if (standaloneProp.value === ts.SyntaxKind.TrueKeyword && hasImports) { // To keep the migration idempotent, we'll only remove the standalone prop when there are imports newProperties = properties.filter((p) => p !== standaloneProp.property); } if (newProperties) { // At this point we know that we need to add standalone: false or // remove an existing standalone: true property. const newPropsArr = ts.factory.createNodeArray(newProperties); const newFirstArg = ts.factory.createObjectLiteralExpression(newPropsArr, true); changeTracker.replaceNode(decoratorArgument, newFirstArg); } }); }); // Write the changes. for (const changesInFile of changeTracker.recordChanges().values()) { for (const change of changesInFile) { rewriteFn(change.start, change.removeLength ?? 0, change.text); } } } function getStandaloneProperty(properties) { for (const prop of properties) { if (ts.isShorthandPropertyAssignment(prop) && prop.name.text) { return { property: prop, value: prop.objectAssignmentInitializer }; } if (isStandaloneProperty(prop)) { if (prop.initializer.kind === ts.SyntaxKind.TrueKeyword || prop.initializer.kind === ts.SyntaxKind.FalseKeyword) { return { property: prop, value: prop.initializer.kind }; } else { return { property: prop, value: prop.initializer }; } } } return undefined; } function isStandaloneProperty(prop) { return (ts.isPropertyAssignment(prop) && ts.isIdentifier(prop.name) && prop.name.text === 'standalone'); } function decoratorHasImports(decoratorArgument) { for (const prop of decoratorArgument.properties) { if (ts.isPropertyAssignment(prop) && ts.isIdentifier(prop.name) && prop.name.text === 'imports') { if (prop.initializer.kind === ts.SyntaxKind.ArrayLiteralExpression || prop.initializer.kind === ts.SyntaxKind.Identifier) { return true; } } } return false; } function migrate() { return async (tree) => { const { buildPaths, testPaths } = await project_tsconfig_paths.getProjectTsConfigPaths(tree); const basePath = process.cwd(); const allPaths = [...buildPaths, ...testPaths]; if (!allPaths.length) { throw new schematics.SchematicsException('Could not find any tsconfig file. Cannot run the explicit-standalone-flag migration.'); } for (const tsconfigPath of allPaths) { runMigration(tree, tsconfigPath, basePath); } }; } function runMigration(tree, tsconfigPath, basePath) { const program = compiler_host.createMigrationProgram(tree, tsconfigPath, basePath); const sourceFiles = program .getSourceFiles() .filter((sourceFile) => compiler_host.canMigrateFile(basePath, sourceFile, program)); for (const sourceFile of sourceFiles) { let update = null; const rewriter = (startPos, width, text) => { if (update === null) { // Lazily initialize update, because most files will not require migration. update = tree.beginUpdate(p.relative(basePath, sourceFile.fileName)); } update.remove(startPos, width); if (text !== null) { update.insertLeft(startPos, text); } }; migrateFile(sourceFile, rewriter); if (update !== null) { tree.commitUpdate(update); } } } exports.migrate = migrate;