fix(compiler): generate inputs with aliases properly (#26774)
PR Close #26774
This commit is contained in:
parent
c048358cf9
commit
19fcfc3d00
|
@ -110,12 +110,14 @@ export function extractDirectiveMetadata(
|
||||||
// fields.
|
// fields.
|
||||||
const inputsFromMeta = parseFieldToPropertyMapping(directive, 'inputs', reflector, checker);
|
const inputsFromMeta = parseFieldToPropertyMapping(directive, 'inputs', reflector, checker);
|
||||||
const inputsFromFields = parseDecoratedFields(
|
const inputsFromFields = parseDecoratedFields(
|
||||||
filterToMembersWithDecorator(decoratedElements, 'Input', coreModule), reflector, checker);
|
filterToMembersWithDecorator(decoratedElements, 'Input', coreModule), reflector, checker,
|
||||||
|
resolveInput);
|
||||||
|
|
||||||
// And outputs.
|
// And outputs.
|
||||||
const outputsFromMeta = parseFieldToPropertyMapping(directive, 'outputs', reflector, checker);
|
const outputsFromMeta = parseFieldToPropertyMapping(directive, 'outputs', reflector, checker);
|
||||||
const outputsFromFields = parseDecoratedFields(
|
const outputsFromFields = parseDecoratedFields(
|
||||||
filterToMembersWithDecorator(decoratedElements, 'Output', coreModule), reflector, checker);
|
filterToMembersWithDecorator(decoratedElements, 'Output', coreModule), reflector, checker,
|
||||||
|
resolveOutput) as{[field: string]: string};
|
||||||
// Construct the list of queries.
|
// Construct the list of queries.
|
||||||
const contentChildFromFields = queriesFromFields(
|
const contentChildFromFields = queriesFromFields(
|
||||||
filterToMembersWithDecorator(decoratedElements, 'ContentChild', coreModule), reflector,
|
filterToMembersWithDecorator(decoratedElements, 'ContentChild', coreModule), reflector,
|
||||||
|
@ -330,7 +332,8 @@ function parseFieldToPropertyMapping(
|
||||||
*/
|
*/
|
||||||
function parseDecoratedFields(
|
function parseDecoratedFields(
|
||||||
fields: {member: ClassMember, decorators: Decorator[]}[], reflector: ReflectionHost,
|
fields: {member: ClassMember, decorators: Decorator[]}[], reflector: ReflectionHost,
|
||||||
checker: ts.TypeChecker): {[field: string]: string} {
|
checker: ts.TypeChecker, mapValueResolver: (publicName: string, internalName: string) =>
|
||||||
|
string | string[]): {[field: string]: string | string[]} {
|
||||||
return fields.reduce(
|
return fields.reduce(
|
||||||
(results, field) => {
|
(results, field) => {
|
||||||
const fieldName = field.member.name;
|
const fieldName = field.member.name;
|
||||||
|
@ -344,7 +347,7 @@ function parseDecoratedFields(
|
||||||
if (typeof property !== 'string') {
|
if (typeof property !== 'string') {
|
||||||
throw new Error(`Decorator argument must resolve to a string`);
|
throw new Error(`Decorator argument must resolve to a string`);
|
||||||
}
|
}
|
||||||
results[fieldName] = property;
|
results[fieldName] = mapValueResolver(property, fieldName);
|
||||||
} else {
|
} else {
|
||||||
// Too many arguments.
|
// Too many arguments.
|
||||||
throw new Error(
|
throw new Error(
|
||||||
|
@ -353,7 +356,15 @@ function parseDecoratedFields(
|
||||||
});
|
});
|
||||||
return results;
|
return results;
|
||||||
},
|
},
|
||||||
{} as{[field: string]: string});
|
{} as{[field: string]: string | string[]});
|
||||||
|
}
|
||||||
|
|
||||||
|
function resolveInput(publicName: string, internalName: string) {
|
||||||
|
return [publicName, internalName];
|
||||||
|
}
|
||||||
|
|
||||||
|
function resolveOutput(publicName: string, internalName: string) {
|
||||||
|
return publicName;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function queriesFromFields(
|
export function queriesFromFields(
|
||||||
|
|
|
@ -518,7 +518,10 @@ function tcbGetInputBindingExpressions(
|
||||||
// is desired. Invert `dir.inputs` into `propMatch` to create this map.
|
// is desired. Invert `dir.inputs` into `propMatch` to create this map.
|
||||||
const propMatch = new Map<string, string>();
|
const propMatch = new Map<string, string>();
|
||||||
const inputs = dir.inputs;
|
const inputs = dir.inputs;
|
||||||
Object.keys(inputs).forEach(key => propMatch.set(inputs[key], key));
|
Object.keys(inputs).forEach(key => {
|
||||||
|
Array.isArray(inputs[key]) ? propMatch.set(inputs[key][0], key) :
|
||||||
|
propMatch.set(inputs[key] as string, key);
|
||||||
|
});
|
||||||
|
|
||||||
// Add a binding expression to the map for each input of the directive that has a
|
// Add a binding expression to the map for each input of the directive that has a
|
||||||
// matching binding.
|
// matching binding.
|
||||||
|
|
|
@ -1887,7 +1887,7 @@ describe('compiler compliance', () => {
|
||||||
type: LifecycleComp,
|
type: LifecycleComp,
|
||||||
selectors: [["lifecycle-comp"]],
|
selectors: [["lifecycle-comp"]],
|
||||||
factory: function LifecycleComp_Factory(t) { return new (t || LifecycleComp)(); },
|
factory: function LifecycleComp_Factory(t) { return new (t || LifecycleComp)(); },
|
||||||
inputs: {nameMin: "name"},
|
inputs: {nameMin: ["name", "nameMin"]},
|
||||||
features: [$r3$.ɵNgOnChangesFeature],
|
features: [$r3$.ɵNgOnChangesFeature],
|
||||||
consts: 0,
|
consts: 0,
|
||||||
vars: 0,
|
vars: 0,
|
||||||
|
@ -2301,7 +2301,7 @@ describe('compiler compliance', () => {
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('inherited bare classes', () => {
|
describe('inherited base classes', () => {
|
||||||
it('should add ngBaseDef if one or more @Input is present', () => {
|
it('should add ngBaseDef if one or more @Input is present', () => {
|
||||||
const files = {
|
const files = {
|
||||||
app: {
|
app: {
|
||||||
|
|
|
@ -56,7 +56,7 @@ describe('compiler compliance: listen()', () => {
|
||||||
…
|
…
|
||||||
inputs:{
|
inputs:{
|
||||||
componentInput: "componentInput",
|
componentInput: "componentInput",
|
||||||
originalComponentInput: "renamedComponentInput"
|
originalComponentInput: ["renamedComponentInput", "originalComponentInput"]
|
||||||
},
|
},
|
||||||
outputs: {
|
outputs: {
|
||||||
componentOutput: "componentOutput",
|
componentOutput: "componentOutput",
|
||||||
|
@ -70,7 +70,7 @@ describe('compiler compliance: listen()', () => {
|
||||||
…
|
…
|
||||||
inputs:{
|
inputs:{
|
||||||
directiveInput: "directiveInput",
|
directiveInput: "directiveInput",
|
||||||
originalDirectiveInput: "renamedDirectiveInput"
|
originalDirectiveInput: ["renamedDirectiveInput", "originalDirectiveInput"]
|
||||||
},
|
},
|
||||||
outputs: {
|
outputs: {
|
||||||
directiveOutput: "directiveOutput",
|
directiveOutput: "directiveOutput",
|
||||||
|
|
|
@ -86,7 +86,7 @@ export interface R3DirectiveMetadata {
|
||||||
/**
|
/**
|
||||||
* A mapping of input field names to the property names.
|
* A mapping of input field names to the property names.
|
||||||
*/
|
*/
|
||||||
inputs: {[field: string]: string};
|
inputs: {[field: string]: string | string[]};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A mapping of output field names to the property names.
|
* A mapping of output field names to the property names.
|
||||||
|
|
|
@ -532,12 +532,15 @@ function stringAsType(str: string): o.Type {
|
||||||
return o.expressionType(o.literal(str));
|
return o.expressionType(o.literal(str));
|
||||||
}
|
}
|
||||||
|
|
||||||
function stringMapAsType(map: {[key: string]: string}): o.Type {
|
function stringMapAsType(map: {[key: string]: string | string[]}): o.Type {
|
||||||
const mapValues = Object.keys(map).map(key => ({
|
const mapValues = Object.keys(map).map(key => {
|
||||||
key,
|
const value = Array.isArray(map[key]) ? map[key][0] : map[key];
|
||||||
value: o.literal(map[key]),
|
return {
|
||||||
quoted: true,
|
key,
|
||||||
}));
|
value: o.literal(value),
|
||||||
|
quoted: true,
|
||||||
|
};
|
||||||
|
});
|
||||||
return o.expressionType(o.literalMap(mapValues));
|
return o.expressionType(o.literalMap(mapValues));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -44,7 +44,7 @@ export interface DirectiveMeta {
|
||||||
*
|
*
|
||||||
* Goes from property names to field names.
|
* Goes from property names to field names.
|
||||||
*/
|
*/
|
||||||
inputs: {[property: string]: string};
|
inputs: {[property: string]: string | string[]};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Set of outputs which this directive claims.
|
* Set of outputs which this directive claims.
|
||||||
|
|
|
@ -67,8 +67,8 @@ export function asLiteral(value: any): o.Expression {
|
||||||
return o.literal(value, o.INFERRED_TYPE);
|
return o.literal(value, o.INFERRED_TYPE);
|
||||||
}
|
}
|
||||||
|
|
||||||
export function conditionallyCreateMapObjectLiteral(keys: {[key: string]: string}): o.Expression|
|
export function conditionallyCreateMapObjectLiteral(keys: {[key: string]: string | string[]}):
|
||||||
null {
|
o.Expression|null {
|
||||||
if (Object.getOwnPropertyNames(keys).length > 0) {
|
if (Object.getOwnPropertyNames(keys).length > 0) {
|
||||||
return mapToExpression(keys);
|
return mapToExpression(keys);
|
||||||
}
|
}
|
||||||
|
|
|
@ -153,13 +153,14 @@ function directiveMetadata(type: Type<any>, metadata: Directive): R3DirectiveMet
|
||||||
const inputsFromMetadata = parseInputOutputs(metadata.inputs || []);
|
const inputsFromMetadata = parseInputOutputs(metadata.inputs || []);
|
||||||
const outputsFromMetadata = parseInputOutputs(metadata.outputs || []);
|
const outputsFromMetadata = parseInputOutputs(metadata.outputs || []);
|
||||||
|
|
||||||
const inputsFromType: StringMap = {};
|
const inputsFromType: {[key: string]: string | string[]} = {};
|
||||||
const outputsFromType: StringMap = {};
|
const outputsFromType: StringMap = {};
|
||||||
for (const field in propMetadata) {
|
for (const field in propMetadata) {
|
||||||
if (propMetadata.hasOwnProperty(field)) {
|
if (propMetadata.hasOwnProperty(field)) {
|
||||||
propMetadata[field].forEach(ann => {
|
propMetadata[field].forEach(ann => {
|
||||||
if (isInput(ann)) {
|
if (isInput(ann)) {
|
||||||
inputsFromType[field] = ann.bindingPropertyName || field;
|
inputsFromType[field] =
|
||||||
|
ann.bindingPropertyName ? [ann.bindingPropertyName, field] : field;
|
||||||
} else if (isOutput(ann)) {
|
} else if (isOutput(ann)) {
|
||||||
outputsFromType[field] = ann.bindingPropertyName || field;
|
outputsFromType[field] = ann.bindingPropertyName || field;
|
||||||
}
|
}
|
||||||
|
|
|
@ -12,7 +12,7 @@ import {InjectorDef, defineInjectable} from '@angular/core/src/di/defs';
|
||||||
import {Injectable} from '@angular/core/src/di/injectable';
|
import {Injectable} from '@angular/core/src/di/injectable';
|
||||||
import {inject, setCurrentInjector} from '@angular/core/src/di/injector_compatibility';
|
import {inject, setCurrentInjector} from '@angular/core/src/di/injector_compatibility';
|
||||||
import {ivyEnabled} from '@angular/core/src/ivy_switch';
|
import {ivyEnabled} from '@angular/core/src/ivy_switch';
|
||||||
import {Component, HostBinding, HostListener, Input, Output, Pipe} from '@angular/core/src/metadata/directives';
|
import {Component, Directive, HostBinding, HostListener, Input, Output, Pipe} from '@angular/core/src/metadata/directives';
|
||||||
import {NgModule, NgModuleDef} from '@angular/core/src/metadata/ng_module';
|
import {NgModule, NgModuleDef} from '@angular/core/src/metadata/ng_module';
|
||||||
import {ComponentDef, PipeDef} from '@angular/core/src/render3/interfaces/definition';
|
import {ComponentDef, PipeDef} from '@angular/core/src/render3/interfaces/definition';
|
||||||
|
|
||||||
|
@ -252,6 +252,33 @@ ivyEnabled && describe('render3 jit', () => {
|
||||||
expect(pipeDef.pure).toBe(true, 'pipe should be pure');
|
expect(pipeDef.pure).toBe(true, 'pipe should be pure');
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('should add @Input properties to a component', () => {
|
||||||
|
@Component({
|
||||||
|
selector: 'input-comp',
|
||||||
|
template: 'test',
|
||||||
|
})
|
||||||
|
class InputComp {
|
||||||
|
@Input('publicName') privateName = 'name1';
|
||||||
|
}
|
||||||
|
|
||||||
|
const InputCompAny = InputComp as any;
|
||||||
|
expect(InputCompAny.ngComponentDef.inputs).toEqual({publicName: 'privateName'});
|
||||||
|
expect(InputCompAny.ngComponentDef.declaredInputs).toEqual({privateName: 'privateName'});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should add @Input properties to a directive', () => {
|
||||||
|
@Directive({
|
||||||
|
selector: '[dir]',
|
||||||
|
})
|
||||||
|
class InputDir {
|
||||||
|
@Input('publicName') privateName = 'name1';
|
||||||
|
}
|
||||||
|
|
||||||
|
const InputDirAny = InputDir as any;
|
||||||
|
expect(InputDirAny.ngDirectiveDef.inputs).toEqual({publicName: 'privateName'});
|
||||||
|
expect(InputDirAny.ngDirectiveDef.declaredInputs).toEqual({privateName: 'privateName'});
|
||||||
|
});
|
||||||
|
|
||||||
it('should add ngBaseDef to types with @Input properties', () => {
|
it('should add ngBaseDef to types with @Input properties', () => {
|
||||||
class C {
|
class C {
|
||||||
@Input('alias1')
|
@Input('alias1')
|
||||||
|
|
Loading…
Reference in New Issue