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:
Alex Rickabaugh
2019-08-16 16:45:33 -07:00
committed by atscott
parent 904a2018e0
commit 0677cf0cbe
22 changed files with 574 additions and 122 deletions

View File

@ -77,3 +77,6 @@ export enum ChangeDetectionStrategy {
OnPush = 0,
Default = 1
}
export const CUSTOM_ELEMENTS_SCHEMA: any = false;
export const NO_ERRORS_SCHEMA: any = false;

View File

@ -346,6 +346,129 @@ export declare class CommonModule {
expect(diags[1].length).toEqual(15);
});
describe('legacy schema checking with the DOM schema', () => {
beforeEach(
() => { env.tsconfig({ivyTemplateTypeCheck: true, fullTemplateTypeCheck: false}); });
it('should check for unknown elements', () => {
env.write('test.ts', `
import {Component, NgModule} from '@angular/core';
@Component({
selector: 'blah',
template: '<foo>test</foo>',
})
export class FooCmp {}
@NgModule({
declarations: [FooCmp],
})
export class FooModule {}
`);
const diags = env.driveDiagnostics();
expect(diags.length).toBe(1);
expect(diags[0].messageText).toBe(`'foo' is not a valid HTML element.`);
});
it('should check for unknown properties', () => {
env.write('test.ts', `
import {Component, NgModule} from '@angular/core';
@Component({
selector: 'blah',
template: '<div [foo]="1">test</div>',
})
export class FooCmp {}
@NgModule({
declarations: [FooCmp],
})
export class FooModule {}
`);
const diags = env.driveDiagnostics();
expect(diags.length).toBe(1);
expect(diags[0].messageText).toBe(`'foo' is not a valid property of <div>.`);
});
it('should convert property names when binding special properties', () => {
env.write('test.ts', `
import {Component, NgModule} from '@angular/core';
@Component({
selector: 'blah',
template: '<label [for]="test">',
})
export class FooCmp {
test: string = 'test';
}
@NgModule({
declarations: [FooCmp],
})
export class FooModule {}
`);
const diags = env.driveDiagnostics();
// Should not be an error to bind [for] of <label>, even though the actual property in the
// DOM schema.
expect(diags.length).toBe(0);
});
it('should produce diagnostics for custom-elements-style elements when not using the CUSTOM_ELEMENTS_SCHEMA',
() => {
env.write('test.ts', `
import {Component, NgModule} from '@angular/core';
@Component({
selector: 'blah',
template: '<custom-element [foo]="1">test</custom-element>',
})
export class FooCmp {}
@NgModule({
declarations: [FooCmp],
})
export class FooModule {}
`);
const diags = env.driveDiagnostics();
expect(diags.length).toBe(2);
expect(diags[0].messageText).toBe(`'custom-element' is not a valid HTML element.`);
expect(diags[1].messageText).toBe(`'foo' is not a valid property of <custom-element>.`);
});
it('should not produce diagnostics for custom-elements-style elements when using the CUSTOM_ELEMENTS_SCHEMA',
() => {
env.write('test.ts', `
import {Component, NgModule, CUSTOM_ELEMENTS_SCHEMA} from '@angular/core';
@Component({
selector: 'blah',
template: '<custom-element [foo]="1">test</custom-element>',
})
export class FooCmp {}
@NgModule({
declarations: [FooCmp],
schemas: [CUSTOM_ELEMENTS_SCHEMA],
})
export class FooModule {}
`);
const diags = env.driveDiagnostics();
expect(diags).toEqual([]);
});
it('should not produce diagnostics when using the NO_ERRORS_SCHEMA', () => {
env.write('test.ts', `
import {Component, NgModule, NO_ERRORS_SCHEMA} from '@angular/core';
@Component({
selector: 'blah',
template: '<foo [bar]="1"></foo>',
})
export class FooCmp {}
@NgModule({
declarations: [FooCmp],
schemas: [NO_ERRORS_SCHEMA],
})
export class FooModule {}
`);
const diags = env.driveDiagnostics();
expect(diags).toEqual([]);
});
});
describe('error locations', () => {
it('should be correct for direct templates', () => {
env.write('test.ts', `