From 80a5934af6656279b1c4b2cf7f70a97172ca486f Mon Sep 17 00:00:00 2001 From: Kristiyan Kostadinov Date: Tue, 12 Feb 2019 00:03:04 +0100 Subject: [PATCH] fix(ivy): support schemas at runtime (#28637) Accounts for schemas in when validating properties in Ivy. This PR resolves FW-819. A couple of notes: * I had to rework the test slightly, in order to have it fail when we expect it to. The one in master is passing since Ivy's validation runs during the update phase, rather than creation. * I had to deviate from the design in FW-819 and not add an `enableSchema` instruction, because the schema is part of the `NgModule` scope, however the scope is only assigned to a component once all of the module's declarations have been resolved and some of them can be async. Instead, I opted to have the `schemas` on the component definition. PR Close #28637 --- .../src/ngtsc/annotations/src/ng_module.ts | 2 + .../compiler/src/compiler_facade_interface.ts | 3 +- packages/compiler/src/jit_compiler_facade.ts | 1 + .../src/render3/r3_module_compiler.ts | 14 +++++- .../src/compiler/compiler_facade_interface.ts | 3 +- packages/core/src/metadata.ts | 6 ++- packages/core/src/metadata/ng_module.ts | 37 ++------------ packages/core/src/metadata/schema.ts | 40 +++++++++++++++ packages/core/src/render3/component.ts | 8 +-- packages/core/src/render3/component_ref.ts | 4 +- packages/core/src/render3/definition.ts | 8 +++ packages/core/src/render3/instructions.ts | 50 +++++++++++++++---- .../core/src/render3/interfaces/definition.ts | 8 ++- packages/core/src/render3/interfaces/view.ts | 6 +++ packages/core/src/render3/jit/module.ts | 3 ++ .../test/linker/ng_module_integration_spec.ts | 37 ++++++++------ packages/core/test/render3/di_spec.ts | 4 +- .../styling/class_and_style_bindings_spec.ts | 4 +- 18 files changed, 162 insertions(+), 76 deletions(-) create mode 100644 packages/core/src/metadata/schema.ts diff --git a/packages/compiler-cli/src/ngtsc/annotations/src/ng_module.ts b/packages/compiler-cli/src/ngtsc/annotations/src/ng_module.ts index 33ae5ee7f3..b8cb380501 100644 --- a/packages/compiler-cli/src/ngtsc/annotations/src/ng_module.ts +++ b/packages/compiler-cli/src/ngtsc/annotations/src/ng_module.ts @@ -134,6 +134,8 @@ export class NgModuleDecoratorHandler implements DecoratorHandler this._toR3Reference(exp, valueContext, typeContext)), imports: imports.map(imp => this._toR3Reference(imp, valueContext, typeContext)), emitInline: false, + // TODO: to be implemented as a part of FW-1004. + schemas: [], }; const providers: Expression = ngModule.has('providers') ? diff --git a/packages/compiler/src/compiler_facade_interface.ts b/packages/compiler/src/compiler_facade_interface.ts index b375c16592..ad281c618e 100644 --- a/packages/compiler/src/compiler_facade_interface.ts +++ b/packages/compiler/src/compiler_facade_interface.ts @@ -97,6 +97,7 @@ export interface R3NgModuleMetadataFacade { imports: Function[]; exports: Function[]; emitInline: boolean; + schemas: {name: string}[]|null; } export interface R3InjectorMetadataFacade { @@ -155,4 +156,4 @@ export interface ParseSourceSpan { start: any; end: any; details: any; -} \ No newline at end of file +} diff --git a/packages/compiler/src/jit_compiler_facade.ts b/packages/compiler/src/jit_compiler_facade.ts index 9a5b3ba510..9d36aa3098 100644 --- a/packages/compiler/src/jit_compiler_facade.ts +++ b/packages/compiler/src/jit_compiler_facade.ts @@ -86,6 +86,7 @@ export class CompilerFacadeImpl implements CompilerFacade { imports: facade.imports.map(wrapReference), exports: facade.exports.map(wrapReference), emitInline: true, + schemas: facade.schemas ? facade.schemas.map(wrapReference) : null, }; const res = compileNgModule(meta); return this.jitExpression(res.expression, angularCoreEnv, sourceMapUrl, []); diff --git a/packages/compiler/src/render3/r3_module_compiler.ts b/packages/compiler/src/render3/r3_module_compiler.ts index aab985b25b..4a4a3f51d9 100644 --- a/packages/compiler/src/render3/r3_module_compiler.ts +++ b/packages/compiler/src/render3/r3_module_compiler.ts @@ -57,13 +57,18 @@ export interface R3NgModuleMetadata { * does not allow components to be tree-shaken, but is useful for JIT mode. */ emitInline: boolean; + + /** + * The set of schemas that declare elements to be allowed in the NgModule. + */ + schemas: R3Reference[]|null; } /** * Construct an `R3NgModuleDef` for the given `R3NgModuleMetadata`. */ export function compileNgModule(meta: R3NgModuleMetadata): R3NgModuleDef { - const {type: moduleType, bootstrap, declarations, imports, exports} = meta; + const {type: moduleType, bootstrap, declarations, imports, exports, schemas} = meta; const definitionMap = { type: moduleType } as{ @@ -71,7 +76,8 @@ export function compileNgModule(meta: R3NgModuleMetadata): R3NgModuleDef { bootstrap: o.LiteralArrayExpr, declarations: o.LiteralArrayExpr, imports: o.LiteralArrayExpr, - exports: o.LiteralArrayExpr + exports: o.LiteralArrayExpr, + schemas: o.LiteralArrayExpr }; // Only generate the keys in the metadata if the arrays have values. @@ -91,6 +97,10 @@ export function compileNgModule(meta: R3NgModuleMetadata): R3NgModuleDef { definitionMap.exports = o.literalArr(exports.map(ref => ref.value)); } + if (schemas && schemas.length) { + definitionMap.schemas = o.literalArr(schemas.map(ref => ref.value)); + } + const expression = o.importExpr(R3.defineNgModule).callFn([mapToMapExpression(definitionMap)]); const type = new o.ExpressionType(o.importExpr(R3.NgModuleDefWithMeta, [ new o.ExpressionType(moduleType), tupleTypeOf(declarations), tupleTypeOf(imports), diff --git a/packages/core/src/compiler/compiler_facade_interface.ts b/packages/core/src/compiler/compiler_facade_interface.ts index d921bc85ae..a5f1802c2e 100644 --- a/packages/core/src/compiler/compiler_facade_interface.ts +++ b/packages/core/src/compiler/compiler_facade_interface.ts @@ -97,6 +97,7 @@ export interface R3NgModuleMetadataFacade { imports: Function[]; exports: Function[]; emitInline: boolean; + schemas: {name: string}[]|null; } export interface R3InjectorMetadataFacade { @@ -155,4 +156,4 @@ export interface ParseSourceSpan { start: any; end: any; details: any; -} \ No newline at end of file +} diff --git a/packages/core/src/metadata.ts b/packages/core/src/metadata.ts index c1e9281839..ca9b5feea3 100644 --- a/packages/core/src/metadata.ts +++ b/packages/core/src/metadata.ts @@ -14,12 +14,14 @@ import {Attribute} from './di'; import {ContentChild, ContentChildren, Query, ViewChild, ViewChildren} from './metadata/di'; import {Component, Directive, HostBinding, HostListener, Input, Output, Pipe} from './metadata/directives'; -import {DoBootstrap, ModuleWithProviders, NgModule, SchemaMetadata} from './metadata/ng_module'; +import {DoBootstrap, ModuleWithProviders, NgModule} from './metadata/ng_module'; +import {SchemaMetadata} from './metadata/schema'; import {ViewEncapsulation} from './metadata/view'; export {Attribute} from './di'; export {AfterContentChecked, AfterContentInit, AfterViewChecked, AfterViewInit, DoCheck, OnChanges, OnDestroy, OnInit} from './interface/lifecycle_hooks'; export {ANALYZE_FOR_ENTRY_COMPONENTS, ContentChild, ContentChildDecorator, ContentChildren, ContentChildrenDecorator, Query, ViewChild, ViewChildDecorator, ViewChildren, ViewChildrenDecorator} from './metadata/di'; export {Component, ComponentDecorator, Directive, DirectiveDecorator, HostBinding, HostListener, Input, Output, Pipe} from './metadata/directives'; -export {CUSTOM_ELEMENTS_SCHEMA, DoBootstrap, ModuleWithProviders, NO_ERRORS_SCHEMA, NgModule, SchemaMetadata} from './metadata/ng_module'; +export {DoBootstrap, ModuleWithProviders, NgModule} from './metadata/ng_module'; +export {CUSTOM_ELEMENTS_SCHEMA, NO_ERRORS_SCHEMA, SchemaMetadata} from './metadata/schema'; export {ViewEncapsulation} from './metadata/view'; diff --git a/packages/core/src/metadata/ng_module.ts b/packages/core/src/metadata/ng_module.ts index a171c2a720..b2c3a3ee82 100644 --- a/packages/core/src/metadata/ng_module.ts +++ b/packages/core/src/metadata/ng_module.ts @@ -11,6 +11,7 @@ import {InjectorType, defineInjector} from '../di/interface/defs'; import {Provider} from '../di/interface/provider'; import {convertInjectableProviderToFactory} from '../di/util'; import {Type} from '../interface/type'; +import {SchemaMetadata} from '../metadata/schema'; import {NgModuleType} from '../render3'; import {compileNgModule as render3CompileNgModule} from '../render3/jit/module'; import {TypeDecorator, makeDecorator} from '../util/decorators'; @@ -28,6 +29,7 @@ import {TypeDecorator, makeDecorator} from '../util/decorators'; export interface NgModuleTransitiveScopes { compilation: {directives: Set; pipes: Set;}; exported: {directives: Set; pipes: Set;}; + schemas: SchemaMetadata[]|null; } export type NgModuleDefWithMeta = NgModuleDef; @@ -67,6 +69,9 @@ export interface NgModuleDef { * This should never be read directly, but accessed via `transitiveScopesFor`. */ transitiveCompileScopes: NgModuleTransitiveScopes|null; + + /** The set of schemas that declare elements to be allowed in the NgModule. */ + schemas: SchemaMetadata[]|null; } /** @@ -83,38 +88,6 @@ export interface ModuleWithProviders< providers?: Provider[]; } -/** - * A schema definition associated with an NgModule. - * - * @see `@NgModule`, `CUSTOM_ELEMENTS_SCHEMA`, `NO_ERRORS_SCHEMA` - * - * @param name The name of a defined schema. - * - * @publicApi - */ -export interface SchemaMetadata { name: string; } - -/** - * Defines a schema that allows an NgModule to contain the following: - * - Non-Angular elements named with dash case (`-`). - * - Element properties named with dash case (`-`). - * Dash case is the naming convention for custom elements. - * - * @publicApi - */ -export const CUSTOM_ELEMENTS_SCHEMA: SchemaMetadata = { - name: 'custom-elements' -}; - -/** - * Defines a schema that allows any property on any element. - * - * @publicApi - */ -export const NO_ERRORS_SCHEMA: SchemaMetadata = { - name: 'no-errors-schema' -}; - /** * Type of the NgModule decorator / constructor function. diff --git a/packages/core/src/metadata/schema.ts b/packages/core/src/metadata/schema.ts new file mode 100644 index 0000000000..54c3d56dbe --- /dev/null +++ b/packages/core/src/metadata/schema.ts @@ -0,0 +1,40 @@ +/** + * @license + * Copyright Google Inc. All Rights Reserved. + * + * Use of this source code is governed by an MIT-style license that can be + * found in the LICENSE file at https://angular.io/license + */ + + +/** + * A schema definition associated with an NgModule. + * + * @see `@NgModule`, `CUSTOM_ELEMENTS_SCHEMA`, `NO_ERRORS_SCHEMA` + * + * @param name The name of a defined schema. + * + * @publicApi + */ +export interface SchemaMetadata { name: string; } + +/** + * Defines a schema that allows an NgModule to contain the following: + * - Non-Angular elements named with dash case (`-`). + * - Element properties named with dash case (`-`). + * Dash case is the naming convention for custom elements. + * + * @publicApi + */ +export const CUSTOM_ELEMENTS_SCHEMA: SchemaMetadata = { + name: 'custom-elements' +}; + +/** + * Defines a schema that allows any property on any element. + * + * @publicApi + */ +export const NO_ERRORS_SCHEMA: SchemaMetadata = { + name: 'no-errors-schema' +}; diff --git a/packages/core/src/render3/component.ts b/packages/core/src/render3/component.ts index 0078a9a772..12f99a30c0 100644 --- a/packages/core/src/render3/component.ts +++ b/packages/core/src/render3/component.ts @@ -122,7 +122,7 @@ export function renderComponent( const renderer = rendererFactory.createRenderer(hostRNode, componentDef); const rootView: LView = createLView( - null, createTView(-1, null, 1, 0, null, null, null), rootContext, rootFlags, null, null, + null, createTView(-1, null, 1, 0, null, null, null, null), rootContext, rootFlags, null, null, rendererFactory, renderer, undefined, opts.injector || null); const oldView = enterView(rootView, null); @@ -165,9 +165,9 @@ export function createRootComponentView( const tView = rootView[TVIEW]; const tNode: TElementNode = createNodeAtIndex(0, TNodeType.Element, rNode, null, null); const componentView = createLView( - rootView, - getOrCreateTView( - def.template, def.consts, def.vars, def.directiveDefs, def.pipeDefs, def.viewQuery), + rootView, getOrCreateTView( + def.template, def.consts, def.vars, def.directiveDefs, def.pipeDefs, + def.viewQuery, def.schemas), null, def.onPush ? LViewFlags.Dirty : LViewFlags.CheckAlways, rootView[HEADER_OFFSET], tNode, rendererFactory, renderer, sanitizer); diff --git a/packages/core/src/render3/component_ref.ts b/packages/core/src/render3/component_ref.ts index 794508126a..15d856938b 100644 --- a/packages/core/src/render3/component_ref.ts +++ b/packages/core/src/render3/component_ref.ts @@ -162,8 +162,8 @@ export class ComponentFactory extends viewEngine_ComponentFactory { // Create the root view. Uses empty TView and ContentTemplate. const rootLView = createLView( - null, createTView(-1, null, 1, 0, null, null, null), rootContext, rootFlags, null, null, - rendererFactory, renderer, sanitizer, rootViewInjector); + null, createTView(-1, null, 1, 0, null, null, null, null), rootContext, rootFlags, null, + null, rendererFactory, renderer, sanitizer, rootViewInjector); // rootView is the parent when bootstrapping const oldLView = enterView(rootLView, null); diff --git a/packages/core/src/render3/definition.ts b/packages/core/src/render3/definition.ts index 8a035b0c97..87d644ab6c 100644 --- a/packages/core/src/render3/definition.ts +++ b/packages/core/src/render3/definition.ts @@ -11,6 +11,7 @@ import '../util/ng_dev_mode'; import {ChangeDetectionStrategy} from '../change_detection/constants'; import {Mutable, Type} from '../interface/type'; import {NgModuleDef} from '../metadata/ng_module'; +import {SchemaMetadata} from '../metadata/schema'; import {ViewEncapsulation} from '../metadata/view'; import {noSideEffects} from '../util/closure'; import {stringify} from '../util/stringify'; @@ -234,6 +235,11 @@ export function defineComponent(componentDefinition: { * `PipeDefs`s. The function is necessary to be able to support forward declarations. */ pipes?: PipeTypesOrFactory | null; + + /** + * The set of schemas that declare elements to be allowed in the component's template. + */ + schemas?: SchemaMetadata[] | null; }): never { const type = componentDefinition.type; const typePrototype = type.prototype; @@ -274,6 +280,7 @@ export function defineComponent(componentDefinition: { styles: componentDefinition.styles || EMPTY_ARRAY, _: null as never, setInput: null, + schemas: componentDefinition.schemas || null, }; def._ = noSideEffects(() => { const directiveTypes = componentDefinition.directives !; @@ -326,6 +333,7 @@ export function defineNgModule(def: {type: T} & Partial>): nev imports: def.imports || EMPTY_ARRAY, exports: def.exports || EMPTY_ARRAY, transitiveCompileScopes: null, + schemas: def.schemas || null, }; return res as never; } diff --git a/packages/core/src/render3/instructions.ts b/packages/core/src/render3/instructions.ts index 268aed3dc8..2cede59d00 100644 --- a/packages/core/src/render3/instructions.ts +++ b/packages/core/src/render3/instructions.ts @@ -10,6 +10,7 @@ import {InjectFlags, InjectionToken, Injector} from '../di'; import {resolveForwardRef} from '../di/forward_ref'; import {ErrorHandler} from '../error_handler'; import {Type} from '../interface/type'; +import {CUSTOM_ELEMENTS_SCHEMA, NO_ERRORS_SCHEMA, SchemaMetadata} from '../metadata/schema'; import {validateAgainstEventAttributes, validateAgainstEventProperties} from '../sanitization/sanitization'; import {Sanitizer} from '../sanitization/security'; import {StyleSanitizeFn} from '../sanitization/style_sanitizer'; @@ -320,12 +321,12 @@ export function renderTemplate( // We need to create a root view so it's possible to look up the host element through its index const hostLView = createLView( - null, createTView(-1, null, 1, 0, null, null, null), {}, + null, createTView(-1, null, 1, 0, null, null, null, null), {}, LViewFlags.CheckAlways | LViewFlags.IsRoot, null, null, providedRendererFactory, renderer); enterView(hostLView, null); // SUSPECT! why do we need to enter the View? const componentTView = - getOrCreateTView(templateFn, consts, vars, directives || null, pipes || null, null); + getOrCreateTView(templateFn, consts, vars, directives || null, pipes || null, null, null); const hostTNode = createNodeAtIndex(0, TNodeType.Element, hostNode, null, null); componentView = createLView( hostLView, componentTView, context, LViewFlags.CheckAlways, hostNode, hostTNode, @@ -728,12 +729,14 @@ function saveResolvedLocalsInData( * @param vars The number of bindings and pure function bindings in this view * @param directives Directive defs that should be saved on TView * @param pipes Pipe defs that should be saved on TView + * @param viewQuery View query that should be saved on TView + * @param schemas Schemas that should be saved on TView * @returns TView */ export function getOrCreateTView( templateFn: ComponentTemplate, consts: number, vars: number, directives: DirectiveDefListOrFactory | null, pipes: PipeDefListOrFactory | null, - viewQuery: ViewQueriesFunction| null): TView { + viewQuery: ViewQueriesFunction| null, schemas: SchemaMetadata[] | null): TView { // TODO(misko): reading `ngPrivateData` here is problematic for two reasons // 1. It is a megamorphic call on each invocation. // 2. For nested embedded views (ngFor inside ngFor) the template instance is per @@ -742,8 +745,8 @@ export function getOrCreateTView( // and not on embedded templates. return templateFn.ngPrivateData || - (templateFn.ngPrivateData = - createTView(-1, templateFn, consts, vars, directives, pipes, viewQuery) as never); + (templateFn.ngPrivateData = createTView( + -1, templateFn, consts, vars, directives, pipes, viewQuery, schemas) as never); } /** @@ -754,11 +757,13 @@ export function getOrCreateTView( * @param consts The number of nodes, local refs, and pipes in this template * @param directives Registry of directives for this view * @param pipes Registry of pipes for this view + * @param viewQuery View queries for this view + * @param schemas Schemas for this view */ export function createTView( viewIndex: number, templateFn: ComponentTemplate| null, consts: number, vars: number, directives: DirectiveDefListOrFactory | null, pipes: PipeDefListOrFactory | null, - viewQuery: ViewQueriesFunction| null): TView { + viewQuery: ViewQueriesFunction| null, schemas: SchemaMetadata[] | null): TView { ngDevMode && ngDevMode.tView++; const bindingStartIndex = HEADER_OFFSET + consts; // This length does not yet contain host bindings from child directives because at this point, @@ -792,6 +797,7 @@ export function createTView( directiveRegistry: typeof directives === 'function' ? directives() : directives, pipeRegistry: typeof pipes === 'function' ? pipes() : pipes, firstChild: null, + schemas: schemas, }; } @@ -1218,7 +1224,7 @@ function elementPropertyInternal( } else if (tNode.type === TNodeType.Element) { if (ngDevMode) { validateAgainstEventProperties(propName); - validateAgainstUnknownProperties(element, propName, tNode); + validateAgainstUnknownProperties(lView, element, propName, tNode); ngDevMode.rendererSetProperty++; } @@ -1238,7 +1244,12 @@ function elementPropertyInternal( } function validateAgainstUnknownProperties( - element: RElement | RComment, propName: string, tNode: TNode) { + hostView: LView, element: RElement | RComment, propName: string, tNode: TNode) { + // If the tag matches any of the schemas we shouldn't throw. + if (matchingSchemas(hostView, tNode.tagName)) { + return; + } + // If prop is not a known property of the HTML element... if (!(propName in element) && // and we are in a browser context... (web worker nodes should be skipped) @@ -1251,6 +1262,22 @@ function validateAgainstUnknownProperties( } } +function matchingSchemas(hostView: LView, tagName: string | null): boolean { + const schemas = hostView[TVIEW].schemas; + + if (schemas !== null) { + for (let i = 0; i < schemas.length; i++) { + const schema = schemas[i]; + if (schema === NO_ERRORS_SCHEMA || + schema === CUSTOM_ELEMENTS_SCHEMA && tagName && tagName.indexOf('-') > -1) { + return true; + } + } + } + + return false; +} + /** * Stores debugging data for this property binding on first template pass. * This enables features like DebugElement.properties. @@ -2040,7 +2067,8 @@ function addComponentLogic( const native = getNativeByTNode(previousOrParentTNode, lView); const tView = getOrCreateTView( - def.template, def.consts, def.vars, def.directiveDefs, def.pipeDefs, def.viewQuery); + def.template, def.consts, def.vars, def.directiveDefs, def.pipeDefs, def.viewQuery, + def.schemas); // Only component views should be added to the view tree directly. Embedded views are // accessed through their containers because they may be removed / re-added later. @@ -2197,7 +2225,7 @@ export function template( const tContainerNode = containerInternal(index, tagName || null, attrs || null); if (tView.firstTemplatePass) { tContainerNode.tViews = createTView( - -1, templateFn, consts, vars, tView.directiveRegistry, tView.pipeRegistry, null); + -1, templateFn, consts, vars, tView.directiveRegistry, tView.pipeRegistry, null, null); } createDirectivesAndLocals(tView, lView, localRefs, localRefExtractor); @@ -2435,7 +2463,7 @@ function getOrCreateEmbeddedTView( ngDevMode && assertEqual(Array.isArray(containerTViews), true, 'TViews should be in an array'); if (viewIndex >= containerTViews.length || containerTViews[viewIndex] == null) { containerTViews[viewIndex] = createTView( - viewIndex, null, consts, vars, tView.directiveRegistry, tView.pipeRegistry, null); + viewIndex, null, consts, vars, tView.directiveRegistry, tView.pipeRegistry, null, null); } return containerTViews[viewIndex]; } diff --git a/packages/core/src/render3/interfaces/definition.ts b/packages/core/src/render3/interfaces/definition.ts index 1228249cef..f60da2940b 100644 --- a/packages/core/src/render3/interfaces/definition.ts +++ b/packages/core/src/render3/interfaces/definition.ts @@ -6,7 +6,7 @@ * found in the LICENSE file at https://angular.io/license */ -import {ViewEncapsulation} from '../../core'; +import {SchemaMetadata, ViewEncapsulation} from '../../core'; import {Type} from '../../interface/type'; import {CssSelectorList} from './projection'; @@ -264,7 +264,6 @@ export interface ComponentDef extends DirectiveDef { readonly onPush: boolean; /** - * Registry of directives and components that may be found in this view. * * The property is either an array of `DirectiveDef`s or a function which returns the array of @@ -280,6 +279,11 @@ export interface ComponentDef extends DirectiveDef { */ pipeDefs: PipeDefListOrFactory|null; + /** + * The set of schemas that declare elements to be allowed in the component's template. + */ + schemas: SchemaMetadata[]|null; + /** * Used to store the result of `noSideEffects` function so that it is not removed by closure * compiler. The property should never be read. diff --git a/packages/core/src/render3/interfaces/view.ts b/packages/core/src/render3/interfaces/view.ts index 7516848d1a..500984ab4d 100644 --- a/packages/core/src/render3/interfaces/view.ts +++ b/packages/core/src/render3/interfaces/view.ts @@ -10,6 +10,7 @@ import {InjectionToken} from '../../di/injection_token'; import {Injector} from '../../di/injector'; import {Type} from '../../interface/type'; import {QueryList} from '../../linker'; +import {SchemaMetadata} from '../../metadata'; import {Sanitizer} from '../../sanitization/security'; import {LContainer} from './container'; @@ -535,6 +536,11 @@ export interface TView { * A list of indices for child directives that have content queries. */ contentQueries: number[]|null; + + /** + * Set of schemas that declare elements to be allowed inside the view. + */ + schemas: SchemaMetadata[]|null; } export const enum RootContextFlags {Empty = 0b00, DetectChanges = 0b01, FlushPlayers = 0b10} diff --git a/packages/core/src/render3/jit/module.ts b/packages/core/src/render3/jit/module.ts index f706d178c8..0ef0cc7298 100644 --- a/packages/core/src/render3/jit/module.ts +++ b/packages/core/src/render3/jit/module.ts @@ -115,6 +115,7 @@ export function compileNgModuleDefs(moduleType: NgModuleType, ngModule: NgModule exports: flatten(ngModule.exports || EMPTY_ARRAY, resolveForwardRef) .map(expandModuleWithProviders), emitInline: true, + schemas: ngModule.schemas ? flatten(ngModule.schemas) : null, }); } return ngModuleDef; @@ -353,6 +354,7 @@ export function patchComponentDefWithScope( .filter(def => !!def); componentDef.pipeDefs = () => Array.from(transitiveScopes.compilation.pipes).map(pipe => getPipeDef(pipe) !); + componentDef.schemas = transitiveScopes.schemas; } /** @@ -375,6 +377,7 @@ export function transitiveScopesFor( } const scopes: NgModuleTransitiveScopes = { + schemas: def.schemas || null, compilation: { directives: new Set(), pipes: new Set(), diff --git a/packages/core/test/linker/ng_module_integration_spec.ts b/packages/core/test/linker/ng_module_integration_spec.ts index ad9a3e7e2a..669d48da96 100644 --- a/packages/core/test/linker/ng_module_integration_spec.ts +++ b/packages/core/test/linker/ng_module_integration_spec.ts @@ -6,7 +6,7 @@ * found in the LICENSE file at https://angular.io/license */ -import {ANALYZE_FOR_ENTRY_COMPONENTS, CUSTOM_ELEMENTS_SCHEMA, Compiler, Component, ComponentFactoryResolver, Directive, HostBinding, Inject, Injectable, InjectionToken, Injector, Input, NgModule, NgModuleRef, Optional, Pipe, Provider, Self, Type, forwardRef, getModuleFactory, ɵivyEnabled as ivyEnabled} from '@angular/core'; +import {ANALYZE_FOR_ENTRY_COMPONENTS, CUSTOM_ELEMENTS_SCHEMA, ChangeDetectorRef, Compiler, Component, ComponentFactoryResolver, Directive, HostBinding, Inject, Injectable, InjectionToken, Injector, Input, NgModule, NgModuleRef, Optional, Pipe, Provider, Self, Type, forwardRef, getModuleFactory, ɵivyEnabled as ivyEnabled} from '@angular/core'; import {Console} from '@angular/core/src/console'; import {InjectableDef, defineInjectable} from '@angular/core/src/di/interface/defs'; import {getNgModuleDef} from '@angular/core/src/render3/definition'; @@ -266,22 +266,29 @@ function declareTests(config?: {useJit: boolean}) { expect(() => fixture.detectChanges()).toThrowError(/Can't bind to 'someUnknownProp'/); }); - fixmeIvy('FW-819: ngtsc compiler should support schemas') - .it('should not error on unknown bound properties on custom elements when using the CUSTOM_ELEMENTS_SCHEMA', - () => { - @Component({template: ''}) - class ComponentUsingInvalidProperty { - } + it('should not error on unknown bound properties on custom elements when using the CUSTOM_ELEMENTS_SCHEMA', + () => { + @Component({template: ''}) + class ComponentUsingInvalidProperty { + } - @NgModule({ - schemas: [CUSTOM_ELEMENTS_SCHEMA], - declarations: [ComponentUsingInvalidProperty] - }) - class SomeModule { - } + @NgModule({ + schemas: [CUSTOM_ELEMENTS_SCHEMA], + declarations: [ComponentUsingInvalidProperty], - expect(() => createModule(SomeModule)).not.toThrow(); - }); + // Note that we need to add the component to `entryComponents`, because of the + // `createComp` call below. In Ivy the property validation happens during the + // update phase so we need to create the component, in order for it to run. + entryComponents: [ComponentUsingInvalidProperty] + }) + class SomeModule { + } + + expect(() => { + const fixture = createComp(ComponentUsingInvalidProperty, SomeModule); + fixture.detectChanges(); + }).not.toThrow(); + }); }); describe('id', () => { diff --git a/packages/core/test/render3/di_spec.ts b/packages/core/test/render3/di_spec.ts index 2ff5a1600c..0aa0c0eded 100644 --- a/packages/core/test/render3/di_spec.ts +++ b/packages/core/test/render3/di_spec.ts @@ -2320,8 +2320,8 @@ describe('di', () => { describe('getOrCreateNodeInjector', () => { it('should handle initial undefined state', () => { const contentView = createLView( - null, createTView(-1, null, 1, 0, null, null, null), null, LViewFlags.CheckAlways, null, - null, {} as any, {} as any); + null, createTView(-1, null, 1, 0, null, null, null, null), null, LViewFlags.CheckAlways, + null, null, {} as any, {} as any); const oldView = enterView(contentView, null); try { const parentTNode = createNodeAtIndex(0, TNodeType.Element, null, null, null); diff --git a/packages/core/test/render3/styling/class_and_style_bindings_spec.ts b/packages/core/test/render3/styling/class_and_style_bindings_spec.ts index f4c4a9e892..d109d8ec73 100644 --- a/packages/core/test/render3/styling/class_and_style_bindings_spec.ts +++ b/packages/core/test/render3/styling/class_and_style_bindings_spec.ts @@ -34,8 +34,8 @@ describe('style and class based bindings', () => { const rootContext = createRootContext(requestAnimationFrame.bind(window), playerHandler || null); const lView = createLView( - null, createTView(-1, null, 1, 0, null, null, null), rootContext, LViewFlags.IsRoot, null, - null, domRendererFactory3, domRendererFactory3.createRenderer(element, null)); + null, createTView(-1, null, 1, 0, null, null, null, null), rootContext, LViewFlags.IsRoot, + null, null, domRendererFactory3, domRendererFactory3.createRenderer(element, null)); return lView; }