feat(ivy): support for template type-checking pipe bindings (#29698)

This commit adds support for template type-checking a pipe binding which
previously was not handled by the type-checking engine. In compatibility
mode, the arguments to transform() are not checked and the type returned
by a pipe is 'any'. In full type-checking mode, the transform() method's
type signature is used to check the pipe usage and infer the return type
of the pipe.

Testing strategy: TCB tests included.

PR Close #29698
This commit is contained in:
Alex Rickabaugh
2019-04-02 10:27:33 -07:00
committed by Ben Lesh
parent 98f86de8da
commit 5268ae61a0
11 changed files with 206 additions and 25 deletions

View File

@ -64,6 +64,7 @@ export interface SimpleChanges { [propName: string]: any; }
export type ɵɵNgModuleDefWithMeta<ModuleT, DeclarationsT, ImportsT, ExportsT> = any;
export type ɵɵDirectiveDefWithMeta<DirT, SelectorT, ExportAsT, InputsT, OutputsT, QueriesT> = any;
export type ɵɵPipeDefWithMeta<PipeT, NameT> = any;
export enum ViewEncapsulation {
Emulated = 0,

View File

@ -6,6 +6,8 @@
* found in the LICENSE file at https://angular.io/license
*/
import * as ts from 'typescript';
import {NgtscTestEnvironment} from './env';
function setupCommon(env: NgtscTestEnvironment): void {
@ -23,6 +25,12 @@ export declare class NgForOfContext<T> {
readonly odd: boolean;
}
export declare class IndexPipe {
transform<T>(value: T[], index: number): T;
static ngPipeDef: i0.ɵPipeDefWithMeta<IndexPipe, 'index'>;
}
export declare class NgForOf<T> {
ngForOf: T[];
static ngTemplateContextGuard<T>(dir: NgForOf<T>, ctx: any): ctx is NgForOfContext<T>;
@ -36,7 +44,7 @@ export declare class NgIf {
}
export declare class CommonModule {
static ngModuleDef: i0.ɵɵNgModuleDefWithMeta<CommonModule, [typeof NgIf, typeof NgForOf], never, [typeof NgIf, typeof NgForOf]>;
static ngModuleDef: i0.ɵɵNgModuleDefWithMeta<CommonModule, [typeof NgIf, typeof NgForOf, typeof IndexPipe], never, [typeof NgIf, typeof NgForOf, typeof IndexPipe]>;
}
`);
}
@ -140,6 +148,57 @@ describe('ngtsc type checking', () => {
expect(diags[0].messageText).toContain('does_not_exist');
});
it('should report an error with pipe bindings', () => {
env.write('test.ts', `
import {CommonModule} from '@angular/common';
import {Component, NgModule} from '@angular/core';
@Component({
selector: 'test',
template: \`
checking the input type to the pipe:
{{user | index: 1}}
checking the return type of the pipe:
{{(users | index: 1).does_not_exist}}
checking the argument type:
{{users | index: 'test'}}
checking the argument count:
{{users | index: 1:2}}
\`
})
class TestCmp {
user: {name: string};
users: {name: string}[];
}
@NgModule({
declarations: [TestCmp],
imports: [CommonModule],
})
class Module {}
`);
const diags = env.driveDiagnostics();
expect(diags.length).toBe(4);
const allErrors = [
`'does_not_exist' does not exist on type '{ name: string; }'`,
`Expected 2 arguments, but got 3.`,
`Argument of type '"test"' is not assignable to parameter of type 'number'`,
`Argument of type '{ name: string; }' is not assignable to parameter of type '{}[]'`,
];
for (const error of allErrors) {
if (!diags.some(
diag => ts.flattenDiagnosticMessageText(diag.messageText, '').indexOf(error) > -1)) {
fail(`Expected a diagnostic message with text: ${error}`);
}
}
});
it('should constrain types using type parameter bounds', () => {
env.write('test.ts', `
import {CommonModule} from '@angular/common';