@angular/core
Version:
Angular - the core framework
179 lines (174 loc) • 7.29 kB
JavaScript
/**
* @license Angular v19.2.7
* (c) 2010-2025 Google LLC. https://angular.io/
* License: MIT
*/
;
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;
;