Skip to content

Commit d3e76b1

Browse files
SkyZeroZxmmalerba
authored andcommitted
fix(migrations): preserve component imports when pruning NgModules in standalone migration (angular#64186)
This fix preserves component imports by adding missing statements and replacing module references with the correct component PR Close angular#64186
1 parent e23815b commit d3e76b1

File tree

2 files changed

+437
-3
lines changed

2 files changed

+437
-3
lines changed

packages/core/schematics/ng-generate/standalone-migration/prune-modules.ts

Lines changed: 62 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -113,6 +113,7 @@ export function pruneNgModules(
113113
replaceInComponentImportsArray(
114114
componentImportArrays,
115115
classesToRemove,
116+
removalLocations,
116117
tracker,
117118
typeChecker,
118119
templateTypeChecker,
@@ -257,6 +258,7 @@ function collectChangeLocations(
257258
* Replaces all the leftover modules in component `imports` arrays with their exports.
258259
* @param componentImportArrays All the imports arrays and their nodes that represent NgModules.
259260
* @param classesToRemove Set of classes that were marked for removal.
261+
* @param removalLocations Tracks the different places from which imports should be removed.
260262
* @param tracker
261263
* @param typeChecker
262264
* @param templateTypeChecker
@@ -265,6 +267,7 @@ function collectChangeLocations(
265267
function replaceInComponentImportsArray(
266268
componentImportArrays: UniqueItemTracker<ts.ArrayLiteralExpression, ts.Node>,
267269
classesToRemove: Set<ts.ClassDeclaration>,
270+
removalLocations: RemovalLocations,
268271
tracker: ChangeTracker,
269272
typeChecker: ts.TypeChecker,
270273
templateTypeChecker: TemplateTypeChecker,
@@ -281,6 +284,7 @@ function replaceInComponentImportsArray(
281284
const usedImports = new Set(
282285
findTemplateDependencies(closestClass, templateTypeChecker).map((ref) => ref.node),
283286
);
287+
const nodesToRemove = new Set<ts.Node>();
284288

285289
for (const node of toReplace) {
286290
const moduleDecl = findClassDeclaration(node, typeChecker);
@@ -289,11 +293,29 @@ function replaceInComponentImportsArray(
289293
const moduleMeta = templateTypeChecker.getNgModuleMetadata(moduleDecl);
290294

291295
if (moduleMeta) {
296+
let hasUsedExports = false;
292297
moduleMeta.exports.forEach((exp) => {
293298
if (usedImports.has(exp.node as NamedClassDeclaration)) {
294299
replacements.track(node, exp as Reference<NamedClassDeclaration>);
300+
hasUsedExports = true;
295301
}
296302
});
303+
304+
// If none of the module's exports are used, track the node for removal
305+
if (!hasUsedExports) {
306+
nodesToRemove.add(node);
307+
} else if (ts.isIdentifier(node)) {
308+
// Track the import statement for removal when replacing with exports
309+
const symbol = typeChecker.getSymbolAtLocation(node);
310+
const declarations = symbol?.declarations;
311+
if (declarations) {
312+
for (const declaration of declarations) {
313+
if (ts.isImportSpecifier(declaration)) {
314+
removalLocations.imports.track(declaration.parent, declaration);
315+
}
316+
}
317+
}
318+
}
297319
} else {
298320
// It's unlikely not to have module metadata at this point, but just in
299321
// case unmark the class for removal to reduce the chance of breakages.
@@ -302,13 +324,21 @@ function replaceInComponentImportsArray(
302324
}
303325
}
304326

305-
replaceModulesInImportsArray(array, replacements, tracker, templateTypeChecker, importRemapper);
327+
replaceModulesInImportsArray(
328+
array,
329+
replacements,
330+
nodesToRemove,
331+
tracker,
332+
templateTypeChecker,
333+
importRemapper,
334+
);
306335
}
307336
}
308337

309338
/**
310339
* Replaces all the leftover modules in testing `imports` arrays with their exports.
311340
* @param testImportArrays All test `imports` arrays and their nodes that represent modules.
341+
* @param removalLocations Tracks the different places from which imports should be removed.
312342
* @param classesToRemove Classes marked for removal by the migration.
313343
* @param tracker
314344
* @param typeChecker
@@ -326,6 +356,7 @@ function replaceInTestImportsArray(
326356
) {
327357
for (const [array, toReplace] of testImportArrays.getEntries()) {
328358
const replacements = new UniqueItemTracker<ts.Node, Reference<NamedClassDeclaration>>();
359+
const nodesToRemove = new Set<ts.Node>();
329360

330361
for (const node of toReplace) {
331362
const moduleDecl = findClassDeclaration(node, typeChecker);
@@ -344,6 +375,19 @@ function replaceInTestImportsArray(
344375
exports.forEach((exp) =>
345376
replacements.track(node, exp as Reference<NamedClassDeclaration>),
346377
);
378+
379+
// Track the import statement for removal when replacing with exports
380+
if (ts.isIdentifier(node)) {
381+
const symbol = typeChecker.getSymbolAtLocation(node);
382+
const declarations = symbol?.declarations;
383+
if (declarations) {
384+
for (const declaration of declarations) {
385+
if (ts.isImportSpecifier(declaration)) {
386+
removalLocations.imports.track(declaration.parent, declaration);
387+
}
388+
}
389+
}
390+
}
347391
} else {
348392
removalLocations.arrays.track(array, node);
349393
}
@@ -355,26 +399,36 @@ function replaceInTestImportsArray(
355399
}
356400
}
357401

358-
replaceModulesInImportsArray(array, replacements, tracker, templateTypeChecker, importRemapper);
402+
replaceModulesInImportsArray(
403+
array,
404+
replacements,
405+
nodesToRemove,
406+
tracker,
407+
templateTypeChecker,
408+
importRemapper,
409+
);
359410
}
360411
}
361412

362413
/**
363414
* Replaces any leftover modules in an `imports` arrays with a set of specified exports
364415
* @param array Imports array which is being migrated.
365416
* @param replacements Map of NgModule references to their exports.
417+
* @param nodesToRemove Set of nodes that should be removed without replacement (unused modules).
366418
* @param tracker
419+
* @param typeChecker
367420
* @param templateTypeChecker
368421
* @param importRemapper
369422
*/
370423
function replaceModulesInImportsArray(
371424
array: ts.ArrayLiteralExpression,
372425
replacements: UniqueItemTracker<ts.Node, Reference<NamedClassDeclaration>>,
426+
nodesToRemove: Set<ts.Node>,
373427
tracker: ChangeTracker,
374428
templateTypeChecker: TemplateTypeChecker,
375429
importRemapper?: DeclarationImportsRemapper,
376430
): void {
377-
if (replacements.isEmpty()) {
431+
if (replacements.isEmpty() && nodesToRemove.size === 0) {
378432
return;
379433
}
380434

@@ -388,6 +442,11 @@ function replaceModulesInImportsArray(
388442
}
389443

390444
for (const element of array.elements) {
445+
// Check if this element should be removed entirely (unused module)
446+
if (nodesToRemove.has(element)) {
447+
continue;
448+
}
449+
391450
const replacementRefs = replacements.get(element);
392451

393452
if (!replacementRefs) {

0 commit comments

Comments
 (0)