diff --git a/packages/core/schematics/migrations/missing-injectable/BUILD.bazel b/packages/core/schematics/migrations/missing-injectable/BUILD.bazel index 19d71dff7e963..0c2002a6c7235 100644 --- a/packages/core/schematics/migrations/missing-injectable/BUILD.bazel +++ b/packages/core/schematics/migrations/missing-injectable/BUILD.bazel @@ -11,6 +11,7 @@ ts_library( "//packages/core/schematics/test:__pkg__", ], deps = [ + "//packages/compiler-cli/src/ngtsc/annotations", "//packages/compiler-cli/src/ngtsc/imports", "//packages/compiler-cli/src/ngtsc/partial_evaluator", "//packages/compiler-cli/src/ngtsc/reflection", diff --git a/packages/core/schematics/migrations/missing-injectable/transform.ts b/packages/core/schematics/migrations/missing-injectable/transform.ts index 543ee428d3000..12a9c324a48e1 100644 --- a/packages/core/schematics/migrations/missing-injectable/transform.ts +++ b/packages/core/schematics/migrations/missing-injectable/transform.ts @@ -6,6 +6,7 @@ * found in the LICENSE file at https://angular.io/license */ +import {forwardRefResolver} from '@angular/compiler-cli/src/ngtsc/annotations/src/util'; import {Reference} from '@angular/compiler-cli/src/ngtsc/imports'; import {DynamicValue, PartialEvaluator, ResolvedValue} from '@angular/compiler-cli/src/ngtsc/partial_evaluator'; import {TypeScriptReflectionHost} from '@angular/compiler-cli/src/ngtsc/reflection'; @@ -67,7 +68,7 @@ export class MissingInjectableTransform { return []; } - const evaluatedExpr = this.partialEvaluator.evaluate(module.providersExpr); + const evaluatedExpr = this._evaluateExpression(module.providersExpr); if (!Array.isArray(evaluatedExpr)) { return [{ @@ -89,7 +90,7 @@ export class MissingInjectableTransform { // Migrate "providers" on directives and components if defined. if (directive.providersExpr) { - const evaluatedExpr = this.partialEvaluator.evaluate(directive.providersExpr); + const evaluatedExpr = this._evaluateExpression(directive.providersExpr); if (!Array.isArray(evaluatedExpr)) { return [ {node: directive.providersExpr, message: `Providers are not statically analyzable.`} @@ -100,7 +101,7 @@ export class MissingInjectableTransform { // Migrate "viewProviders" on components if defined. if (directive.viewProvidersExpr) { - const evaluatedExpr = this.partialEvaluator.evaluate(directive.viewProvidersExpr); + const evaluatedExpr = this._evaluateExpression(directive.viewProvidersExpr); if (!Array.isArray(evaluatedExpr)) { return [ {node: directive.viewProvidersExpr, message: `Providers are not statically analyzable.`} @@ -150,6 +151,14 @@ export class MissingInjectableTransform { } } + /** + * Evaluates the given TypeScript expression using the partial evaluator with + * the foreign function resolver for handling "forwardRef" calls. + */ + private _evaluateExpression(expr: ts.Expression): ResolvedValue { + return this.partialEvaluator.evaluate(expr, forwardRefResolver); + } + /** * Visits the given resolved value of a provider. Providers can be nested in * arrays and we need to recursively walk through the providers to be able to diff --git a/packages/core/schematics/test/missing_injectable_migration_spec.ts b/packages/core/schematics/test/missing_injectable_migration_spec.ts index cca3a0cc36e3d..3f9ba6b6bb45d 100644 --- a/packages/core/schematics/test/missing_injectable_migration_spec.ts +++ b/packages/core/schematics/test/missing_injectable_migration_spec.ts @@ -48,6 +48,10 @@ describe('Missing injectable migration', () => { // Switch into the temporary directory path. This allows us to run // the schematic against our custom unit test tree. shx.cd(tmpDirPath); + + writeFile('/node_modules/@angular/core/index.d.ts', ` + export declare function forwardRef(fn: Function); + `); }); afterEach(() => { @@ -135,6 +139,24 @@ describe('Missing injectable migration', () => { .toContain(`{ ${type}, Injectable } from '@angular/core`); }); + it(`should migrate object literal provider with forwardRef in ${type}`, async() => { + writeFile('/index.ts', ` + import {${type}, forwardRef} from '@angular/core'; + + @${type}({${propName}: [{provide: forwardRef(() => MyService)}]}) + export class TestClass {} + + export class MyService {} + `); + + await runMigration(); + + expect(warnOutput.length).toBe(0); + expect(tree.readContent('/index.ts')).toMatch(/@Injectable\(\)\s+export class MyService/); + expect(tree.readContent('/index.ts')) + .toContain(`{ ${type}, forwardRef, Injectable } from '@angular/core`); + }); + it(`should not migrate object literal provider with "useValue" in ${type}`, async() => { writeFile('/index.ts', ` import {${type}} from '@angular/core';