diff --git a/packages/compiler-cli/src/ngtsc/annotations/src/component.ts b/packages/compiler-cli/src/ngtsc/annotations/src/component.ts index 4578da8b48e02..0290a535c71cf 100644 --- a/packages/compiler-cli/src/ngtsc/annotations/src/component.ts +++ b/packages/compiler-cli/src/ngtsc/annotations/src/component.ts @@ -6,7 +6,7 @@ * found in the LICENSE file at https://angular.io/license */ -import {ConstantPool, CssSelector, DEFAULT_INTERPOLATION_CONFIG, DomElementSchemaRegistry, Expression, ExternalExpr, Identifiers, InterpolationConfig, LexerRange, ParseError, ParseSourceFile, ParseTemplateOptions, R3ComponentMetadata, R3TargetBinder, SchemaMetadata, SelectorMatcher, Statement, TmplAstNode, WrappedNodeExpr, compileComponentFromMetadata, makeBindingParser, parseTemplate} from '@angular/compiler'; +import {ConstantPool, CssSelector, DEFAULT_INTERPOLATION_CONFIG, DomElementSchemaRegistry, Expression, ExternalExpr, Identifiers, InterpolationConfig, LexerRange, ParseError, ParseSourceFile, ParseTemplateOptions, R3ComponentMetadata, R3FactoryTarget, R3TargetBinder, SchemaMetadata, SelectorMatcher, Statement, TmplAstNode, WrappedNodeExpr, compileComponentFromMetadata, makeBindingParser, parseTemplate} from '@angular/compiler'; import * as ts from 'typescript'; import {CycleAnalyzer} from '../../cycles'; @@ -489,7 +489,8 @@ export class ComponentDecoratorHandler implements CompileResult[] { const meta = analysis.meta; const res = compileComponentFromMetadata(meta, pool, makeBindingParser()); - const factoryRes = compileNgFactoryDefField({...meta, injectFn: Identifiers.directiveInject}); + const factoryRes = compileNgFactoryDefField( + {...meta, injectFn: Identifiers.directiveInject, target: R3FactoryTarget.Component}); if (analysis.metadataStmt !== null) { factoryRes.statements.push(analysis.metadataStmt); } diff --git a/packages/compiler-cli/src/ngtsc/annotations/src/directive.ts b/packages/compiler-cli/src/ngtsc/annotations/src/directive.ts index d4cd46cabdffb..bf414681d0b3a 100644 --- a/packages/compiler-cli/src/ngtsc/annotations/src/directive.ts +++ b/packages/compiler-cli/src/ngtsc/annotations/src/directive.ts @@ -6,7 +6,7 @@ * found in the LICENSE file at https://angular.io/license */ -import {ConstantPool, EMPTY_SOURCE_SPAN, Expression, Identifiers, ParseError, ParsedHostBindings, R3DirectiveMetadata, R3QueryMetadata, Statement, WrappedNodeExpr, compileDirectiveFromMetadata, makeBindingParser, parseHostBindings, verifyHostBindings} from '@angular/compiler'; +import {ConstantPool, EMPTY_SOURCE_SPAN, Expression, Identifiers, ParseError, ParsedHostBindings, R3DependencyMetadata, R3DirectiveMetadata, R3FactoryTarget, R3QueryMetadata, Statement, WrappedNodeExpr, compileDirectiveFromMetadata, makeBindingParser, parseHostBindings, verifyHostBindings} from '@angular/compiler'; import * as ts from 'typescript'; import {ErrorCode, FatalDiagnosticError} from '../../diagnostics'; @@ -19,7 +19,7 @@ import {AnalysisOutput, CompileResult, DecoratorHandler, DetectResult, HandlerPr import {compileNgFactoryDefField} from './factory'; import {generateSetClassMetadataCall} from './metadata'; -import {findAngularDecorator, getValidConstructorDependencies, readBaseClass, unwrapExpression, unwrapForwardRef} from './util'; +import {findAngularDecorator, getConstructorDependencies, readBaseClass, unwrapConstructorDependencies, unwrapExpression, unwrapForwardRef, validateConstructorDependencies} from './util'; const EMPTY_OBJECT: {[key: string]: string} = {}; @@ -87,7 +87,8 @@ export class DirectiveDecoratorHandler implements CompileResult[] { const meta = analysis.meta; const res = compileDirectiveFromMetadata(meta, pool, makeBindingParser()); - const factoryRes = compileNgFactoryDefField({...meta, injectFn: Identifiers.directiveInject}); + const factoryRes = compileNgFactoryDefField( + {...meta, injectFn: Identifiers.directiveInject, target: R3FactoryTarget.Directive}); if (analysis.metadataStmt !== null) { factoryRes.statements.push(analysis.metadataStmt); } @@ -226,11 +227,23 @@ export function extractDirectiveMetadata( exportAs = resolved.split(',').map(part => part.trim()); } + const rawCtorDeps = getConstructorDependencies(clazz, reflector, defaultImportRecorder, isCore); + let ctorDeps: R3DependencyMetadata[]|'invalid'|null; + + // Non-abstract directives (those with a selector) require valid constructor dependencies, whereas + // abstract directives are allowed to have invalid dependencies, given that a subclass may call + // the constructor explicitly. + if (selector !== null) { + ctorDeps = validateConstructorDependencies(clazz, rawCtorDeps); + } else { + ctorDeps = unwrapConstructorDependencies(rawCtorDeps); + } + // Detect if the component inherits from another class const usesInheritance = reflector.hasBaseClass(clazz); const metadata: R3DirectiveMetadata = { name: clazz.name.text, - deps: getValidConstructorDependencies(clazz, reflector, defaultImportRecorder, isCore), host, + deps: ctorDeps, host, lifecycle: { usesOnChanges, }, diff --git a/packages/compiler-cli/src/ngtsc/annotations/src/injectable.ts b/packages/compiler-cli/src/ngtsc/annotations/src/injectable.ts index b2b6a4d58330f..81c0b7ee67ba5 100644 --- a/packages/compiler-cli/src/ngtsc/annotations/src/injectable.ts +++ b/packages/compiler-cli/src/ngtsc/annotations/src/injectable.ts @@ -6,7 +6,7 @@ * found in the LICENSE file at https://angular.io/license */ -import {Expression, Identifiers, LiteralExpr, R3DependencyMetadata, R3InjectableMetadata, R3ResolvedDependencyType, Statement, WrappedNodeExpr, compileInjectable as compileIvyInjectable} from '@angular/compiler'; +import {Expression, Identifiers, LiteralExpr, R3DependencyMetadata, R3FactoryTarget, R3InjectableMetadata, R3ResolvedDependencyType, Statement, WrappedNodeExpr, compileInjectable as compileIvyInjectable} from '@angular/compiler'; import * as ts from 'typescript'; import {ErrorCode, FatalDiagnosticError} from '../../diagnostics'; @@ -16,7 +16,7 @@ import {AnalysisOutput, CompileResult, DecoratorHandler, DetectResult, HandlerPr import {compileNgFactoryDefField} from './factory'; import {generateSetClassMetadataCall} from './metadata'; -import {findAngularDecorator, getConstructorDependencies, getValidConstructorDependencies, isAngularCore, unwrapForwardRef, validateConstructorDependencies} from './util'; +import {findAngularDecorator, getConstructorDependencies, getValidConstructorDependencies, isAngularCore, unwrapConstructorDependencies, unwrapForwardRef, validateConstructorDependencies} from './util'; export interface InjectableHandlerData { meta: R3InjectableMetadata; @@ -83,7 +83,8 @@ export class InjectableDecoratorHandler implements type: meta.type, typeArgumentCount: meta.typeArgumentCount, deps: analysis.ctorDeps, - injectFn: Identifiers.inject + injectFn: Identifiers.inject, + target: R3FactoryTarget.Injectable, }); if (analysis.metadataStmt !== null) { factoryRes.statements.push(analysis.metadataStmt); @@ -214,45 +215,25 @@ function extractInjectableCtorDeps( // Angular's DI. // // To deal with this, @Injectable() without an argument is more lenient, and if the - // constructor signature does not work for DI then a provider def (ɵprov) that throws. + // constructor signature does not work for DI then a factory definition (ɵfac) that throws is + // generated. if (strictCtorDeps) { ctorDeps = getValidConstructorDependencies(clazz, reflector, defaultImportRecorder, isCore); } else { - const possibleCtorDeps = - getConstructorDependencies(clazz, reflector, defaultImportRecorder, isCore); - if (possibleCtorDeps !== null) { - if (possibleCtorDeps.deps !== null) { - // This use of @Injectable has valid constructor dependencies. - ctorDeps = possibleCtorDeps.deps; - } else { - // This use of @Injectable is technically invalid. Generate a factory function which - // throws - // an error. - // TODO(alxhub): log warnings for the bad use of @Injectable. - ctorDeps = 'invalid'; - } - } + ctorDeps = unwrapConstructorDependencies( + getConstructorDependencies(clazz, reflector, defaultImportRecorder, isCore)); } return ctorDeps; } else if (decorator.args.length === 1) { const rawCtorDeps = getConstructorDependencies(clazz, reflector, defaultImportRecorder, isCore); - // rawCtorDeps will be null if the class has no constructor. - if (rawCtorDeps !== null) { - if (rawCtorDeps.deps !== null) { - // A constructor existed and had valid dependencies. - ctorDeps = rawCtorDeps.deps; - } else { - // A constructor existed but had invalid dependencies. - ctorDeps = 'invalid'; - } - } - - if (strictCtorDeps && !meta.useValue && !meta.useExisting && !meta.useClass && - !meta.useFactory) { + if (strictCtorDeps && meta.useValue === undefined && meta.useExisting === undefined && + meta.useClass === undefined && meta.useFactory === undefined) { // Since use* was not provided, validate the deps according to strictCtorDeps. - validateConstructorDependencies(clazz, rawCtorDeps); + ctorDeps = validateConstructorDependencies(clazz, rawCtorDeps); + } else { + ctorDeps = unwrapConstructorDependencies(rawCtorDeps); } } diff --git a/packages/compiler-cli/src/ngtsc/annotations/src/pipe.ts b/packages/compiler-cli/src/ngtsc/annotations/src/pipe.ts index d8668b439a29f..d5545286cacc6 100644 --- a/packages/compiler-cli/src/ngtsc/annotations/src/pipe.ts +++ b/packages/compiler-cli/src/ngtsc/annotations/src/pipe.ts @@ -6,7 +6,7 @@ * found in the LICENSE file at https://angular.io/license */ -import {Identifiers, R3PipeMetadata, Statement, WrappedNodeExpr, compilePipeFromMetadata} from '@angular/compiler'; +import {Identifiers, R3FactoryTarget, R3PipeMetadata, Statement, WrappedNodeExpr, compilePipeFromMetadata} from '@angular/compiler'; import * as ts from 'typescript'; import {ErrorCode, FatalDiagnosticError} from '../../diagnostics'; @@ -112,7 +112,7 @@ export class PipeDecoratorHandler implements DecoratorHandler { env.write('test.ts', ` import {Injectable} from '@angular/core'; - @Injectable() + @Injectable({providedIn: 'root'}) export class Test { constructor(private notInjectable: string) {} } @@ -1363,7 +1363,75 @@ runInEachFileSystem(os => { env.driveMain(); const jsContents = env.getContents('test.js'); - expect(jsContents).toMatch(/function Test_Factory\(t\) { throw new Error\(/ms); + expect(jsContents) + .toMatch(/function Test_Factory\(t\) { i0\.ɵɵinvalidFactory\(\)/ms); + }); + + it('should not give a compile-time error if an invalid @Injectable is used with useFactory', + () => { + env.tsconfig({strictInjectionParameters: true}); + env.write('test.ts', ` + import {Injectable} from '@angular/core'; + + @Injectable({ + providedIn: 'root', + useFactory: () => '42', + }) + export class Test { + constructor(private notInjectable: string) {} + } + `); + + env.driveMain(); + const jsContents = env.getContents('test.js'); + expect(jsContents) + .toMatch(/function Test_Factory\(t\) { i0\.ɵɵinvalidFactory\(\)/ms); + }); + + it('should not give a compile-time error if an invalid @Injectable is used with useExisting', + () => { + env.tsconfig({strictInjectionParameters: true}); + env.write('test.ts', ` + import {Injectable} from '@angular/core'; + + export class MyService {} + + @Injectable({ + providedIn: 'root', + useExisting: MyService, + }) + export class Test { + constructor(private notInjectable: string) {} + } + `); + + env.driveMain(); + const jsContents = env.getContents('test.js'); + expect(jsContents) + .toMatch(/function Test_Factory\(t\) { i0\.ɵɵinvalidFactory\(\)/ms); + }); + + it('should not give a compile-time error if an invalid @Injectable is used with useClass', + () => { + env.tsconfig({strictInjectionParameters: true}); + env.write('test.ts', ` + import {Injectable} from '@angular/core'; + + export class MyService {} + + @Injectable({ + providedIn: 'root', + useClass: MyService, + }) + export class Test { + constructor(private notInjectable: string) {} + } + `); + + env.driveMain(); + const jsContents = env.getContents('test.js'); + expect(jsContents) + .toMatch(/function Test_Factory\(t\) { i0\.ɵɵinvalidFactory\(\)/ms); }); }); @@ -1381,7 +1449,8 @@ runInEachFileSystem(os => { env.driveMain(); const jsContents = env.getContents('test.js'); - expect(jsContents).toContain('Test.ɵfac = function Test_Factory(t) { throw new Error('); + expect(jsContents) + .toContain('Test.ɵfac = function Test_Factory(t) { i0.ɵɵinvalidFactory()'); }); it('should compile an @Injectable provided in the root on a class with a non-injectable constructor', @@ -1398,12 +1467,54 @@ runInEachFileSystem(os => { env.driveMain(); const jsContents = env.getContents('test.js'); expect(jsContents) - .toContain('Test.ɵfac = function Test_Factory(t) { throw new Error('); + .toContain('Test.ɵfac = function Test_Factory(t) { i0.ɵɵinvalidFactory()'); }); }); }); + describe('compiling invalid @Directives', () => { + describe('directives with a selector', () => { + it('should give a compile-time error if an invalid constructor is used', () => { + env.tsconfig({strictInjectionParameters: true}); + env.write('test.ts', ` + import {Directive} from '@angular/core'; + + @Directive({selector: 'app-test'}) + export class Test { + constructor(private notInjectable: string) {} + } + `); + + const errors = env.driveDiagnostics(); + expect(errors.length).toBe(1); + expect(errors[0].messageText).toContain('No suitable injection token for parameter'); + }); + }); + + describe('abstract directives', () => { + it('should generate a factory function that throws', () => { + env.tsconfig({strictInjectionParameters: false}); + env.write('test.ts', ` + import {Directive} from '@angular/core'; + + @Directive() + export class Test { + constructor(private notInjectable: string) {} + } + `); + + env.driveMain(); + const jsContents = env.getContents('test.js'); + expect(jsContents) + .toContain('Test.ɵfac = function Test_Factory(t) { i0.ɵɵinvalidFactory()'); + expect(jsContents) + .toContain( + '("Test has a constructor which is not compatible with Dependency Injection.")'); + }); + }); + }); + describe('templateUrl and styleUrls processing', () => { const testsForResource = (resource: string) => [ // [component location, resource location, resource reference] diff --git a/packages/compiler/src/compiler.ts b/packages/compiler/src/compiler.ts index 7f9f6bb9dd6e3..6e6ad80e2f919 100644 --- a/packages/compiler/src/compiler.ts +++ b/packages/compiler/src/compiler.ts @@ -96,7 +96,7 @@ export {BoundAttribute as TmplAstBoundAttribute, BoundEvent as TmplAstBoundEvent export * from './render3/view/t2_api'; export * from './render3/view/t2_binder'; export {Identifiers as R3Identifiers} from './render3/r3_identifiers'; -export {R3DependencyMetadata, R3FactoryDefMetadata, R3ResolvedDependencyType, compileFactoryFromMetadata, R3FactoryMetadata} from './render3/r3_factory'; +export {R3DependencyMetadata, R3FactoryDefMetadata, R3ResolvedDependencyType, compileFactoryFromMetadata, R3FactoryMetadata, R3FactoryTarget} from './render3/r3_factory'; export {compileInjector, compileNgModule, R3InjectorMetadata, R3NgModuleMetadata} from './render3/r3_module_compiler'; export {compilePipeFromMetadata, R3PipeMetadata} from './render3/r3_pipe_compiler'; export {makeBindingParser, parseTemplate, ParseTemplateOptions} from './render3/view/template'; diff --git a/packages/compiler/src/compiler_facade_interface.ts b/packages/compiler/src/compiler_facade_interface.ts index b30398b9e5858..f8231fccf46c2 100644 --- a/packages/compiler/src/compiler_facade_interface.ts +++ b/packages/compiler/src/compiler_facade_interface.ts @@ -45,6 +45,7 @@ export interface CompilerFacade { createParseSourceSpan(kind: string, typeName: string, sourceUrl: string): ParseSourceSpan; R3ResolvedDependencyType: typeof R3ResolvedDependencyType; + R3FactoryTarget: typeof R3FactoryTarget; ResourceLoader: {new (): ResourceLoader}; } @@ -70,6 +71,14 @@ export enum R3ResolvedDependencyType { ChangeDetectorRef = 2, } +export enum R3FactoryTarget { + Directive = 0, + Component = 1, + Injectable = 2, + Pipe = 3, + NgModule = 4, +} + export interface R3DependencyMetadataFacade { token: any; resolved: R3ResolvedDependencyType; @@ -167,7 +176,7 @@ export interface R3FactoryDefMetadataFacade { typeArgumentCount: number; deps: R3DependencyMetadataFacade[]|null; injectFn: 'directiveInject'|'inject'; - isPipe: boolean; + target: R3FactoryTarget; } export type ViewEncapsulation = number; diff --git a/packages/compiler/src/injectable_compiler_2.ts b/packages/compiler/src/injectable_compiler_2.ts index a52d4bc7a5123..2c480564d75b7 100644 --- a/packages/compiler/src/injectable_compiler_2.ts +++ b/packages/compiler/src/injectable_compiler_2.ts @@ -8,7 +8,7 @@ import {Identifiers} from './identifiers'; import * as o from './output/output_ast'; -import {R3DependencyMetadata, R3FactoryDelegateType, compileFactoryFunction} from './render3/r3_factory'; +import {R3DependencyMetadata, R3FactoryDelegateType, R3FactoryTarget, compileFactoryFunction} from './render3/r3_factory'; import {mapToMapExpression, typeWithParameters} from './render3/util'; export interface InjectableDef { @@ -56,25 +56,29 @@ export function compileInjectable(meta: R3InjectableMetadata): InjectableDef { if (deps !== undefined) { // factory: () => new meta.useClass(...deps) - result = compileFactoryFunction({ - ...factoryMeta, - delegate: meta.useClass, - delegateDeps: deps, - delegateType: R3FactoryDelegateType.Class, - }); + result = compileFactoryFunction( + { + ...factoryMeta, + delegate: meta.useClass, + delegateDeps: deps, + delegateType: R3FactoryDelegateType.Class, + }, + R3FactoryTarget.Injectable); } else if (useClassOnSelf) { - result = compileFactoryFunction(factoryMeta); + result = compileFactoryFunction(factoryMeta, R3FactoryTarget.Injectable); } else { result = delegateToFactory(meta.useClass); } } else if (meta.useFactory !== undefined) { if (meta.userDeps !== undefined) { - result = compileFactoryFunction({ - ...factoryMeta, - delegate: meta.useFactory, - delegateDeps: meta.userDeps || [], - delegateType: R3FactoryDelegateType.Function, - }); + result = compileFactoryFunction( + { + ...factoryMeta, + delegate: meta.useFactory, + delegateDeps: meta.userDeps || [], + delegateType: R3FactoryDelegateType.Function, + }, + R3FactoryTarget.Injectable); } else { result = { statements: [], @@ -85,16 +89,20 @@ export function compileInjectable(meta: R3InjectableMetadata): InjectableDef { // Note: it's safe to use `meta.useValue` instead of the `USE_VALUE in meta` check used for // client code because meta.useValue is an Expression which will be defined even if the actual // value is undefined. - result = compileFactoryFunction({ - ...factoryMeta, - expression: meta.useValue, - }); + result = compileFactoryFunction( + { + ...factoryMeta, + expression: meta.useValue, + }, + R3FactoryTarget.Injectable); } else if (meta.useExisting !== undefined) { // useExisting is an `inject` call on the existing token. - result = compileFactoryFunction({ - ...factoryMeta, - expression: o.importExpr(Identifiers.inject).callFn([meta.useExisting]), - }); + result = compileFactoryFunction( + { + ...factoryMeta, + expression: o.importExpr(Identifiers.inject).callFn([meta.useExisting]), + }, + R3FactoryTarget.Injectable); } else { result = delegateToFactory(meta.type); } diff --git a/packages/compiler/src/jit_compiler_facade.ts b/packages/compiler/src/jit_compiler_facade.ts index 8be8159abd013..dde766a54cd8c 100644 --- a/packages/compiler/src/jit_compiler_facade.ts +++ b/packages/compiler/src/jit_compiler_facade.ts @@ -16,7 +16,7 @@ import {DEFAULT_INTERPOLATION_CONFIG, InterpolationConfig} from './ml_parser/int import {DeclareVarStmt, Expression, LiteralExpr, Statement, StmtModifier, WrappedNodeExpr} from './output/output_ast'; import {JitEvaluator} from './output/output_jit'; import {ParseError, ParseSourceSpan, r3JitTypeSourceSpan} from './parse_util'; -import {R3DependencyMetadata, R3ResolvedDependencyType, compileFactoryFromMetadata} from './render3/r3_factory'; +import {R3DependencyMetadata, R3FactoryTarget, R3ResolvedDependencyType, compileFactoryFromMetadata} from './render3/r3_factory'; import {R3JitReflector} from './render3/r3_jit'; import {R3InjectorMetadata, R3NgModuleMetadata, compileInjector, compileNgModule} from './render3/r3_module_compiler'; import {compilePipeFromMetadata} from './render3/r3_pipe_compiler'; @@ -29,6 +29,7 @@ import {DomElementSchemaRegistry} from './schema/dom_element_schema_registry'; export class CompilerFacadeImpl implements CompilerFacade { R3ResolvedDependencyType = R3ResolvedDependencyType as any; + R3FactoryTarget = R3FactoryTarget as any; ResourceLoader = ResourceLoader; private elementSchemaRegistry = new DomElementSchemaRegistry(); @@ -162,7 +163,7 @@ export class CompilerFacadeImpl implements CompilerFacade { deps: convertR3DependencyMetadataArray(meta.deps), injectFn: meta.injectFn === 'directiveInject' ? Identifiers.directiveInject : Identifiers.inject, - isPipe: meta.isPipe + target: meta.target, }); return this.jitExpression( factoryRes.factory, angularCoreEnv, sourceMapUrl, factoryRes.statements); diff --git a/packages/compiler/src/render3/r3_factory.ts b/packages/compiler/src/render3/r3_factory.ts index 96ad4adf13102..202be4f511a90 100644 --- a/packages/compiler/src/render3/r3_factory.ts +++ b/packages/compiler/src/render3/r3_factory.ts @@ -82,13 +82,21 @@ export interface R3ExpressionFactoryMetadata extends R3ConstructorFactoryMetadat export type R3FactoryMetadata = R3ConstructorFactoryMetadata | R3DelegatedFactoryMetadata | R3DelegatedFnOrClassMetadata | R3ExpressionFactoryMetadata; +export enum R3FactoryTarget { + Directive = 0, + Component = 1, + Injectable = 2, + Pipe = 3, + NgModule = 4, +} + export interface R3FactoryDefMetadata { name: string; type: o.Expression; typeArgumentCount: number; deps: R3DependencyMetadata[]|'invalid'|null; injectFn: o.ExternalReference; - isPipe?: boolean; + target: R3FactoryTarget; } /** @@ -163,7 +171,8 @@ export interface R3FactoryFn { /** * Construct a factory function expression for the given `R3FactoryMetadata`. */ -export function compileFactoryFunction(meta: R3FactoryMetadata, isPipe = false): R3FactoryFn { +export function compileFactoryFunction( + meta: R3FactoryMetadata, target: R3FactoryTarget): R3FactoryFn { const t = o.variable('t'); const statements: o.Statement[] = []; @@ -179,8 +188,9 @@ export function compileFactoryFunction(meta: R3FactoryMetadata, isPipe = false): if (meta.deps !== null) { // There is a constructor (either explicitly or implicitly defined). if (meta.deps !== 'invalid') { - ctorExpr = - new o.InstantiateExpr(typeForCtor, injectDependencies(meta.deps, meta.injectFn, isPipe)); + ctorExpr = new o.InstantiateExpr( + typeForCtor, + injectDependencies(meta.deps, meta.injectFn, target === R3FactoryTarget.Pipe)); } } else { const baseFactory = o.variable(`ɵ${meta.name}_BaseFactory`); @@ -206,7 +216,7 @@ export function compileFactoryFunction(meta: R3FactoryMetadata, isPipe = false): if (ctorExprFinal !== null) { ctorStmt = r.set(ctorExprFinal).toStmt(); } else { - ctorStmt = makeErrorStmt(meta.name); + ctorStmt = o.importExpr(R3.invalidFactory).callFn([]).toStmt(); } body.push(o.ifStmt(t, [ctorStmt], [r.set(nonCtorExpr).toStmt()])); return r; @@ -228,8 +238,9 @@ export function compileFactoryFunction(meta: R3FactoryMetadata, isPipe = false): } else if (isDelegatedMetadata(meta)) { // This type is created with a delegated factory. If a type parameter is not specified, call // the factory instead. - const delegateArgs = injectDependencies(meta.delegateDeps, meta.injectFn, isPipe); - // Either call `new delegate(...)` or `delegate(...)` depending on meta.useNewForDelegate. + const delegateArgs = + injectDependencies(meta.delegateDeps, meta.injectFn, target === R3FactoryTarget.Pipe); + // Either call `new delegate(...)` or `delegate(...)` depending on meta.delegateType. const factoryExpr = new ( meta.delegateType === R3FactoryDelegateType.Class ? o.InstantiateExpr : @@ -245,7 +256,7 @@ export function compileFactoryFunction(meta: R3FactoryMetadata, isPipe = false): if (retExpr !== null) { body.push(new o.ReturnStatement(retExpr)); } else { - body.push(makeErrorStmt(meta.name)); + body.push(o.importExpr(R3.invalidFactory).callFn([]).toStmt()); } return { @@ -270,7 +281,7 @@ export function compileFactoryFromMetadata(meta: R3FactoryDefMetadata): R3Factor typeArgumentCount: meta.typeArgumentCount, injectFn: meta.injectFn, }, - meta.isPipe); + meta.target); } function injectDependencies( @@ -358,13 +369,6 @@ export function dependenciesFromGlobalMetadata( return deps; } -function makeErrorStmt(name: string): o.Statement { - return new o.ThrowStmt(new o.InstantiateExpr(new o.ReadVarExpr('Error'), [ - o.literal( - `${name} has a constructor which is not compatible with Dependency Injection. It should probably not be @Injectable().`) - ])); -} - function isDelegatedMetadata(meta: R3FactoryMetadata): meta is R3DelegatedFactoryMetadata| R3DelegatedFnOrClassMetadata { return (meta as any).delegateType !== undefined; diff --git a/packages/compiler/src/render3/r3_identifiers.ts b/packages/compiler/src/render3/r3_identifiers.ts index 6b648511aafcf..ae8187db792ce 100644 --- a/packages/compiler/src/render3/r3_identifiers.ts +++ b/packages/compiler/src/render3/r3_identifiers.ts @@ -211,6 +211,7 @@ export class Identifiers { o.ExternalReference = {name: 'ɵɵinjectPipeChangeDetectorRef', moduleName: CORE}; static directiveInject: o.ExternalReference = {name: 'ɵɵdirectiveInject', moduleName: CORE}; + static invalidFactory: o.ExternalReference = {name: 'ɵɵinvalidFactory', moduleName: CORE}; static templateRefExtractor: o.ExternalReference = {name: 'ɵɵtemplateRefExtractor', moduleName: CORE}; diff --git a/packages/compiler/src/render3/r3_module_compiler.ts b/packages/compiler/src/render3/r3_module_compiler.ts index ecbe9b1369bcb..0ee23cd094284 100644 --- a/packages/compiler/src/render3/r3_module_compiler.ts +++ b/packages/compiler/src/render3/r3_module_compiler.ts @@ -12,7 +12,7 @@ import {mapLiteral} from '../output/map_util'; import * as o from '../output/output_ast'; import {OutputContext} from '../util'; -import {R3DependencyMetadata, compileFactoryFunction} from './r3_factory'; +import {R3DependencyMetadata, R3FactoryTarget, compileFactoryFunction} from './r3_factory'; import {Identifiers as R3} from './r3_identifiers'; import {R3Reference, convertMetaToOutput, mapToMapExpression} from './util'; @@ -204,13 +204,15 @@ export interface R3InjectorMetadata { } export function compileInjector(meta: R3InjectorMetadata): R3InjectorDef { - const result = compileFactoryFunction({ - name: meta.name, - type: meta.type, - typeArgumentCount: 0, - deps: meta.deps, - injectFn: R3.inject, - }); + const result = compileFactoryFunction( + { + name: meta.name, + type: meta.type, + typeArgumentCount: 0, + deps: meta.deps, + injectFn: R3.inject, + }, + R3FactoryTarget.NgModule); const definitionMap = { factory: result.factory, } as{factory: o.Expression, providers: o.Expression, imports: o.Expression}; diff --git a/packages/compiler/src/render3/r3_pipe_compiler.ts b/packages/compiler/src/render3/r3_pipe_compiler.ts index 22689ed0c3a2a..6ce1cdfb4405b 100644 --- a/packages/compiler/src/render3/r3_pipe_compiler.ts +++ b/packages/compiler/src/render3/r3_pipe_compiler.ts @@ -12,7 +12,7 @@ import {DefinitionKind} from '../constant_pool'; import * as o from '../output/output_ast'; import {OutputContext, error} from '../util'; -import {R3DependencyMetadata, compileFactoryFromMetadata, compileFactoryFunction, dependenciesFromGlobalMetadata} from './r3_factory'; +import {R3DependencyMetadata, R3FactoryTarget, compileFactoryFromMetadata, dependenciesFromGlobalMetadata} from './r3_factory'; import {Identifiers as R3} from './r3_identifiers'; import {typeWithParameters} from './util'; @@ -89,8 +89,8 @@ export function compilePipeFromRender2( pure: pipe.pure, }; const res = compilePipeFromMetadata(metadata); - const factoryRes = - compileFactoryFromMetadata({...metadata, injectFn: R3.directiveInject, isPipe: true}); + const factoryRes = compileFactoryFromMetadata( + {...metadata, injectFn: R3.directiveInject, target: R3FactoryTarget.Pipe}); const definitionField = outputCtx.constantPool.propertyNameOf(DefinitionKind.Pipe); const ngFactoryDefStatement = new o.ClassStmt( /* name */ name, diff --git a/packages/compiler/src/render3/view/api.ts b/packages/compiler/src/render3/view/api.ts index 44ad40ef1b53e..3995598d5a77e 100644 --- a/packages/compiler/src/render3/view/api.ts +++ b/packages/compiler/src/render3/view/api.ts @@ -41,7 +41,7 @@ export interface R3DirectiveMetadata { /** * Dependencies of the directive's constructor. */ - deps: R3DependencyMetadata[]|null; + deps: R3DependencyMetadata[]|'invalid'|null; /** * Unparsed selector of the directive, or `null` if there was no selector. diff --git a/packages/compiler/src/render3/view/compiler.ts b/packages/compiler/src/render3/view/compiler.ts index 9769f31a5a1ab..a7e5d8dd40a6a 100644 --- a/packages/compiler/src/render3/view/compiler.ts +++ b/packages/compiler/src/render3/view/compiler.ts @@ -12,17 +12,17 @@ import {CompileReflector} from '../../compile_reflector'; import {BindingForm, convertPropertyBinding} from '../../compiler_util/expression_converter'; import {ConstantPool, DefinitionKind} from '../../constant_pool'; import * as core from '../../core'; -import {AST, Interpolation, ParsedEvent, ParsedEventType, ParsedProperty} from '../../expression_parser/ast'; +import {AST, ParsedEvent, ParsedEventType, ParsedProperty} from '../../expression_parser/ast'; import {DEFAULT_INTERPOLATION_CONFIG} from '../../ml_parser/interpolation_config'; import * as o from '../../output/output_ast'; -import {ParseError, ParseSourceSpan, typeSourceSpan} from '../../parse_util'; +import {ParseError, ParseSourceSpan} from '../../parse_util'; import {CssSelector, SelectorMatcher} from '../../selector'; import {ShadowCss} from '../../shadow_css'; import {CONTENT_ATTR, HOST_ATTR} from '../../style_compiler'; import {BindingParser} from '../../template_parser/binding_parser'; import {OutputContext, error} from '../../util'; import {BoundEvent} from '../r3_ast'; -import {compileFactoryFromMetadata, compileFactoryFunction, dependenciesFromGlobalMetadata} from '../r3_factory'; +import {R3FactoryTarget, compileFactoryFromMetadata} from '../r3_factory'; import {Identifiers as R3} from '../r3_identifiers'; import {Render3ParseResult} from '../r3_template_transform'; import {prepareSyntheticListenerFunctionName, prepareSyntheticPropertyName, typeWithParameters} from '../util'; @@ -327,7 +327,8 @@ export function compileDirectiveFromRender2( const meta = directiveMetadataFromGlobalMetadata(directive, outputCtx, reflector); const res = compileDirectiveFromMetadata(meta, outputCtx.constantPool, bindingParser); - const factoryRes = compileFactoryFromMetadata({...meta, injectFn: R3.directiveInject}); + const factoryRes = compileFactoryFromMetadata( + {...meta, injectFn: R3.directiveInject, target: R3FactoryTarget.Directive}); const ngFactoryDefStatement = new o.ClassStmt( name, null, [new o.ClassField('ɵfac', o.INFERRED_TYPE, [o.StmtModifier.Static], factoryRes.factory)], [], @@ -379,7 +380,8 @@ export function compileComponentFromRender2( i18nUseExternalIds: true, }; const res = compileComponentFromMetadata(meta, outputCtx.constantPool, bindingParser); - const factoryRes = compileFactoryFromMetadata({...meta, injectFn: R3.directiveInject}); + const factoryRes = compileFactoryFromMetadata( + {...meta, injectFn: R3.directiveInject, target: R3FactoryTarget.Directive}); const ngFactoryDefStatement = new o.ClassStmt( name, null, [new o.ClassField('ɵfac', o.INFERRED_TYPE, [o.StmtModifier.Static], factoryRes.factory)], [], diff --git a/packages/compiler/test/compiler_facade_interface_spec.ts b/packages/compiler/test/compiler_facade_interface_spec.ts index efd1f23d1f1e7..506c14274aa5a 100644 --- a/packages/compiler/test/compiler_facade_interface_spec.ts +++ b/packages/compiler/test/compiler_facade_interface_spec.ts @@ -7,7 +7,7 @@ */ import * as core from '../../core/src/compiler/compiler_facade_interface'; -import {R3ResolvedDependencyType} from '../public_api'; +import {R3FactoryTarget, R3ResolvedDependencyType} from '../public_api'; import * as compiler from '../src/compiler_facade_interface'; /** @@ -60,6 +60,15 @@ const coreR3ResolvedDependencyType3: core.R3ResolvedDependencyType = const compilerR3ResolvedDependencyType3: compiler.R3ResolvedDependencyType = null !as R3ResolvedDependencyType; +const coreR3FactoryTarget: core.R3FactoryTarget = null !as compiler.R3FactoryTarget; +const compilerR3FactoryTarget: compiler.R3FactoryTarget = null !as core.R3FactoryTarget; + +const coreR3FactoryTarget2: R3FactoryTarget = null !as core.R3FactoryTarget; +const compilerR3FactoryTarget2: R3FactoryTarget = null !as core.R3FactoryTarget; + +const coreR3FactoryTarget3: core.R3FactoryTarget = null !as R3FactoryTarget; +const compilerR3FactoryTarget3: compiler.R3FactoryTarget = null !as R3FactoryTarget; + const coreR3DependencyMetadataFacade: core.R3DependencyMetadataFacade = null !as compiler.R3DependencyMetadataFacade; const compilerR3DependencyMetadataFacade: compiler.R3DependencyMetadataFacade = diff --git a/packages/core/src/compiler/compiler_facade_interface.ts b/packages/core/src/compiler/compiler_facade_interface.ts index b30398b9e5858..f8231fccf46c2 100644 --- a/packages/core/src/compiler/compiler_facade_interface.ts +++ b/packages/core/src/compiler/compiler_facade_interface.ts @@ -45,6 +45,7 @@ export interface CompilerFacade { createParseSourceSpan(kind: string, typeName: string, sourceUrl: string): ParseSourceSpan; R3ResolvedDependencyType: typeof R3ResolvedDependencyType; + R3FactoryTarget: typeof R3FactoryTarget; ResourceLoader: {new (): ResourceLoader}; } @@ -70,6 +71,14 @@ export enum R3ResolvedDependencyType { ChangeDetectorRef = 2, } +export enum R3FactoryTarget { + Directive = 0, + Component = 1, + Injectable = 2, + Pipe = 3, + NgModule = 4, +} + export interface R3DependencyMetadataFacade { token: any; resolved: R3ResolvedDependencyType; @@ -167,7 +176,7 @@ export interface R3FactoryDefMetadataFacade { typeArgumentCount: number; deps: R3DependencyMetadataFacade[]|null; injectFn: 'directiveInject'|'inject'; - isPipe: boolean; + target: R3FactoryTarget; } export type ViewEncapsulation = number; diff --git a/packages/core/src/core_render3_private_export.ts b/packages/core/src/core_render3_private_export.ts index eff2b5a2bcbe6..573a403a5628b 100644 --- a/packages/core/src/core_render3_private_export.ts +++ b/packages/core/src/core_render3_private_export.ts @@ -34,6 +34,7 @@ export { ɵɵdirectiveInject, ɵɵinjectAttribute, ɵɵinjectPipeChangeDetectorRef, + ɵɵinvalidFactory, ɵɵgetFactoryOf, ɵɵgetInheritedFactory, ɵɵsetComponentScope, diff --git a/packages/core/src/di/jit/injectable.ts b/packages/core/src/di/jit/injectable.ts index c22bf4f1422fe..c01ad1b12128b 100644 --- a/packages/core/src/di/jit/injectable.ts +++ b/packages/core/src/di/jit/injectable.ts @@ -48,15 +48,15 @@ export function compileInjectable(type: Type, srcMeta?: Injectable): void { get: () => { if (ngFactoryDef === null) { const metadata = getInjectableMetadata(type, srcMeta); - ngFactoryDef = - getCompilerFacade().compileFactory(angularCoreDiEnv, `ng:///${type.name}/ɵfac.js`, { - name: metadata.name, - type: metadata.type, - typeArgumentCount: metadata.typeArgumentCount, - deps: reflectDependencies(type), - injectFn: 'inject', - isPipe: false - }); + const compiler = getCompilerFacade(); + ngFactoryDef = compiler.compileFactory(angularCoreDiEnv, `ng:///${type.name}/ɵfac.js`, { + name: metadata.name, + type: metadata.type, + typeArgumentCount: metadata.typeArgumentCount, + deps: reflectDependencies(type), + injectFn: 'inject', + target: compiler.R3FactoryTarget.Pipe + }); } return ngFactoryDef; }, diff --git a/packages/core/src/render3/index.ts b/packages/core/src/render3/index.ts index 18dd27c5d6af7..e1c2b3a6b5a1a 100644 --- a/packages/core/src/render3/index.ts +++ b/packages/core/src/render3/index.ts @@ -55,6 +55,7 @@ export { ɵɵcontainerRefreshStart, ɵɵdirectiveInject, + ɵɵinvalidFactory, ɵɵelement, ɵɵelementContainer, diff --git a/packages/core/src/render3/instructions/di.ts b/packages/core/src/render3/instructions/di.ts index 3da2860c03040..4d52f2ea59fbc 100644 --- a/packages/core/src/render3/instructions/di.ts +++ b/packages/core/src/render3/instructions/di.ts @@ -59,3 +59,21 @@ export function ɵɵdirectiveInject( export function ɵɵinjectAttribute(attrNameToInject: string): string|null { return injectAttributeImpl(getPreviousOrParentTNode(), attrNameToInject); } + +/** + * Throws an error indicating that a factory function could not be generated by the compiler for a + * particular class. + * + * This instruction allows the actual error message to be optimized away when ngDevMode is turned + * off, saving bytes of generated code while still providing a good experience in dev mode. + * + * The name of the class is not mentioned here, but will be in the generated factory function name + * and thus in the stack trace. + * + * @codeGenApi + */ +export function ɵɵinvalidFactory(): never { + const msg = + ngDevMode ? `This constructor was not compatible with Dependency Injection.` : 'invalid'; + throw new Error(msg); +} diff --git a/packages/core/src/render3/jit/directive.ts b/packages/core/src/render3/jit/directive.ts index a2c53e8186969..3080ddd18ae71 100644 --- a/packages/core/src/render3/jit/directive.ts +++ b/packages/core/src/render3/jit/directive.ts @@ -165,9 +165,10 @@ function addDirectiveFactoryDef(type: Type, metadata: Directive | Component get: () => { if (ngFactoryDef === null) { const meta = getDirectiveMetadata(type, metadata); - ngFactoryDef = getCompilerFacade().compileFactory( + const compiler = getCompilerFacade(); + ngFactoryDef = compiler.compileFactory( angularCoreEnv, `ng:///${type.name}/ɵfac.js`, - {...meta.metadata, injectFn: 'directiveInject', isPipe: false}); + {...meta.metadata, injectFn: 'directiveInject', target: compiler.R3FactoryTarget.Pipe}); } return ngFactoryDef; }, diff --git a/packages/core/src/render3/jit/environment.ts b/packages/core/src/render3/jit/environment.ts index 8f60f5a4d8ab6..ff700ee9abbd8 100644 --- a/packages/core/src/render3/jit/environment.ts +++ b/packages/core/src/render3/jit/environment.ts @@ -42,6 +42,7 @@ export const angularCoreEnv: {[name: string]: Function} = 'ɵɵgetInheritedFactory': r3.ɵɵgetInheritedFactory, 'ɵɵinject': ɵɵinject, 'ɵɵinjectAttribute': r3.ɵɵinjectAttribute, + 'ɵɵinvalidFactory': r3.ɵɵinvalidFactory, 'ɵɵinjectPipeChangeDetectorRef': r3.ɵɵinjectPipeChangeDetectorRef, 'ɵɵtemplateRefExtractor': r3.ɵɵtemplateRefExtractor, 'ɵɵNgOnChangesFeature': r3.ɵɵNgOnChangesFeature, diff --git a/packages/core/src/render3/jit/pipe.ts b/packages/core/src/render3/jit/pipe.ts index 4025cfbd71104..2ecd57799f07d 100644 --- a/packages/core/src/render3/jit/pipe.ts +++ b/packages/core/src/render3/jit/pipe.ts @@ -22,9 +22,10 @@ export function compilePipe(type: Type, meta: Pipe): void { get: () => { if (ngFactoryDef === null) { const metadata = getPipeMetadata(type, meta); - ngFactoryDef = getCompilerFacade().compileFactory( + const compiler = getCompilerFacade(); + ngFactoryDef = compiler.compileFactory( angularCoreEnv, `ng:///${metadata.name}/ɵfac.js`, - {...metadata, injectFn: 'directiveInject', isPipe: true}); + {...metadata, injectFn: 'directiveInject', target: compiler.R3FactoryTarget.Pipe}); } return ngFactoryDef; },