fix(compiler): correct confusion between field and property names (#38685)

The `R3TargetBinder` accepts an interface for directive metadata which
declares types for `input` and `output` objects. These types convey the
mapping between the property names for an input or output and the
corresponding property name on the component class. Due to
`R3TargetBinder`'s requirements, this mapping was specified with property
names as keys and field names as values.

However, because of duck typing, this interface was accidentally satisifed
by the opposite mapping, of field names to property names, that was produced
in other parts of the compiler. This form more naturally represents the data
model for inputs.

Rather than accept the field -> property mapping and invert it, this commit
introduces a new abstraction for such mappings which is bidirectional,
eliminating the ambiguous plain object type. This mapping uses new,
unambiguous terminology ("class property name" and "binding property name")
and can be used to satisfy both the needs of the binder as well as those of
the template type-checker (field -> property).

A new test ensures that the input/output metadata produced by the compiler
during analysis is directly compatible with the binder via this unambiguous
new interface.

PR Close #38685
This commit is contained in:
Alex Rickabaugh
2020-09-02 15:18:29 -04:00
committed by atscott
parent b084bffb64
commit a1c34c6f0a
20 changed files with 399 additions and 110 deletions

View File

@ -8,41 +8,56 @@
import * as e from '../../../src/expression_parser/ast';
import * as a from '../../../src/render3/r3_ast';
import {DirectiveMeta} from '../../../src/render3/view/t2_api';
import {DirectiveMeta, InputOutputPropertySet} from '../../../src/render3/view/t2_api';
import {R3TargetBinder} from '../../../src/render3/view/t2_binder';
import {parseTemplate} from '../../../src/render3/view/template';
import {CssSelector, SelectorMatcher} from '../../../src/selector';
import {findExpression} from './util';
/**
* A `InputOutputPropertySet` which only uses an identity mapping for fields and properties.
*/
class IdentityInputMapping implements InputOutputPropertySet {
private names: Set<string>;
constructor(names: string[]) {
this.names = new Set(names);
}
hasBindingPropertyName(propertyName: string): boolean {
return this.names.has(propertyName);
}
}
function makeSelectorMatcher(): SelectorMatcher<DirectiveMeta> {
const matcher = new SelectorMatcher<DirectiveMeta>();
matcher.addSelectables(CssSelector.parse('[ngFor][ngForOf]'), {
name: 'NgFor',
exportAs: null,
inputs: {'ngForOf': 'ngForOf'},
outputs: {},
inputs: new IdentityInputMapping(['ngForOf']),
outputs: new IdentityInputMapping([]),
isComponent: false,
});
matcher.addSelectables(CssSelector.parse('[dir]'), {
name: 'Dir',
exportAs: null,
inputs: {},
outputs: {},
inputs: new IdentityInputMapping([]),
outputs: new IdentityInputMapping([]),
isComponent: false,
});
matcher.addSelectables(CssSelector.parse('[hasOutput]'), {
name: 'HasOutput',
exportAs: null,
inputs: {},
outputs: {'outputBinding': 'outputBinding'},
inputs: new IdentityInputMapping([]),
outputs: new IdentityInputMapping(['outputBinding']),
isComponent: false,
});
matcher.addSelectables(CssSelector.parse('[hasInput]'), {
name: 'HasInput',
exportAs: null,
inputs: {'inputBinding': 'inputBinding'},
outputs: {},
inputs: new IdentityInputMapping(['inputBinding']),
outputs: new IdentityInputMapping([]),
isComponent: false,
});
return matcher;
@ -85,8 +100,8 @@ describe('t2 binding', () => {
matcher.addSelectables(CssSelector.parse('text[dir]'), {
name: 'Dir',
exportAs: null,
inputs: {},
outputs: {},
inputs: new IdentityInputMapping([]),
outputs: new IdentityInputMapping([]),
isComponent: false,
});
const binder = new R3TargetBinder(matcher);