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:
parent
643c96184c
commit
eaace44d57
|
@ -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};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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;
|
||||||
}
|
}
|
||||||
|
|
|
@ -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};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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);
|
|
@ -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
|
Loading…
Reference in New Issue