refactor(compiler-cli): use keySpan for output event lookup (#39515)

PR #39665 added the `keySpan` to the output field access so we no longer
need to get there from the call expression and can instead just find the
node we want directly.

PR Close #39515
This commit is contained in:
Andrew Scott 2020-11-17 13:00:11 -08:00 committed by Alex Rickabaugh
parent 29298f4f3d
commit 702d6bfe8f
2 changed files with 11 additions and 45 deletions

View File

@ -18,7 +18,6 @@ import {DirectiveSymbol, DomBindingSymbol, ElementSymbol, ExpressionSymbol, Inpu
import {ExpressionIdentifier, findAllMatchingNodes, findFirstMatchingNode, hasExpressionIdentifier} from './comments'; import {ExpressionIdentifier, findAllMatchingNodes, findFirstMatchingNode, hasExpressionIdentifier} from './comments';
import {TemplateData} from './context'; import {TemplateData} from './context';
import {isAccessExpression} from './ts_util'; import {isAccessExpression} from './ts_util';
import {TcbDirectiveOutputsOp} from './type_check_block';
/** /**
* Generates and caches `Symbol`s for various template structures for a given component. * Generates and caches `Symbol`s for various template structures for a given component.
@ -169,9 +168,9 @@ export class SymbolBuilder {
// Outputs are a `ts.CallExpression` that look like one of the two: // Outputs are a `ts.CallExpression` that look like one of the two:
// * _outputHelper(_t1["outputField"]).subscribe(handler); // * _outputHelper(_t1["outputField"]).subscribe(handler);
// * _t1.addEventListener(handler); // * _t1.addEventListener(handler);
const node = findFirstMatchingNode( const outputFieldAccess = findFirstMatchingNode(
this.typeCheckBlock, {withSpan: eventBinding.sourceSpan, filter: ts.isCallExpression}); this.typeCheckBlock, {withSpan: eventBinding.keySpan, filter: isAccessExpression});
if (node === null) { if (outputFieldAccess === null) {
return null; return null;
} }
@ -181,12 +180,12 @@ export class SymbolBuilder {
} }
if (consumer instanceof TmplAstTemplate || consumer instanceof TmplAstElement) { if (consumer instanceof TmplAstTemplate || consumer instanceof TmplAstElement) {
if (!ts.isPropertyAccessExpression(node.expression) || if (!ts.isPropertyAccessExpression(outputFieldAccess) ||
node.expression.name.text !== 'addEventListener') { outputFieldAccess.name.text !== 'addEventListener') {
return null; return null;
} }
const addEventListener = node.expression.name; const addEventListener = outputFieldAccess.name;
const tsSymbol = this.getTypeChecker().getSymbolAtLocation(addEventListener); const tsSymbol = this.getTypeChecker().getSymbolAtLocation(addEventListener);
const tsType = this.getTypeChecker().getTypeAtLocation(addEventListener); const tsType = this.getTypeChecker().getTypeAtLocation(addEventListener);
const positionInShimFile = this.getShimPositionForNode(addEventListener); const positionInShimFile = this.getShimPositionForNode(addEventListener);
@ -207,11 +206,9 @@ export class SymbolBuilder {
}], }],
}; };
} else { } else {
const outputFieldAccess = TcbDirectiveOutputsOp.decodeOutputCallExpression(node); if (!ts.isElementAccessExpression(outputFieldAccess)) {
if (outputFieldAccess === null) {
return null; return null;
} }
const tsSymbol = const tsSymbol =
this.getTypeChecker().getSymbolAtLocation(outputFieldAccess.argumentExpression); this.getTypeChecker().getSymbolAtLocation(outputFieldAccess.argumentExpression);
if (tsSymbol === undefined) { if (tsSymbol === undefined) {
@ -225,7 +222,7 @@ export class SymbolBuilder {
} }
const positionInShimFile = this.getShimPositionForNode(outputFieldAccess); const positionInShimFile = this.getShimPositionForNode(outputFieldAccess);
const tsType = this.getTypeChecker().getTypeAtLocation(node); const tsType = this.getTypeChecker().getTypeAtLocation(outputFieldAccess);
return { return {
kind: SymbolKind.Output, kind: SymbolKind.Output,
bindings: [{ bindings: [{

View File

@ -902,39 +902,6 @@ export class TcbDirectiveOutputsOp extends TcbOp {
return null; return null;
} }
/**
* Outputs are a `ts.CallExpression` that look like one of the two:
* - `_outputHelper(_t1["outputField"]).subscribe(handler);`
* - `_t1.addEventListener(handler);`
* This method reverses the operations to create a call expression for a directive output.
* It unpacks the given call expression and returns the original element access (i.e.
* `_t1["outputField"]` in the example above). Returns `null` if the given call expression is not
* the expected structure of an output binding
*/
static decodeOutputCallExpression(node: ts.CallExpression): ts.ElementAccessExpression|null {
// `node.expression` === `_outputHelper(_t1["outputField"]).subscribe` or `_t1.addEventListener`
if (!ts.isPropertyAccessExpression(node.expression) ||
node.expression.name.text === 'addEventListener') {
// `addEventListener` outputs do not have an `ElementAccessExpression` for the output field.
return null;
}
if (!ts.isCallExpression(node.expression.expression)) {
return null;
}
// `node.expression.expression` === `_outputHelper(_t1["outputField"])`
if (node.expression.expression.arguments.length === 0) {
return null;
}
const [outputFieldAccess] = node.expression.expression.arguments;
if (!ts.isElementAccessExpression(outputFieldAccess)) {
return null;
}
return outputFieldAccess;
}
} }
/** /**
@ -984,8 +951,10 @@ class TcbUnclaimedOutputsOp extends TcbOp {
if (elId === null) { if (elId === null) {
elId = this.scope.resolve(this.element); elId = this.scope.resolve(this.element);
} }
const propertyAccess = ts.createPropertyAccess(elId, 'addEventListener');
addParseSpanInfo(propertyAccess, output.keySpan);
const call = ts.createCall( const call = ts.createCall(
/* expression */ ts.createPropertyAccess(elId, 'addEventListener'), /* expression */ propertyAccess,
/* typeArguments */ undefined, /* typeArguments */ undefined,
/* arguments */[ts.createStringLiteral(output.name), handler]); /* arguments */[ts.createStringLiteral(output.name), handler]);
addParseSpanInfo(call, output.sourceSpan); addParseSpanInfo(call, output.sourceSpan);