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 {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 {DirectiveSymbol, DomBindingSymbol, ElementSymbol, ShimLocation, Symbol, SymbolKind, TemplateSymbol} from '@angular/compiler-cli/src/ngtsc/typecheck/api';
import * as ts from 'typescript'; 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'; 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 { interface DefinitionMeta {
node: AST|TmplAstNode; node: AST|TmplAstNode;
path: Array<AST|TmplAstNode>; parent: AST|TmplAstNode|null;
symbol: Symbol; symbol: Symbol;
} }
@ -156,7 +155,7 @@ export class DefinitionBuilder {
return {definitions, textSpan: getTextSpanOfNode(definitionMeta.node)}; 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 { TemplateInfo): readonly ts.DefinitionInfo[]|undefined {
switch (symbol.kind) { switch (symbol.kind) {
case SymbolKind.Directive: case SymbolKind.Directive:
@ -174,7 +173,7 @@ export class DefinitionBuilder {
const bindingDefs = this.getDefinitionsForSymbols(...symbol.bindings); const bindingDefs = this.getDefinitionsForSymbols(...symbol.bindings);
// Also attempt to get directive matches for the input name. If there is a directive that // 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. // 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]; return [...bindingDefs, ...directiveDefs];
} }
case SymbolKind.Variable: 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 // 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. // has the input name as part of the selector, we want to return that as well.
const directiveDefs = this.getDirectiveTypeDefsForBindingNode( const directiveDefs = this.getDirectiveTypeDefsForBindingNode(
node, definitionMeta.path, templateInfo.component); node, definitionMeta.parent, templateInfo.component);
return [...bindingDefs, ...directiveDefs]; return [...bindingDefs, ...directiveDefs];
} }
case SymbolKind.Reference: case SymbolKind.Reference:
@ -273,13 +272,13 @@ export class DefinitionBuilder {
} }
private getDirectiveTypeDefsForBindingNode( 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) && if (!(node instanceof TmplAstBoundAttribute) && !(node instanceof TmplAstTextAttribute) &&
!(node instanceof TmplAstBoundEvent)) { !(node instanceof TmplAstBoundEvent)) {
return []; return [];
} }
const parent = pathToNode[pathToNode.length - 2]; if (parent === null ||
if (!(parent instanceof TmplAstTemplate || parent instanceof TmplAstElement)) { !(parent instanceof TmplAstTemplate || parent instanceof TmplAstElement)) {
return []; return [];
} }
const templateOrElementSymbol = const templateOrElementSymbol =
@ -303,16 +302,16 @@ export class DefinitionBuilder {
private getDefinitionMetaAtPosition({template, component}: TemplateInfo, position: number): private getDefinitionMetaAtPosition({template, component}: TemplateInfo, position: number):
DefinitionMeta|undefined { DefinitionMeta|undefined {
const path = getPathToNodeAtPosition(template, position); const target = getTargetAtPosition(template, position);
if (path === undefined) { if (target === null) {
return; return undefined;
} }
const {node, parent} = target;
const node = path[path.length - 1];
const symbol = this.compiler.getTemplateTypeChecker().getSymbolOfNode(node, component); const symbol = this.compiler.getTemplateTypeChecker().getSymbolOfNode(node, component);
if (symbol === null) { 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 {DefinitionBuilder} from './definitions';
import {LanguageServiceAdapter} from './language_service_adapter'; import {LanguageServiceAdapter} from './language_service_adapter';
import {QuickInfoBuilder} from './quick_info'; import {QuickInfoBuilder} from './quick_info';
import {isTypeScriptFile} from './utils'; import {getTargetAtPosition} from './template_target';
import {getTemplateInfoAtPosition, isTypeScriptFile} from './utils';
export class LanguageService { export class LanguageService {
private options: CompilerOptions; private options: CompilerOptions;
@ -73,8 +74,19 @@ export class LanguageService {
} }
getQuickInfoAtPosition(fileName: string, position: number): ts.QuickInfo|undefined { getQuickInfoAtPosition(fileName: string, position: number): ts.QuickInfo|undefined {
const program = this.strategy.getProgram();
const compiler = this.compilerFactory.getOrCreateWithChangedFile(fileName, this.options); 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(); this.compilerFactory.registerLastKnownProgram();
return results; return results;
} }

View File

@ -11,59 +11,50 @@ import {DirectiveSymbol, DomBindingSymbol, ElementSymbol, ExpressionSymbol, Inpu
import * as ts from 'typescript'; import * as ts from 'typescript';
import {createDisplayParts, DisplayInfoKind, SYMBOL_PUNC, SYMBOL_SPACE, SYMBOL_TEXT, unsafeCastDisplayInfoKindToScriptElementKind} from './display_parts'; 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'; import {filterAliasImports, getDirectiveMatchesForAttribute, getDirectiveMatchesForElementTag, getTemplateInfoAtPosition, getTextSpanOfNode} from './utils';
export class QuickInfoBuilder { export class QuickInfoBuilder {
private readonly typeChecker = this.compiler.getNextProgram().getTypeChecker(); 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 { constructor(
const templateInfo = getTemplateInfoAtPosition(fileName, position, this.compiler); private readonly tsLS: ts.LanguageService, private readonly compiler: NgCompiler,
if (templateInfo === undefined) { private readonly component: ts.ClassDeclaration, private node: TmplAstNode|AST) {}
return undefined;
}
const {template, component} = templateInfo;
const node = findNodeAtPosition(template, position); get(): ts.QuickInfo|undefined {
if (node === undefined) { const symbol =
return undefined; this.compiler.getTemplateTypeChecker().getSymbolOfNode(this.node, this.component);
}
const symbol = this.compiler.getTemplateTypeChecker().getSymbolOfNode(node, component);
if (symbol === null) { 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) { switch (symbol.kind) {
case SymbolKind.Input: case SymbolKind.Input:
case SymbolKind.Output: case SymbolKind.Output:
return this.getQuickInfoForBindingSymbol(symbol, node); return this.getQuickInfoForBindingSymbol(symbol);
case SymbolKind.Template: case SymbolKind.Template:
return createNgTemplateQuickInfo(node); return createNgTemplateQuickInfo(this.node);
case SymbolKind.Element: case SymbolKind.Element:
return this.getQuickInfoForElementSymbol(symbol); return this.getQuickInfoForElementSymbol(symbol);
case SymbolKind.Variable: case SymbolKind.Variable:
return this.getQuickInfoForVariableSymbol(symbol, node); return this.getQuickInfoForVariableSymbol(symbol);
case SymbolKind.Reference: case SymbolKind.Reference:
return this.getQuickInfoForReferenceSymbol(symbol, node); return this.getQuickInfoForReferenceSymbol(symbol);
case SymbolKind.DomBinding: case SymbolKind.DomBinding:
return this.getQuickInfoForDomBinding(node, symbol); return this.getQuickInfoForDomBinding(symbol);
case SymbolKind.Directive: case SymbolKind.Directive:
return this.getQuickInfoAtShimLocation(symbol.shimLocation, node); return this.getQuickInfoAtShimLocation(symbol.shimLocation);
case SymbolKind.Expression: case SymbolKind.Expression:
return node instanceof BindingPipe ? return this.node instanceof BindingPipe ?
this.getQuickInfoForPipeSymbol(symbol, node) : this.getQuickInfoForPipeSymbol(symbol) :
this.getQuickInfoAtShimLocation(symbol.shimLocation, node); this.getQuickInfoAtShimLocation(symbol.shimLocation);
} }
} }
private getQuickInfoForBindingSymbol( private getQuickInfoForBindingSymbol(symbol: InputBindingSymbol|OutputBindingSymbol): ts.QuickInfo
symbol: InputBindingSymbol|OutputBindingSymbol, node: TmplAstNode|AST): ts.QuickInfo
|undefined { |undefined {
if (symbol.bindings.length === 0) { if (symbol.bindings.length === 0) {
return undefined; return undefined;
@ -72,7 +63,7 @@ export class QuickInfoBuilder {
const kind = const kind =
symbol.kind === SymbolKind.Input ? DisplayInfoKind.PROPERTY : DisplayInfoKind.EVENT; 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); return quickInfo === undefined ? undefined : updateQuickInfoKind(quickInfo, kind);
} }
@ -88,43 +79,41 @@ export class QuickInfoBuilder {
undefined /* containerName */, this.typeChecker.typeToString(symbol.tsType)); undefined /* containerName */, this.typeChecker.typeToString(symbol.tsType));
} }
private getQuickInfoForVariableSymbol(symbol: VariableSymbol, node: TmplAstNode|AST): private getQuickInfoForVariableSymbol(symbol: VariableSymbol): ts.QuickInfo {
ts.QuickInfo {
const documentation = this.getDocumentationFromTypeDefAtLocation(symbol.shimLocation); const documentation = this.getDocumentationFromTypeDefAtLocation(symbol.shimLocation);
return createQuickInfo( 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); undefined /* containerName */, this.typeChecker.typeToString(symbol.tsType), documentation);
} }
private getQuickInfoForReferenceSymbol(symbol: ReferenceSymbol, node: TmplAstNode|AST): private getQuickInfoForReferenceSymbol(symbol: ReferenceSymbol): ts.QuickInfo {
ts.QuickInfo {
const documentation = this.getDocumentationFromTypeDefAtLocation(symbol.shimLocation); const documentation = this.getDocumentationFromTypeDefAtLocation(symbol.shimLocation);
return createQuickInfo( 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); undefined /* containerName */, this.typeChecker.typeToString(symbol.tsType), documentation);
} }
private getQuickInfoForPipeSymbol(symbol: ExpressionSymbol, node: TmplAstNode|AST): ts.QuickInfo private getQuickInfoForPipeSymbol(symbol: ExpressionSymbol): ts.QuickInfo|undefined {
|undefined { const quickInfo = this.getQuickInfoAtShimLocation(symbol.shimLocation);
const quickInfo = this.getQuickInfoAtShimLocation(symbol.shimLocation, node);
return quickInfo === undefined ? undefined : return quickInfo === undefined ? undefined :
updateQuickInfoKind(quickInfo, DisplayInfoKind.PIPE); updateQuickInfoKind(quickInfo, DisplayInfoKind.PIPE);
} }
private getQuickInfoForDomBinding(node: TmplAstNode|AST, symbol: DomBindingSymbol) { private getQuickInfoForDomBinding(symbol: DomBindingSymbol) {
if (!(node instanceof TmplAstTextAttribute) && !(node instanceof TmplAstBoundAttribute)) { if (!(this.node instanceof TmplAstTextAttribute) &&
!(this.node instanceof TmplAstBoundAttribute)) {
return undefined; return undefined;
} }
const directives = getDirectiveMatchesForAttribute( const directives = getDirectiveMatchesForAttribute(
node.name, symbol.host.templateNode, symbol.host.directives); this.node.name, symbol.host.templateNode, symbol.host.directives);
if (directives.size === 0) { if (directives.size === 0) {
return undefined; 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 { ts.QuickInfo {
const kind = dir.isComponent ? DisplayInfoKind.COMPONENT : DisplayInfoKind.DIRECTIVE; const kind = dir.isComponent ? DisplayInfoKind.COMPONENT : DisplayInfoKind.DIRECTIVE;
const documentation = this.getDocumentationFromTypeDefAtLocation(dir.shimLocation); const documentation = this.getDocumentationFromTypeDefAtLocation(dir.shimLocation);
@ -134,8 +123,8 @@ export class QuickInfoBuilder {
} }
return createQuickInfo( return createQuickInfo(
this.typeChecker.typeToString(dir.tsType), kind, getTextSpanOfNode(node), containerName, this.typeChecker.typeToString(dir.tsType), kind, getTextSpanOfNode(this.node),
undefined, documentation); containerName, undefined, documentation);
} }
private getDocumentationFromTypeDefAtLocation(shimLocation: ShimLocation): private getDocumentationFromTypeDefAtLocation(shimLocation: ShimLocation):
@ -149,8 +138,7 @@ export class QuickInfoBuilder {
?.documentation; ?.documentation;
} }
private getQuickInfoAtShimLocation(location: ShimLocation, node: TmplAstNode|AST): ts.QuickInfo private getQuickInfoAtShimLocation(location: ShimLocation): ts.QuickInfo|undefined {
|undefined {
const quickInfo = const quickInfo =
this.tsLS.getQuickInfoAtPosition(location.shimPath, location.positionInShimFile); this.tsLS.getQuickInfoAtPosition(location.shimPath, location.positionInShimFile);
if (quickInfo === undefined || quickInfo.displayParts === undefined) { if (quickInfo === undefined || quickInfo.displayParts === undefined) {
@ -159,7 +147,7 @@ export class QuickInfoBuilder {
quickInfo.displayParts = filterAliasImports(quickInfo.displayParts); quickInfo.displayParts = filterAliasImports(quickInfo.displayParts);
const textSpan = getTextSpanOfNode(node); const textSpan = getTextSpanOfNode(this.node);
return {...quickInfo, textSpan}; 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'; 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`. * represents the node at the specified cursor `position`.
* *
* @param ast AST tree * @param template AST tree of the template
* @param position cursor position * @param position target cursor position
*/ */
export function getPathToNodeAtPosition(ast: t.Node[], position: number): Array<t.Node|e.AST>| export function getTargetAtPosition(template: t.Node[], position: number): TemplateTarget|null {
undefined { const path = TemplateTargetVisitor.visitTemplate(template, position);
const visitor = new R3Visitor(position); if (path.length === 0) {
visitor.visitAll(ast); return null;
const candidate = visitor.path[visitor.path.length - 1];
if (!candidate) {
return;
} }
const candidate = path[path.length - 1];
if (isTemplateNodeWithKeyAndValue(candidate)) { if (isTemplateNodeWithKeyAndValue(candidate)) {
const {keySpan, valueSpan} = candidate; const {keySpan, valueSpan} = candidate;
const isWithinKeyValue = const isWithinKeyValue =
@ -34,34 +59,46 @@ export function getPathToNodeAtPosition(ast: t.Node[], position: number): Array<
if (!isWithinKeyValue) { if (!isWithinKeyValue) {
// If cursor is within source span but not within key span or value span, // If cursor is within source span but not within key span or value span,
// do not return the node. // 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 * Visitor which, given a position and a template, identifies the node within the template at that
* represents the node at the specified cursor `position`. * position, as well as records the path of increasingly nested nodes that were traversed to reach
* * that position.
* @param ast AST tree
* @param position cursor position
*/ */
export function findNodeAtPosition(ast: t.Node[], position: number): t.Node|e.AST|undefined { class TemplateTargetVisitor implements t.Visitor {
const path = getPathToNodeAtPosition(ast, position);
if (!path) {
return;
}
return path[path.length - 1];
}
class R3Visitor implements t.Visitor {
// We need to keep a path instead of the last node because we might need more // 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? // context for the last node, for example what is the parent node?
readonly path: Array<t.Node|e.AST> = []; 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. // Position must be absolute in the source file.
constructor(private readonly position: number) {} private constructor(private readonly position: number) {}
visit(node: t.Node) { visit(node: t.Node) {
const {start, end} = getSpanIncludingEndTag(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 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 * 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'; import {isExpressionNode, isTemplateNode} from '../utils';
interface ParseResult { 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', () => { it('should locate element in opening tag', () => {
const {errors, nodes, position} = parse(`<di¦v></div>`); const {errors, nodes, position} = parse(`<di¦v></div>`);
expect(errors).toBe(null); expect(errors).toBe(null);
const node = findNodeAtPosition(nodes, position); const {node} = getTargetAtPosition(nodes, position)!;
expect(isTemplateNode(node!)).toBe(true); expect(isTemplateNode(node!)).toBe(true);
expect(node).toBeInstanceOf(t.Element); expect(node).toBeInstanceOf(t.Element);
}); });
@ -44,7 +44,7 @@ describe('findNodeAtPosition for template AST', () => {
it('should locate element in closing tag', () => { it('should locate element in closing tag', () => {
const {errors, nodes, position} = parse(`<div></di¦v>`); const {errors, nodes, position} = parse(`<div></di¦v>`);
expect(errors).toBe(null); expect(errors).toBe(null);
const node = findNodeAtPosition(nodes, position); const {node} = getTargetAtPosition(nodes, position)!;
expect(isTemplateNode(node!)).toBe(true); expect(isTemplateNode(node!)).toBe(true);
expect(node).toBeInstanceOf(t.Element); expect(node).toBeInstanceOf(t.Element);
}); });
@ -52,7 +52,7 @@ describe('findNodeAtPosition for template AST', () => {
it('should locate element when cursor is at the beginning', () => { it('should locate element when cursor is at the beginning', () => {
const {errors, nodes, position} = parse(`<¦div></div>`); const {errors, nodes, position} = parse(`<¦div></div>`);
expect(errors).toBe(null); expect(errors).toBe(null);
const node = findNodeAtPosition(nodes, position); const {node} = getTargetAtPosition(nodes, position)!;
expect(isTemplateNode(node!)).toBe(true); expect(isTemplateNode(node!)).toBe(true);
expect(node).toBeInstanceOf(t.Element); expect(node).toBeInstanceOf(t.Element);
}); });
@ -60,7 +60,7 @@ describe('findNodeAtPosition for template AST', () => {
it('should locate element when cursor is at the end', () => { it('should locate element when cursor is at the end', () => {
const {errors, nodes, position} = parse(`<div¦></div>`); const {errors, nodes, position} = parse(`<div¦></div>`);
expect(errors).toBe(null); expect(errors).toBe(null);
const node = findNodeAtPosition(nodes, position); const {node} = getTargetAtPosition(nodes, position)!;
expect(isTemplateNode(node!)).toBe(true); expect(isTemplateNode(node!)).toBe(true);
expect(node).toBeInstanceOf(t.Element); expect(node).toBeInstanceOf(t.Element);
}); });
@ -68,7 +68,7 @@ describe('findNodeAtPosition for template AST', () => {
it('should locate attribute key', () => { it('should locate attribute key', () => {
const {errors, nodes, position} = parse(`<div cla¦ss="foo"></div>`); const {errors, nodes, position} = parse(`<div cla¦ss="foo"></div>`);
expect(errors).toBe(null); expect(errors).toBe(null);
const node = findNodeAtPosition(nodes, position); const {node} = getTargetAtPosition(nodes, position)!;
expect(isTemplateNode(node!)).toBe(true); expect(isTemplateNode(node!)).toBe(true);
expect(node).toBeInstanceOf(t.TextAttribute); expect(node).toBeInstanceOf(t.TextAttribute);
}); });
@ -76,7 +76,7 @@ describe('findNodeAtPosition for template AST', () => {
it('should locate attribute value', () => { it('should locate attribute value', () => {
const {errors, nodes, position} = parse(`<div class="fo¦o"></div>`); const {errors, nodes, position} = parse(`<div class="fo¦o"></div>`);
expect(errors).toBe(null); expect(errors).toBe(null);
const node = findNodeAtPosition(nodes, position); const {node} = getTargetAtPosition(nodes, position)!;
expect(isTemplateNode(node!)).toBe(true); expect(isTemplateNode(node!)).toBe(true);
// TODO: Note that we do not have the ability to detect the RHS (yet) // TODO: Note that we do not have the ability to detect the RHS (yet)
expect(node).toBeInstanceOf(t.TextAttribute); expect(node).toBeInstanceOf(t.TextAttribute);
@ -85,7 +85,7 @@ describe('findNodeAtPosition for template AST', () => {
it('should locate bound attribute key', () => { it('should locate bound attribute key', () => {
const {errors, nodes, position} = parse(`<test-cmp [fo¦o]="bar"></test-cmp>`); const {errors, nodes, position} = parse(`<test-cmp [fo¦o]="bar"></test-cmp>`);
expect(errors).toBe(null); expect(errors).toBe(null);
const node = findNodeAtPosition(nodes, position); const {node} = getTargetAtPosition(nodes, position)!;
expect(isTemplateNode(node!)).toBe(true); expect(isTemplateNode(node!)).toBe(true);
expect(node).toBeInstanceOf(t.BoundAttribute); expect(node).toBeInstanceOf(t.BoundAttribute);
}); });
@ -93,7 +93,7 @@ describe('findNodeAtPosition for template AST', () => {
it('should locate bound attribute value', () => { it('should locate bound attribute value', () => {
const {errors, nodes, position} = parse(`<test-cmp [foo]="b¦ar"></test-cmp>`); const {errors, nodes, position} = parse(`<test-cmp [foo]="b¦ar"></test-cmp>`);
expect(errors).toBe(null); expect(errors).toBe(null);
const node = findNodeAtPosition(nodes, position); const {node} = getTargetAtPosition(nodes, position)!;
expect(isExpressionNode(node!)).toBe(true); expect(isExpressionNode(node!)).toBe(true);
expect(node).toBeInstanceOf(e.PropertyRead); 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', () => { it('should not locate bound attribute if cursor is between key and value', () => {
const {errors, nodes, position} = parse(`<test-cmp [foo]¦="bar"></test-cmp>`); const {errors, nodes, position} = parse(`<test-cmp [foo]¦="bar"></test-cmp>`);
expect(errors).toBeNull(); expect(errors).toBeNull();
const node = findNodeAtPosition(nodes, position); const nodeInfo = getTargetAtPosition(nodes, position)!;
expect(node).toBeUndefined(); expect(nodeInfo).toBeNull();
}); });
it('should locate bound event key', () => { it('should locate bound event key', () => {
const {errors, nodes, position} = parse(`<test-cmp (fo¦o)="bar()"></test-cmp>`); const {errors, nodes, position} = parse(`<test-cmp (fo¦o)="bar()"></test-cmp>`);
expect(errors).toBe(null); expect(errors).toBe(null);
const node = findNodeAtPosition(nodes, position); const {node} = getTargetAtPosition(nodes, position)!;
expect(isTemplateNode(node!)).toBe(true); expect(isTemplateNode(node!)).toBe(true);
expect(node).toBeInstanceOf(t.BoundEvent); expect(node).toBeInstanceOf(t.BoundEvent);
}); });
@ -116,7 +116,7 @@ describe('findNodeAtPosition for template AST', () => {
it('should locate bound event value', () => { it('should locate bound event value', () => {
const {errors, nodes, position} = parse(`<test-cmp (foo)="b¦ar()"></test-cmp>`); const {errors, nodes, position} = parse(`<test-cmp (foo)="b¦ar()"></test-cmp>`);
expect(errors).toBe(null); expect(errors).toBe(null);
const node = findNodeAtPosition(nodes, position); const {node} = getTargetAtPosition(nodes, position)!;
expect(isExpressionNode(node!)).toBe(true); expect(isExpressionNode(node!)).toBe(true);
expect(node).toBeInstanceOf(e.MethodCall); expect(node).toBeInstanceOf(e.MethodCall);
}); });
@ -124,7 +124,7 @@ describe('findNodeAtPosition for template AST', () => {
it('should locate element children', () => { it('should locate element children', () => {
const {errors, nodes, position} = parse(`<div><sp¦an></span></div>`); const {errors, nodes, position} = parse(`<div><sp¦an></span></div>`);
expect(errors).toBe(null); expect(errors).toBe(null);
const node = findNodeAtPosition(nodes, position); const {node} = getTargetAtPosition(nodes, position)!;
expect(isTemplateNode(node!)).toBe(true); expect(isTemplateNode(node!)).toBe(true);
expect(node).toBeInstanceOf(t.Element); expect(node).toBeInstanceOf(t.Element);
expect((node as t.Element).name).toBe('span'); expect((node as t.Element).name).toBe('span');
@ -133,7 +133,7 @@ describe('findNodeAtPosition for template AST', () => {
it('should locate element reference', () => { it('should locate element reference', () => {
const {errors, nodes, position} = parse(`<div #my¦div></div>`); const {errors, nodes, position} = parse(`<div #my¦div></div>`);
expect(errors).toBe(null); expect(errors).toBe(null);
const node = findNodeAtPosition(nodes, position); const {node} = getTargetAtPosition(nodes, position)!;
expect(isTemplateNode(node!)).toBe(true); expect(isTemplateNode(node!)).toBe(true);
expect(node).toBeInstanceOf(t.Reference); expect(node).toBeInstanceOf(t.Reference);
}); });
@ -141,7 +141,7 @@ describe('findNodeAtPosition for template AST', () => {
it('should locate template text attribute', () => { it('should locate template text attribute', () => {
const {errors, nodes, position} = parse(`<ng-template ng¦If></ng-template>`); const {errors, nodes, position} = parse(`<ng-template ng¦If></ng-template>`);
expect(errors).toBe(null); expect(errors).toBe(null);
const node = findNodeAtPosition(nodes, position); const {node} = getTargetAtPosition(nodes, position)!;
expect(isTemplateNode(node!)).toBe(true); expect(isTemplateNode(node!)).toBe(true);
expect(node).toBeInstanceOf(t.TextAttribute); expect(node).toBeInstanceOf(t.TextAttribute);
}); });
@ -149,7 +149,7 @@ describe('findNodeAtPosition for template AST', () => {
it('should locate template bound attribute key', () => { it('should locate template bound attribute key', () => {
const {errors, nodes, position} = parse(`<ng-template [ng¦If]="foo"></ng-template>`); const {errors, nodes, position} = parse(`<ng-template [ng¦If]="foo"></ng-template>`);
expect(errors).toBe(null); expect(errors).toBe(null);
const node = findNodeAtPosition(nodes, position); const {node} = getTargetAtPosition(nodes, position)!;
expect(isTemplateNode(node!)).toBe(true); expect(isTemplateNode(node!)).toBe(true);
expect(node).toBeInstanceOf(t.BoundAttribute); expect(node).toBeInstanceOf(t.BoundAttribute);
}); });
@ -157,7 +157,7 @@ describe('findNodeAtPosition for template AST', () => {
it('should locate template bound attribute value', () => { it('should locate template bound attribute value', () => {
const {errors, nodes, position} = parse(`<ng-template [ngIf]="f¦oo"></ng-template>`); const {errors, nodes, position} = parse(`<ng-template [ngIf]="f¦oo"></ng-template>`);
expect(errors).toBe(null); expect(errors).toBe(null);
const node = findNodeAtPosition(nodes, position); const {node} = getTargetAtPosition(nodes, position)!;
expect(isExpressionNode(node!)).toBe(true); expect(isExpressionNode(node!)).toBe(true);
expect(node).toBeInstanceOf(e.PropertyRead); 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', () => { it('should locate template bound attribute key in two-way binding', () => {
const {errors, nodes, position} = parse(`<ng-template [(f¦oo)]="bar"></ng-template>`); const {errors, nodes, position} = parse(`<ng-template [(f¦oo)]="bar"></ng-template>`);
expect(errors).toBe(null); expect(errors).toBe(null);
const node = findNodeAtPosition(nodes, position); const {node} = getTargetAtPosition(nodes, position)!;
expect(isTemplateNode(node!)).toBe(true); expect(isTemplateNode(node!)).toBe(true);
expect(node).toBeInstanceOf(t.BoundAttribute); expect(node).toBeInstanceOf(t.BoundAttribute);
expect((node as t.BoundAttribute).name).toBe('foo'); 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', () => { it('should locate template bound attribute value in two-way binding', () => {
const {errors, nodes, position} = parse(`<ng-template [(foo)]="b¦ar"></ng-template>`); const {errors, nodes, position} = parse(`<ng-template [(foo)]="b¦ar"></ng-template>`);
expect(errors).toBe(null); expect(errors).toBe(null);
const node = findNodeAtPosition(nodes, position); const {node} = getTargetAtPosition(nodes, position)!;
expect(isExpressionNode(node!)).toBe(true); expect(isExpressionNode(node!)).toBe(true);
expect(node).toBeInstanceOf(e.PropertyRead); expect(node).toBeInstanceOf(e.PropertyRead);
expect((node as e.PropertyRead).name).toBe('bar'); expect((node as e.PropertyRead).name).toBe('bar');
@ -183,7 +183,7 @@ describe('findNodeAtPosition for template AST', () => {
it('should locate template bound event key', () => { it('should locate template bound event key', () => {
const {errors, nodes, position} = parse(`<ng-template (cl¦ick)="foo()"></ng-template>`); const {errors, nodes, position} = parse(`<ng-template (cl¦ick)="foo()"></ng-template>`);
expect(errors).toBe(null); expect(errors).toBe(null);
const node = findNodeAtPosition(nodes, position); const {node} = getTargetAtPosition(nodes, position)!;
expect(isTemplateNode(node!)).toBe(true); expect(isTemplateNode(node!)).toBe(true);
expect(node).toBeInstanceOf(t.BoundEvent); expect(node).toBeInstanceOf(t.BoundEvent);
}); });
@ -191,14 +191,14 @@ describe('findNodeAtPosition for template AST', () => {
it('should locate template bound event value', () => { it('should locate template bound event value', () => {
const {errors, nodes, position} = parse(`<ng-template (click)="f¦oo()"></ng-template>`); const {errors, nodes, position} = parse(`<ng-template (click)="f¦oo()"></ng-template>`);
expect(errors).toBe(null); expect(errors).toBe(null);
const node = findNodeAtPosition(nodes, position); const {node} = getTargetAtPosition(nodes, position)!;
expect(node).toBeInstanceOf(e.MethodCall); expect(node).toBeInstanceOf(e.MethodCall);
}); });
it('should locate template attribute key', () => { it('should locate template attribute key', () => {
const {errors, nodes, position} = parse(`<ng-template i¦d="foo"></ng-template>`); const {errors, nodes, position} = parse(`<ng-template i¦d="foo"></ng-template>`);
expect(errors).toBe(null); expect(errors).toBe(null);
const node = findNodeAtPosition(nodes, position); const {node} = getTargetAtPosition(nodes, position)!;
expect(isTemplateNode(node!)).toBe(true); expect(isTemplateNode(node!)).toBe(true);
expect(node).toBeInstanceOf(t.TextAttribute); expect(node).toBeInstanceOf(t.TextAttribute);
}); });
@ -206,7 +206,7 @@ describe('findNodeAtPosition for template AST', () => {
it('should locate template attribute value', () => { it('should locate template attribute value', () => {
const {errors, nodes, position} = parse(`<ng-template id="f¦oo"></ng-template>`); const {errors, nodes, position} = parse(`<ng-template id="f¦oo"></ng-template>`);
expect(errors).toBe(null); expect(errors).toBe(null);
const node = findNodeAtPosition(nodes, position); const {node} = getTargetAtPosition(nodes, position)!;
expect(isTemplateNode(node!)).toBe(true); expect(isTemplateNode(node!)).toBe(true);
// TODO: Note that we do not have the ability to detect the RHS (yet) // TODO: Note that we do not have the ability to detect the RHS (yet)
expect(node).toBeInstanceOf(t.TextAttribute); expect(node).toBeInstanceOf(t.TextAttribute);
@ -215,7 +215,7 @@ describe('findNodeAtPosition for template AST', () => {
it('should locate template reference key via the # notation', () => { it('should locate template reference key via the # notation', () => {
const {errors, nodes, position} = parse(`<ng-template #f¦oo></ng-template>`); const {errors, nodes, position} = parse(`<ng-template #f¦oo></ng-template>`);
expect(errors).toBe(null); expect(errors).toBe(null);
const node = findNodeAtPosition(nodes, position); const {node} = getTargetAtPosition(nodes, position)!;
expect(isTemplateNode(node!)).toBe(true); expect(isTemplateNode(node!)).toBe(true);
expect(node).toBeInstanceOf(t.Reference); expect(node).toBeInstanceOf(t.Reference);
expect((node as t.Reference).name).toBe('foo'); 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', () => { it('should locate template reference key via the ref- notation', () => {
const {errors, nodes, position} = parse(`<ng-template ref-fo¦o></ng-template>`); const {errors, nodes, position} = parse(`<ng-template ref-fo¦o></ng-template>`);
expect(errors).toBe(null); expect(errors).toBe(null);
const node = findNodeAtPosition(nodes, position); const {node} = getTargetAtPosition(nodes, position)!;
expect(isTemplateNode(node!)).toBe(true); expect(isTemplateNode(node!)).toBe(true);
expect(node).toBeInstanceOf(t.Reference); expect(node).toBeInstanceOf(t.Reference);
expect((node as t.Reference).name).toBe('foo'); 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', () => { it('should locate template reference value via the # notation', () => {
const {errors, nodes, position} = parse(`<ng-template #foo="export¦As"></ng-template>`); const {errors, nodes, position} = parse(`<ng-template #foo="export¦As"></ng-template>`);
expect(errors).toBe(null); expect(errors).toBe(null);
const node = findNodeAtPosition(nodes, position); const {node} = getTargetAtPosition(nodes, position)!;
expect(isTemplateNode(node!)).toBe(true); expect(isTemplateNode(node!)).toBe(true);
expect(node).toBeInstanceOf(t.Reference); expect(node).toBeInstanceOf(t.Reference);
expect((node as t.Reference).value).toBe('exportAs'); 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', () => { it('should locate template reference value via the ref- notation', () => {
const {errors, nodes, position} = parse(`<ng-template ref-foo="export¦As"></ng-template>`); const {errors, nodes, position} = parse(`<ng-template ref-foo="export¦As"></ng-template>`);
expect(errors).toBe(null); expect(errors).toBe(null);
const node = findNodeAtPosition(nodes, position); const {node} = getTargetAtPosition(nodes, position)!;
expect(isTemplateNode(node!)).toBe(true); expect(isTemplateNode(node!)).toBe(true);
expect(node).toBeInstanceOf(t.Reference); expect(node).toBeInstanceOf(t.Reference);
expect((node as t.Reference).value).toBe('exportAs'); expect((node as t.Reference).value).toBe('exportAs');
@ -253,7 +253,7 @@ describe('findNodeAtPosition for template AST', () => {
it('should locate template variable key', () => { it('should locate template variable key', () => {
const {errors, nodes, position} = parse(`<ng-template let-f¦oo="bar"></ng-template>`); const {errors, nodes, position} = parse(`<ng-template let-f¦oo="bar"></ng-template>`);
expect(errors).toBe(null); expect(errors).toBe(null);
const node = findNodeAtPosition(nodes, position); const {node} = getTargetAtPosition(nodes, position)!;
expect(isTemplateNode(node!)).toBe(true); expect(isTemplateNode(node!)).toBe(true);
expect(node).toBeInstanceOf(t.Variable); expect(node).toBeInstanceOf(t.Variable);
}); });
@ -261,7 +261,7 @@ describe('findNodeAtPosition for template AST', () => {
it('should locate template variable value', () => { it('should locate template variable value', () => {
const {errors, nodes, position} = parse(`<ng-template let-foo="b¦ar"></ng-template>`); const {errors, nodes, position} = parse(`<ng-template let-foo="b¦ar"></ng-template>`);
expect(errors).toBe(null); expect(errors).toBe(null);
const node = findNodeAtPosition(nodes, position); const {node} = getTargetAtPosition(nodes, position)!;
expect(isTemplateNode(node!)).toBe(true); expect(isTemplateNode(node!)).toBe(true);
expect(node).toBeInstanceOf(t.Variable); expect(node).toBeInstanceOf(t.Variable);
}); });
@ -269,7 +269,7 @@ describe('findNodeAtPosition for template AST', () => {
it('should locate template children', () => { it('should locate template children', () => {
const {errors, nodes, position} = parse(`<ng-template><d¦iv></div></ng-template>`); const {errors, nodes, position} = parse(`<ng-template><d¦iv></div></ng-template>`);
expect(errors).toBe(null); expect(errors).toBe(null);
const node = findNodeAtPosition(nodes, position); const {node} = getTargetAtPosition(nodes, position)!;
expect(isTemplateNode(node!)).toBe(true); expect(isTemplateNode(node!)).toBe(true);
expect(node).toBeInstanceOf(t.Element); expect(node).toBeInstanceOf(t.Element);
}); });
@ -277,7 +277,7 @@ describe('findNodeAtPosition for template AST', () => {
it('should locate ng-content', () => { it('should locate ng-content', () => {
const {errors, nodes, position} = parse(`<ng-co¦ntent></ng-content>`); const {errors, nodes, position} = parse(`<ng-co¦ntent></ng-content>`);
expect(errors).toBe(null); expect(errors).toBe(null);
const node = findNodeAtPosition(nodes, position); const {node} = getTargetAtPosition(nodes, position)!;
expect(isTemplateNode(node!)).toBe(true); expect(isTemplateNode(node!)).toBe(true);
expect(node).toBeInstanceOf(t.Content); expect(node).toBeInstanceOf(t.Content);
}); });
@ -285,7 +285,7 @@ describe('findNodeAtPosition for template AST', () => {
it('should locate ng-content attribute key', () => { it('should locate ng-content attribute key', () => {
const {errors, nodes, position} = parse('<ng-content cla¦ss="red"></ng-content>'); const {errors, nodes, position} = parse('<ng-content cla¦ss="red"></ng-content>');
expect(errors).toBe(null); expect(errors).toBe(null);
const node = findNodeAtPosition(nodes, position); const {node} = getTargetAtPosition(nodes, position)!;
expect(isTemplateNode(node!)).toBe(true); expect(isTemplateNode(node!)).toBe(true);
expect(node).toBeInstanceOf(t.TextAttribute); expect(node).toBeInstanceOf(t.TextAttribute);
}); });
@ -293,7 +293,7 @@ describe('findNodeAtPosition for template AST', () => {
it('should locate ng-content attribute value', () => { it('should locate ng-content attribute value', () => {
const {errors, nodes, position} = parse('<ng-content class="r¦ed"></ng-content>'); const {errors, nodes, position} = parse('<ng-content class="r¦ed"></ng-content>');
expect(errors).toBe(null); 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) // TODO: Note that we do not have the ability to detect the RHS (yet)
expect(isTemplateNode(node!)).toBe(true); expect(isTemplateNode(node!)).toBe(true);
expect(node).toBeInstanceOf(t.TextAttribute); expect(node).toBeInstanceOf(t.TextAttribute);
@ -302,7 +302,7 @@ describe('findNodeAtPosition for template AST', () => {
it('should not locate implicit receiver', () => { it('should not locate implicit receiver', () => {
const {errors, nodes, position} = parse(`<div [foo]="¦bar"></div>`); const {errors, nodes, position} = parse(`<div [foo]="¦bar"></div>`);
expect(errors).toBe(null); expect(errors).toBe(null);
const node = findNodeAtPosition(nodes, position); const {node} = getTargetAtPosition(nodes, position)!;
expect(isExpressionNode(node!)).toBe(true); expect(isExpressionNode(node!)).toBe(true);
expect(node).toBeInstanceOf(e.PropertyRead); expect(node).toBeInstanceOf(e.PropertyRead);
}); });
@ -310,7 +310,7 @@ describe('findNodeAtPosition for template AST', () => {
it('should locate bound attribute key in two-way binding', () => { it('should locate bound attribute key in two-way binding', () => {
const {errors, nodes, position} = parse(`<cmp [(f¦oo)]="bar"></cmp>`); const {errors, nodes, position} = parse(`<cmp [(f¦oo)]="bar"></cmp>`);
expect(errors).toBe(null); expect(errors).toBe(null);
const node = findNodeAtPosition(nodes, position); const {node} = getTargetAtPosition(nodes, position)!;
expect(isTemplateNode(node!)).toBe(true); expect(isTemplateNode(node!)).toBe(true);
expect(node).toBeInstanceOf(t.BoundAttribute); expect(node).toBeInstanceOf(t.BoundAttribute);
expect((node as t.BoundAttribute).name).toBe('foo'); 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', () => { it('should locate bound attribute value in two-way binding', () => {
const {errors, nodes, position} = parse(`<cmp [(foo)]="b¦ar"></cmp>`); const {errors, nodes, position} = parse(`<cmp [(foo)]="b¦ar"></cmp>`);
expect(errors).toBe(null); expect(errors).toBe(null);
const node = findNodeAtPosition(nodes, position); const {node} = getTargetAtPosition(nodes, position)!;
expect(isExpressionNode(node!)).toBe(true); expect(isExpressionNode(node!)).toBe(true);
expect(node).toBeInstanceOf(e.PropertyRead); expect(node).toBeInstanceOf(e.PropertyRead);
expect((node as e.PropertyRead).name).toBe('bar'); expect((node as e.PropertyRead).name).toBe('bar');
@ -328,7 +328,7 @@ describe('findNodeAtPosition for template AST', () => {
it('should locate switch value in ICUs', () => { it('should locate switch value in ICUs', () => {
const {errors, nodes, position} = parse(`<span i18n>{sw¦itch, plural, other {text}}"></span>`); const {errors, nodes, position} = parse(`<span i18n>{sw¦itch, plural, other {text}}"></span>`);
expect(errors).toBe(null); expect(errors).toBe(null);
const node = findNodeAtPosition(nodes, position); const {node} = getTargetAtPosition(nodes, position)!;
expect(isExpressionNode(node!)).toBe(true); expect(isExpressionNode(node!)).toBe(true);
expect(node).toBeInstanceOf(e.PropertyRead); expect(node).toBeInstanceOf(e.PropertyRead);
expect((node as e.PropertyRead).name).toBe('switch'); expect((node as e.PropertyRead).name).toBe('switch');
@ -338,7 +338,7 @@ describe('findNodeAtPosition for template AST', () => {
const {errors, nodes, position} = parse( const {errors, nodes, position} = parse(
`<span i18n>{expr, plural, other { {ne¦sted, plural, =1 { {{nestedInterpolation}} }} }}"></span>`); `<span i18n>{expr, plural, other { {ne¦sted, plural, =1 { {{nestedInterpolation}} }} }}"></span>`);
expect(errors).toBe(null); expect(errors).toBe(null);
const node = findNodeAtPosition(nodes, position); const {node} = getTargetAtPosition(nodes, position)!;
expect(isExpressionNode(node!)).toBe(true); expect(isExpressionNode(node!)).toBe(true);
expect(node).toBeInstanceOf(e.PropertyRead); expect(node).toBeInstanceOf(e.PropertyRead);
expect((node as e.PropertyRead).name).toBe('nested'); expect((node as e.PropertyRead).name).toBe('nested');
@ -348,7 +348,7 @@ describe('findNodeAtPosition for template AST', () => {
const {errors, nodes, position} = const {errors, nodes, position} =
parse(`<span i18n>{expr, plural, other { {{ i¦nterpolation }} }}"></span>`); parse(`<span i18n>{expr, plural, other { {{ i¦nterpolation }} }}"></span>`);
expect(errors).toBe(null); expect(errors).toBe(null);
const node = findNodeAtPosition(nodes, position); const {node} = getTargetAtPosition(nodes, position)!;
expect(isExpressionNode(node!)).toBe(true); expect(isExpressionNode(node!)).toBe(true);
expect(node).toBeInstanceOf(e.PropertyRead); expect(node).toBeInstanceOf(e.PropertyRead);
expect((node as e.PropertyRead).name).toBe('interpolation'); expect((node as e.PropertyRead).name).toBe('interpolation');
@ -358,18 +358,18 @@ describe('findNodeAtPosition for template AST', () => {
const {errors, nodes, position} = parse( const {errors, nodes, position} = parse(
`<span i18n>{expr, plural, other { {nested, plural, =1 { {{n¦estedInterpolation}} }} }}"></span>`); `<span i18n>{expr, plural, other { {nested, plural, =1 { {{n¦estedInterpolation}} }} }}"></span>`);
expect(errors).toBe(null); expect(errors).toBe(null);
const node = findNodeAtPosition(nodes, position); const {node} = getTargetAtPosition(nodes, position)!;
expect(isExpressionNode(node!)).toBe(true); expect(isExpressionNode(node!)).toBe(true);
expect(node).toBeInstanceOf(e.PropertyRead); expect(node).toBeInstanceOf(e.PropertyRead);
expect((node as e.PropertyRead).name).toBe('nestedInterpolation'); expect((node as e.PropertyRead).name).toBe('nestedInterpolation');
}); });
}); });
describe('findNodeAtPosition for expression AST', () => { describe('getTargetAtPosition for expression AST', () => {
it('should not locate implicit receiver', () => { it('should not locate implicit receiver', () => {
const {errors, nodes, position} = parse(`{{ ¦title }}`); const {errors, nodes, position} = parse(`{{ ¦title }}`);
expect(errors).toBe(null); expect(errors).toBe(null);
const node = findNodeAtPosition(nodes, position); const {node} = getTargetAtPosition(nodes, position)!;
expect(isExpressionNode(node!)).toBe(true); expect(isExpressionNode(node!)).toBe(true);
expect(node).toBeInstanceOf(e.PropertyRead); expect(node).toBeInstanceOf(e.PropertyRead);
expect((node as e.PropertyRead).name).toBe('title'); expect((node as e.PropertyRead).name).toBe('title');
@ -378,7 +378,7 @@ describe('findNodeAtPosition for expression AST', () => {
it('should locate property read', () => { it('should locate property read', () => {
const {errors, nodes, position} = parse(`{{ ti¦tle }}`); const {errors, nodes, position} = parse(`{{ ti¦tle }}`);
expect(errors).toBe(null); expect(errors).toBe(null);
const node = findNodeAtPosition(nodes, position); const {node} = getTargetAtPosition(nodes, position)!;
expect(isExpressionNode(node!)).toBe(true); expect(isExpressionNode(node!)).toBe(true);
expect(node).toBeInstanceOf(e.PropertyRead); expect(node).toBeInstanceOf(e.PropertyRead);
expect((node as e.PropertyRead).name).toBe('title'); expect((node as e.PropertyRead).name).toBe('title');
@ -387,7 +387,7 @@ describe('findNodeAtPosition for expression AST', () => {
it('should locate safe property read', () => { it('should locate safe property read', () => {
const {errors, nodes, position} = parse(`{{ foo?¦.bar }}`); const {errors, nodes, position} = parse(`{{ foo?¦.bar }}`);
expect(errors).toBe(null); expect(errors).toBe(null);
const node = findNodeAtPosition(nodes, position); const {node} = getTargetAtPosition(nodes, position)!;
expect(isExpressionNode(node!)).toBe(true); expect(isExpressionNode(node!)).toBe(true);
expect(node).toBeInstanceOf(e.SafePropertyRead); expect(node).toBeInstanceOf(e.SafePropertyRead);
expect((node as e.SafePropertyRead).name).toBe('bar'); expect((node as e.SafePropertyRead).name).toBe('bar');
@ -396,7 +396,7 @@ describe('findNodeAtPosition for expression AST', () => {
it('should locate keyed read', () => { it('should locate keyed read', () => {
const {errors, nodes, position} = parse(`{{ foo['bar']¦ }}`); const {errors, nodes, position} = parse(`{{ foo['bar']¦ }}`);
expect(errors).toBe(null); expect(errors).toBe(null);
const node = findNodeAtPosition(nodes, position); const {node} = getTargetAtPosition(nodes, position)!;
expect(isExpressionNode(node!)).toBe(true); expect(isExpressionNode(node!)).toBe(true);
expect(node).toBeInstanceOf(e.KeyedRead); expect(node).toBeInstanceOf(e.KeyedRead);
}); });
@ -404,7 +404,7 @@ describe('findNodeAtPosition for expression AST', () => {
it('should locate property write', () => { it('should locate property write', () => {
const {errors, nodes, position} = parse(`<div (foo)="b¦ar=$event"></div>`); const {errors, nodes, position} = parse(`<div (foo)="b¦ar=$event"></div>`);
expect(errors).toBe(null); expect(errors).toBe(null);
const node = findNodeAtPosition(nodes, position); const {node} = getTargetAtPosition(nodes, position)!;
expect(isExpressionNode(node!)).toBe(true); expect(isExpressionNode(node!)).toBe(true);
expect(node).toBeInstanceOf(e.PropertyWrite); expect(node).toBeInstanceOf(e.PropertyWrite);
}); });
@ -412,7 +412,7 @@ describe('findNodeAtPosition for expression AST', () => {
it('should locate keyed write', () => { it('should locate keyed write', () => {
const {errors, nodes, position} = parse(`<div (foo)="bar['baz']¦=$event"></div>`); const {errors, nodes, position} = parse(`<div (foo)="bar['baz']¦=$event"></div>`);
expect(errors).toBe(null); expect(errors).toBe(null);
const node = findNodeAtPosition(nodes, position); const {node} = getTargetAtPosition(nodes, position)!;
expect(isExpressionNode(node!)).toBe(true); expect(isExpressionNode(node!)).toBe(true);
expect(node).toBeInstanceOf(e.KeyedWrite); expect(node).toBeInstanceOf(e.KeyedWrite);
}); });
@ -420,7 +420,7 @@ describe('findNodeAtPosition for expression AST', () => {
it('should locate binary', () => { it('should locate binary', () => {
const {errors, nodes, position} = parse(`{{ 1 +¦ 2 }}`); const {errors, nodes, position} = parse(`{{ 1 +¦ 2 }}`);
expect(errors).toBe(null); expect(errors).toBe(null);
const node = findNodeAtPosition(nodes, position); const {node} = getTargetAtPosition(nodes, position)!;
expect(isExpressionNode(node!)).toBe(true); expect(isExpressionNode(node!)).toBe(true);
expect(node).toBeInstanceOf(e.Binary); expect(node).toBeInstanceOf(e.Binary);
}); });
@ -428,7 +428,7 @@ describe('findNodeAtPosition for expression AST', () => {
it('should locate binding pipe with an identifier', () => { it('should locate binding pipe with an identifier', () => {
const {errors, nodes, position} = parse(`{{ title | p¦ }}`); const {errors, nodes, position} = parse(`{{ title | p¦ }}`);
expect(errors).toBe(null); expect(errors).toBe(null);
const node = findNodeAtPosition(nodes, position); const {node} = getTargetAtPosition(nodes, position)!;
expect(isExpressionNode(node!)).toBe(true); expect(isExpressionNode(node!)).toBe(true);
expect(node).toBeInstanceOf(e.BindingPipe); expect(node).toBeInstanceOf(e.BindingPipe);
}); });
@ -439,16 +439,28 @@ describe('findNodeAtPosition for expression AST', () => {
expect(errors![0].toString()) expect(errors![0].toString())
.toContain( .toContain(
'Unexpected end of input, expected identifier or keyword at the end of the expression'); '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); expect(isExpressionNode(node!)).toBe(true);
// TODO: We want this to be a BindingPipe. // TODO: We want this to be a BindingPipe.
expect(node).toBeInstanceOf(e.Interpolation); 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', () => { it('should locate method call', () => {
const {errors, nodes, position} = parse(`{{ title.toString(¦) }}`); const {errors, nodes, position} = parse(`{{ title.toString(¦) }}`);
expect(errors).toBe(null); expect(errors).toBe(null);
const node = findNodeAtPosition(nodes, position); const {node} = getTargetAtPosition(nodes, position)!;
expect(isExpressionNode(node!)).toBe(true); expect(isExpressionNode(node!)).toBe(true);
expect(node).toBeInstanceOf(e.MethodCall); expect(node).toBeInstanceOf(e.MethodCall);
}); });
@ -456,7 +468,7 @@ describe('findNodeAtPosition for expression AST', () => {
it('should locate safe method call', () => { it('should locate safe method call', () => {
const {errors, nodes, position} = parse(`{{ title?.toString(¦) }}`); const {errors, nodes, position} = parse(`{{ title?.toString(¦) }}`);
expect(errors).toBe(null); expect(errors).toBe(null);
const node = findNodeAtPosition(nodes, position); const {node} = getTargetAtPosition(nodes, position)!;
expect(isExpressionNode(node!)).toBe(true); expect(isExpressionNode(node!)).toBe(true);
expect(node).toBeInstanceOf(e.SafeMethodCall); expect(node).toBeInstanceOf(e.SafeMethodCall);
}); });
@ -464,7 +476,7 @@ describe('findNodeAtPosition for expression AST', () => {
it('should locate literal primitive in interpolation', () => { it('should locate literal primitive in interpolation', () => {
const {errors, nodes, position} = parse(`{{ title.indexOf('t¦') }}`); const {errors, nodes, position} = parse(`{{ title.indexOf('t¦') }}`);
expect(errors).toBe(null); expect(errors).toBe(null);
const node = findNodeAtPosition(nodes, position); const {node} = getTargetAtPosition(nodes, position)!;
expect(isExpressionNode(node!)).toBe(true); expect(isExpressionNode(node!)).toBe(true);
expect(node).toBeInstanceOf(e.LiteralPrimitive); expect(node).toBeInstanceOf(e.LiteralPrimitive);
expect((node as e.LiteralPrimitive).value).toBe('t'); expect((node as e.LiteralPrimitive).value).toBe('t');
@ -473,7 +485,7 @@ describe('findNodeAtPosition for expression AST', () => {
it('should locate literal primitive in binding', () => { it('should locate literal primitive in binding', () => {
const {errors, nodes, position} = parse(`<div [id]="'t¦'"></div>`); const {errors, nodes, position} = parse(`<div [id]="'t¦'"></div>`);
expect(errors).toBe(null); expect(errors).toBe(null);
const node = findNodeAtPosition(nodes, position); const {node} = getTargetAtPosition(nodes, position)!;
expect(isExpressionNode(node!)).toBe(true); expect(isExpressionNode(node!)).toBe(true);
expect(node).toBeInstanceOf(e.LiteralPrimitive); expect(node).toBeInstanceOf(e.LiteralPrimitive);
expect((node as e.LiteralPrimitive).value).toBe('t'); expect((node as e.LiteralPrimitive).value).toBe('t');
@ -482,7 +494,7 @@ describe('findNodeAtPosition for expression AST', () => {
it('should locate empty expression', () => { it('should locate empty expression', () => {
const {errors, nodes, position} = parse(`<div [id]="¦"></div>`); const {errors, nodes, position} = parse(`<div [id]="¦"></div>`);
expect(errors).toBe(null); expect(errors).toBe(null);
const node = findNodeAtPosition(nodes, position); const {node} = getTargetAtPosition(nodes, position)!;
expect(isExpressionNode(node!)).toBe(true); expect(isExpressionNode(node!)).toBe(true);
expect(node).toBeInstanceOf(e.EmptyExpr); expect(node).toBeInstanceOf(e.EmptyExpr);
}); });
@ -490,7 +502,7 @@ describe('findNodeAtPosition for expression AST', () => {
it('should locate literal array', () => { it('should locate literal array', () => {
const {errors, nodes, position} = parse(`{{ [1, 2,¦ 3] }}`); const {errors, nodes, position} = parse(`{{ [1, 2,¦ 3] }}`);
expect(errors).toBe(null); expect(errors).toBe(null);
const node = findNodeAtPosition(nodes, position); const {node} = getTargetAtPosition(nodes, position)!;
expect(isExpressionNode(node!)).toBe(true); expect(isExpressionNode(node!)).toBe(true);
expect(node).toBeInstanceOf(e.LiteralArray); expect(node).toBeInstanceOf(e.LiteralArray);
}); });
@ -498,7 +510,7 @@ describe('findNodeAtPosition for expression AST', () => {
it('should locate literal map', () => { it('should locate literal map', () => {
const {errors, nodes, position} = parse(`{{ { hello:¦ "world" } }}`); const {errors, nodes, position} = parse(`{{ { hello:¦ "world" } }}`);
expect(errors).toBe(null); expect(errors).toBe(null);
const node = findNodeAtPosition(nodes, position); const {node} = getTargetAtPosition(nodes, position)!;
expect(isExpressionNode(node!)).toBe(true); expect(isExpressionNode(node!)).toBe(true);
expect(node).toBeInstanceOf(e.LiteralMap); expect(node).toBeInstanceOf(e.LiteralMap);
}); });
@ -506,7 +518,7 @@ describe('findNodeAtPosition for expression AST', () => {
it('should locate conditional', () => { it('should locate conditional', () => {
const {errors, nodes, position} = parse(`{{ cond ?¦ true : false }}`); const {errors, nodes, position} = parse(`{{ cond ?¦ true : false }}`);
expect(errors).toBe(null); expect(errors).toBe(null);
const node = findNodeAtPosition(nodes, position); const {node} = getTargetAtPosition(nodes, position)!;
expect(isExpressionNode(node!)).toBe(true); expect(isExpressionNode(node!)).toBe(true);
expect(node).toBeInstanceOf(e.Conditional); expect(node).toBeInstanceOf(e.Conditional);
}); });
@ -516,7 +528,7 @@ describe('findNodeAtPosition for microsyntax expression', () => {
it('should locate template key', () => { it('should locate template key', () => {
const {errors, nodes, position} = parse(`<div *ng¦If="foo"></div>`); const {errors, nodes, position} = parse(`<div *ng¦If="foo"></div>`);
expect(errors).toBe(null); expect(errors).toBe(null);
const node = findNodeAtPosition(nodes, position); const {node} = getTargetAtPosition(nodes, position)!;
expect(isTemplateNode(node!)).toBe(true); expect(isTemplateNode(node!)).toBe(true);
expect(node).toBeInstanceOf(t.BoundAttribute); expect(node).toBeInstanceOf(t.BoundAttribute);
}); });
@ -524,7 +536,7 @@ describe('findNodeAtPosition for microsyntax expression', () => {
it('should locate template value', () => { it('should locate template value', () => {
const {errors, nodes, position} = parse(`<div *ngIf="f¦oo"></div>`); const {errors, nodes, position} = parse(`<div *ngIf="f¦oo"></div>`);
expect(errors).toBe(null); expect(errors).toBe(null);
const node = findNodeAtPosition(nodes, position); const {node} = getTargetAtPosition(nodes, position)!;
expect(isExpressionNode(node!)).toBe(true); expect(isExpressionNode(node!)).toBe(true);
expect(node).toBeInstanceOf(e.PropertyRead); expect(node).toBeInstanceOf(e.PropertyRead);
}); });
@ -534,23 +546,23 @@ describe('findNodeAtPosition for microsyntax expression', () => {
// ngFor is a text attribute because the desugared form is // ngFor is a text attribute because the desugared form is
// <ng-template ngFor let-item [ngForOf]="items"> // <ng-template ngFor let-item [ngForOf]="items">
expect(errors).toBe(null); expect(errors).toBe(null);
const node = findNodeAtPosition(nodes, position); const {node} = getTargetAtPosition(nodes, position)!;
expect(isTemplateNode(node!)).toBeTrue(); expect(isTemplateNode(node!)).toBeTrue();
expect(node).toBeInstanceOf(t.TextAttribute); expect(node).toBeInstanceOf(t.TextAttribute);
expect((node as t.TextAttribute).name).toBe('ngFor'); 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>`); const {errors, nodes, position} = parse(`<div *ngFor="l¦et item of items"></div>`);
expect(errors).toBe(null); expect(errors).toBeNull();
const node = findNodeAtPosition(nodes, position); const target = getTargetAtPosition(nodes, position)!;
expect(node).toBeUndefined(); expect(target).toBeNull();
}); });
it('should locate let variable', () => { it('should locate let variable', () => {
const {errors, nodes, position} = parse(`<div *ngFor="let i¦tem of items"></div>`); const {errors, nodes, position} = parse(`<div *ngFor="let i¦tem of items"></div>`);
expect(errors).toBe(null); expect(errors).toBe(null);
const node = findNodeAtPosition(nodes, position); const {node} = getTargetAtPosition(nodes, position)!;
expect(isTemplateNode(node!)).toBe(true); expect(isTemplateNode(node!)).toBe(true);
expect(node).toBeInstanceOf(t.Variable); expect(node).toBeInstanceOf(t.Variable);
expect((node as t.Variable).name).toBe('item'); expect((node as t.Variable).name).toBe('item');
@ -559,7 +571,7 @@ describe('findNodeAtPosition for microsyntax expression', () => {
it('should locate bound attribute key', () => { it('should locate bound attribute key', () => {
const {errors, nodes, position} = parse(`<div *ngFor="let item o¦f items"></div>`); const {errors, nodes, position} = parse(`<div *ngFor="let item o¦f items"></div>`);
expect(errors).toBe(null); expect(errors).toBe(null);
const node = findNodeAtPosition(nodes, position); const {node} = getTargetAtPosition(nodes, position)!;
expect(isTemplateNode(node!)).toBe(true); expect(isTemplateNode(node!)).toBe(true);
expect(node).toBeInstanceOf(t.BoundAttribute); expect(node).toBeInstanceOf(t.BoundAttribute);
expect((node as t.BoundAttribute).name).toBe('ngForOf'); expect((node as t.BoundAttribute).name).toBe('ngForOf');
@ -569,7 +581,7 @@ describe('findNodeAtPosition for microsyntax expression', () => {
const {errors, nodes, position} = const {errors, nodes, position} =
parse(`<div *ngFor="let item of items; trac¦kBy: trackByFn"></div>`); parse(`<div *ngFor="let item of items; trac¦kBy: trackByFn"></div>`);
expect(errors).toBe(null); expect(errors).toBe(null);
const node = findNodeAtPosition(nodes, position); const {node} = getTargetAtPosition(nodes, position)!;
expect(isTemplateNode(node!)).toBe(true); expect(isTemplateNode(node!)).toBe(true);
expect(node).toBeInstanceOf(t.BoundAttribute); expect(node).toBeInstanceOf(t.BoundAttribute);
expect((node as t.BoundAttribute).name).toBe('ngForTrackBy'); expect((node as t.BoundAttribute).name).toBe('ngForTrackBy');
@ -582,7 +594,7 @@ describe('findNodeAtPosition for microsyntax expression', () => {
const {errors, nodes, position} = const {errors, nodes, position} =
parse(`<div *ngFor="let item o¦f items; trackBy: trackByFn"></div>`); parse(`<div *ngFor="let item o¦f items; trackBy: trackByFn"></div>`);
expect(errors).toBe(null); expect(errors).toBe(null);
const node = findNodeAtPosition(nodes, position); const {node} = getTargetAtPosition(nodes, position)!;
expect(isTemplateNode(node!)).toBe(true); expect(isTemplateNode(node!)).toBe(true);
expect(node).toBeInstanceOf(t.BoundAttribute); expect(node).toBeInstanceOf(t.BoundAttribute);
expect((node as t.BoundAttribute).name).toBe('ngForOf'); expect((node as t.BoundAttribute).name).toBe('ngForOf');
@ -591,7 +603,7 @@ describe('findNodeAtPosition for microsyntax expression', () => {
it('should locate bound attribute value', () => { it('should locate bound attribute value', () => {
const {errors, nodes, position} = parse(`<div *ngFor="let item of it¦ems"></div>`); const {errors, nodes, position} = parse(`<div *ngFor="let item of it¦ems"></div>`);
expect(errors).toBe(null); expect(errors).toBe(null);
const node = findNodeAtPosition(nodes, position); const {node} = getTargetAtPosition(nodes, position)!;
expect(isExpressionNode(node!)).toBe(true); expect(isExpressionNode(node!)).toBe(true);
expect(node).toBeInstanceOf(e.PropertyRead); expect(node).toBeInstanceOf(e.PropertyRead);
expect((node as e.PropertyRead).name).toBe('items'); expect((node as e.PropertyRead).name).toBe('items');
@ -600,10 +612,11 @@ describe('findNodeAtPosition for microsyntax expression', () => {
it('should locate template children', () => { it('should locate template children', () => {
const {errors, nodes, position} = parse(`<di¦v *ngIf></div>`); const {errors, nodes, position} = parse(`<di¦v *ngIf></div>`);
expect(errors).toBe(null); expect(errors).toBe(null);
const node = findNodeAtPosition(nodes, position); const {node, context} = getTargetAtPosition(nodes, position)!;
expect(isTemplateNode(node!)).toBe(true); expect(isTemplateNode(node!)).toBe(true);
expect(node).toBeInstanceOf(t.Element); expect(node).toBeInstanceOf(t.Element);
expect((node as t.Element).name).toBe('div'); expect((node as t.Element).name).toBe('div');
expect(context).toBeInstanceOf(t.Template);
}); });
it('should locate property read of variable declared within template', () => { it('should locate property read of variable declared within template', () => {
@ -612,7 +625,7 @@ describe('findNodeAtPosition for microsyntax expression', () => {
{{ i¦ }} {{ i¦ }}
</div>`); </div>`);
expect(errors).toBe(null); expect(errors).toBe(null);
const node = findNodeAtPosition(nodes, position); const {node} = getTargetAtPosition(nodes, position)!;
expect(isExpressionNode(node!)).toBe(true); expect(isExpressionNode(node!)).toBe(true);
expect(node).toBeInstanceOf(e.PropertyRead); expect(node).toBeInstanceOf(e.PropertyRead);
}); });
@ -620,7 +633,7 @@ describe('findNodeAtPosition for microsyntax expression', () => {
it('should locate LHS of variable declaration', () => { it('should locate LHS of variable declaration', () => {
const {errors, nodes, position} = parse(`<div *ngFor="let item of items; let i¦=index">`); const {errors, nodes, position} = parse(`<div *ngFor="let item of items; let i¦=index">`);
expect(errors).toBe(null); expect(errors).toBe(null);
const node = findNodeAtPosition(nodes, position); const {node} = getTargetAtPosition(nodes, position)!;
expect(isTemplateNode(node!)).toBe(true); expect(isTemplateNode(node!)).toBe(true);
expect(node).toBeInstanceOf(t.Variable); expect(node).toBeInstanceOf(t.Variable);
// TODO: Currently there is no way to distinguish LHS from RHS // 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', () => { it('should locate RHS of variable declaration', () => {
const {errors, nodes, position} = parse(`<div *ngFor="let item of items; let i=in¦dex">`); const {errors, nodes, position} = parse(`<div *ngFor="let item of items; let i=in¦dex">`);
expect(errors).toBe(null); expect(errors).toBe(null);
const node = findNodeAtPosition(nodes, position); const {node} = getTargetAtPosition(nodes, position)!;
expect(isTemplateNode(node!)).toBe(true); expect(isTemplateNode(node!)).toBe(true);
expect(node).toBeInstanceOf(t.Variable); expect(node).toBeInstanceOf(t.Variable);
// TODO: Currently there is no way to distinguish LHS from RHS // TODO: Currently there is no way to distinguish LHS from RHS