diff --git a/packages/language-service/ivy/definitions.ts b/packages/language-service/ivy/definitions.ts index 94f2feeb73..e9215eec18 100644 --- a/packages/language-service/ivy/definitions.ts +++ b/packages/language-service/ivy/definitions.ts @@ -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; + 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, 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}; } } diff --git a/packages/language-service/ivy/language_service.ts b/packages/language-service/ivy/language_service.ts index b8d7e5fbfe..a7538a8dfe 100644 --- a/packages/language-service/ivy/language_service.ts +++ b/packages/language-service/ivy/language_service.ts @@ -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; } diff --git a/packages/language-service/ivy/quick_info.ts b/packages/language-service/ivy/quick_info.ts index 312c23fddd..0079bc0e2f 100644 --- a/packages/language-service/ivy/quick_info.ts +++ b/packages/language-service/ivy/quick_info.ts @@ -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}; } } diff --git a/packages/language-service/ivy/hybrid_visitor.ts b/packages/language-service/ivy/template_target.ts similarity index 77% rename from packages/language-service/ivy/hybrid_visitor.ts rename to packages/language-service/ivy/template_target.ts index 391809e38d..39d3edaaed 100644 --- a/packages/language-service/ivy/hybrid_visitor.ts +++ b/packages/language-service/ivy/template_target.ts @@ -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| - 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 = []; + static visitTemplate(template: t.Node[], position: number): Array { + 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); diff --git a/packages/language-service/ivy/test/hybrid_visitor_spec.ts b/packages/language-service/ivy/test/template_target_spec.ts similarity index 81% rename from packages/language-service/ivy/test/hybrid_visitor_spec.ts rename to packages/language-service/ivy/test/template_target_spec.ts index c238cb97f5..8e904c62f9 100644 --- a/packages/language-service/ivy/test/hybrid_visitor_spec.ts +++ b/packages/language-service/ivy/test/template_target_spec.ts @@ -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(``); 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(`
`); 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>
`); 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(``); 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(`
`); 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(`
`); 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(``); 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(``); 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(``); 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(``); 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(``); 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(`
`); 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(`
`); 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(``); 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(``); 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(``); 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(``); 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(``); 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(``); 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(``); 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(``); 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(``); 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(``); 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(``); 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(``); 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(``); 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(``); 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(``); 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(``); 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(``); 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(''); 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(''); 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(`
`); 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(``); 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(``); 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(`{sw¦itch, plural, other {text}}">`); 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( `{expr, plural, other { {ne¦sted, plural, =1 { {{nestedInterpolation}} }} }}">`); 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(`{expr, plural, other { {{ i¦nterpolation }} }}">`); 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( `{expr, plural, other { {nested, plural, =1 { {{n¦estedInterpolation}} }} }}">`); 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(`
`); 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(`
`); 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(`
`); 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(`
`); 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(`
`); 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(`
`); 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 // 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(`
`); - 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(`
`); 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(`
`); 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(`
`); 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(`
`); 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(`
`); 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(``); 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¦ }} `); 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(`
`); 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(`
`); 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