import { CancellationToken } from 'vscode-languageserver' ; import { getFileInfo } from '../analyzer/analyzerNodeInfo' ; import { Declaration , DeclarationType , isFunctionDeclaration } from '../analyzer/declaration' ; import * as ParseTreeUtils from '../analyzer/parseTreeUtils' ; import { isStubFile , SourceMapper } from '../analyzer/sourceMapper' ; import { TypeEvaluator } from '../analyzer/typeEvaluatorTypes' ; import { isOverloadedFunction , TypeCategory } from '../analyzer/types' ; import { doForEachSubtype } from '../analyzer/typeUtils' ; import { throwIfCancellationRequested } from '../common/cancellationUtils' ; import { isDefined } from '../common/core' ; import { convertPositionToOffset } from '../common/positionUtils' ; import { DocumentRange , Position , rangesAreEqual } from '../common/textRange' ; import { ParseNodeType } from '../parser/parseNodes' ; import { ParseResults } from '../parser/parser' ; export enum DefinitionFilter { All = 'all' , PreferSource = 'preferSource' , PreferStubs = 'preferStubs' } export class DefinitionProvider { static getDefinitionsForPosition ( sourceMapper , parseResults , position , filter , evaluator , token ) { throwIfCancellationRequested ( token ) ; const offset = convertPositionToOffset ( position , parseResults . tokenizerOutput . lines ) ; if ( offset === undefined ) { return undefined ; } const node = ParseTreeUtils . findNodeByOffset ( parseResults . parseTree , offset ) ; if ( node === undefined ) { return undefined ; } const definitions = [ ] ; if ( node . nodeType === ParseNodeType . Name ) { const declarations = evaluator . getDeclarationsForNameNode ( node ) ; DefinitionProvider . _resolveDeclarations ( declarations , evaluator , definitions , sourceMapper ) ; } if ( definitions . length === 0 ) { return undefined ; } if ( filter === DefinitionFilter . All ) { return definitions ; } const preferStubs = filter === DefinitionFilter . PreferStubs ; const wantedFile = ( v ) => preferStubs === isStubFile ( v . path ) ; if ( definitions . find ( wantedFile ) ) { return definitions . filter ( wantedFile ) ; } return definitions ; } static getTypeDefinitionsForPosition ( sourceMapper , parseResults , position , evaluator , filePath , token ) { throwIfCancellationRequested ( token ) ; const offset = convertPositionToOffset ( position , parseResults . tokenizerOutput . lines ) ; if ( offset === undefined ) { return undefined ; } const node = ParseTreeUtils . findNodeByOffset ( parseResults . parseTree , offset ) ; if ( node === undefined ) { return undefined ; } const definitions = [ ] ; if ( node . nodeType === ParseNodeType . Name ) { const type = evaluator . getType ( node ) ; if ( type ) { let declarations = [ ] ; doForEachSubtype ( type , ( subtype ) => { if ( subtype ?. category === TypeCategory . Class ) { declarations . push ( ... sourceMapper . findClassDeclarationsByType ( filePath , subtype ) ) ; } } ) ; if ( declarations . length === 0 ) { declarations = evaluator . getDeclarationsForNameNode ( node ) ?? [ ] ; } DefinitionProvider . _resolveDeclarations ( declarations , evaluator , definitions , sourceMapper ) ; } } if ( definitions . length === 0 ) { return undefined ; } return definitions ; } private static _resolveDeclarations ( declarations , evaluator , definitions , sourceMapper ) { if ( declarations ) { declarations . forEach ( ( decl ) => { let resolvedDecl = evaluator . resolveAliasDeclaration ( decl , true , true ) ; if ( resolvedDecl && resolvedDecl . path ) { if ( resolvedDecl . type === DeclarationType . Alias && resolvedDecl . isUnresolved ) { return ; } if ( resolvedDecl . type === DeclarationType . Alias && resolvedDecl . symbolName && resolvedDecl . submoduleFallback && resolvedDecl . submoduleFallback . path ) { resolvedDecl = resolvedDecl . submoduleFallback ; } this . _addIfUnique ( definitions , { path : resolvedDecl . path , range : resolvedDecl . range , } ) ; if ( isFunctionDeclaration ( resolvedDecl ) ) { const functionType = evaluator . getTypeForDeclaration ( resolvedDecl ) ; if ( functionType && isOverloadedFunction ( functionType ) ) { for ( const overloadDecl of functionType . overloads . map ( ( o ) => o . details . declaration ) . filter ( isDefined ) ) { this . _addIfUnique ( definitions , { path : overloadDecl . path , range : overloadDecl . range , } ) ; } } } if ( isStubFile ( resolvedDecl . path ) ) { if ( resolvedDecl . type === DeclarationType . Alias ) { sourceMapper . findModules ( resolvedDecl . path ) . map ( ( m ) => getFileInfo ( m ) ?. filePath ) . filter ( isDefined ) . forEach ( ( f ) => this . _addIfUnique ( definitions , this . _createModuleEntry ( f ) ) ) ; } else { const implDecls = sourceMapper . findDeclarations ( resolvedDecl ) ; for ( const implDecl of implDecls ) { if ( implDecl && implDecl . path ) { this . _addIfUnique ( definitions , { path : implDecl . path , range : implDecl . range , } ) ; } } } } } } ) ; } } private static _createModuleEntry ( filePath ) { return { path : filePath , range : { start : { line : 0 , character : 0 } , end : { line : 0 , character : 0 } , } , } ; } private static _addIfUnique ( definitions , itemToAdd ) { for ( const def of definitions ) { if ( def . path === itemToAdd . path && rangesAreEqual ( def . range , itemToAdd . range ) ) { return ; } } definitions . push ( itemToAdd ) ; } }