diff --git a/packages/compiler-cli/src/ngtsc/annotations/src/component.ts b/packages/compiler-cli/src/ngtsc/annotations/src/component.ts index 28f8d03f8bc4b..560f5dde95c9d 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'; @@ -490,7 +490,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 adbb5069408d5..b1e58f00b3a0f 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, HandlerFl 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} = {}; @@ -89,7 +89,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); } @@ -228,11 +229,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/factory.ts b/packages/compiler-cli/src/ngtsc/annotations/src/factory.ts index 28027d7f511bb..0aa284ca7697b 100644 --- a/packages/compiler-cli/src/ngtsc/annotations/src/factory.ts +++ b/packages/compiler-cli/src/ngtsc/annotations/src/factory.ts @@ -6,11 +6,11 @@ * found in the LICENSE file at https://angular.io/license */ -import {R3FactoryDefMetadata, compileFactoryFromMetadata} from '@angular/compiler'; +import {R3FactoryMetadata, compileFactoryFunction} from '@angular/compiler'; import {CompileResult} from '../../transform'; -export function compileNgFactoryDefField(metadata: R3FactoryDefMetadata): CompileResult { - const res = compileFactoryFromMetadata(metadata); +export function compileNgFactoryDefField(metadata: R3FactoryMetadata): CompileResult { + const res = compileFactoryFunction(metadata); return {name: 'ɵfac', initializer: res.factory, statements: res.statements, type: res.type}; } diff --git a/packages/compiler-cli/src/ngtsc/annotations/src/injectable.ts b/packages/compiler-cli/src/ngtsc/annotations/src/injectable.ts index c1ca25603b266..36dd84b8360e2 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); @@ -216,45 +217,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 0857548a99850..5ae8d93ac7ce7 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'; @@ -114,7 +114,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) {} } @@ -1364,7 +1364,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); }); }); @@ -1382,7 +1450,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', @@ -1399,12 +1468,69 @@ 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()'); + }); + }); + + it('should generate a factory function that throws, even under strictInjectionParameters', + () => { + env.tsconfig({strictInjectionParameters: true}); + 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()'); + }); + }); + 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..d26b1c65ad5f8 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, R3ResolvedDependencyType, compileFactoryFunction, 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..de71856eecacd 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 { @@ -38,6 +38,7 @@ export function compileInjectable(meta: R3InjectableMetadata): InjectableDef { typeArgumentCount: meta.typeArgumentCount, deps: [], injectFn: Identifiers.inject, + target: R3FactoryTarget.Injectable, }; if (meta.useClass !== undefined) { diff --git a/packages/compiler/src/jit_compiler_facade.ts b/packages/compiler/src/jit_compiler_facade.ts index c4a2c307abf49..f01c9af701316 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, compileFactoryFunction} 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(); @@ -155,14 +156,14 @@ export class CompilerFacadeImpl implements CompilerFacade { compileFactory( angularCoreEnv: CoreEnvironment, sourceMapUrl: string, meta: R3FactoryDefMetadataFacade) { - const factoryRes = compileFactoryFromMetadata({ + const factoryRes = compileFactoryFunction({ name: meta.name, type: new WrappedNodeExpr(meta.type), typeArgumentCount: meta.typeArgumentCount, 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..5e8027f5fe8e5 100644 --- a/packages/compiler/src/render3/r3_factory.ts +++ b/packages/compiler/src/render3/r3_factory.ts @@ -56,6 +56,11 @@ export interface R3ConstructorFactoryMetadata { * function could be different, and other options control how it will be invoked. */ injectFn: o.ExternalReference; + + /** + * Type of the target being created by the factory. + */ + target: R3FactoryTarget; } export enum R3FactoryDelegateType { @@ -82,13 +87,12 @@ export interface R3ExpressionFactoryMetadata extends R3ConstructorFactoryMetadat export type R3FactoryMetadata = R3ConstructorFactoryMetadata | R3DelegatedFactoryMetadata | R3DelegatedFnOrClassMetadata | R3ExpressionFactoryMetadata; -export interface R3FactoryDefMetadata { - name: string; - type: o.Expression; - typeArgumentCount: number; - deps: R3DependencyMetadata[]|'invalid'|null; - injectFn: o.ExternalReference; - isPipe?: boolean; +export enum R3FactoryTarget { + Directive = 0, + Component = 1, + Injectable = 2, + Pipe = 3, + NgModule = 4, } /** @@ -163,7 +167,7 @@ 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): R3FactoryFn { const t = o.variable('t'); const statements: o.Statement[] = []; @@ -179,8 +183,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, meta.target === R3FactoryTarget.Pipe)); } } else { const baseFactory = o.variable(`ɵ${meta.name}_BaseFactory`); @@ -206,7 +211,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 +233,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, meta.target === R3FactoryTarget.Pipe); + // Either call `new delegate(...)` or `delegate(...)` depending on meta.delegateType. const factoryExpr = new ( meta.delegateType === R3FactoryDelegateType.Class ? o.InstantiateExpr : @@ -245,7 +251,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 { @@ -258,21 +264,6 @@ export function compileFactoryFunction(meta: R3FactoryMetadata, isPipe = false): }; } -/** - * Constructs the factory def (`ɵfac`) from directive/component/pipe metadata. - */ -export function compileFactoryFromMetadata(meta: R3FactoryDefMetadata): R3FactoryFn { - return compileFactoryFunction( - { - name: meta.name, - type: meta.type, - deps: meta.deps, - typeArgumentCount: meta.typeArgumentCount, - injectFn: meta.injectFn, - }, - meta.isPipe); -} - function injectDependencies( deps: R3DependencyMetadata[], injectFn: o.ExternalReference, isPipe: boolean): o.Expression[] { return deps.map(dep => compileInjectDependency(dep, injectFn, isPipe)); @@ -358,13 +349,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 286659d8b17c5..b525b14ec22c6 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..ebe48a109a664 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'; @@ -210,6 +210,7 @@ export function compileInjector(meta: R3InjectorMetadata): R3InjectorDef { typeArgumentCount: 0, deps: meta.deps, injectFn: R3.inject, + target: R3FactoryTarget.NgModule, }); const definitionMap = { factory: result.factory, diff --git a/packages/compiler/src/render3/r3_pipe_compiler.ts b/packages/compiler/src/render3/r3_pipe_compiler.ts index 22689ed0c3a2a..c4551fa27e926 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, compileFactoryFunction, 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 = compileFactoryFunction( + {...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 4db8c1d4f6fe6..57ae0eec1bce1 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 97251967acb5b..186eb8e0d7bbd 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, compileFactoryFunction} from '../r3_factory'; import {Identifiers as R3} from '../r3_identifiers'; import {Render3ParseResult} from '../r3_template_transform'; import {prepareSyntheticListenerFunctionName, prepareSyntheticPropertyName, typeWithParameters} from '../util'; @@ -330,7 +330,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 = compileFactoryFunction( + {...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)], [], @@ -382,7 +383,8 @@ export function compileComponentFromRender2( i18nUseExternalIds: true, }; const res = compileComponentFromMetadata(meta, outputCtx.constantPool, bindingParser); - const factoryRes = compileFactoryFromMetadata({...meta, injectFn: R3.directiveInject}); + const factoryRes = compileFactoryFunction( + {...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 739d8af84f7f6..39f8469bf195c 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 0f56d2fc30de3..f63bbefaeb277 100644 --- a/packages/core/src/render3/index.ts +++ b/packages/core/src/render3/index.ts @@ -56,6 +56,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..3948e5ea7787a 100644 --- a/packages/core/src/render3/jit/directive.ts +++ b/packages/core/src/render3/jit/directive.ts @@ -165,9 +165,12 @@ function addDirectiveFactoryDef(type: Type, metadata: Directive | Component get: () => { if (ngFactoryDef === null) { const meta = getDirectiveMetadata(type, metadata); - ngFactoryDef = getCompilerFacade().compileFactory( - angularCoreEnv, `ng:///${type.name}/ɵfac.js`, - {...meta.metadata, injectFn: 'directiveInject', isPipe: false}); + const compiler = getCompilerFacade(); + ngFactoryDef = compiler.compileFactory(angularCoreEnv, `ng:///${type.name}/ɵfac.js`, { + ...meta.metadata, + injectFn: 'directiveInject', + target: compiler.R3FactoryTarget.Directive + }); } return ngFactoryDef; }, diff --git a/packages/core/src/render3/jit/environment.ts b/packages/core/src/render3/jit/environment.ts index 01b0f72c97fb9..6503cd3fefe0a 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; }, diff --git a/tools/public_api_guard/core/core.d.ts b/tools/public_api_guard/core/core.d.ts index 6bc18b230fb23..cbb3f6e4b4e12 100644 --- a/tools/public_api_guard/core/core.d.ts +++ b/tools/public_api_guard/core/core.d.ts @@ -921,6 +921,8 @@ export interface ɵɵInjectorDef { export declare function ɵɵinjectPipeChangeDetectorRef(flags?: InjectFlags): ChangeDetectorRef | null; +export declare function ɵɵinvalidFactory(): never; + export declare function ɵɵlistener(eventName: string, listenerFn: (e?: any) => any, useCapture?: boolean, eventTargetResolver?: GlobalTargetResolver): void; export declare function ɵɵloadQuery(): QueryList;