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
This commit is contained in:
parent
7cbc36fdac
commit
80a5934af6
|
@ -134,6 +134,8 @@ export class NgModuleDecoratorHandler implements DecoratorHandler<NgModuleAnalys
|
|||
exports: exports.map(exp => 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') ?
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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, []);
|
||||
|
|
|
@ -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),
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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';
|
||||
|
|
|
@ -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<any>; pipes: Set<any>;};
|
||||
exported: {directives: Set<any>; pipes: Set<any>;};
|
||||
schemas: SchemaMetadata[]|null;
|
||||
}
|
||||
|
||||
export type NgModuleDefWithMeta<T, Declarations, Imports, Exports> = NgModuleDef<T>;
|
||||
|
@ -67,6 +69,9 @@ export interface NgModuleDef<T> {
|
|||
* 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.
|
||||
|
|
|
@ -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'
|
||||
};
|
|
@ -122,7 +122,7 @@ export function renderComponent<T>(
|
|||
|
||||
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);
|
||||
|
||||
|
|
|
@ -162,8 +162,8 @@ export class ComponentFactory<T> extends viewEngine_ComponentFactory<T> {
|
|||
|
||||
// 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);
|
||||
|
|
|
@ -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<T>(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<T>(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<T>(def: {type: T} & Partial<NgModuleDef<T>>): nev
|
|||
imports: def.imports || EMPTY_ARRAY,
|
||||
exports: def.exports || EMPTY_ARRAY,
|
||||
transitiveCompileScopes: null,
|
||||
schemas: def.schemas || null,
|
||||
};
|
||||
return res as never;
|
||||
}
|
||||
|
|
|
@ -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<T>(
|
|||
|
||||
// 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<any>, consts: number, vars: number,
|
||||
directives: DirectiveDefListOrFactory | null, pipes: PipeDefListOrFactory | null,
|
||||
viewQuery: ViewQueriesFunction<any>| null): TView {
|
||||
viewQuery: ViewQueriesFunction<any>| 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<any>| null, consts: number, vars: number,
|
||||
directives: DirectiveDefListOrFactory | null, pipes: PipeDefListOrFactory | null,
|
||||
viewQuery: ViewQueriesFunction<any>| null): TView {
|
||||
viewQuery: ViewQueriesFunction<any>| 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<T>(
|
|||
} 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<T>(
|
|||
}
|
||||
|
||||
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<T>(
|
|||
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];
|
||||
}
|
||||
|
|
|
@ -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<T> extends DirectiveDef<T> {
|
|||
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<T> extends DirectiveDef<T> {
|
|||
*/
|
||||
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.
|
||||
|
|
|
@ -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}
|
||||
|
|
|
@ -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<C>(
|
|||
.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<T>(
|
|||
}
|
||||
|
||||
const scopes: NgModuleTransitiveScopes = {
|
||||
schemas: def.schemas || null,
|
||||
compilation: {
|
||||
directives: new Set<any>(),
|
||||
pipes: new Set<any>(),
|
||||
|
|
|
@ -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: '<some-element [someUnknownProp]="true"></some-element>'})
|
||||
class ComponentUsingInvalidProperty {
|
||||
}
|
||||
it('should not error on unknown bound properties on custom elements when using the CUSTOM_ELEMENTS_SCHEMA',
|
||||
() => {
|
||||
@Component({template: '<some-element [someUnknownProp]="true"></some-element>'})
|
||||
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', () => {
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
||||
|
|
Loading…
Reference in New Issue