feat(ivy): use the schema registry to check DOM bindings (#32171)
Previously, ngtsc attempted to use the .d.ts schema for HTML elements to check bindings to DOM properties. However, the TypeScript lib.dom.d.ts schema does not perfectly align with the Angular DomElementSchemaRegistry, and these inconsistencies would cause issues in apps. There is also the concern of supporting both CUSTOM_ELEMENTS_SCHEMA and NO_ERRORS_SCHEMA which would have been very difficult to do in the existing system. With this commit, the DomElementSchemaRegistry is employed in ngtsc to check bindings to the DOM. Previous work on producing template diagnostics is used to support generation of this different kind of error with the same high quality of error message. PR Close #32171
This commit is contained in:
@ -6,7 +6,7 @@
|
||||
* found in the LICENSE file at https://angular.io/license
|
||||
*/
|
||||
|
||||
import {ConstantPool, CssSelector, DEFAULT_INTERPOLATION_CONFIG, DomElementSchemaRegistry, Expression, ExternalExpr, InterpolationConfig, LexerRange, ParseError, ParseSourceFile, ParseTemplateOptions, R3ComponentMetadata, R3TargetBinder, SelectorMatcher, Statement, TmplAstNode, WrappedNodeExpr, compileComponentFromMetadata, makeBindingParser, parseTemplate} from '@angular/compiler';
|
||||
import {ConstantPool, CssSelector, DEFAULT_INTERPOLATION_CONFIG, DomElementSchemaRegistry, Expression, ExternalExpr, InterpolationConfig, LexerRange, ParseError, ParseSourceFile, ParseTemplateOptions, R3ComponentMetadata, R3TargetBinder, SchemaMetadata, SelectorMatcher, Statement, TmplAstNode, WrappedNodeExpr, compileComponentFromMetadata, makeBindingParser, parseTemplate} from '@angular/compiler';
|
||||
import * as ts from 'typescript';
|
||||
|
||||
import {CycleAnalyzer} from '../../cycles';
|
||||
@ -396,6 +396,7 @@ export class ComponentDecoratorHandler implements
|
||||
|
||||
const matcher = new SelectorMatcher<DirectiveMeta>();
|
||||
const pipes = new Map<string, Reference<ClassDeclaration<ts.ClassDeclaration>>>();
|
||||
let schemas: SchemaMetadata[] = [];
|
||||
|
||||
const scope = this.scopeReader.getScopeForComponent(node);
|
||||
if (scope !== null) {
|
||||
@ -410,10 +411,12 @@ export class ComponentDecoratorHandler implements
|
||||
}
|
||||
pipes.set(name, ref as Reference<ClassDeclaration<ts.ClassDeclaration>>);
|
||||
}
|
||||
schemas = scope.schemas;
|
||||
}
|
||||
|
||||
const bound = new R3TargetBinder(matcher).bind({template: template.nodes});
|
||||
ctx.addTemplate(new Reference(node), bound, pipes, meta.templateSourceMapping, template.file);
|
||||
ctx.addTemplate(
|
||||
new Reference(node), bound, pipes, schemas, meta.templateSourceMapping, template.file);
|
||||
}
|
||||
|
||||
resolve(node: ClassDeclaration, analysis: ComponentHandlerData): ResolveResult {
|
||||
|
@ -6,7 +6,7 @@
|
||||
* found in the LICENSE file at https://angular.io/license
|
||||
*/
|
||||
|
||||
import {Expression, ExternalExpr, InvokeFunctionExpr, LiteralArrayExpr, LiteralExpr, R3Identifiers, R3InjectorMetadata, R3NgModuleMetadata, R3Reference, STRING_TYPE, Statement, WrappedNodeExpr, compileInjector, compileNgModule} from '@angular/compiler';
|
||||
import {CUSTOM_ELEMENTS_SCHEMA, Expression, ExternalExpr, InvokeFunctionExpr, LiteralArrayExpr, LiteralExpr, NO_ERRORS_SCHEMA, R3Identifiers, R3InjectorMetadata, R3NgModuleMetadata, R3Reference, STRING_TYPE, SchemaMetadata, Statement, WrappedNodeExpr, compileInjector, compileNgModule} from '@angular/compiler';
|
||||
import * as ts from 'typescript';
|
||||
|
||||
import {ErrorCode, FatalDiagnosticError} from '../../diagnostics';
|
||||
@ -122,6 +122,45 @@ export class NgModuleDecoratorHandler implements DecoratorHandler<NgModuleAnalys
|
||||
bootstrapRefs = this.resolveTypeList(expr, bootstrapMeta, name, 'bootstrap');
|
||||
}
|
||||
|
||||
const schemas: SchemaMetadata[] = [];
|
||||
if (ngModule.has('schemas')) {
|
||||
const rawExpr = ngModule.get('schemas') !;
|
||||
const result = this.evaluator.evaluate(rawExpr);
|
||||
if (!Array.isArray(result)) {
|
||||
throw new FatalDiagnosticError(
|
||||
ErrorCode.VALUE_HAS_WRONG_TYPE, rawExpr, `NgModule.schemas must be an array`);
|
||||
}
|
||||
|
||||
for (const schemaRef of result) {
|
||||
if (!(schemaRef instanceof Reference)) {
|
||||
throw new FatalDiagnosticError(
|
||||
ErrorCode.VALUE_HAS_WRONG_TYPE, rawExpr,
|
||||
'NgModule.schemas must be an array of schemas');
|
||||
}
|
||||
const id = schemaRef.getIdentityIn(schemaRef.node.getSourceFile());
|
||||
if (id === null || schemaRef.ownedByModuleGuess !== '@angular/core') {
|
||||
throw new FatalDiagnosticError(
|
||||
ErrorCode.VALUE_HAS_WRONG_TYPE, rawExpr,
|
||||
'NgModule.schemas must be an array of schemas');
|
||||
}
|
||||
// Since `id` is the `ts.Identifer` within the schema ref's declaration file, it's safe to
|
||||
// use `id.text` here to figure out which schema is in use. Even if the actual reference was
|
||||
// renamed when the user imported it, these names will match.
|
||||
switch (id.text) {
|
||||
case 'CUSTOM_ELEMENTS_SCHEMA':
|
||||
schemas.push(CUSTOM_ELEMENTS_SCHEMA);
|
||||
break;
|
||||
case 'NO_ERRORS_SCHEMA':
|
||||
schemas.push(NO_ERRORS_SCHEMA);
|
||||
break;
|
||||
default:
|
||||
throw new FatalDiagnosticError(
|
||||
ErrorCode.VALUE_HAS_WRONG_TYPE, rawExpr,
|
||||
`'${schemaRef.debugName}' is not a valid NgModule schema`);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const id: Expression|null =
|
||||
ngModule.has('id') ? new WrappedNodeExpr(ngModule.get('id') !) : null;
|
||||
|
||||
@ -130,9 +169,10 @@ export class NgModuleDecoratorHandler implements DecoratorHandler<NgModuleAnalys
|
||||
// computation.
|
||||
this.metaRegistry.registerNgModuleMetadata({
|
||||
ref: new Reference(node),
|
||||
schemas,
|
||||
declarations: declarationRefs,
|
||||
imports: importRefs,
|
||||
exports: exportRefs
|
||||
exports: exportRefs,
|
||||
});
|
||||
|
||||
const valueContext = node.getSourceFile();
|
||||
|
Reference in New Issue
Block a user