Skip to content

Commit 1fe001e

Browse files
yjaaidithePunderWoman
authored andcommitted
fix(migrations): fix provide-initializer migration when using useFactory (angular#58518)
Priori to this commit, initializer functions with dependencies were not migrated correctly. With this commit, the function is executed in a injection context to allow the usage of `inject`. PR Close angular#58518
1 parent 3fc14ec commit 1fe001e

File tree

2 files changed

+91
-30
lines changed

2 files changed

+91
-30
lines changed

packages/core/schematics/migrations/provide-initializer/utils.ts

Lines changed: 33 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -90,36 +90,40 @@ function tryParseProviderExpression(node: ts.Node): ProviderInfo | undefined {
9090
let deps: string[] = [];
9191
let initializerToken: string | undefined;
9292
let useExisting: ts.Expression | undefined;
93-
let useFactory: ts.Expression | undefined;
93+
let useFactoryCode: string | undefined;
9494
let useValue: ts.Expression | undefined;
9595
let multi = false;
9696

9797
for (const property of node.properties) {
98-
if (!ts.isPropertyAssignment(property) || !ts.isIdentifier(property.name)) {
99-
continue;
98+
if (ts.isPropertyAssignment(property) && ts.isIdentifier(property.name)) {
99+
switch (property.name.text) {
100+
case 'deps':
101+
if (ts.isArrayLiteralExpression(property.initializer)) {
102+
deps = property.initializer.elements.map((el) => el.getText());
103+
}
104+
break;
105+
case 'provide':
106+
initializerToken = property.initializer.getText();
107+
break;
108+
case 'useExisting':
109+
useExisting = property.initializer;
110+
break;
111+
case 'useFactory':
112+
useFactoryCode = property.initializer.getText();
113+
break;
114+
case 'useValue':
115+
useValue = property.initializer;
116+
break;
117+
case 'multi':
118+
multi = property.initializer.kind === ts.SyntaxKind.TrueKeyword;
119+
break;
120+
}
100121
}
101122

102-
switch (property.name.text) {
103-
case 'deps':
104-
if (ts.isArrayLiteralExpression(property.initializer)) {
105-
deps = property.initializer.elements.map((el) => el.getText());
106-
}
107-
break;
108-
case 'provide':
109-
initializerToken = property.initializer.getText();
110-
break;
111-
case 'useExisting':
112-
useExisting = property.initializer;
113-
break;
114-
case 'useFactory':
115-
useFactory = property.initializer;
116-
break;
117-
case 'useValue':
118-
useValue = property.initializer;
119-
break;
120-
case 'multi':
121-
multi = property.initializer.kind === ts.SyntaxKind.TrueKeyword;
122-
break;
123+
// Handle the `useFactory() {}` shorthand case.
124+
if (ts.isMethodDeclaration(property) && property.name.getText() === 'useFactory') {
125+
const params = property.parameters.map((param) => param.getText()).join(', ');
126+
useFactoryCode = `(${params}) => ${property.body?.getText()}`;
123127
}
124128
}
125129

@@ -146,12 +150,15 @@ function tryParseProviderExpression(node: ts.Node): ProviderInfo | undefined {
146150
};
147151
}
148152

149-
if (useFactory) {
153+
if (useFactoryCode) {
150154
const args = deps.map((dep) => `inject(${dep})`);
151155
return {
152156
...info,
153157
importInject: deps.length > 0,
154-
initializerCode: `(${useFactory.getText()})(${args.join(', ')})`,
158+
initializerCode: `() => {
159+
const initializerFn = (${useFactoryCode})(${args.join(', ')});
160+
return initializerFn();
161+
}`,
155162
};
156163
}
157164

packages/core/schematics/test/provide_initializer_spec.ts

Lines changed: 58 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -110,6 +110,29 @@ describe('Provide initializer migration', () => {
110110
expect(content).toContain(`const providers = [provideAppInitializer(initializerFn)];`);
111111
});
112112

113+
it('should transform APP_INITIALIZER + useFactory shorthand into provideAppInitializer', async () => {
114+
const content = await migrateCode(`
115+
import { APP_INITIALIZER } from '@angular/core';
116+
117+
const providers = [{
118+
provide: APP_INITIALIZER,
119+
useFactory() {
120+
const service = inject(Service);
121+
return () => service.init();
122+
},
123+
multi: true,
124+
}];
125+
`);
126+
127+
expect(content).toContain(`const providers = [provideAppInitializer(() => {
128+
const initializerFn = (() => {
129+
const service = inject(Service);
130+
return () => service.init();
131+
})();
132+
return initializerFn();
133+
})];`);
134+
});
135+
113136
it('should transform APP_INITIALIZER + useFactory into provideAppInitializer', async () => {
114137
const content = await migrateCode(`
115138
import { APP_INITIALIZER } from '@angular/core';
@@ -124,10 +147,13 @@ describe('Provide initializer migration', () => {
124147
}];
125148
`);
126149

127-
expect(content).toContain(`const providers = [provideAppInitializer((() => {
150+
expect(content).toContain(`const providers = [provideAppInitializer(() => {
151+
const initializerFn = (() => {
128152
const service = inject(Service);
129153
return () => service.init();
130-
})())];`);
154+
})();
155+
return initializerFn();
156+
})];`);
131157
});
132158

133159
it('should transform APP_INITIALIZER + useExisting into provideAppInitializer', async () => {
@@ -147,6 +173,31 @@ describe('Provide initializer migration', () => {
147173
);
148174
});
149175

176+
it('should transform APP_INITIALIZER + deps + useFactory shorthand into provideAppInitializer', async () => {
177+
const content = await migrateCode(`
178+
import { APP_INITIALIZER } from '@angular/core';
179+
180+
const providers = [{
181+
provide: APP_INITIALIZER,
182+
useFactory(a: ServiceA, b: ServiceB) {
183+
return () => a.init();
184+
},
185+
deps: [ServiceA, ServiceB],
186+
multi: true,
187+
}];
188+
`);
189+
190+
expect(content).toContain(`import { inject, provideAppInitializer } from '@angular/core';`);
191+
expect(content).toContain(
192+
`const providers = [provideAppInitializer(() => {
193+
const initializerFn = ((a: ServiceA, b: ServiceB) => {
194+
return () => a.init();
195+
})(inject(ServiceA), inject(ServiceB));
196+
return initializerFn();
197+
})];`,
198+
);
199+
});
200+
150201
it('should transform APP_INITIALIZER + deps into provideAppInitializer', async () => {
151202
const content = await migrateCode(`
152203
import { APP_INITIALIZER } from '@angular/core';
@@ -163,9 +214,12 @@ describe('Provide initializer migration', () => {
163214

164215
expect(content).toContain(`import { inject, provideAppInitializer } from '@angular/core';`);
165216
expect(content).toContain(
166-
`const providers = [provideAppInitializer(((a: ServiceA, b: ServiceB) => {
217+
`const providers = [provideAppInitializer(() => {
218+
const initializerFn = ((a: ServiceA, b: ServiceB) => {
167219
return () => a.init();
168-
})(inject(ServiceA), inject(ServiceB)))];`,
220+
})(inject(ServiceA), inject(ServiceB));
221+
return initializerFn();
222+
})];`,
169223
);
170224
});
171225

0 commit comments

Comments
 (0)