revert: feat(ivy): input type coercion for template type-checking (#33243) (#33299)

This reverts commit 1b4eaea6d4fa00a722eaed011c904e5cca048501.

PR Close #33299
This commit is contained in:
Matias Niemelä 2019-10-21 08:57:55 -07:00
parent 160547dad6
commit c0ebecf54d
8 changed files with 5 additions and 142 deletions

View File

@ -34,7 +34,6 @@ export interface DirectiveMeta extends T2DirectiveMeta {
queries: string[]; queries: string[];
ngTemplateGuards: TemplateGuardMeta[]; ngTemplateGuards: TemplateGuardMeta[];
hasNgTemplateContextGuard: boolean; hasNgTemplateContextGuard: boolean;
coercedInputs: Set<string>;
/** /**
* A `Reference` to the base class for the directive, if one was detected. * A `Reference` to the base class for the directive, if one was detected.

View File

@ -82,25 +82,20 @@ export function readStringArrayType(type: ts.TypeNode): string[] {
export function extractDirectiveGuards(node: ClassDeclaration, reflector: ReflectionHost): { export function extractDirectiveGuards(node: ClassDeclaration, reflector: ReflectionHost): {
ngTemplateGuards: TemplateGuardMeta[], ngTemplateGuards: TemplateGuardMeta[],
hasNgTemplateContextGuard: boolean, hasNgTemplateContextGuard: boolean,
coercedInputs: Set<string>,
} { } {
const staticMembers = reflector.getMembersOfClass(node).filter(member => member.isStatic); const staticMembers = reflector.getMembersOfClass(node).filter(member => member.isStatic);
const ngTemplateGuards = staticMembers.map(extractTemplateGuard) const ngTemplateGuards = staticMembers.map(extractTemplateGuard)
.filter((guard): guard is TemplateGuardMeta => guard !== null); .filter((guard): guard is TemplateGuardMeta => guard !== null);
const hasNgTemplateContextGuard = staticMembers.some( const hasNgTemplateContextGuard = staticMembers.some(
member => member.kind === ClassMemberKind.Method && member.name === 'ngTemplateContextGuard'); member => member.kind === ClassMemberKind.Method && member.name === 'ngTemplateContextGuard');
return {hasNgTemplateContextGuard, ngTemplateGuards};
const coercedInputs =
new Set(staticMembers.map(extractCoercedInput)
.filter((inputName): inputName is string => inputName !== null));
return {hasNgTemplateContextGuard, ngTemplateGuards, coercedInputs};
} }
function extractTemplateGuard(member: ClassMember): TemplateGuardMeta|null { function extractTemplateGuard(member: ClassMember): TemplateGuardMeta|null {
if (!member.name.startsWith('ngTemplateGuard_')) { if (!member.name.startsWith('ngTemplateGuard_')) {
return null; return null;
} }
const inputName = afterUnderscore(member.name); const inputName = member.name.split('_', 2)[1];
if (member.kind === ClassMemberKind.Property) { if (member.kind === ClassMemberKind.Property) {
let type: string|null = null; let type: string|null = null;
if (member.type !== null && ts.isLiteralTypeNode(member.type) && if (member.type !== null && ts.isLiteralTypeNode(member.type) &&
@ -120,13 +115,6 @@ function extractTemplateGuard(member: ClassMember): TemplateGuardMeta|null {
} }
} }
function extractCoercedInput(member: ClassMember): string|null {
if (!member.name.startsWith('ngCoerceInput_')) {
return null !;
}
return afterUnderscore(member.name);
}
/** /**
* A `MetadataReader` that reads from an ordered set of child readers until it obtains the requested * A `MetadataReader` that reads from an ordered set of child readers until it obtains the requested
* metadata. * metadata.
@ -170,11 +158,3 @@ export class CompoundMetadataReader implements MetadataReader {
return null; return null;
} }
} }
function afterUnderscore(str: string): string {
const pos = str.indexOf('_');
if (pos === -1) {
throw new Error(`Expected '${str}' to contain '_'`);
}
return str.substr(pos + 1);
}

View File

@ -228,7 +228,6 @@ function fakeDirective(ref: Reference<ClassDeclaration>): DirectiveMeta {
queries: [], queries: [],
hasNgTemplateContextGuard: false, hasNgTemplateContextGuard: false,
ngTemplateGuards: [], ngTemplateGuards: [],
coercedInputs: new Set<string>(),
baseClass: null, baseClass: null,
}; };
} }

View File

@ -21,7 +21,6 @@ export interface TypeCheckableDirectiveMeta extends DirectiveMeta {
ref: Reference<ClassDeclaration>; ref: Reference<ClassDeclaration>;
queries: string[]; queries: string[];
ngTemplateGuards: TemplateGuardMeta[]; ngTemplateGuards: TemplateGuardMeta[];
coercedInputs: Set<string>;
hasNgTemplateContextGuard: boolean; hasNgTemplateContextGuard: boolean;
} }

View File

@ -1144,16 +1144,6 @@ function tcbGetDirectiveInputs(
expr = ts.createStringLiteral(attr.value); expr = ts.createStringLiteral(attr.value);
} }
// Wrap the expression if the directive has a coercion function provided.
if (dir.coercedInputs.has(attr.name)) {
const dirId = tcb.env.reference(dir.ref as Reference<ClassDeclaration<ts.ClassDeclaration>>);
const coercionFn = ts.createPropertyAccess(dirId, `ngCoerceInput_${attr.name}`);
expr = ts.createCall(
/* expression */ coercionFn,
/* typeArguments */ undefined,
/* argumentsArray */[expr]);
}
directiveInputs.push({ directiveInputs.push({
type: 'binding', type: 'binding',
field: field, field: field,

View File

@ -160,14 +160,9 @@ export const ALL_ENABLED_CONFIG: TypeCheckingConfig = {
}; };
// Remove 'ref' from TypeCheckableDirectiveMeta and add a 'selector' instead. // Remove 'ref' from TypeCheckableDirectiveMeta and add a 'selector' instead.
export type TestDirective = Partial<Pick< export type TestDirective =
TypeCheckableDirectiveMeta, Partial<Pick<TypeCheckableDirectiveMeta, Exclude<keyof TypeCheckableDirectiveMeta, 'ref'>>>&
Exclude<keyof TypeCheckableDirectiveMeta, 'ref'|'coercedInputs'>>>& {selector: string, name: string, file?: AbsoluteFsPath, type: 'directive'};
{
selector: string,
name: string, file?: AbsoluteFsPath,
type: 'directive', coercedInputs?: string[],
};
export type TestPipe = { export type TestPipe = {
name: string, name: string,
file?: AbsoluteFsPath, file?: AbsoluteFsPath,
@ -300,7 +295,6 @@ function prepareDeclarations(
inputs: decl.inputs || {}, inputs: decl.inputs || {},
isComponent: decl.isComponent || false, isComponent: decl.isComponent || false,
ngTemplateGuards: decl.ngTemplateGuards || [], ngTemplateGuards: decl.ngTemplateGuards || [],
coercedInputs: new Set<string>(decl.coercedInputs || []),
outputs: decl.outputs || {}, outputs: decl.outputs || {},
queries: decl.queries || [], queries: decl.queries || [],
}; };

View File

@ -268,34 +268,7 @@ describe('type check blocks', () => {
expect(block).toContain( expect(block).toContain(
'_t1.addEventListener("event", $event => (ctx).foo(($event as any)));'); '_t1.addEventListener("event", $event => (ctx).foo(($event as any)));');
}); });
});
describe('input coercion', () => {
it('should coerce a basic input', () => {
const DIRECTIVES: TestDeclaration[] = [{
type: 'directive',
name: 'MatInput',
selector: '[matInput]',
inputs: {'value': 'value'},
coercedInputs: ['value'],
}];
const TEMPLATE = `<input matInput [value]="expr">`;
const block = tcb(TEMPLATE, DIRECTIVES);
expect(block).toContain('value: (MatInput.ngCoerceInput_value((ctx).expr))');
});
it('should coerce based on input name, not field name', () => {
const DIRECTIVES: TestDeclaration[] = [{
type: 'directive',
name: 'MatInput',
selector: '[matInput]',
inputs: {'field': 'value'},
coercedInputs: ['value'],
}];
const TEMPLATE = `<input matInput [value]="expr">`;
const block = tcb(TEMPLATE, DIRECTIVES);
expect(block).toContain('field: (MatInput.ngCoerceInput_value((ctx).expr))');
});
}); });
describe('config', () => { describe('config', () => {

View File

@ -406,77 +406,6 @@ export declare class CommonModule {
expect(diags[1].length).toEqual(15); expect(diags[1].length).toEqual(15);
}); });
describe('input coercion', () => {
beforeEach(() => {
env.tsconfig({
'fullTemplateTypeCheck': true,
});
env.write('node_modules/@angular/material/index.d.ts', `
import * as i0 from '@angular/core';
export declare class MatInput {
value: string;
static ɵdir: i0.ɵɵDirectiveDefWithMeta<MatInput, '[matInput]', never, {'value': 'value'}, {}, never>;
static ngCoerceInput_value(v: string|number): string;
}
export declare class MatInputModule {
static ɵmod: i0.ɵɵNgModuleDefWithMeta<MatInputModule, [typeof MatInput], never, [typeof MatInput]>;
}
`);
});
it('should coerce an input using a coercion function if provided', () => {
env.write('test.ts', `
import {Component, NgModule} from '@angular/core';
import {MatInputModule} from '@angular/material';
@Component({
selector: 'blah',
template: '<input matInput [value]="someNumber">',
})
export class FooCmp {
someNumber = 3;
}
@NgModule({
declarations: [FooCmp],
imports: [MatInputModule],
})
export class FooModule {}
`);
const diags = env.driveDiagnostics();
expect(diags.length).toBe(0);
});
it('should give an error if the binding expression type is not accepted by the coercion function',
() => {
env.write('test.ts', `
import {Component, NgModule} from '@angular/core';
import {MatInputModule} from '@angular/material';
@Component({
selector: 'blah',
template: '<input matInput [value]="invalidType">',
})
export class FooCmp {
invalidType = true;
}
@NgModule({
declarations: [FooCmp],
imports: [MatInputModule],
})
export class FooModule {}
`);
const diags = env.driveDiagnostics();
expect(diags.length).toBe(1);
expect(diags[0].messageText)
.toBe(
`Argument of type 'boolean' is not assignable to parameter of type 'string | number'.`);
});
});
describe('legacy schema checking with the DOM schema', () => { describe('legacy schema checking with the DOM schema', () => {
beforeEach( beforeEach(
() => { env.tsconfig({ivyTemplateTypeCheck: true, fullTemplateTypeCheck: false}); }); () => { env.tsconfig({ivyTemplateTypeCheck: true, fullTemplateTypeCheck: false}); });