fix(ivy): inherit static coercion members from base classes (#34296)

For Ivy's template type checker it is possible to let a directive
specify static members to allow a wider type for some input:

```typescript
export class MatSelect {
  @Input() disabled: boolean;

  static ngAcceptInputType_disabled: boolean | string;
}
```

This allows a binding to the `MatSelect.disabled` input to be of type
boolean or string, whereas the `disabled` property itself is only of
type boolean.

Up until now, any static `ngAcceptInputType_*` property was not
inherited for subclasses of a directive class. This is cumbersome, as
the directive's inputs are inherited, so any acceptance member should as
well. To resolve this limitation, this commit extends the flattening of
directive metadata to include the acceptance members.

Fixes #33830
Resolves FW-1759

PR Close #34296
This commit is contained in:
JoostK 2019-12-07 16:43:08 +01:00 committed by Andrew Kushnir
parent c9df9afddd
commit 22ad701134
2 changed files with 41 additions and 5 deletions

View File

@ -27,6 +27,7 @@ export function flattenInheritedDirectiveMetadata(
let inputs: {[key: string]: string | [string, string]} = {};
let outputs: {[key: string]: string} = {};
let coercedInputFields = new Set<string>();
let isDynamic = false;
const addMetadata = (meta: DirectiveMeta): void => {
@ -43,6 +44,10 @@ export function flattenInheritedDirectiveMetadata(
}
inputs = {...inputs, ...meta.inputs};
outputs = {...outputs, ...meta.outputs};
for (const coercedInputField of meta.coercedInputFields) {
coercedInputFields.add(coercedInputField);
}
};
addMetadata(topMeta);
@ -51,6 +56,7 @@ export function flattenInheritedDirectiveMetadata(
...topMeta,
inputs,
outputs,
coercedInputFields,
baseClass: isDynamic ? 'dynamic' : null,
};
}

View File

@ -1123,9 +1123,7 @@ export declare class AnimationEvent {
describe('input coercion', () => {
beforeEach(() => {
env.tsconfig({
'fullTemplateTypeCheck': true,
});
env.tsconfig({fullTemplateTypeCheck: true, strictInputTypes: true});
env.write('node_modules/@angular/material/index.d.ts', `
import * as i0 from '@angular/core';
@ -1164,10 +1162,42 @@ export declare class AnimationEvent {
expect(diags.length).toBe(0);
});
it('should apply coercion members of base classes', () => {
env.write('test.ts', `
import {Component, Directive, Input, NgModule} from '@angular/core';
@Directive()
export class BaseDir {
@Input()
value: string;
static ngAcceptInputType_value: string|number;
}
@Directive({
selector: '[dir]',
})
export class MyDir extends BaseDir {}
@Component({
selector: 'blah',
template: '<input dir [value]="someNumber">',
})
export class FooCmp {
someNumber = 3;
}
@NgModule({
declarations: [MyDir, FooCmp],
})
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.tsconfig({fullTemplateTypeCheck: true, strictInputTypes: true});
env.write('test.ts', `
import {Component, NgModule} from '@angular/core';
import {MatInputModule} from '@angular/material';