Skip to content

Commit 5d76401

Browse files
crisbetoAndrewKushnir
authored andcommitted
fix(migrations): preserve optional parameters (#57367)
Makes it so the inject migration preserves the optional token when declaring a parameter. This came up in some testing as something that can be potentially breaking for classes that implement interfaces. PR Close #57367
1 parent de85979 commit 5d76401

File tree

2 files changed

+76
-5
lines changed

2 files changed

+76
-5
lines changed

packages/core/schematics/ng-generate/inject-migration/migration.ts

Lines changed: 22 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -321,12 +321,17 @@ function migrateParameter(
321321
// If the parameter declares a property, we need to declare it (e.g. `private foo: Foo`).
322322
if (declaresProp) {
323323
const prop = ts.factory.createPropertyDeclaration(
324-
node.modifiers?.filter((modifier) => {
325-
// Strip out the DI decorators, as well as `public` which is redundant.
326-
return !ts.isDecorator(modifier) && modifier.kind !== ts.SyntaxKind.PublicKeyword;
327-
}),
324+
cloneModifiers(
325+
node.modifiers?.filter((modifier) => {
326+
// Strip out the DI decorators, as well as `public` which is redundant.
327+
return !ts.isDecorator(modifier) && modifier.kind !== ts.SyntaxKind.PublicKeyword;
328+
}),
329+
),
328330
name,
329-
undefined,
331+
// Don't add the question token to private properties since it won't affect interface implementation.
332+
node.modifiers?.some((modifier) => modifier.kind === ts.SyntaxKind.PrivateKeyword)
333+
? undefined
334+
: node.questionToken,
330335
// We can't initialize the property if it's referenced within a `super` call.
331336
// See the logic further below for the initialization.
332337
usedInSuper ? node.type : undefined,
@@ -610,3 +615,15 @@ function replaceNodePlaceholder(
610615
const result = printer.printNode(ts.EmitHint.Unspecified, node, sourceFile);
611616
return result.replace(PLACEHOLDER, replacement);
612617
}
618+
619+
/**
620+
* Clones an optional array of modifiers. Can be useful to
621+
* strip the comments from a node with modifiers.
622+
*/
623+
function cloneModifiers(modifiers: ts.ModifierLike[] | ts.NodeArray<ts.ModifierLike> | undefined) {
624+
return modifiers?.map((modifier) => {
625+
return ts.isDecorator(modifier)
626+
? ts.factory.createDecorator(modifier.expression)
627+
: ts.factory.createModifier(modifier.kind);
628+
});
629+
}

packages/core/schematics/test/inject_migration_spec.ts

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1341,6 +1341,60 @@ describe('inject migration', () => {
13411341
]);
13421342
});
13431343

1344+
it('should mark optional members if they correspond to optional parameters', async () => {
1345+
writeFile(
1346+
'/dir.ts',
1347+
[
1348+
`import { Directive, Optional } from '@angular/core';`,
1349+
`import { Foo } from 'foo';`,
1350+
``,
1351+
`@Directive()`,
1352+
`class MyDir {`,
1353+
` constructor(@Optional() public foo?: Foo) {}`,
1354+
`}`,
1355+
].join('\n'),
1356+
);
1357+
1358+
await runMigration();
1359+
1360+
expect(tree.readContent('/dir.ts').split('\n')).toEqual([
1361+
`import { Directive, inject } from '@angular/core';`,
1362+
`import { Foo } from 'foo';`,
1363+
``,
1364+
`@Directive()`,
1365+
`class MyDir {`,
1366+
` foo? = inject(Foo, { optional: true });`,
1367+
`}`,
1368+
]);
1369+
});
1370+
1371+
it('should not mark private members as optional', async () => {
1372+
writeFile(
1373+
'/dir.ts',
1374+
[
1375+
`import { Directive, Optional } from '@angular/core';`,
1376+
`import { Foo } from 'foo';`,
1377+
``,
1378+
`@Directive()`,
1379+
`class MyDir {`,
1380+
` constructor(@Optional() private foo?: Foo) {}`,
1381+
`}`,
1382+
].join('\n'),
1383+
);
1384+
1385+
await runMigration();
1386+
1387+
expect(tree.readContent('/dir.ts').split('\n')).toEqual([
1388+
`import { Directive, inject } from '@angular/core';`,
1389+
`import { Foo } from 'foo';`,
1390+
``,
1391+
`@Directive()`,
1392+
`class MyDir {`,
1393+
` private foo = inject(Foo, { optional: true });`,
1394+
`}`,
1395+
]);
1396+
});
1397+
13441398
describe('internal-only behavior', () => {
13451399
function runInternalMigration() {
13461400
return runMigration({_internalCombineMemberInitializers: true});

0 commit comments

Comments
 (0)