Skip to content

Commit 5c86694

Browse files
crisbetopkozlowski-opensource
authored andcommitted
fix(migrations): account for explicit standalone: false in migration (#57803)
Fixes that the standalone migration was duplicating the `standalone` flag if the declaration was `standalone: false`. PR Close #57803
1 parent 156f0d0 commit 5c86694

File tree

2 files changed

+60
-25
lines changed

2 files changed

+60
-25
lines changed

packages/core/schematics/ng-generate/standalone-migration/to-standalone.ts

Lines changed: 34 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -140,15 +140,13 @@ export function convertNgModuleDeclarationToStandalone(
140140
const hasTrailingComma =
141141
importsToAdd.length > 2 &&
142142
!!extractMetadataLiteral(directiveMeta.decorator)?.properties.hasTrailingComma;
143-
decorator = addPropertyToAngularDecorator(
143+
decorator = setPropertyOnAngularDecorator(
144144
decorator,
145-
ts.factory.createPropertyAssignment(
146-
'imports',
147-
ts.factory.createArrayLiteralExpression(
148-
// Create a multi-line array when it has a trailing comma.
149-
ts.factory.createNodeArray(importsToAdd, hasTrailingComma),
150-
hasTrailingComma,
151-
),
145+
'imports',
146+
ts.factory.createArrayLiteralExpression(
147+
// Create a multi-line array when it has a trailing comma.
148+
ts.factory.createNodeArray(importsToAdd, hasTrailingComma),
149+
hasTrailingComma,
152150
),
153151
);
154152
}
@@ -430,23 +428,24 @@ function moveDeclarationsToImports(
430428

431429
/** Adds `standalone: true` to a decorator node. */
432430
function addStandaloneToDecorator(node: ts.Decorator): ts.Decorator {
433-
return addPropertyToAngularDecorator(
431+
return setPropertyOnAngularDecorator(
434432
node,
435-
ts.factory.createPropertyAssignment(
436-
'standalone',
437-
ts.factory.createToken(ts.SyntaxKind.TrueKeyword),
438-
),
433+
'standalone',
434+
ts.factory.createToken(ts.SyntaxKind.TrueKeyword),
439435
);
440436
}
441437

442438
/**
443-
* Adds a property to an Angular decorator node.
439+
* Sets a property on an Angular decorator node. If the property
440+
* already exists, its initializer will be replaced.
444441
* @param node Decorator to which to add the property.
445-
* @param property Property to add.
442+
* @param name Name of the property to be added.
443+
* @param initializer Initializer for the new property.
446444
*/
447-
function addPropertyToAngularDecorator(
445+
function setPropertyOnAngularDecorator(
448446
node: ts.Decorator,
449-
property: ts.PropertyAssignment,
447+
name: string,
448+
initializer: ts.Expression,
450449
): ts.Decorator {
451450
// Invalid decorator.
452451
if (!ts.isCallExpression(node.expression) || node.expression.arguments.length > 1) {
@@ -457,10 +456,22 @@ function addPropertyToAngularDecorator(
457456
let hasTrailingComma = false;
458457

459458
if (node.expression.arguments.length === 0) {
460-
literalProperties = [property];
459+
literalProperties = [ts.factory.createPropertyAssignment(name, initializer)];
461460
} else if (ts.isObjectLiteralExpression(node.expression.arguments[0])) {
462-
hasTrailingComma = node.expression.arguments[0].properties.hasTrailingComma;
463-
literalProperties = [...node.expression.arguments[0].properties, property];
461+
const literal = node.expression.arguments[0];
462+
const existingProperty = findLiteralProperty(literal, name);
463+
hasTrailingComma = literal.properties.hasTrailingComma;
464+
465+
if (existingProperty && ts.isPropertyAssignment(existingProperty)) {
466+
literalProperties = literal.properties.slice();
467+
literalProperties[literalProperties.indexOf(existingProperty)] =
468+
ts.factory.updatePropertyAssignment(existingProperty, existingProperty.name, initializer);
469+
} else {
470+
literalProperties = [
471+
...literal.properties,
472+
ts.factory.createPropertyAssignment(name, initializer),
473+
];
474+
}
464475
} else {
465476
// Unsupported case (e.g. `@Component(SOME_CONST)`). Return the original node.
466477
return node;
@@ -756,12 +767,10 @@ export function migrateTestDeclarations(
756767

757768
tracker.replaceNode(
758769
decorator.node,
759-
addPropertyToAngularDecorator(
770+
setPropertyOnAngularDecorator(
760771
newDecorator,
761-
ts.factory.createPropertyAssignment(
762-
'imports',
763-
ts.factory.createArrayLiteralExpression(importsArray),
764-
),
772+
'imports',
773+
ts.factory.createArrayLiteralExpression(importsArray),
765774
),
766775
);
767776
} else {

packages/core/schematics/test/standalone_migration_spec.ts

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2218,6 +2218,32 @@ describe('standalone migration', () => {
22182218
);
22192219
});
22202220

2221+
it('should handle a directive that is explicitly standalone: false', async () => {
2222+
writeFile(
2223+
'module.ts',
2224+
`
2225+
import {NgModule, Directive} from '@angular/core';
2226+
2227+
@Directive({selector: '[dir]', standalone: false})
2228+
export class MyDir {}
2229+
2230+
@NgModule({declarations: [MyDir], exports: [MyDir]})
2231+
export class Mod {}
2232+
`,
2233+
);
2234+
2235+
await runMigration('convert-to-standalone');
2236+
2237+
const result = tree.readContent('module.ts');
2238+
2239+
expect(stripWhitespace(result)).toContain(
2240+
stripWhitespace(`@Directive({selector: '[dir]', standalone: true})`),
2241+
);
2242+
expect(stripWhitespace(result)).toContain(
2243+
stripWhitespace(`@NgModule({imports: [MyDir], exports: [MyDir]})`),
2244+
);
2245+
});
2246+
22212247
it('should remove a module that only has imports and exports', async () => {
22222248
writeFile(
22232249
'app.module.ts',

0 commit comments

Comments
 (0)