Files
angular/packages/compiler-cli/ngcc/src/utils.ts
Alex Rickabaugh bea85ffe9c fix(ivy): match microsyntax template directives correctly (#29698)
Previously, Template.templateAttrs was introduced to capture attribute
bindings which originated from microsyntax (e.g. bindings in *ngFor="...").
This means that a Template node can have two different structures, depending
on whether it originated from microsyntax or from a literal <ng-template>.

In the literal case, the node behaves much like an Element node, it has
attributes, inputs, and outputs which determine which directives apply.
In the microsyntax case, though, only the templateAttrs should be used
to determine which directives apply.

Previously, both the t2_binder and the TemplateDefinitionBuilder were using
the wrong set of attributes to match directives - combining the attributes,
inputs, outputs, and templateAttrs of the Template node regardless of its
origin. In the TDB's case this wasn't a problem, since the TDB collects a
global Set of directives used in the template, so it didn't matter whether
the directive was also recognized on the <ng-template>. t2_binder's API
distinguishes between directives on specific nodes, though, so it's more
sensitive to mismatching.

In particular, this showed up as an assertion failure in template type-
checking in certain cases, when a directive was accidentally matched on
a microsyntax template element and also had a binding which referenced a
variable declared in the microsyntax. This resulted in the type-checker
attempting to generate a reference to a variable that didn't exist in that
scope.

The fix is to distinguish between the two cases and select the appropriate
set of attributes to match on accordingly.

Testing strategy: tested in the t2_binder tests.

PR Close #29698
2019-04-19 11:15:25 -07:00

54 lines
1.8 KiB
TypeScript

/**
* @license
* Copyright Google Inc. All Rights Reserved.
*
* Use of this source code is governed by an MIT-style license that can be
* found in the LICENSE file at https://angular.io/license
*/
import * as ts from 'typescript';
export function getOriginalSymbol(checker: ts.TypeChecker): (symbol: ts.Symbol) => ts.Symbol {
return function(symbol: ts.Symbol) {
return ts.SymbolFlags.Alias & symbol.flags ? checker.getAliasedSymbol(symbol) : symbol;
};
}
export function isDefined<T>(value: T | undefined | null): value is T {
return (value !== undefined) && (value !== null);
}
export function getNameText(name: ts.PropertyName | ts.BindingName): string {
return ts.isIdentifier(name) || ts.isLiteralExpression(name) ? name.text : name.getText();
}
/**
* Parse down the AST and capture all the nodes that satisfy the test.
* @param node The start node.
* @param test The function that tests whether a node should be included.
* @returns a collection of nodes that satisfy the test.
*/
export function findAll<T>(node: ts.Node, test: (node: ts.Node) => node is ts.Node & T): T[] {
const nodes: T[] = [];
findAllVisitor(node);
return nodes;
function findAllVisitor(n: ts.Node) {
if (test(n)) {
nodes.push(n);
} else {
n.forEachChild(child => findAllVisitor(child));
}
}
}
/**
* Does the given declaration have a name which is an identifier?
* @param declaration The declaration to test.
* @returns true if the declaration has an identifier for a name.
*/
export function hasNameIdentifier(declaration: ts.Declaration): declaration is ts.Declaration&
{name: ts.Identifier} {
const namedDeclaration: ts.Declaration&{name?: ts.Node} = declaration;
return namedDeclaration.name !== undefined && ts.isIdentifier(namedDeclaration.name);
}