Add support for Call Hierarchies in language server (#35176)

* Add support for Call Hierarchies in language server

* Use baselines for callHierarchy tests

* Clean up commented code

* Support multiple hierarchy items when an implementation can't be found

* Use optional chaining in a few places

* Use getFileAndProject
This commit is contained in:
Ron Buckton
2019-12-22 13:25:09 -08:00
committed by GitHub
parent 114dad7f56
commit 6c413e0bbb
55 changed files with 2712 additions and 55 deletions
+485
View File
@@ -0,0 +1,485 @@
/* @internal */
namespace ts.CallHierarchy {
export type NamedExpression =
| ClassExpression & { name: Identifier }
| FunctionExpression & { name: Identifier }
;
/** Indictates whether a node is named function or class expression. */
function isNamedExpression(node: Node): node is NamedExpression {
return (isFunctionExpression(node) || isClassExpression(node)) && isNamedDeclaration(node);
}
export type ConstNamedExpression =
| ClassExpression & { name: undefined, parent: VariableDeclaration & { name: Identifier } }
| FunctionExpression & { name: undefined, parent: VariableDeclaration & { name: Identifier } }
| ArrowFunction & { name: undefined, parent: VariableDeclaration & { name: Identifier } }
;
/** Indicates whether a node is a function, arrow, or class expression assigned to a constant variable. */
function isConstNamedExpression(node: Node): node is ConstNamedExpression {
return (isFunctionExpression(node) || isArrowFunction(node) || isClassExpression(node))
&& isVariableDeclaration(node.parent)
&& node === node.parent.initializer
&& isIdentifier(node.parent.name)
&& !!(getCombinedNodeFlags(node.parent) & NodeFlags.Const);
}
export type CallHierarchyDeclaration =
| SourceFile
| ModuleDeclaration & { name: Identifier }
| FunctionDeclaration
| ClassDeclaration
| MethodDeclaration
| GetAccessorDeclaration
| SetAccessorDeclaration
| NamedExpression
| ConstNamedExpression
;
/**
* Indicates whether a node could possibly be a call hierarchy declaration.
*
* See `resolveCallHierarchyDeclaration` for the specific rules.
*/
function isPossibleCallHierarchyDeclaration(node: Node) {
return isSourceFile(node)
|| isModuleDeclaration(node)
|| isFunctionDeclaration(node)
|| isFunctionExpression(node)
|| isClassDeclaration(node)
|| isClassExpression(node)
|| isMethodDeclaration(node)
|| isMethodSignature(node)
|| isGetAccessorDeclaration(node)
|| isSetAccessorDeclaration(node);
}
/**
* Indicates whether a node is a valid a call hierarchy declaration.
*
* See `resolveCallHierarchyDeclaration` for the specific rules.
*/
function isValidCallHierarchyDeclaration(node: Node): node is CallHierarchyDeclaration {
return isSourceFile(node)
|| isModuleDeclaration(node) && isIdentifier(node.name)
|| isFunctionDeclaration(node)
|| isClassDeclaration(node)
|| isMethodDeclaration(node)
|| isMethodSignature(node)
|| isGetAccessorDeclaration(node)
|| isSetAccessorDeclaration(node)
|| isNamedExpression(node)
|| isConstNamedExpression(node);
}
/** Gets the node that can be used as a reference to a call hierarchy declaration. */
function getCallHierarchyDeclarationReferenceNode(node: CallHierarchyDeclaration) {
if (isSourceFile(node)) return node;
if (isNamedDeclaration(node)) return node.name;
if (isConstNamedExpression(node)) return node.parent.name;
return Debug.assertDefined(node.modifiers && find(node.modifiers, isDefaultModifier));
}
function isDefaultModifier(node: Node) {
return node.kind === SyntaxKind.DefaultKeyword;
}
/** Gets the symbol for a call hierarchy declaration. */
function getSymbolOfCallHierarchyDeclaration(typeChecker: TypeChecker, node: CallHierarchyDeclaration) {
const location = getCallHierarchyDeclarationReferenceNode(node);
return location && typeChecker.getSymbolAtLocation(location);
}
/** Gets the text and range for the name of a call hierarchy declaration. */
function getCallHierarchyItemName(program: Program, node: CallHierarchyDeclaration): { text: string, pos: number, end: number } {
if (isSourceFile(node)) {
return { text: node.fileName, pos: 0, end: 0 };
}
if ((isFunctionDeclaration(node) || isClassDeclaration(node)) && !isNamedDeclaration(node)) {
const defaultModifier = node.modifiers && find(node.modifiers, isDefaultModifier);
if (defaultModifier) {
return { text: "default", pos: defaultModifier.getStart(), end: defaultModifier.getEnd() };
}
}
const declName = isConstNamedExpression(node) ? node.parent.name :
Debug.assertDefined(getNameOfDeclaration(node), "Expected call hierarchy item to have a name");
let text =
isIdentifier(declName) ? idText(declName) :
isStringOrNumericLiteralLike(declName) ? declName.text :
isComputedPropertyName(declName) ?
isStringOrNumericLiteralLike(declName.expression) ? declName.expression.text :
undefined :
undefined;
if (text === undefined) {
const typeChecker = program.getTypeChecker();
const symbol = typeChecker.getSymbolAtLocation(declName);
if (symbol) {
text = typeChecker.symbolToString(symbol, node);
}
}
if (text === undefined) {
// get the text from printing the node on a single line without comments...
const printer = createPrinter({ removeComments: true, omitTrailingSemicolon: true });
text = usingSingleLineStringWriter(writer => printer.writeNode(EmitHint.Unspecified, node, node.getSourceFile(), writer));
}
return { text, pos: declName.getStart(), end: declName.getEnd() };
}
/** Finds the implementation of a function-like declaration, if one exists. */
function findImplementation(typeChecker: TypeChecker, node: Extract<CallHierarchyDeclaration, FunctionLikeDeclaration>): Extract<CallHierarchyDeclaration, FunctionLikeDeclaration> | undefined;
function findImplementation(typeChecker: TypeChecker, node: FunctionLikeDeclaration): FunctionLikeDeclaration | undefined;
function findImplementation(typeChecker: TypeChecker, node: FunctionLikeDeclaration): FunctionLikeDeclaration | undefined {
if (node.body) {
return node;
}
if (isConstructorDeclaration(node)) {
return getFirstConstructorWithBody(node.parent);
}
if (isFunctionDeclaration(node) || isMethodDeclaration(node)) {
const symbol = getSymbolOfCallHierarchyDeclaration(typeChecker, node);
if (symbol && symbol.valueDeclaration && isFunctionLikeDeclaration(symbol.valueDeclaration) && symbol.valueDeclaration.body) {
return symbol.valueDeclaration;
}
return undefined;
}
return node;
}
function findAllInitialDeclarations(typeChecker: TypeChecker, node: CallHierarchyDeclaration) {
const symbol = getSymbolOfCallHierarchyDeclaration(typeChecker, node);
let declarations: CallHierarchyDeclaration[] | undefined;
if (symbol && symbol.declarations) {
const indices = indicesOf(symbol.declarations);
const keys = map(symbol.declarations, decl => ({ file: decl.getSourceFile().fileName, pos: decl.pos }));
indices.sort((a, b) => compareStringsCaseSensitive(keys[a].file, keys[b].file) || keys[a].pos - keys[b].pos);
const sortedDeclarations = map(indices, i => symbol.declarations[i]);
let lastDecl: CallHierarchyDeclaration | undefined;
for (const decl of sortedDeclarations) {
if (isValidCallHierarchyDeclaration(decl)) {
if (!lastDecl || lastDecl.parent !== decl.parent || lastDecl.end !== decl.pos) {
declarations = append(declarations, decl);
}
lastDecl = decl;
}
}
}
return declarations;
}
/** Find the implementation or the first declaration for a call hierarchy declaration. */
function findImplementationOrAllInitialDeclarations(typeChecker: TypeChecker, node: CallHierarchyDeclaration): CallHierarchyDeclaration | CallHierarchyDeclaration[] {
if (isFunctionLikeDeclaration(node)) {
return findImplementation(typeChecker, node) ??
findAllInitialDeclarations(typeChecker, node) ??
node;
}
return findAllInitialDeclarations(typeChecker, node) ?? node;
}
/** Resolves the call hierarchy declaration for a node. */
export function resolveCallHierarchyDeclaration(program: Program, location: Node): CallHierarchyDeclaration | CallHierarchyDeclaration[] | undefined {
// A call hierarchy item must refer to either a SourceFile, Module Declaration, or something intrinsically callable that has a name:
// - Class Declarations
// - Class Expressions (with a name)
// - Function Declarations
// - Function Expressions (with a name or assigned to a const variable)
// - Arrow Functions (assigned to a const variable)
// - Constructors
// - Methods
// - Accessors
//
// If a call is contained in a non-named callable Node (function expression, arrow function, etc.), then
// its containing `CallHierarchyItem` is a containing function or SourceFile that matches the above list.
const typeChecker = program.getTypeChecker();
let followingSymbol = false;
while (true) {
if (isValidCallHierarchyDeclaration(location)) {
return findImplementationOrAllInitialDeclarations(typeChecker, location);
}
if (isPossibleCallHierarchyDeclaration(location)) {
const ancestor = findAncestor(location, isValidCallHierarchyDeclaration);
return ancestor && findImplementationOrAllInitialDeclarations(typeChecker, ancestor);
}
if (isDeclarationName(location)) {
if (isValidCallHierarchyDeclaration(location.parent)) {
return findImplementationOrAllInitialDeclarations(typeChecker, location.parent);
}
if (isPossibleCallHierarchyDeclaration(location.parent)) {
const ancestor = findAncestor(location.parent, isValidCallHierarchyDeclaration);
return ancestor && findImplementationOrAllInitialDeclarations(typeChecker, ancestor);
}
if (isVariableDeclaration(location.parent) && location.parent.initializer && isConstNamedExpression(location.parent.initializer)) {
return location.parent.initializer;
}
return undefined;
}
if (isConstructorDeclaration(location)) {
if (isValidCallHierarchyDeclaration(location.parent)) {
return location.parent;
}
return undefined;
}
if (!followingSymbol) {
let symbol = typeChecker.getSymbolAtLocation(location);
if (symbol) {
if (symbol.flags & SymbolFlags.Alias) {
symbol = typeChecker.getAliasedSymbol(symbol);
}
if (symbol.valueDeclaration) {
followingSymbol = true;
location = symbol.valueDeclaration;
continue;
}
}
}
return undefined;
}
}
/** Creates a `CallHierarchyItem` for a call hierarchy declaration. */
export function createCallHierarchyItem(program: Program, node: CallHierarchyDeclaration): CallHierarchyItem {
const sourceFile = node.getSourceFile();
const name = getCallHierarchyItemName(program, node);
const kind = getNodeKind(node);
const span = createTextSpanFromBounds(skipTrivia(sourceFile.text, node.getFullStart(), /*stopAfterLineBreak*/ false, /*stopAtComments*/ true), node.getEnd());
const selectionSpan = createTextSpanFromBounds(name.pos, name.end);
return { file: sourceFile.fileName, kind, name: name.text, span, selectionSpan };
}
function isDefined<T>(x: T): x is NonNullable<T> {
return x !== undefined;
}
interface CallSite {
declaration: CallHierarchyDeclaration;
range: TextRange;
}
function convertEntryToCallSite(entry: FindAllReferences.Entry, _originalNode: Node, typeChecker: TypeChecker): CallSite | undefined {
if (entry.kind === FindAllReferences.EntryKind.Node) {
if (isCallOrNewExpressionTarget(entry.node, /*includeElementAccess*/ true, /*skipPastOuterExpressions*/ true)
|| isTaggedTemplateTag(entry.node, /*includeElementAccess*/ true, /*skipPastOuterExpressions*/ true)
|| isDecoratorTarget(entry.node, /*includeElementAccess*/ true, /*skipPastOuterExpressions*/ true)
|| isJsxOpeningLikeElementTagName(entry.node, /*includeElementAccess*/ true, /*skipPastOuterExpressions*/ true)
|| isRightSideOfPropertyAccess(entry.node)
|| isArgumentExpressionOfElementAccess(entry.node)) {
const ancestor = findAncestor(entry.node, isValidCallHierarchyDeclaration) || entry.node.getSourceFile();
return { declaration: firstOrOnly(findImplementationOrAllInitialDeclarations(typeChecker, ancestor)), range: createTextRangeFromNode(entry.node, entry.node.getSourceFile()) };
}
}
}
function getCallSiteGroupKey(entry: CallSite) {
return "" + getNodeId(entry.declaration);
}
function createCallHierarchyIncomingCall(from: CallHierarchyItem, fromSpans: TextSpan[]): CallHierarchyIncomingCall {
return { from, fromSpans };
}
function convertCallSiteGroupToIncomingCall(program: Program, entries: readonly CallSite[]) {
return createCallHierarchyIncomingCall(createCallHierarchyItem(program, entries[0].declaration), map(entries, entry => createTextSpanFromRange(entry.range)));
}
/** Gets the call sites that call into the provided call hierarchy declaration. */
export function getIncomingCalls(program: Program, declaration: CallHierarchyDeclaration, cancellationToken: CancellationToken): CallHierarchyIncomingCall[] {
// Source files and modules have no incoming calls.
if (isSourceFile(declaration) || isModuleDeclaration(declaration)) {
return [];
}
const location = getCallHierarchyDeclarationReferenceNode(declaration);
const calls = filter(FindAllReferences.findReferenceOrRenameEntries(program, cancellationToken, program.getSourceFiles(), location, /*position*/ 0, /*options*/ undefined, convertEntryToCallSite), isDefined);
return calls ? group(calls, getCallSiteGroupKey, entries => convertCallSiteGroupToIncomingCall(program, entries)) : [];
}
function createCallSiteCollector(program: Program, callSites: CallSite[]): (node: Node | undefined) => void {
function recordCallSite(node: CallExpression | NewExpression | TaggedTemplateExpression | PropertyAccessExpression | ElementAccessExpression | Decorator | JsxOpeningLikeElement) {
const target =
isTaggedTemplateExpression(node) ? node.tag :
isJsxOpeningLikeElement(node) ? node.tagName :
isAccessExpression(node) ? node :
node.expression;
const declaration = resolveCallHierarchyDeclaration(program, target);
if (declaration) {
const range = createTextRangeFromNode(target, node.getSourceFile());
if (isArray(declaration)) {
for (const decl of declaration) {
callSites.push({ declaration: decl, range });
}
}
else {
callSites.push({ declaration, range });
}
}
}
function collect(node: Node | undefined) {
if (!node) return;
if (node.flags & NodeFlags.Ambient) {
// do not descend into ambient nodes.
return;
}
if (isValidCallHierarchyDeclaration(node)) {
// do not descend into other call site declarations, other than class member names
if (isClassLike(node)) {
for (const member of node.members) {
if (member.name && isComputedPropertyName(member.name)) {
collect(member.name.expression);
}
}
}
return;
}
switch (node.kind) {
case SyntaxKind.Identifier:
case SyntaxKind.ImportEqualsDeclaration:
case SyntaxKind.ImportDeclaration:
case SyntaxKind.ExportDeclaration:
case SyntaxKind.InterfaceDeclaration:
case SyntaxKind.TypeAliasDeclaration:
// do not descend into nodes that cannot contain callable nodes
return;
case SyntaxKind.TypeAssertionExpression:
case SyntaxKind.AsExpression:
// do not descend into the type side of an assertion
collect((node as TypeAssertion | AsExpression).expression);
return;
case SyntaxKind.VariableDeclaration:
case SyntaxKind.Parameter:
// do not descend into the type of a variable or parameter declaration
collect((node as VariableDeclaration | ParameterDeclaration).name);
collect((node as VariableDeclaration | ParameterDeclaration).initializer);
return;
case SyntaxKind.CallExpression:
// do not descend into the type arguments of a call expression
recordCallSite(node as CallExpression);
collect((node as CallExpression).expression);
forEach((node as CallExpression).arguments, collect);
return;
case SyntaxKind.NewExpression:
// do not descend into the type arguments of a new expression
recordCallSite(node as NewExpression);
collect((node as NewExpression).expression);
forEach((node as NewExpression).arguments, collect);
return;
case SyntaxKind.TaggedTemplateExpression:
// do not descend into the type arguments of a tagged template expression
recordCallSite(node as TaggedTemplateExpression);
collect((node as TaggedTemplateExpression).tag);
collect((node as TaggedTemplateExpression).template);
return;
case SyntaxKind.JsxOpeningElement:
case SyntaxKind.JsxSelfClosingElement:
// do not descend into the type arguments of a JsxOpeningLikeElement
recordCallSite(node as JsxOpeningLikeElement);
collect((node as JsxOpeningLikeElement).tagName);
collect((node as JsxOpeningLikeElement).attributes);
return;
case SyntaxKind.Decorator:
recordCallSite(node as Decorator);
collect((node as Decorator).expression);
return;
case SyntaxKind.PropertyAccessExpression:
case SyntaxKind.ElementAccessExpression:
recordCallSite(node as AccessExpression);
forEachChild(node, collect);
break;
}
if (isPartOfTypeNode(node)) {
// do not descend into types
return;
}
forEachChild(node, collect);
}
return collect;
}
function collectCallSitesOfSourceFile(node: SourceFile, collect: (node: Node | undefined) => void) {
forEach(node.statements, collect);
}
function collectCallSitesOfModuleDeclaration(node: ModuleDeclaration, collect: (node: Node | undefined) => void) {
if (!hasModifier(node, ModifierFlags.Ambient) && node.body && isModuleBlock(node.body)) {
forEach(node.body.statements, collect);
}
}
function collectCallSitesOfFunctionLikeDeclaration(typeChecker: TypeChecker, node: FunctionLikeDeclaration, collect: (node: Node | undefined) => void) {
const implementation = findImplementation(typeChecker, node);
if (implementation) {
forEach(implementation.parameters, collect);
collect(implementation.body);
}
}
function collectCallSitesOfClassLikeDeclaration(node: ClassLikeDeclaration, collect: (node: Node | undefined) => void) {
forEach(node.decorators, collect);
const heritage = getClassExtendsHeritageElement(node);
if (heritage) {
collect(heritage.expression);
}
for (const member of node.members) {
forEach(member.decorators, collect);
if (isPropertyDeclaration(member)) {
collect(member.initializer);
}
else if (isConstructorDeclaration(member) && member.body) {
forEach(member.parameters, collect);
collect(member.body);
}
}
}
function collectCallSites(program: Program, node: CallHierarchyDeclaration) {
const callSites: CallSite[] = [];
const collect = createCallSiteCollector(program, callSites);
switch (node.kind) {
case SyntaxKind.SourceFile:
collectCallSitesOfSourceFile(node, collect);
break;
case SyntaxKind.ModuleDeclaration:
collectCallSitesOfModuleDeclaration(node, collect);
break;
case SyntaxKind.FunctionDeclaration:
case SyntaxKind.FunctionExpression:
case SyntaxKind.ArrowFunction:
case SyntaxKind.MethodDeclaration:
case SyntaxKind.GetAccessor:
case SyntaxKind.SetAccessor:
collectCallSitesOfFunctionLikeDeclaration(program.getTypeChecker(), node, collect);
break;
case SyntaxKind.ClassDeclaration:
case SyntaxKind.ClassExpression:
collectCallSitesOfClassLikeDeclaration(node, collect);
break;
default:
Debug.assertNever(node);
}
return callSites;
}
function createCallHierarchyOutgoingCall(to: CallHierarchyItem, fromSpans: TextSpan[]): CallHierarchyOutgoingCall {
return { to, fromSpans };
}
function convertCallSiteGroupToOutgoingCall(program: Program, entries: readonly CallSite[]) {
return createCallHierarchyOutgoingCall(createCallHierarchyItem(program, entries[0].declaration), map(entries, entry => createTextSpanFromRange(entry.range)));
}
/** Gets the call sites that call out of the provided call hierarchy declaration. */
export function getOutgoingCalls(program: Program, declaration: CallHierarchyDeclaration): CallHierarchyOutgoingCall[] {
if (declaration.flags & NodeFlags.Ambient || isMethodSignature(declaration)) {
return [];
}
return group(collectCallSites(program, declaration), getCallSiteGroupKey, entries => convertCallSiteGroupToOutgoingCall(program, entries));
}
}
+23
View File
@@ -2137,6 +2137,26 @@ namespace ts {
return refactor.getEditsForRefactor(getRefactorContext(file, positionOrRange, preferences, formatOptions), refactorName, actionName);
}
function prepareCallHierarchy(fileName: string, position: number): CallHierarchyItem | CallHierarchyItem[] | undefined {
synchronizeHostData();
const declarations = CallHierarchy.resolveCallHierarchyDeclaration(program, getTouchingPropertyName(getValidSourceFile(fileName), position));
return declarations && mapOneOrMany(declarations, declaration => CallHierarchy.createCallHierarchyItem(program, declaration));
}
function provideCallHierarchyIncomingCalls(fileName: string, position: number): CallHierarchyIncomingCall[] {
synchronizeHostData();
const sourceFile = getValidSourceFile(fileName);
const declaration = firstOrOnly(CallHierarchy.resolveCallHierarchyDeclaration(program, position === 0 ? sourceFile : getTouchingPropertyName(sourceFile, position)));
return declaration ? CallHierarchy.getIncomingCalls(program, declaration, cancellationToken) : [];
}
function provideCallHierarchyOutgoingCalls(fileName: string, position: number): CallHierarchyOutgoingCall[] {
synchronizeHostData();
const sourceFile = getValidSourceFile(fileName);
const declaration = firstOrOnly(CallHierarchy.resolveCallHierarchyDeclaration(program, position === 0 ? sourceFile : getTouchingPropertyName(sourceFile, position)));
return declaration ? CallHierarchy.getOutgoingCalls(program, declaration) : [];
}
return {
dispose,
cleanupSemanticCache,
@@ -2192,6 +2212,9 @@ namespace ts {
getEditsForRefactor,
toLineColumnOffset: sourceMapper.toLineColumnOffset,
getSourceMapper: () => sourceMapper,
prepareCallHierarchy,
provideCallHierarchyIncomingCalls,
provideCallHierarchyOutgoingCalls
};
}
+27
View File
@@ -271,6 +271,10 @@ namespace ts {
*/
getSpanOfEnclosingComment(fileName: string, position: number, onlyMultiLine: boolean): string;
prepareCallHierarchy(fileName: string, position: number): string;
provideCallHierarchyIncomingCalls(fileName: string, position: number): string;
provideCallHierarchyOutgoingCalls(fileName: string, position: number): string;
getEmitOutput(fileName: string): string;
getEmitOutputObject(fileName: string): EmitOutput;
}
@@ -1020,6 +1024,29 @@ namespace ts {
);
}
/// CALL HIERARCHY
public prepareCallHierarchy(fileName: string, position: number): string {
return this.forwardJSONCall(
`prepareCallHierarchy('${fileName}', ${position})`,
() => this.languageService.prepareCallHierarchy(fileName, position)
);
}
public provideCallHierarchyIncomingCalls(fileName: string, position: number): string {
return this.forwardJSONCall(
`provideCallHierarchyIncomingCalls('${fileName}', ${position})`,
() => this.languageService.provideCallHierarchyIncomingCalls(fileName, position)
);
}
public provideCallHierarchyOutgoingCalls(fileName: string, position: number): string {
return this.forwardJSONCall(
`provideCallHierarchyOutgoingCalls('${fileName}', ${position})`,
() => this.languageService.provideCallHierarchyOutgoingCalls(fileName, position)
);
}
/// Emit
public getEmitOutput(fileName: string): string {
return this.forwardJSONCall(
+1
View File
@@ -18,6 +18,7 @@
"documentRegistry.ts",
"importTracker.ts",
"findAllReferences.ts",
"callHierarchy.ts",
"getEditsForFileRename.ts",
"goToDefinition.ts",
"jsDoc.ts",
+22
View File
@@ -351,6 +351,10 @@ namespace ts {
getNavigationBarItems(fileName: string): NavigationBarItem[];
getNavigationTree(fileName: string): NavigationTree;
prepareCallHierarchy(fileName: string, position: number): CallHierarchyItem | CallHierarchyItem[] | undefined;
provideCallHierarchyIncomingCalls(fileName: string, position: number): CallHierarchyIncomingCall[];
provideCallHierarchyOutgoingCalls(fileName: string, position: number): CallHierarchyOutgoingCall[];
getOutliningSpans(fileName: string): OutliningSpan[];
getTodoComments(fileName: string, descriptors: TodoCommentDescriptor[]): TodoComment[];
getBraceMatchingAtPosition(fileName: string, position: number): TextSpan[];
@@ -522,6 +526,24 @@ namespace ts {
childItems?: NavigationTree[];
}
export interface CallHierarchyItem {
name: string;
kind: ScriptElementKind;
file: string;
span: TextSpan;
selectionSpan: TextSpan;
}
export interface CallHierarchyIncomingCall {
from: CallHierarchyItem;
fromSpans: TextSpan[];
}
export interface CallHierarchyOutgoingCall {
to: CallHierarchyItem;
fromSpans: TextSpan[];
}
export interface TodoCommentDescriptor {
text: string;
priority: number;
+71 -18
View File
@@ -196,27 +196,58 @@ namespace ts {
return false;
}
export function isCallExpressionTarget(node: Node): boolean {
return isCallOrNewExpressionTargetWorker(node, isCallExpression);
export function isCallExpressionTarget(node: Node, includeElementAccess = false, skipPastOuterExpressions = false): boolean {
return isCalleeWorker(node, isCallExpression, selectExpressionOfCallOrNewExpressionOrDecorator, includeElementAccess, skipPastOuterExpressions);
}
export function isNewExpressionTarget(node: Node): boolean {
return isCallOrNewExpressionTargetWorker(node, isNewExpression);
export function isNewExpressionTarget(node: Node, includeElementAccess = false, skipPastOuterExpressions = false): boolean {
return isCalleeWorker(node, isNewExpression, selectExpressionOfCallOrNewExpressionOrDecorator, includeElementAccess, skipPastOuterExpressions);
}
export function isCallOrNewExpressionTarget(node: Node): boolean {
return isCallOrNewExpressionTargetWorker(node, isCallOrNewExpression);
export function isCallOrNewExpressionTarget(node: Node, includeElementAccess = false, skipPastOuterExpressions = false): boolean {
return isCalleeWorker(node, isCallOrNewExpression, selectExpressionOfCallOrNewExpressionOrDecorator, includeElementAccess, skipPastOuterExpressions);
}
function isCallOrNewExpressionTargetWorker<T extends CallExpression | NewExpression>(node: Node, pred: (node: Node) => node is T): boolean {
const target = climbPastPropertyAccess(node);
return !!target && !!target.parent && pred(target.parent) && target.parent.expression === target;
export function isTaggedTemplateTag(node: Node, includeElementAccess = false, skipPastOuterExpressions = false): boolean {
return isCalleeWorker(node, isTaggedTemplateExpression, selectTagOfTaggedTemplateExpression, includeElementAccess, skipPastOuterExpressions);
}
export function isDecoratorTarget(node: Node, includeElementAccess = false, skipPastOuterExpressions = false): boolean {
return isCalleeWorker(node, isDecorator, selectExpressionOfCallOrNewExpressionOrDecorator, includeElementAccess, skipPastOuterExpressions);
}
export function isJsxOpeningLikeElementTagName(node: Node, includeElementAccess = false, skipPastOuterExpressions = false): boolean {
return isCalleeWorker(node, isJsxOpeningLikeElement, selectTagNameOfJsxOpeningLikeElement, includeElementAccess, skipPastOuterExpressions);
}
function selectExpressionOfCallOrNewExpressionOrDecorator(node: CallExpression | NewExpression | Decorator) {
return node.expression;
}
function selectTagOfTaggedTemplateExpression(node: TaggedTemplateExpression) {
return node.tag;
}
function selectTagNameOfJsxOpeningLikeElement(node: JsxOpeningLikeElement) {
return node.tagName;
}
function isCalleeWorker<T extends CallExpression | NewExpression | TaggedTemplateExpression | Decorator | JsxOpeningLikeElement>(node: Node, pred: (node: Node) => node is T, calleeSelector: (node: T) => Expression, includeElementAccess: boolean, skipPastOuterExpressions: boolean) {
let target = includeElementAccess ? climbPastPropertyOrElementAccess(node) : climbPastPropertyAccess(node);
if (skipPastOuterExpressions) {
target = skipOuterExpressions(target);
}
return !!target && !!target.parent && pred(target.parent) && calleeSelector(target.parent) === target;
}
export function climbPastPropertyAccess(node: Node) {
return isRightSideOfPropertyAccess(node) ? node.parent : node;
}
export function climbPastPropertyOrElementAccess(node: Node) {
return isRightSideOfPropertyAccess(node) || isArgumentExpressionOfElementAccess(node) ? node.parent : node;
}
export function getTargetLabel(referenceNode: Node, labelName: string): Identifier | undefined {
while (referenceNode) {
if (referenceNode.kind === SyntaxKind.LabeledStatement && (<LabeledStatement>referenceNode).label.escapedText === labelName) {
@@ -236,11 +267,11 @@ namespace ts {
}
export function isJumpStatementTarget(node: Node): node is Identifier & { parent: BreakOrContinueStatement } {
return node.kind === SyntaxKind.Identifier && isBreakOrContinueStatement(node.parent) && node.parent.label === node;
return isIdentifier(node) && tryCast(node.parent, isBreakOrContinueStatement)?.label === node;
}
export function isLabelOfLabeledStatement(node: Node): node is Identifier {
return node.kind === SyntaxKind.Identifier && isLabeledStatement(node.parent) && node.parent.label === node;
return isIdentifier(node) && tryCast(node.parent, isLabeledStatement)?.label === node;
}
export function isLabelName(node: Node): boolean {
@@ -248,24 +279,27 @@ namespace ts {
}
export function isTagName(node: Node): boolean {
return isJSDocTag(node.parent) && node.parent.tagName === node;
return tryCast(node.parent, isJSDocTag)?.tagName === node;
}
export function isRightSideOfQualifiedName(node: Node) {
return node.parent.kind === SyntaxKind.QualifiedName && (<QualifiedName>node.parent).right === node;
return tryCast(node.parent, isQualifiedName)?.right === node;
}
export function isRightSideOfPropertyAccess(node: Node) {
return node && node.parent && node.parent.kind === SyntaxKind.PropertyAccessExpression && (<PropertyAccessExpression>node.parent).name === node;
return tryCast(node.parent, isPropertyAccessExpression)?.name === node;
}
export function isArgumentExpressionOfElementAccess(node: Node) {
return tryCast(node.parent, isElementAccessExpression)?.argumentExpression === node;
}
export function isNameOfModuleDeclaration(node: Node) {
return node.parent.kind === SyntaxKind.ModuleDeclaration && (<ModuleDeclaration>node.parent).name === node;
return tryCast(node.parent, isModuleDeclaration)?.name === node;
}
export function isNameOfFunctionDeclaration(node: Node): boolean {
return node.kind === SyntaxKind.Identifier &&
isFunctionLike(node.parent) && (<FunctionLikeDeclaration>node.parent).name === node;
return isIdentifier(node) && tryCast(node.parent, isFunctionLike)?.name === node;
}
export function isLiteralNameOfPropertyDeclarationOrIndexAccess(node: StringLiteral | NumericLiteral | NoSubstitutionTemplateLiteral): boolean {
@@ -2298,4 +2332,23 @@ namespace ts {
export function getRefactorContextSpan({ startPosition, endPosition }: RefactorContext): TextSpan {
return createTextSpanFromBounds(startPosition, endPosition === undefined ? startPosition : endPosition);
}
}
/**
* If the provided value is an array, the mapping function is applied to each element; otherwise, the mapping function is applied
* to the provided value itself.
*/
export function mapOneOrMany<T, U>(valueOrArray: T | readonly T[], f: (x: T, i: number) => U): U | U[];
export function mapOneOrMany<T, U>(valueOrArray: T | readonly T[] | undefined, f: (x: T, i: number) => U): U | U[] | undefined;
export function mapOneOrMany<T, U>(valueOrArray: T | readonly T[], f: (x: T, i: number) => U, resultSelector: (x: U[]) => U): U;
export function mapOneOrMany<T, U>(valueOrArray: T | readonly T[] | undefined, f: (x: T, i: number) => U, resultSelector: (x: U[]) => U): U | undefined;
export function mapOneOrMany<T, U>(valueOrArray: T | readonly T[] | undefined, f: (x: T, i: number) => U, resultSelector: (x: U[]) => U | U[] = identity): U | U[] | undefined {
return valueOrArray ? isArray(valueOrArray) ? resultSelector(map(valueOrArray, f)) : f(valueOrArray, 0) : undefined;
}
/**
* If the provided value is an array, the first element of the array is returned; otherwise, the provided value is returned instead.
*/
export function firstOrOnly<T>(valueOrArray: T | readonly T[]): T {
return isArray(valueOrArray) ? first(valueOrArray) : valueOrArray;
}
}