https://github.com/Microsoft/TypeScript
Raw File
Tip revision: af083df737c8e1e225c502b8cf1688f5483cd8a1 authored by Ron Buckton on 28 August 2017, 06:41:34 UTC
Rename 'promised' to 'awaited'
Tip revision: af083df
goToDefinition.ts
/* @internal */
namespace ts.GoToDefinition {
    export function getDefinitionAtPosition(program: Program, sourceFile: SourceFile, position: number): DefinitionInfo[] {
        /// Triple slash reference comments
        const comment = findReferenceInPosition(sourceFile.referencedFiles, position);
        if (comment) {
            const referenceFile = tryResolveScriptReference(program, sourceFile, comment);
            if (referenceFile) {
                return [getDefinitionInfoForFileReference(comment.fileName, referenceFile.fileName)];
            }
            // Might still be on jsdoc, so keep looking.
        }

        // Type reference directives
        const typeReferenceDirective = findReferenceInPosition(sourceFile.typeReferenceDirectives, position);
        if (typeReferenceDirective) {
            const referenceFile = program.getResolvedTypeReferenceDirectives().get(typeReferenceDirective.fileName);
            return referenceFile && referenceFile.resolvedFileName &&
                [getDefinitionInfoForFileReference(typeReferenceDirective.fileName, referenceFile.resolvedFileName)];
        }

        const node = getTouchingPropertyName(sourceFile, position, /*includeJsDocComment*/ true);
        if (node === sourceFile) {
            return undefined;
        }

        // Labels
        if (isJumpStatementTarget(node)) {
            const labelName = (<Identifier>node).text;
            const label = getTargetLabel((<BreakOrContinueStatement>node.parent), labelName);
            return label ? [createDefinitionInfoFromName(label, ScriptElementKind.label, labelName, /*containerName*/ undefined)] : undefined;
        }

        const typeChecker = program.getTypeChecker();

        const calledDeclaration = tryGetSignatureDeclaration(typeChecker, node);
        if (calledDeclaration) {
            return [createDefinitionFromSignatureDeclaration(typeChecker, calledDeclaration)];
        }

        let symbol = typeChecker.getSymbolAtLocation(node);

        // Could not find a symbol e.g. node is string or number keyword,
        // or the symbol was an internal symbol and does not have a declaration e.g. undefined symbol
        if (!symbol) {
            return undefined;
        }

        // If this is an alias, and the request came at the declaration location
        // get the aliased symbol instead. This allows for goto def on an import e.g.
        //   import {A, B} from "mod";
        // to jump to the implementation directly.
        if (symbol.flags & SymbolFlags.Alias && shouldSkipAlias(node, symbol.declarations[0])) {
            const aliased = typeChecker.getAliasedSymbol(symbol);
            if (aliased.declarations) {
                symbol = aliased;
            }
        }

        // Because name in short-hand property assignment has two different meanings: property name and property value,
        // using go-to-definition at such position should go to the variable declaration of the property value rather than
        // go to the declaration of the property name (in this case stay at the same position). However, if go-to-definition
        // is performed at the location of property access, we would like to go to definition of the property in the short-hand
        // assignment. This case and others are handled by the following code.
        if (node.parent.kind === SyntaxKind.ShorthandPropertyAssignment) {
            const shorthandSymbol = typeChecker.getShorthandAssignmentValueSymbol(symbol.valueDeclaration);
            if (!shorthandSymbol) {
                return [];
            }

            const shorthandDeclarations = shorthandSymbol.getDeclarations();
            const shorthandSymbolKind = SymbolDisplay.getSymbolKind(typeChecker, shorthandSymbol, node);
            const shorthandSymbolName = typeChecker.symbolToString(shorthandSymbol);
            const shorthandContainerName = typeChecker.symbolToString(symbol.parent, node);
            return map(shorthandDeclarations,
                declaration => createDefinitionInfo(declaration, shorthandSymbolKind, shorthandSymbolName, shorthandContainerName));
        }

        // If the node is the name of a BindingElement within an ObjectBindingPattern instead of just returning the
        // declaration the symbol (which is itself), we should try to get to the original type of the ObjectBindingPattern
        // and return the property declaration for the referenced property.
        // For example:
        //      import('./foo').then(({ b/*goto*/ar }) => undefined); => should get use to the declaration in file "./foo"
        //
        //      function bar<T>(onfulfilled: (value: T) => void) { //....}
        //      interface Test {
        //          pr/*destination*/op1: number
        //      }
        //      bar<Test>(({pr/*goto*/op1})=>{});
        if (isPropertyName(node) && isBindingElement(node.parent) && isObjectBindingPattern(node.parent.parent) &&
             (node === (node.parent.propertyName || node.parent.name))) {
            const type = typeChecker.getTypeAtLocation(node.parent.parent);
            if (type) {
                const propSymbols = getPropertySymbolsFromType(type, node);
                if (propSymbols) {
                    return flatMap(propSymbols, propSymbol => getDefinitionFromSymbol(typeChecker, propSymbol, node));
                }
            }
        }

        // If the current location we want to find its definition is in an object literal, try to get the contextual type for the
        // object literal, lookup the property symbol in the contextual type, and use this for goto-definition.
        // For example
        //      interface Props{
        //          /*first*/prop1: number
        //          prop2: boolean
        //      }
        //      function Foo(arg: Props) {}
        //      Foo( { pr/*1*/op1: 10, prop2: true })
        const element = getContainingObjectLiteralElement(node);
        if (element && typeChecker.getContextualType(element.parent as Expression)) {
            return flatMap(getPropertySymbolsFromContextualType(typeChecker, element), propertySymbol =>
                getDefinitionFromSymbol(typeChecker, propertySymbol, node));
        }
        return getDefinitionFromSymbol(typeChecker, symbol, node);
    }

    /// Goto type
    export function getTypeDefinitionAtPosition(typeChecker: TypeChecker, sourceFile: SourceFile, position: number): DefinitionInfo[] {
        const node = getTouchingPropertyName(sourceFile, position, /*includeJsDocComment*/ true);
        if (node === sourceFile) {
            return undefined;
        }

        const symbol = typeChecker.getSymbolAtLocation(node);
        if (!symbol) {
            return undefined;
        }

        const type = typeChecker.getTypeOfSymbolAtLocation(symbol, node);
        if (!type) {
            return undefined;
        }

        if (type.flags & TypeFlags.Union && !(type.flags & TypeFlags.Enum)) {
            const result: DefinitionInfo[] = [];
            forEach((<UnionType>type).types, t => {
                if (t.symbol) {
                    addRange(/*to*/ result, /*from*/ getDefinitionFromSymbol(typeChecker, t.symbol, node));
                }
            });
            return result;
        }

        if (!type.symbol) {
            return undefined;
        }

        return getDefinitionFromSymbol(typeChecker, type.symbol, node);
    }

    // Go to the original declaration for cases:
    //
    //   (1) when the aliased symbol was declared in the location(parent).
    //   (2) when the aliased symbol is originating from an import.
    //
    function shouldSkipAlias(node: Node, declaration: Node): boolean {
        if (node.kind !== SyntaxKind.Identifier) {
            return false;
        }
        if (node.parent === declaration) {
            return true;
        }
        switch (declaration.kind) {
            case SyntaxKind.ImportClause:
            case SyntaxKind.ImportEqualsDeclaration:
                return true;
            case SyntaxKind.ImportSpecifier:
                return declaration.parent.kind === SyntaxKind.NamedImports;
            default:
                return false;
        }
    }

    function getDefinitionFromSymbol(typeChecker: TypeChecker, symbol: Symbol, node: Node): DefinitionInfo[] {
        const result: DefinitionInfo[] = [];
        const declarations = symbol.getDeclarations();
        const { symbolName, symbolKind, containerName } = getSymbolInfo(typeChecker, symbol, node);

        if (!tryAddConstructSignature(symbol, node, symbolKind, symbolName, containerName, result) &&
            !tryAddCallSignature(symbol, node, symbolKind, symbolName, containerName, result)) {
            // Just add all the declarations.
            forEach(declarations, declaration => {
                result.push(createDefinitionInfo(declaration, symbolKind, symbolName, containerName));
            });
        }

        return result;

        function tryAddConstructSignature(symbol: Symbol, location: Node, symbolKind: ScriptElementKind, symbolName: string, containerName: string, result: DefinitionInfo[]) {
            // Applicable only if we are in a new expression, or we are on a constructor declaration
            // and in either case the symbol has a construct signature definition, i.e. class
            if (isNewExpressionTarget(location) || location.kind === SyntaxKind.ConstructorKeyword) {
                if (symbol.flags & SymbolFlags.Class) {
                    // Find the first class-like declaration and try to get the construct signature.
                    for (const declaration of symbol.getDeclarations()) {
                        if (isClassLike(declaration)) {
                            return tryAddSignature(
                                declaration.members, /*selectConstructors*/ true, symbolKind, symbolName, containerName, result);
                        }
                    }

                    Debug.fail("Expected declaration to have at least one class-like declaration");
                }
            }
            return false;
        }

        function tryAddCallSignature(symbol: Symbol, location: Node, symbolKind: ScriptElementKind, symbolName: string, containerName: string, result: DefinitionInfo[]) {
            if (isCallExpressionTarget(location) || isNewExpressionTarget(location) || isNameOfFunctionDeclaration(location)) {
                return tryAddSignature(symbol.declarations, /*selectConstructors*/ false, symbolKind, symbolName, containerName, result);
            }
            return false;
        }

        function tryAddSignature(signatureDeclarations: ReadonlyArray<Declaration> | undefined, selectConstructors: boolean, symbolKind: ScriptElementKind, symbolName: string, containerName: string, result: DefinitionInfo[]) {
            if (!signatureDeclarations) {
                return false;
            }

            const declarations: Declaration[] = [];
            let definition: Declaration | undefined;

            for (const d of signatureDeclarations) {
                if (selectConstructors ? d.kind === SyntaxKind.Constructor : isSignatureDeclaration(d)) {
                    declarations.push(d);
                    if ((<FunctionLikeDeclaration>d).body) definition = d;
                }
            }

            if (declarations.length) {
                result.push(createDefinitionInfo(definition || lastOrUndefined(declarations), symbolKind, symbolName, containerName));
                return true;
            }
            return false;
        }
    }

    function isSignatureDeclaration(node: Node): boolean {
        switch (node.kind) {
            case ts.SyntaxKind.Constructor:
            case ts.SyntaxKind.FunctionDeclaration:
            case ts.SyntaxKind.MethodDeclaration:
            case ts.SyntaxKind.MethodSignature:
                return true;
            default:
                return false;
        }
    }

    /** Creates a DefinitionInfo from a Declaration, using the declaration's name if possible. */
    function createDefinitionInfo(node: Declaration, symbolKind: ScriptElementKind, symbolName: string, containerName: string): DefinitionInfo {
        return createDefinitionInfoFromName(getNameOfDeclaration(node) || node, symbolKind, symbolName, containerName);
    }

    /** Creates a DefinitionInfo directly from the name of a declaration. */
    function createDefinitionInfoFromName(name: Node, symbolKind: ScriptElementKind, symbolName: string, containerName: string): DefinitionInfo {
        const sourceFile = name.getSourceFile();
        return {
            fileName: sourceFile.fileName,
            textSpan: createTextSpanFromNode(name, sourceFile),
            kind: symbolKind,
            name: symbolName,
            containerKind: undefined,
            containerName
        };
    }

    function getSymbolInfo(typeChecker: TypeChecker, symbol: Symbol, node: Node) {
        return {
            symbolName: typeChecker.symbolToString(symbol), // Do not get scoped name, just the name of the symbol
            symbolKind: SymbolDisplay.getSymbolKind(typeChecker, symbol, node),
            containerName: symbol.parent ? typeChecker.symbolToString(symbol.parent, node) : ""
        };
    }

    function createDefinitionFromSignatureDeclaration(typeChecker: TypeChecker, decl: SignatureDeclaration): DefinitionInfo {
        const { symbolName, symbolKind, containerName } = getSymbolInfo(typeChecker, decl.symbol, decl);
        return createDefinitionInfo(decl, symbolKind, symbolName, containerName);
    }

    function findReferenceInPosition(refs: ReadonlyArray<FileReference>, pos: number): FileReference {
        for (const ref of refs) {
            if (ref.pos <= pos && pos <= ref.end) {
                return ref;
            }
        }
        return undefined;
    }

    function getDefinitionInfoForFileReference(name: string, targetFileName: string): DefinitionInfo {
        return {
            fileName: targetFileName,
            textSpan: createTextSpanFromBounds(0, 0),
            kind: ScriptElementKind.scriptElement,
            name,
            containerName: undefined,
            containerKind: undefined
        };
    }

    /** Returns a CallLikeExpression where `node` is the target being invoked. */
    function getAncestorCallLikeExpression(node: Node): CallLikeExpression | undefined {
        const target = climbPastManyPropertyAccesses(node);
        const callLike = target.parent;
        return callLike && isCallLikeExpression(callLike) && getInvokedExpression(callLike) === target && callLike;
    }

    function climbPastManyPropertyAccesses(node: Node): Node {
        return isRightSideOfPropertyAccess(node) ? climbPastManyPropertyAccesses(node.parent) : node;
    }

    function tryGetSignatureDeclaration(typeChecker: TypeChecker, node: Node): SignatureDeclaration | undefined {
        const callLike = getAncestorCallLikeExpression(node);
        const signature = callLike && typeChecker.getResolvedSignature(callLike);
        if (signature) {
            const decl = signature.declaration;
            if (decl && isSignatureDeclaration(decl)) {
                return decl;
            }
        }
        // Don't go to a function type, go to the value having that type.
        return undefined;
    }
}
back to top