refactor(compiler-cli): Return symbols for all matching inputs (#40144)

This commit ensures that the template type checker returns symbols for
all inputs if an attribute binds to more than one.

PR Close #40144
This commit is contained in:
Andrew Scott
2020-12-15 15:55:54 -08:00
committed by atscott
parent 13020f904f
commit da2be4b710
5 changed files with 108 additions and 78 deletions
@@ -13,7 +13,7 @@ import {AbsoluteFsPath} from '../../file_system';
import {ClassDeclaration} from '../../reflection';
import {ComponentScopeReader} from '../../scope';
import {isAssignment} from '../../util/src/typescript';
import {DirectiveSymbol, DomBindingSymbol, ElementSymbol, ExpressionSymbol, InputBindingSymbol, OutputBindingSymbol, PipeSymbol, ReferenceSymbol, ShimLocation, Symbol, SymbolKind, TemplateSymbol, TsNodeSymbolInfo, TypeCheckableDirectiveMeta, VariableSymbol} from '../api';
import {BindingSymbol, DirectiveSymbol, DomBindingSymbol, ElementSymbol, ExpressionSymbol, InputBindingSymbol, OutputBindingSymbol, PipeSymbol, ReferenceSymbol, ShimLocation, Symbol, SymbolKind, TemplateSymbol, TsNodeSymbolInfo, TypeCheckableDirectiveMeta, VariableSymbol} from '../api';
import {ExpressionIdentifier, findAllMatchingNodes, findFirstMatchingNode, hasExpressionIdentifier} from './comments';
import {TemplateData} from './context';
@@ -253,31 +253,35 @@ export class SymbolBuilder {
return host !== null ? {kind: SymbolKind.DomBinding, host} : null;
}
const node = findFirstMatchingNode(
const nodes = findAllMatchingNodes(
this.typeCheckBlock, {withSpan: binding.sourceSpan, filter: isAssignment});
if (node === null || !isAccessExpression(node.left)) {
return null;
}
const bindings: BindingSymbol[] = [];
for (const node of nodes) {
if (!isAccessExpression(node.left)) {
continue;
}
const symbolInfo = this.getSymbolOfTsNode(node.left);
if (symbolInfo === null || symbolInfo.tsSymbol === null) {
return null;
}
const symbolInfo = this.getSymbolOfTsNode(node.left);
if (symbolInfo === null || symbolInfo.tsSymbol === null) {
continue;
}
const target = this.getDirectiveSymbolForAccessExpression(node.left, consumer);
if (target === null) {
return null;
}
return {
kind: SymbolKind.Input,
bindings: [{
const target = this.getDirectiveSymbolForAccessExpression(node.left, consumer);
if (target === null) {
continue;
}
bindings.push({
...symbolInfo,
tsSymbol: symbolInfo.tsSymbol,
kind: SymbolKind.Binding,
target,
}],
};
});
}
if (bindings.length === 0) {
return null;
}
return {kind: SymbolKind.Input, bindings};
}
private getDirectiveSymbolForAccessExpression(
@@ -1136,7 +1136,7 @@ runInEachFileSystem(() => {
.toEqual('TestDir');
});
it('returns the first directive match when two directives have the same input', () => {
it('returns the all inputs when two directives have the same input', () => {
const fileName = absoluteFrom('/main.ts');
const dirFile = absoluteFrom('/dir.ts');
const templateString = `<div dir otherDir [inputA]="'my input A'"></div>`;
@@ -1178,12 +1178,12 @@ runInEachFileSystem(() => {
const inputAbinding = (nodes[0] as TmplAstElement).inputs[0];
const symbol = templateTypeChecker.getSymbolOfNode(inputAbinding, cmp)!;
assertInputBindingSymbol(symbol);
expect(
(symbol.bindings[0].tsSymbol!.declarations[0] as ts.PropertyDeclaration).name.getText())
.toEqual('inputA');
expect((symbol.bindings[0].tsSymbol!.declarations[0] as ts.PropertyDeclaration)
.parent.name?.text)
.toEqual('TestDir');
expect(new Set(symbol.bindings.map(
b => (b.tsSymbol!.declarations[0] as ts.PropertyDeclaration).name.getText())))
.toEqual(new Set(['inputA', 'otherDirInputA']));
expect(new Set(symbol.bindings.map(
b => (b.tsSymbol!.declarations[0] as ts.PropertyDeclaration).parent.name?.text)))
.toEqual(new Set(['TestDir', 'OtherDir']));
});
});