refactor(language-service): refactor `HybridVisitor` and expand its capabilities (#39505)

This commit takes the `HybridVisitor` in the language service and gives it
the ability to return not just a node but the template context in which it
appears. In the future, more context regarding where a node appears in the
template might become necessary (ex: the microsyntax container for binding
nodes), and this refactoring enables that.

In the process, `HybridVisitor` is renamed and the concept of a
`TemplateTarget` interface is introduced to contain the results of this
operation.

PR Close #39505
This commit is contained in:
Alex Rickabaugh 2020-10-13 10:28:15 -07:00 committed by Joey Perrott
parent 643c96184c
commit eaace44d57
5 changed files with 217 additions and 168 deletions

View File

@ -10,8 +10,8 @@ import {AST, TmplAstBoundAttribute, TmplAstBoundEvent, TmplAstElement, TmplAstNo
import {NgCompiler} from '@angular/compiler-cli/src/ngtsc/core';
import {DirectiveSymbol, DomBindingSymbol, ElementSymbol, ShimLocation, Symbol, SymbolKind, TemplateSymbol} from '@angular/compiler-cli/src/ngtsc/typecheck/api';
import * as ts from 'typescript';
import {getTargetAtPosition} from './template_target';
import {getPathToNodeAtPosition} from './hybrid_visitor';
import {findTightestNode, flatMap, getClassDeclFromDecoratorProp, getDirectiveMatchesForAttribute, getDirectiveMatchesForElementTag, getPropertyAssignmentFromValue, getTemplateInfoAtPosition, getTextSpanOfNode, isDollarEvent, isTypeScriptFile, TemplateInfo, toTextSpan} from './utils';
@ -111,10 +111,9 @@ function getUrlFromProperty(urlNode: ts.StringLiteralLike, resourceResolver: Res
};
}
interface DefinitionMeta {
node: AST|TmplAstNode;
path: Array<AST|TmplAstNode>;
parent: AST|TmplAstNode|null;
symbol: Symbol;
}
@ -156,7 +155,7 @@ export class DefinitionBuilder {
return {definitions, textSpan: getTextSpanOfNode(definitionMeta.node)};
}
private getDefinitionsForSymbol({symbol, node, path, component}: DefinitionMeta&
private getDefinitionsForSymbol({symbol, node, parent, component}: DefinitionMeta&
TemplateInfo): readonly ts.DefinitionInfo[]|undefined {
switch (symbol.kind) {
case SymbolKind.Directive:
@ -174,7 +173,7 @@ export class DefinitionBuilder {
const bindingDefs = this.getDefinitionsForSymbols(...symbol.bindings);
// Also attempt to get directive matches for the input name. If there is a directive that
// has the input name as part of the selector, we want to return that as well.
const directiveDefs = this.getDirectiveTypeDefsForBindingNode(node, path, component);
const directiveDefs = this.getDirectiveTypeDefsForBindingNode(node, parent, component);
return [...bindingDefs, ...directiveDefs];
}
case SymbolKind.Variable:
@ -233,7 +232,7 @@ export class DefinitionBuilder {
// Also attempt to get directive matches for the input name. If there is a directive that
// has the input name as part of the selector, we want to return that as well.
const directiveDefs = this.getDirectiveTypeDefsForBindingNode(
node, definitionMeta.path, templateInfo.component);
node, definitionMeta.parent, templateInfo.component);
return [...bindingDefs, ...directiveDefs];
}
case SymbolKind.Reference:
@ -273,13 +272,13 @@ export class DefinitionBuilder {
}
private getDirectiveTypeDefsForBindingNode(
node: TmplAstNode|AST, pathToNode: Array<TmplAstNode|AST>, component: ts.ClassDeclaration) {
node: TmplAstNode|AST, parent: TmplAstNode|AST|null, component: ts.ClassDeclaration) {
if (!(node instanceof TmplAstBoundAttribute) && !(node instanceof TmplAstTextAttribute) &&
!(node instanceof TmplAstBoundEvent)) {
return [];
}
const parent = pathToNode[pathToNode.length - 2];
if (!(parent instanceof TmplAstTemplate || parent instanceof TmplAstElement)) {
if (parent === null ||
!(parent instanceof TmplAstTemplate || parent instanceof TmplAstElement)) {
return [];
}
const templateOrElementSymbol =
@ -303,16 +302,16 @@ export class DefinitionBuilder {
private getDefinitionMetaAtPosition({template, component}: TemplateInfo, position: number):
DefinitionMeta|undefined {
const path = getPathToNodeAtPosition(template, position);
if (path === undefined) {
return;
const target = getTargetAtPosition(template, position);
if (target === null) {
return undefined;
}
const {node, parent} = target;
const node = path[path.length - 1];
const symbol = this.compiler.getTemplateTypeChecker().getSymbolOfNode(node, component);
if (symbol === null) {
return;
return undefined;
}
return {node, path, symbol};
return {node, parent, symbol};
}
}

View File

@ -16,7 +16,8 @@ import {CompilerFactory} from './compiler_factory';
import {DefinitionBuilder} from './definitions';
import {LanguageServiceAdapter} from './language_service_adapter';
import {QuickInfoBuilder} from './quick_info';
import {isTypeScriptFile} from './utils';
import {getTargetAtPosition} from './template_target';
import {getTemplateInfoAtPosition, isTypeScriptFile} from './utils';
export class LanguageService {
private options: CompilerOptions;
@ -73,8 +74,19 @@ export class LanguageService {
}
getQuickInfoAtPosition(fileName: string, position: number): ts.QuickInfo|undefined {
const program = this.strategy.getProgram();
const compiler = this.compilerFactory.getOrCreateWithChangedFile(fileName, this.options);
const results = new QuickInfoBuilder(this.tsLS, compiler).get(fileName, position);
const templateInfo = getTemplateInfoAtPosition(fileName, position, compiler);
if (templateInfo === undefined) {
return undefined;
}
const positionDetails = getTargetAtPosition(templateInfo.template, position);
if (positionDetails === null) {
return undefined;
}
const results =
new QuickInfoBuilder(this.tsLS, compiler, templateInfo.component, positionDetails.node)
.get();
this.compilerFactory.registerLastKnownProgram();
return results;
}

View File

@ -11,59 +11,50 @@ import {DirectiveSymbol, DomBindingSymbol, ElementSymbol, ExpressionSymbol, Inpu
import * as ts from 'typescript';
import {createDisplayParts, DisplayInfoKind, SYMBOL_PUNC, SYMBOL_SPACE, SYMBOL_TEXT, unsafeCastDisplayInfoKindToScriptElementKind} from './display_parts';
import {findNodeAtPosition} from './hybrid_visitor';
import {filterAliasImports, getDirectiveMatchesForAttribute, getDirectiveMatchesForElementTag, getTemplateInfoAtPosition, getTextSpanOfNode} from './utils';
export class QuickInfoBuilder {
private readonly typeChecker = this.compiler.getNextProgram().getTypeChecker();
constructor(private readonly tsLS: ts.LanguageService, private readonly compiler: NgCompiler) {}
get(fileName: string, position: number): ts.QuickInfo|undefined {
const templateInfo = getTemplateInfoAtPosition(fileName, position, this.compiler);
if (templateInfo === undefined) {
return undefined;
}
const {template, component} = templateInfo;
constructor(
private readonly tsLS: ts.LanguageService, private readonly compiler: NgCompiler,
private readonly component: ts.ClassDeclaration, private node: TmplAstNode|AST) {}
const node = findNodeAtPosition(template, position);
if (node === undefined) {
return undefined;
}
const symbol = this.compiler.getTemplateTypeChecker().getSymbolOfNode(node, component);
get(): ts.QuickInfo|undefined {
const symbol =
this.compiler.getTemplateTypeChecker().getSymbolOfNode(this.node, this.component);
if (symbol === null) {
return isDollarAny(node) ? createDollarAnyQuickInfo(node) : undefined;
return isDollarAny(this.node) ? createDollarAnyQuickInfo(this.node) : undefined;
}
return this.getQuickInfoForSymbol(symbol, node);
return this.getQuickInfoForSymbol(symbol);
}
private getQuickInfoForSymbol(symbol: Symbol, node: TmplAstNode|AST): ts.QuickInfo|undefined {
private getQuickInfoForSymbol(symbol: Symbol): ts.QuickInfo|undefined {
switch (symbol.kind) {
case SymbolKind.Input:
case SymbolKind.Output:
return this.getQuickInfoForBindingSymbol(symbol, node);
return this.getQuickInfoForBindingSymbol(symbol);
case SymbolKind.Template:
return createNgTemplateQuickInfo(node);
return createNgTemplateQuickInfo(this.node);
case SymbolKind.Element:
return this.getQuickInfoForElementSymbol(symbol);
case SymbolKind.Variable:
return this.getQuickInfoForVariableSymbol(symbol, node);
return this.getQuickInfoForVariableSymbol(symbol);
case SymbolKind.Reference:
return this.getQuickInfoForReferenceSymbol(symbol, node);
return this.getQuickInfoForReferenceSymbol(symbol);
case SymbolKind.DomBinding:
return this.getQuickInfoForDomBinding(node, symbol);
return this.getQuickInfoForDomBinding(symbol);
case SymbolKind.Directive:
return this.getQuickInfoAtShimLocation(symbol.shimLocation, node);
return this.getQuickInfoAtShimLocation(symbol.shimLocation);
case SymbolKind.Expression:
return node instanceof BindingPipe ?
this.getQuickInfoForPipeSymbol(symbol, node) :
this.getQuickInfoAtShimLocation(symbol.shimLocation, node);
return this.node instanceof BindingPipe ?
this.getQuickInfoForPipeSymbol(symbol) :
this.getQuickInfoAtShimLocation(symbol.shimLocation);
}
}
private getQuickInfoForBindingSymbol(
symbol: InputBindingSymbol|OutputBindingSymbol, node: TmplAstNode|AST): ts.QuickInfo
private getQuickInfoForBindingSymbol(symbol: InputBindingSymbol|OutputBindingSymbol): ts.QuickInfo
|undefined {
if (symbol.bindings.length === 0) {
return undefined;
@ -72,7 +63,7 @@ export class QuickInfoBuilder {
const kind =
symbol.kind === SymbolKind.Input ? DisplayInfoKind.PROPERTY : DisplayInfoKind.EVENT;
const quickInfo = this.getQuickInfoAtShimLocation(symbol.bindings[0].shimLocation, node);
const quickInfo = this.getQuickInfoAtShimLocation(symbol.bindings[0].shimLocation);
return quickInfo === undefined ? undefined : updateQuickInfoKind(quickInfo, kind);
}
@ -88,43 +79,41 @@ export class QuickInfoBuilder {
undefined /* containerName */, this.typeChecker.typeToString(symbol.tsType));
}
private getQuickInfoForVariableSymbol(symbol: VariableSymbol, node: TmplAstNode|AST):
ts.QuickInfo {
private getQuickInfoForVariableSymbol(symbol: VariableSymbol): ts.QuickInfo {
const documentation = this.getDocumentationFromTypeDefAtLocation(symbol.shimLocation);
return createQuickInfo(
symbol.declaration.name, DisplayInfoKind.VARIABLE, getTextSpanOfNode(node),
symbol.declaration.name, DisplayInfoKind.VARIABLE, getTextSpanOfNode(this.node),
undefined /* containerName */, this.typeChecker.typeToString(symbol.tsType), documentation);
}
private getQuickInfoForReferenceSymbol(symbol: ReferenceSymbol, node: TmplAstNode|AST):
ts.QuickInfo {
private getQuickInfoForReferenceSymbol(symbol: ReferenceSymbol): ts.QuickInfo {
const documentation = this.getDocumentationFromTypeDefAtLocation(symbol.shimLocation);
return createQuickInfo(
symbol.declaration.name, DisplayInfoKind.REFERENCE, getTextSpanOfNode(node),
symbol.declaration.name, DisplayInfoKind.REFERENCE, getTextSpanOfNode(this.node),
undefined /* containerName */, this.typeChecker.typeToString(symbol.tsType), documentation);
}
private getQuickInfoForPipeSymbol(symbol: ExpressionSymbol, node: TmplAstNode|AST): ts.QuickInfo
|undefined {
const quickInfo = this.getQuickInfoAtShimLocation(symbol.shimLocation, node);
private getQuickInfoForPipeSymbol(symbol: ExpressionSymbol): ts.QuickInfo|undefined {
const quickInfo = this.getQuickInfoAtShimLocation(symbol.shimLocation);
return quickInfo === undefined ? undefined :
updateQuickInfoKind(quickInfo, DisplayInfoKind.PIPE);
}
private getQuickInfoForDomBinding(node: TmplAstNode|AST, symbol: DomBindingSymbol) {
if (!(node instanceof TmplAstTextAttribute) && !(node instanceof TmplAstBoundAttribute)) {
private getQuickInfoForDomBinding(symbol: DomBindingSymbol) {
if (!(this.node instanceof TmplAstTextAttribute) &&
!(this.node instanceof TmplAstBoundAttribute)) {
return undefined;
}
const directives = getDirectiveMatchesForAttribute(
node.name, symbol.host.templateNode, symbol.host.directives);
this.node.name, symbol.host.templateNode, symbol.host.directives);
if (directives.size === 0) {
return undefined;
}
return this.getQuickInfoForDirectiveSymbol(directives.values().next().value, node);
return this.getQuickInfoForDirectiveSymbol(directives.values().next().value);
}
private getQuickInfoForDirectiveSymbol(dir: DirectiveSymbol, node: TmplAstNode|AST):
private getQuickInfoForDirectiveSymbol(dir: DirectiveSymbol, node: TmplAstNode|AST = this.node):
ts.QuickInfo {
const kind = dir.isComponent ? DisplayInfoKind.COMPONENT : DisplayInfoKind.DIRECTIVE;
const documentation = this.getDocumentationFromTypeDefAtLocation(dir.shimLocation);
@ -134,8 +123,8 @@ export class QuickInfoBuilder {
}
return createQuickInfo(
this.typeChecker.typeToString(dir.tsType), kind, getTextSpanOfNode(node), containerName,
undefined, documentation);
this.typeChecker.typeToString(dir.tsType), kind, getTextSpanOfNode(this.node),
containerName, undefined, documentation);
}
private getDocumentationFromTypeDefAtLocation(shimLocation: ShimLocation):
@ -149,8 +138,7 @@ export class QuickInfoBuilder {
?.documentation;
}
private getQuickInfoAtShimLocation(location: ShimLocation, node: TmplAstNode|AST): ts.QuickInfo
|undefined {
private getQuickInfoAtShimLocation(location: ShimLocation): ts.QuickInfo|undefined {
const quickInfo =
this.tsLS.getQuickInfoAtPosition(location.shimPath, location.positionInShimFile);
if (quickInfo === undefined || quickInfo.displayParts === undefined) {
@ -159,7 +147,7 @@ export class QuickInfoBuilder {
quickInfo.displayParts = filterAliasImports(quickInfo.displayParts);
const textSpan = getTextSpanOfNode(node);
const textSpan = getTextSpanOfNode(this.node);
return {...quickInfo, textSpan};
}
}

View File

@ -13,20 +13,45 @@ import * as t from '@angular/compiler/src/render3/r3_ast'; // t for temp
import {isTemplateNode, isTemplateNodeWithKeyAndValue} from './utils';
/**
* Return the path to the template AST node or expression AST node that most accurately
* Contextual information for a target position within the template.
*/
export interface TemplateTarget {
/**
* Target position within the template.
*/
position: number;
/**
* The template node (or AST expression) closest to the search position.
*/
node: t.Node|e.AST;
/**
* The `t.Template` which contains the found node or expression (or `null` if in the root
* template).
*/
context: t.Template|null;
/**
* The immediate parent node of the targeted node.
*/
parent: t.Node|e.AST|null;
}
/**
* Return the template AST node or expression AST node that most accurately
* represents the node at the specified cursor `position`.
*
* @param ast AST tree
* @param position cursor position
* @param template AST tree of the template
* @param position target cursor position
*/
export function getPathToNodeAtPosition(ast: t.Node[], position: number): Array<t.Node|e.AST>|
undefined {
const visitor = new R3Visitor(position);
visitor.visitAll(ast);
const candidate = visitor.path[visitor.path.length - 1];
if (!candidate) {
return;
export function getTargetAtPosition(template: t.Node[], position: number): TemplateTarget|null {
const path = TemplateTargetVisitor.visitTemplate(template, position);
if (path.length === 0) {
return null;
}
const candidate = path[path.length - 1];
if (isTemplateNodeWithKeyAndValue(candidate)) {
const {keySpan, valueSpan} = candidate;
const isWithinKeyValue =
@ -34,34 +59,46 @@ export function getPathToNodeAtPosition(ast: t.Node[], position: number): Array<
if (!isWithinKeyValue) {
// If cursor is within source span but not within key span or value span,
// do not return the node.
return;
return null;
}
}
return visitor.path;
// Walk up the result nodes to find the nearest `t.Template` which contains the targeted node.
let context: t.Template|null = null;
for (let i = path.length - 2; i >= 0; i--) {
const node = path[i];
if (node instanceof t.Template) {
context = node;
break;
}
}
let parent: t.Node|e.AST|null = null;
if (path.length >= 2) {
parent = path[path.length - 2];
}
return {position, node: candidate, context, parent};
}
/**
* Return the template AST node or expression AST node that most accurately
* represents the node at the specified cursor `position`.
*
* @param ast AST tree
* @param position cursor position
* Visitor which, given a position and a template, identifies the node within the template at that
* position, as well as records the path of increasingly nested nodes that were traversed to reach
* that position.
*/
export function findNodeAtPosition(ast: t.Node[], position: number): t.Node|e.AST|undefined {
const path = getPathToNodeAtPosition(ast, position);
if (!path) {
return;
}
return path[path.length - 1];
}
class R3Visitor implements t.Visitor {
class TemplateTargetVisitor implements t.Visitor {
// We need to keep a path instead of the last node because we might need more
// context for the last node, for example what is the parent node?
readonly path: Array<t.Node|e.AST> = [];
static visitTemplate(template: t.Node[], position: number): Array<t.Node|e.AST> {
const visitor = new TemplateTargetVisitor(position);
visitor.visitAll(template);
return visitor.path;
}
// Position must be absolute in the source file.
constructor(private readonly position: number) {}
private constructor(private readonly position: number) {}
visit(node: t.Node) {
const {start, end} = getSpanIncludingEndTag(node);

View File

@ -10,7 +10,7 @@ import {ParseError, parseTemplate} from '@angular/compiler';
import * as e from '@angular/compiler/src/expression_parser/ast'; // e for expression AST
import * as t from '@angular/compiler/src/render3/r3_ast'; // t for template AST
import {findNodeAtPosition} from '../hybrid_visitor';
import {getTargetAtPosition} from '../template_target';
import {isExpressionNode, isTemplateNode} from '../utils';
interface ParseResult {
@ -32,11 +32,11 @@ function parse(template: string): ParseResult {
};
}
describe('findNodeAtPosition for template AST', () => {
describe('getTargetAtPosition for template AST', () => {
it('should locate element in opening tag', () => {
const {errors, nodes, position} = parse(`<di¦v></div>`);
expect(errors).toBe(null);
const node = findNodeAtPosition(nodes, position);
const {node} = getTargetAtPosition(nodes, position)!;
expect(isTemplateNode(node!)).toBe(true);
expect(node).toBeInstanceOf(t.Element);
});
@ -44,7 +44,7 @@ describe('findNodeAtPosition for template AST', () => {
it('should locate element in closing tag', () => {
const {errors, nodes, position} = parse(`<div></di¦v>`);
expect(errors).toBe(null);
const node = findNodeAtPosition(nodes, position);
const {node} = getTargetAtPosition(nodes, position)!;
expect(isTemplateNode(node!)).toBe(true);
expect(node).toBeInstanceOf(t.Element);
});
@ -52,7 +52,7 @@ describe('findNodeAtPosition for template AST', () => {
it('should locate element when cursor is at the beginning', () => {
const {errors, nodes, position} = parse(`<¦div></div>`);
expect(errors).toBe(null);
const node = findNodeAtPosition(nodes, position);
const {node} = getTargetAtPosition(nodes, position)!;
expect(isTemplateNode(node!)).toBe(true);
expect(node).toBeInstanceOf(t.Element);
});
@ -60,7 +60,7 @@ describe('findNodeAtPosition for template AST', () => {
it('should locate element when cursor is at the end', () => {
const {errors, nodes, position} = parse(`<div¦></div>`);
expect(errors).toBe(null);
const node = findNodeAtPosition(nodes, position);
const {node} = getTargetAtPosition(nodes, position)!;
expect(isTemplateNode(node!)).toBe(true);
expect(node).toBeInstanceOf(t.Element);
});
@ -68,7 +68,7 @@ describe('findNodeAtPosition for template AST', () => {
it('should locate attribute key', () => {
const {errors, nodes, position} = parse(`<div cla¦ss="foo"></div>`);
expect(errors).toBe(null);
const node = findNodeAtPosition(nodes, position);
const {node} = getTargetAtPosition(nodes, position)!;
expect(isTemplateNode(node!)).toBe(true);
expect(node).toBeInstanceOf(t.TextAttribute);
});
@ -76,7 +76,7 @@ describe('findNodeAtPosition for template AST', () => {
it('should locate attribute value', () => {
const {errors, nodes, position} = parse(`<div class="fo¦o"></div>`);
expect(errors).toBe(null);
const node = findNodeAtPosition(nodes, position);
const {node} = getTargetAtPosition(nodes, position)!;
expect(isTemplateNode(node!)).toBe(true);
// TODO: Note that we do not have the ability to detect the RHS (yet)
expect(node).toBeInstanceOf(t.TextAttribute);
@ -85,7 +85,7 @@ describe('findNodeAtPosition for template AST', () => {
it('should locate bound attribute key', () => {
const {errors, nodes, position} = parse(`<test-cmp [fo¦o]="bar"></test-cmp>`);
expect(errors).toBe(null);
const node = findNodeAtPosition(nodes, position);
const {node} = getTargetAtPosition(nodes, position)!;
expect(isTemplateNode(node!)).toBe(true);
expect(node).toBeInstanceOf(t.BoundAttribute);
});
@ -93,7 +93,7 @@ describe('findNodeAtPosition for template AST', () => {
it('should locate bound attribute value', () => {
const {errors, nodes, position} = parse(`<test-cmp [foo]="b¦ar"></test-cmp>`);
expect(errors).toBe(null);
const node = findNodeAtPosition(nodes, position);
const {node} = getTargetAtPosition(nodes, position)!;
expect(isExpressionNode(node!)).toBe(true);
expect(node).toBeInstanceOf(e.PropertyRead);
});
@ -101,14 +101,14 @@ describe('findNodeAtPosition for template AST', () => {
it('should not locate bound attribute if cursor is between key and value', () => {
const {errors, nodes, position} = parse(`<test-cmp [foo]¦="bar"></test-cmp>`);
expect(errors).toBeNull();
const node = findNodeAtPosition(nodes, position);
expect(node).toBeUndefined();
const nodeInfo = getTargetAtPosition(nodes, position)!;
expect(nodeInfo).toBeNull();
});
it('should locate bound event key', () => {
const {errors, nodes, position} = parse(`<test-cmp (fo¦o)="bar()"></test-cmp>`);
expect(errors).toBe(null);
const node = findNodeAtPosition(nodes, position);
const {node} = getTargetAtPosition(nodes, position)!;
expect(isTemplateNode(node!)).toBe(true);
expect(node).toBeInstanceOf(t.BoundEvent);
});
@ -116,7 +116,7 @@ describe('findNodeAtPosition for template AST', () => {
it('should locate bound event value', () => {
const {errors, nodes, position} = parse(`<test-cmp (foo)="b¦ar()"></test-cmp>`);
expect(errors).toBe(null);
const node = findNodeAtPosition(nodes, position);
const {node} = getTargetAtPosition(nodes, position)!;
expect(isExpressionNode(node!)).toBe(true);
expect(node).toBeInstanceOf(e.MethodCall);
});
@ -124,7 +124,7 @@ describe('findNodeAtPosition for template AST', () => {
it('should locate element children', () => {
const {errors, nodes, position} = parse(`<div><sp¦an></span></div>`);
expect(errors).toBe(null);
const node = findNodeAtPosition(nodes, position);
const {node} = getTargetAtPosition(nodes, position)!;
expect(isTemplateNode(node!)).toBe(true);
expect(node).toBeInstanceOf(t.Element);
expect((node as t.Element).name).toBe('span');
@ -133,7 +133,7 @@ describe('findNodeAtPosition for template AST', () => {
it('should locate element reference', () => {
const {errors, nodes, position} = parse(`<div #my¦div></div>`);
expect(errors).toBe(null);
const node = findNodeAtPosition(nodes, position);
const {node} = getTargetAtPosition(nodes, position)!;
expect(isTemplateNode(node!)).toBe(true);
expect(node).toBeInstanceOf(t.Reference);
});
@ -141,7 +141,7 @@ describe('findNodeAtPosition for template AST', () => {
it('should locate template text attribute', () => {
const {errors, nodes, position} = parse(`<ng-template ng¦If></ng-template>`);
expect(errors).toBe(null);
const node = findNodeAtPosition(nodes, position);
const {node} = getTargetAtPosition(nodes, position)!;
expect(isTemplateNode(node!)).toBe(true);
expect(node).toBeInstanceOf(t.TextAttribute);
});
@ -149,7 +149,7 @@ describe('findNodeAtPosition for template AST', () => {
it('should locate template bound attribute key', () => {
const {errors, nodes, position} = parse(`<ng-template [ng¦If]="foo"></ng-template>`);
expect(errors).toBe(null);
const node = findNodeAtPosition(nodes, position);
const {node} = getTargetAtPosition(nodes, position)!;
expect(isTemplateNode(node!)).toBe(true);
expect(node).toBeInstanceOf(t.BoundAttribute);
});
@ -157,7 +157,7 @@ describe('findNodeAtPosition for template AST', () => {
it('should locate template bound attribute value', () => {
const {errors, nodes, position} = parse(`<ng-template [ngIf]="f¦oo"></ng-template>`);
expect(errors).toBe(null);
const node = findNodeAtPosition(nodes, position);
const {node} = getTargetAtPosition(nodes, position)!;
expect(isExpressionNode(node!)).toBe(true);
expect(node).toBeInstanceOf(e.PropertyRead);
});
@ -165,7 +165,7 @@ describe('findNodeAtPosition for template AST', () => {
it('should locate template bound attribute key in two-way binding', () => {
const {errors, nodes, position} = parse(`<ng-template [(f¦oo)]="bar"></ng-template>`);
expect(errors).toBe(null);
const node = findNodeAtPosition(nodes, position);
const {node} = getTargetAtPosition(nodes, position)!;
expect(isTemplateNode(node!)).toBe(true);
expect(node).toBeInstanceOf(t.BoundAttribute);
expect((node as t.BoundAttribute).name).toBe('foo');
@ -174,7 +174,7 @@ describe('findNodeAtPosition for template AST', () => {
it('should locate template bound attribute value in two-way binding', () => {
const {errors, nodes, position} = parse(`<ng-template [(foo)]="b¦ar"></ng-template>`);
expect(errors).toBe(null);
const node = findNodeAtPosition(nodes, position);
const {node} = getTargetAtPosition(nodes, position)!;
expect(isExpressionNode(node!)).toBe(true);
expect(node).toBeInstanceOf(e.PropertyRead);
expect((node as e.PropertyRead).name).toBe('bar');
@ -183,7 +183,7 @@ describe('findNodeAtPosition for template AST', () => {
it('should locate template bound event key', () => {
const {errors, nodes, position} = parse(`<ng-template (cl¦ick)="foo()"></ng-template>`);
expect(errors).toBe(null);
const node = findNodeAtPosition(nodes, position);
const {node} = getTargetAtPosition(nodes, position)!;
expect(isTemplateNode(node!)).toBe(true);
expect(node).toBeInstanceOf(t.BoundEvent);
});
@ -191,14 +191,14 @@ describe('findNodeAtPosition for template AST', () => {
it('should locate template bound event value', () => {
const {errors, nodes, position} = parse(`<ng-template (click)="f¦oo()"></ng-template>`);
expect(errors).toBe(null);
const node = findNodeAtPosition(nodes, position);
const {node} = getTargetAtPosition(nodes, position)!;
expect(node).toBeInstanceOf(e.MethodCall);
});
it('should locate template attribute key', () => {
const {errors, nodes, position} = parse(`<ng-template i¦d="foo"></ng-template>`);
expect(errors).toBe(null);
const node = findNodeAtPosition(nodes, position);
const {node} = getTargetAtPosition(nodes, position)!;
expect(isTemplateNode(node!)).toBe(true);
expect(node).toBeInstanceOf(t.TextAttribute);
});
@ -206,7 +206,7 @@ describe('findNodeAtPosition for template AST', () => {
it('should locate template attribute value', () => {
const {errors, nodes, position} = parse(`<ng-template id="f¦oo"></ng-template>`);
expect(errors).toBe(null);
const node = findNodeAtPosition(nodes, position);
const {node} = getTargetAtPosition(nodes, position)!;
expect(isTemplateNode(node!)).toBe(true);
// TODO: Note that we do not have the ability to detect the RHS (yet)
expect(node).toBeInstanceOf(t.TextAttribute);
@ -215,7 +215,7 @@ describe('findNodeAtPosition for template AST', () => {
it('should locate template reference key via the # notation', () => {
const {errors, nodes, position} = parse(`<ng-template #f¦oo></ng-template>`);
expect(errors).toBe(null);
const node = findNodeAtPosition(nodes, position);
const {node} = getTargetAtPosition(nodes, position)!;
expect(isTemplateNode(node!)).toBe(true);
expect(node).toBeInstanceOf(t.Reference);
expect((node as t.Reference).name).toBe('foo');
@ -224,7 +224,7 @@ describe('findNodeAtPosition for template AST', () => {
it('should locate template reference key via the ref- notation', () => {
const {errors, nodes, position} = parse(`<ng-template ref-fo¦o></ng-template>`);
expect(errors).toBe(null);
const node = findNodeAtPosition(nodes, position);
const {node} = getTargetAtPosition(nodes, position)!;
expect(isTemplateNode(node!)).toBe(true);
expect(node).toBeInstanceOf(t.Reference);
expect((node as t.Reference).name).toBe('foo');
@ -233,7 +233,7 @@ describe('findNodeAtPosition for template AST', () => {
it('should locate template reference value via the # notation', () => {
const {errors, nodes, position} = parse(`<ng-template #foo="export¦As"></ng-template>`);
expect(errors).toBe(null);
const node = findNodeAtPosition(nodes, position);
const {node} = getTargetAtPosition(nodes, position)!;
expect(isTemplateNode(node!)).toBe(true);
expect(node).toBeInstanceOf(t.Reference);
expect((node as t.Reference).value).toBe('exportAs');
@ -243,7 +243,7 @@ describe('findNodeAtPosition for template AST', () => {
it('should locate template reference value via the ref- notation', () => {
const {errors, nodes, position} = parse(`<ng-template ref-foo="export¦As"></ng-template>`);
expect(errors).toBe(null);
const node = findNodeAtPosition(nodes, position);
const {node} = getTargetAtPosition(nodes, position)!;
expect(isTemplateNode(node!)).toBe(true);
expect(node).toBeInstanceOf(t.Reference);
expect((node as t.Reference).value).toBe('exportAs');
@ -253,7 +253,7 @@ describe('findNodeAtPosition for template AST', () => {
it('should locate template variable key', () => {
const {errors, nodes, position} = parse(`<ng-template let-f¦oo="bar"></ng-template>`);
expect(errors).toBe(null);
const node = findNodeAtPosition(nodes, position);
const {node} = getTargetAtPosition(nodes, position)!;
expect(isTemplateNode(node!)).toBe(true);
expect(node).toBeInstanceOf(t.Variable);
});
@ -261,7 +261,7 @@ describe('findNodeAtPosition for template AST', () => {
it('should locate template variable value', () => {
const {errors, nodes, position} = parse(`<ng-template let-foo="b¦ar"></ng-template>`);
expect(errors).toBe(null);
const node = findNodeAtPosition(nodes, position);
const {node} = getTargetAtPosition(nodes, position)!;
expect(isTemplateNode(node!)).toBe(true);
expect(node).toBeInstanceOf(t.Variable);
});
@ -269,7 +269,7 @@ describe('findNodeAtPosition for template AST', () => {
it('should locate template children', () => {
const {errors, nodes, position} = parse(`<ng-template><d¦iv></div></ng-template>`);
expect(errors).toBe(null);
const node = findNodeAtPosition(nodes, position);
const {node} = getTargetAtPosition(nodes, position)!;
expect(isTemplateNode(node!)).toBe(true);
expect(node).toBeInstanceOf(t.Element);
});
@ -277,7 +277,7 @@ describe('findNodeAtPosition for template AST', () => {
it('should locate ng-content', () => {
const {errors, nodes, position} = parse(`<ng-co¦ntent></ng-content>`);
expect(errors).toBe(null);
const node = findNodeAtPosition(nodes, position);
const {node} = getTargetAtPosition(nodes, position)!;
expect(isTemplateNode(node!)).toBe(true);
expect(node).toBeInstanceOf(t.Content);
});
@ -285,7 +285,7 @@ describe('findNodeAtPosition for template AST', () => {
it('should locate ng-content attribute key', () => {
const {errors, nodes, position} = parse('<ng-content cla¦ss="red"></ng-content>');
expect(errors).toBe(null);
const node = findNodeAtPosition(nodes, position);
const {node} = getTargetAtPosition(nodes, position)!;
expect(isTemplateNode(node!)).toBe(true);
expect(node).toBeInstanceOf(t.TextAttribute);
});
@ -293,7 +293,7 @@ describe('findNodeAtPosition for template AST', () => {
it('should locate ng-content attribute value', () => {
const {errors, nodes, position} = parse('<ng-content class="r¦ed"></ng-content>');
expect(errors).toBe(null);
const node = findNodeAtPosition(nodes, position);
const {node} = getTargetAtPosition(nodes, position)!;
// TODO: Note that we do not have the ability to detect the RHS (yet)
expect(isTemplateNode(node!)).toBe(true);
expect(node).toBeInstanceOf(t.TextAttribute);
@ -302,7 +302,7 @@ describe('findNodeAtPosition for template AST', () => {
it('should not locate implicit receiver', () => {
const {errors, nodes, position} = parse(`<div [foo]="¦bar"></div>`);
expect(errors).toBe(null);
const node = findNodeAtPosition(nodes, position);
const {node} = getTargetAtPosition(nodes, position)!;
expect(isExpressionNode(node!)).toBe(true);
expect(node).toBeInstanceOf(e.PropertyRead);
});
@ -310,7 +310,7 @@ describe('findNodeAtPosition for template AST', () => {
it('should locate bound attribute key in two-way binding', () => {
const {errors, nodes, position} = parse(`<cmp [(f¦oo)]="bar"></cmp>`);
expect(errors).toBe(null);
const node = findNodeAtPosition(nodes, position);
const {node} = getTargetAtPosition(nodes, position)!;
expect(isTemplateNode(node!)).toBe(true);
expect(node).toBeInstanceOf(t.BoundAttribute);
expect((node as t.BoundAttribute).name).toBe('foo');
@ -319,7 +319,7 @@ describe('findNodeAtPosition for template AST', () => {
it('should locate bound attribute value in two-way binding', () => {
const {errors, nodes, position} = parse(`<cmp [(foo)]="b¦ar"></cmp>`);
expect(errors).toBe(null);
const node = findNodeAtPosition(nodes, position);
const {node} = getTargetAtPosition(nodes, position)!;
expect(isExpressionNode(node!)).toBe(true);
expect(node).toBeInstanceOf(e.PropertyRead);
expect((node as e.PropertyRead).name).toBe('bar');
@ -328,7 +328,7 @@ describe('findNodeAtPosition for template AST', () => {
it('should locate switch value in ICUs', () => {
const {errors, nodes, position} = parse(`<span i18n>{sw¦itch, plural, other {text}}"></span>`);
expect(errors).toBe(null);
const node = findNodeAtPosition(nodes, position);
const {node} = getTargetAtPosition(nodes, position)!;
expect(isExpressionNode(node!)).toBe(true);
expect(node).toBeInstanceOf(e.PropertyRead);
expect((node as e.PropertyRead).name).toBe('switch');
@ -338,7 +338,7 @@ describe('findNodeAtPosition for template AST', () => {
const {errors, nodes, position} = parse(
`<span i18n>{expr, plural, other { {ne¦sted, plural, =1 { {{nestedInterpolation}} }} }}"></span>`);
expect(errors).toBe(null);
const node = findNodeAtPosition(nodes, position);
const {node} = getTargetAtPosition(nodes, position)!;
expect(isExpressionNode(node!)).toBe(true);
expect(node).toBeInstanceOf(e.PropertyRead);
expect((node as e.PropertyRead).name).toBe('nested');
@ -348,7 +348,7 @@ describe('findNodeAtPosition for template AST', () => {
const {errors, nodes, position} =
parse(`<span i18n>{expr, plural, other { {{ i¦nterpolation }} }}"></span>`);
expect(errors).toBe(null);
const node = findNodeAtPosition(nodes, position);
const {node} = getTargetAtPosition(nodes, position)!;
expect(isExpressionNode(node!)).toBe(true);
expect(node).toBeInstanceOf(e.PropertyRead);
expect((node as e.PropertyRead).name).toBe('interpolation');
@ -358,18 +358,18 @@ describe('findNodeAtPosition for template AST', () => {
const {errors, nodes, position} = parse(
`<span i18n>{expr, plural, other { {nested, plural, =1 { {{n¦estedInterpolation}} }} }}"></span>`);
expect(errors).toBe(null);
const node = findNodeAtPosition(nodes, position);
const {node} = getTargetAtPosition(nodes, position)!;
expect(isExpressionNode(node!)).toBe(true);
expect(node).toBeInstanceOf(e.PropertyRead);
expect((node as e.PropertyRead).name).toBe('nestedInterpolation');
});
});
describe('findNodeAtPosition for expression AST', () => {
describe('getTargetAtPosition for expression AST', () => {
it('should not locate implicit receiver', () => {
const {errors, nodes, position} = parse(`{{ ¦title }}`);
expect(errors).toBe(null);
const node = findNodeAtPosition(nodes, position);
const {node} = getTargetAtPosition(nodes, position)!;
expect(isExpressionNode(node!)).toBe(true);
expect(node).toBeInstanceOf(e.PropertyRead);
expect((node as e.PropertyRead).name).toBe('title');
@ -378,7 +378,7 @@ describe('findNodeAtPosition for expression AST', () => {
it('should locate property read', () => {
const {errors, nodes, position} = parse(`{{ ti¦tle }}`);
expect(errors).toBe(null);
const node = findNodeAtPosition(nodes, position);
const {node} = getTargetAtPosition(nodes, position)!;
expect(isExpressionNode(node!)).toBe(true);
expect(node).toBeInstanceOf(e.PropertyRead);
expect((node as e.PropertyRead).name).toBe('title');
@ -387,7 +387,7 @@ describe('findNodeAtPosition for expression AST', () => {
it('should locate safe property read', () => {
const {errors, nodes, position} = parse(`{{ foo?¦.bar }}`);
expect(errors).toBe(null);
const node = findNodeAtPosition(nodes, position);
const {node} = getTargetAtPosition(nodes, position)!;
expect(isExpressionNode(node!)).toBe(true);
expect(node).toBeInstanceOf(e.SafePropertyRead);
expect((node as e.SafePropertyRead).name).toBe('bar');
@ -396,7 +396,7 @@ describe('findNodeAtPosition for expression AST', () => {
it('should locate keyed read', () => {
const {errors, nodes, position} = parse(`{{ foo['bar']¦ }}`);
expect(errors).toBe(null);
const node = findNodeAtPosition(nodes, position);
const {node} = getTargetAtPosition(nodes, position)!;
expect(isExpressionNode(node!)).toBe(true);
expect(node).toBeInstanceOf(e.KeyedRead);
});
@ -404,7 +404,7 @@ describe('findNodeAtPosition for expression AST', () => {
it('should locate property write', () => {
const {errors, nodes, position} = parse(`<div (foo)="b¦ar=$event"></div>`);
expect(errors).toBe(null);
const node = findNodeAtPosition(nodes, position);
const {node} = getTargetAtPosition(nodes, position)!;
expect(isExpressionNode(node!)).toBe(true);
expect(node).toBeInstanceOf(e.PropertyWrite);
});
@ -412,7 +412,7 @@ describe('findNodeAtPosition for expression AST', () => {
it('should locate keyed write', () => {
const {errors, nodes, position} = parse(`<div (foo)="bar['baz']¦=$event"></div>`);
expect(errors).toBe(null);
const node = findNodeAtPosition(nodes, position);
const {node} = getTargetAtPosition(nodes, position)!;
expect(isExpressionNode(node!)).toBe(true);
expect(node).toBeInstanceOf(e.KeyedWrite);
});
@ -420,7 +420,7 @@ describe('findNodeAtPosition for expression AST', () => {
it('should locate binary', () => {
const {errors, nodes, position} = parse(`{{ 1 +¦ 2 }}`);
expect(errors).toBe(null);
const node = findNodeAtPosition(nodes, position);
const {node} = getTargetAtPosition(nodes, position)!;
expect(isExpressionNode(node!)).toBe(true);
expect(node).toBeInstanceOf(e.Binary);
});
@ -428,7 +428,7 @@ describe('findNodeAtPosition for expression AST', () => {
it('should locate binding pipe with an identifier', () => {
const {errors, nodes, position} = parse(`{{ title | p¦ }}`);
expect(errors).toBe(null);
const node = findNodeAtPosition(nodes, position);
const {node} = getTargetAtPosition(nodes, position)!;
expect(isExpressionNode(node!)).toBe(true);
expect(node).toBeInstanceOf(e.BindingPipe);
});
@ -439,16 +439,28 @@ describe('findNodeAtPosition for expression AST', () => {
expect(errors![0].toString())
.toContain(
'Unexpected end of input, expected identifier or keyword at the end of the expression');
const node = findNodeAtPosition(nodes, position);
const {node} = getTargetAtPosition(nodes, position)!;
expect(isExpressionNode(node!)).toBe(true);
// TODO: We want this to be a BindingPipe.
expect(node).toBeInstanceOf(e.Interpolation);
});
it('should locate binding pipe without identifier',
() => {
// TODO: We are not able to locate pipe if identifier is missing because the
// parser throws an error. This case is important for autocomplete.
// const {errors, nodes, position} = parse(`{{ title | ¦ }}`);
// expect(errors).toBe(null);
// const {node} = findNodeAtPosition(nodes, position)!;
// expect(isExpressionNode(node!)).toBe(true);
// expect(node).toBeInstanceOf(e.BindingPipe);
});
it('should locate method call', () => {
const {errors, nodes, position} = parse(`{{ title.toString(¦) }}`);
expect(errors).toBe(null);
const node = findNodeAtPosition(nodes, position);
const {node} = getTargetAtPosition(nodes, position)!;
expect(isExpressionNode(node!)).toBe(true);
expect(node).toBeInstanceOf(e.MethodCall);
});
@ -456,7 +468,7 @@ describe('findNodeAtPosition for expression AST', () => {
it('should locate safe method call', () => {
const {errors, nodes, position} = parse(`{{ title?.toString(¦) }}`);
expect(errors).toBe(null);
const node = findNodeAtPosition(nodes, position);
const {node} = getTargetAtPosition(nodes, position)!;
expect(isExpressionNode(node!)).toBe(true);
expect(node).toBeInstanceOf(e.SafeMethodCall);
});
@ -464,7 +476,7 @@ describe('findNodeAtPosition for expression AST', () => {
it('should locate literal primitive in interpolation', () => {
const {errors, nodes, position} = parse(`{{ title.indexOf('t¦') }}`);
expect(errors).toBe(null);
const node = findNodeAtPosition(nodes, position);
const {node} = getTargetAtPosition(nodes, position)!;
expect(isExpressionNode(node!)).toBe(true);
expect(node).toBeInstanceOf(e.LiteralPrimitive);
expect((node as e.LiteralPrimitive).value).toBe('t');
@ -473,7 +485,7 @@ describe('findNodeAtPosition for expression AST', () => {
it('should locate literal primitive in binding', () => {
const {errors, nodes, position} = parse(`<div [id]="'t¦'"></div>`);
expect(errors).toBe(null);
const node = findNodeAtPosition(nodes, position);
const {node} = getTargetAtPosition(nodes, position)!;
expect(isExpressionNode(node!)).toBe(true);
expect(node).toBeInstanceOf(e.LiteralPrimitive);
expect((node as e.LiteralPrimitive).value).toBe('t');
@ -482,7 +494,7 @@ describe('findNodeAtPosition for expression AST', () => {
it('should locate empty expression', () => {
const {errors, nodes, position} = parse(`<div [id]="¦"></div>`);
expect(errors).toBe(null);
const node = findNodeAtPosition(nodes, position);
const {node} = getTargetAtPosition(nodes, position)!;
expect(isExpressionNode(node!)).toBe(true);
expect(node).toBeInstanceOf(e.EmptyExpr);
});
@ -490,7 +502,7 @@ describe('findNodeAtPosition for expression AST', () => {
it('should locate literal array', () => {
const {errors, nodes, position} = parse(`{{ [1, 2,¦ 3] }}`);
expect(errors).toBe(null);
const node = findNodeAtPosition(nodes, position);
const {node} = getTargetAtPosition(nodes, position)!;
expect(isExpressionNode(node!)).toBe(true);
expect(node).toBeInstanceOf(e.LiteralArray);
});
@ -498,7 +510,7 @@ describe('findNodeAtPosition for expression AST', () => {
it('should locate literal map', () => {
const {errors, nodes, position} = parse(`{{ { hello:¦ "world" } }}`);
expect(errors).toBe(null);
const node = findNodeAtPosition(nodes, position);
const {node} = getTargetAtPosition(nodes, position)!;
expect(isExpressionNode(node!)).toBe(true);
expect(node).toBeInstanceOf(e.LiteralMap);
});
@ -506,7 +518,7 @@ describe('findNodeAtPosition for expression AST', () => {
it('should locate conditional', () => {
const {errors, nodes, position} = parse(`{{ cond ?¦ true : false }}`);
expect(errors).toBe(null);
const node = findNodeAtPosition(nodes, position);
const {node} = getTargetAtPosition(nodes, position)!;
expect(isExpressionNode(node!)).toBe(true);
expect(node).toBeInstanceOf(e.Conditional);
});
@ -516,7 +528,7 @@ describe('findNodeAtPosition for microsyntax expression', () => {
it('should locate template key', () => {
const {errors, nodes, position} = parse(`<div *ng¦If="foo"></div>`);
expect(errors).toBe(null);
const node = findNodeAtPosition(nodes, position);
const {node} = getTargetAtPosition(nodes, position)!;
expect(isTemplateNode(node!)).toBe(true);
expect(node).toBeInstanceOf(t.BoundAttribute);
});
@ -524,7 +536,7 @@ describe('findNodeAtPosition for microsyntax expression', () => {
it('should locate template value', () => {
const {errors, nodes, position} = parse(`<div *ngIf="f¦oo"></div>`);
expect(errors).toBe(null);
const node = findNodeAtPosition(nodes, position);
const {node} = getTargetAtPosition(nodes, position)!;
expect(isExpressionNode(node!)).toBe(true);
expect(node).toBeInstanceOf(e.PropertyRead);
});
@ -534,23 +546,23 @@ describe('findNodeAtPosition for microsyntax expression', () => {
// ngFor is a text attribute because the desugared form is
// <ng-template ngFor let-item [ngForOf]="items">
expect(errors).toBe(null);
const node = findNodeAtPosition(nodes, position);
const {node} = getTargetAtPosition(nodes, position)!;
expect(isTemplateNode(node!)).toBeTrue();
expect(node).toBeInstanceOf(t.TextAttribute);
expect((node as t.TextAttribute).name).toBe('ngFor');
});
it('should locate not let keyword', () => {
it('should not locate let keyword', () => {
const {errors, nodes, position} = parse(`<div *ngFor="l¦et item of items"></div>`);
expect(errors).toBe(null);
const node = findNodeAtPosition(nodes, position);
expect(node).toBeUndefined();
expect(errors).toBeNull();
const target = getTargetAtPosition(nodes, position)!;
expect(target).toBeNull();
});
it('should locate let variable', () => {
const {errors, nodes, position} = parse(`<div *ngFor="let i¦tem of items"></div>`);
expect(errors).toBe(null);
const node = findNodeAtPosition(nodes, position);
const {node} = getTargetAtPosition(nodes, position)!;
expect(isTemplateNode(node!)).toBe(true);
expect(node).toBeInstanceOf(t.Variable);
expect((node as t.Variable).name).toBe('item');
@ -559,7 +571,7 @@ describe('findNodeAtPosition for microsyntax expression', () => {
it('should locate bound attribute key', () => {
const {errors, nodes, position} = parse(`<div *ngFor="let item o¦f items"></div>`);
expect(errors).toBe(null);
const node = findNodeAtPosition(nodes, position);
const {node} = getTargetAtPosition(nodes, position)!;
expect(isTemplateNode(node!)).toBe(true);
expect(node).toBeInstanceOf(t.BoundAttribute);
expect((node as t.BoundAttribute).name).toBe('ngForOf');
@ -569,7 +581,7 @@ describe('findNodeAtPosition for microsyntax expression', () => {
const {errors, nodes, position} =
parse(`<div *ngFor="let item of items; trac¦kBy: trackByFn"></div>`);
expect(errors).toBe(null);
const node = findNodeAtPosition(nodes, position);
const {node} = getTargetAtPosition(nodes, position)!;
expect(isTemplateNode(node!)).toBe(true);
expect(node).toBeInstanceOf(t.BoundAttribute);
expect((node as t.BoundAttribute).name).toBe('ngForTrackBy');
@ -582,7 +594,7 @@ describe('findNodeAtPosition for microsyntax expression', () => {
const {errors, nodes, position} =
parse(`<div *ngFor="let item o¦f items; trackBy: trackByFn"></div>`);
expect(errors).toBe(null);
const node = findNodeAtPosition(nodes, position);
const {node} = getTargetAtPosition(nodes, position)!;
expect(isTemplateNode(node!)).toBe(true);
expect(node).toBeInstanceOf(t.BoundAttribute);
expect((node as t.BoundAttribute).name).toBe('ngForOf');
@ -591,7 +603,7 @@ describe('findNodeAtPosition for microsyntax expression', () => {
it('should locate bound attribute value', () => {
const {errors, nodes, position} = parse(`<div *ngFor="let item of it¦ems"></div>`);
expect(errors).toBe(null);
const node = findNodeAtPosition(nodes, position);
const {node} = getTargetAtPosition(nodes, position)!;
expect(isExpressionNode(node!)).toBe(true);
expect(node).toBeInstanceOf(e.PropertyRead);
expect((node as e.PropertyRead).name).toBe('items');
@ -600,10 +612,11 @@ describe('findNodeAtPosition for microsyntax expression', () => {
it('should locate template children', () => {
const {errors, nodes, position} = parse(`<di¦v *ngIf></div>`);
expect(errors).toBe(null);
const node = findNodeAtPosition(nodes, position);
const {node, context} = getTargetAtPosition(nodes, position)!;
expect(isTemplateNode(node!)).toBe(true);
expect(node).toBeInstanceOf(t.Element);
expect((node as t.Element).name).toBe('div');
expect(context).toBeInstanceOf(t.Template);
});
it('should locate property read of variable declared within template', () => {
@ -612,7 +625,7 @@ describe('findNodeAtPosition for microsyntax expression', () => {
{{ i¦ }}
</div>`);
expect(errors).toBe(null);
const node = findNodeAtPosition(nodes, position);
const {node} = getTargetAtPosition(nodes, position)!;
expect(isExpressionNode(node!)).toBe(true);
expect(node).toBeInstanceOf(e.PropertyRead);
});
@ -620,7 +633,7 @@ describe('findNodeAtPosition for microsyntax expression', () => {
it('should locate LHS of variable declaration', () => {
const {errors, nodes, position} = parse(`<div *ngFor="let item of items; let i¦=index">`);
expect(errors).toBe(null);
const node = findNodeAtPosition(nodes, position);
const {node} = getTargetAtPosition(nodes, position)!;
expect(isTemplateNode(node!)).toBe(true);
expect(node).toBeInstanceOf(t.Variable);
// TODO: Currently there is no way to distinguish LHS from RHS
@ -630,7 +643,7 @@ describe('findNodeAtPosition for microsyntax expression', () => {
it('should locate RHS of variable declaration', () => {
const {errors, nodes, position} = parse(`<div *ngFor="let item of items; let i=in¦dex">`);
expect(errors).toBe(null);
const node = findNodeAtPosition(nodes, position);
const {node} = getTargetAtPosition(nodes, position)!;
expect(isTemplateNode(node!)).toBe(true);
expect(node).toBeInstanceOf(t.Variable);
// TODO: Currently there is no way to distinguish LHS from RHS