mirror of
https://github.com/microsoft/TypeScript.git
synced 2025-11-18 17:21:48 +00:00
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:
@@ -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));
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -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(
|
||||
|
||||
@@ -18,6 +18,7 @@
|
||||
"documentRegistry.ts",
|
||||
"importTracker.ts",
|
||||
"findAllReferences.ts",
|
||||
"callHierarchy.ts",
|
||||
"getEditsForFileRename.ts",
|
||||
"goToDefinition.ts",
|
||||
"jsDoc.ts",
|
||||
|
||||
@@ -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
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user