import { CancellationToken , CompletionItem , CompletionItemKind , CompletionList , InsertTextFormat , MarkupKind , Range , TextEdit , } from 'vscode-languageserver' ; import { ImportLookup } from '../analyzer/analyzerFileInfo' ; import * as AnalyzerNodeInfo from '../analyzer/analyzerNodeInfo' ; import { Declaration , DeclarationType , FunctionDeclaration , isFunctionDeclaration , VariableDeclaration , } from '../analyzer/declaration' ; import { isDefinedInFile } from '../analyzer/declarationUtils' ; import { convertDocStringToMarkdown , convertDocStringToPlainText } from '../analyzer/docStringConversion' ; import { ImportedModuleDescriptor , ImportResolver } from '../analyzer/importResolver' ; import * as ParseTreeUtils from '../analyzer/parseTreeUtils' ; import { getCallNodeAndActiveParameterIndex } from '../analyzer/parseTreeUtils' ; import { getScopeForNode } from '../analyzer/scopeUtils' ; import { isStubFile , SourceMapper } from '../analyzer/sourceMapper' ; import { Symbol , SymbolTable } from '../analyzer/symbol' ; import * as SymbolNameUtils from '../analyzer/symbolNameUtils' ; import { getLastTypedDeclaredForSymbol } from '../analyzer/symbolUtils' ; import { getTypedDictMembersForClass } from '../analyzer/typedDicts' ; import { getClassDocString , getModuleDocString , getOverloadedFunctionDocStringsInherited , getPropertyDocStringInherited , getVariableDocString , } from '../analyzer/typeDocStringUtils' ; import { CallSignatureInfo , TypeEvaluator } from '../analyzer/typeEvaluatorTypes' ; import { printLiteralValue } from '../analyzer/typePrinter' ; import { ClassType , FunctionType , getTypeAliasInfo , isClass , isClassInstance , isFunction , isInstantiableClass , isModule , isNoneInstance , isOverloadedFunction , isUnbound , isUnknown , Type , TypeBase , UnknownType , } from '../analyzer/types' ; import { doForEachSubtype , getDeclaringModulesForType , getMembersForClass , getMembersForModule , isLiteralType , isLiteralTypeOrUnion , isProperty , } from '../analyzer/typeUtils' ; import { throwIfCancellationRequested } from '../common/cancellationUtils' ; import { ConfigOptions , ExecutionEnvironment } from '../common/configOptions' ; import * as debug from '../common/debug' ; import { fail } from '../common/debug' ; import { TextEditAction } from '../common/editAction' ; import { convertOffsetToPosition , convertPositionToOffset } from '../common/positionUtils' ; import { PythonVersion } from '../common/pythonVersion' ; import * as StringUtils from '../common/stringUtils' ; import { comparePositions , Position } from '../common/textRange' ; import { TextRange } from '../common/textRange' ; import { TextRangeCollection } from '../common/textRangeCollection' ; import { Duration } from '../common/timing' ; import { ArgumentCategory , DecoratorNode , DictionaryKeyEntryNode , DictionaryNode , ErrorExpressionCategory , ErrorNode , ExpressionNode , ImportFromNode , IndexNode , isExpressionNode , ModuleNameNode , NameNode , ParameterCategory , ParameterNode , ParseNode , ParseNodeType , SetNode , StringNode , } from '../parser/parseNodes' ; import { ParseResults } from '../parser/parser' ; import { StringToken , StringTokenFlags , Token , TokenType } from '../parser/tokenizerTypes' ; import { AbbreviationInfo , AutoImporter , AutoImportResult , ModuleSymbolMap } from './autoImporter' ; import { DocumentSymbolCollector } from './documentSymbolCollector' ; import { IndexResults } from './documentSymbolProvider' ; import { getAutoImportText , getFunctionDocStringFromType , getOverloadedFunctionTooltip } from './tooltipUtils' ; namespace Keywords { const base = [ 'True' , 'False' , 'None' , 'and' , 'or' , 'not' , 'is' , 'lambda' , 'yield' , 'assert' , 'break' , 'class' , 'continue' , 'def' , 'del' , 'elif' , 'else' , 'except' , 'finally' , 'for' , 'from' , 'global' , 'if' , 'import' , 'in' , 'nonlocal' , 'pass' , 'raise' , 'return' , 'try' , 'while' , 'with' , ] ; const python3_5 = [ ... base , 'async' , 'await' ] ; const python3_10 = [ ... python3_5 , 'case' , 'match' ] ; export function forVersion ( version ) { if ( version >= PythonVersion . V3_10 ) { return python3_10 ; } if ( version >= PythonVersion . V3_5 ) { return python3_5 ; } return base ; } } enum SortCategory { LikelyKeyword , RecentImportModuleName , ImportModuleName , LiteralValue , NamedParameter , RecentKeywordOrSymbol , RecentAutoImport , Keyword , EnumMember , NormalSymbol , PrivateSymbol , DunderSymbol , AutoImport } export interface CompletionItemData { filePath : string ; workspacePath : string ; position : Position ; autoImportText ? : string | undefined ; symbolLabel ? : string | undefined ; funcParensDisabled ? : boolean | undefined ; } export interface MemberAccessInfo { lastKnownModule ? : string | undefined ; lastKnownMemberName ? : string | undefined ; unknownMemberName ? : string | undefined ; } export interface AutoImportInfo { indexUsed : boolean ; totalTimeInMS : number ; moduleTimeInMS : number ; indexTimeInMS : number ; importAliasTimeInMS : number ; itemCount : number ; symbolCount : number ; indexCount : number ; importAliasCount : number ; additionTimeInMS : number ; } export interface ExtensionInfo { correlationId : string ; selectedItemTelemetryTimeInMS : number ; itemTelemetryTimeInMS : number ; totalTimeInMS : number ; } export interface CompletionResults { completionList : CompletionList | undefined ; memberAccessInfo ? : MemberAccessInfo | undefined ; autoImportInfo ? : AutoImportInfo | undefined ; extensionInfo ? : ExtensionInfo | undefined ; } export interface CompletionOptions { format : MarkupKind ; snippet : boolean ; lazyEdit : boolean ; } export type AbbreviationMap = Map < string , AbbreviationInfo > ; export interface AutoImportMaps { nameMap ? : AbbreviationMap | undefined ; libraryMap ? : Map < string , IndexResults > | undefined ; getModuleSymbolsMap : ( ) => ModuleSymbolMap ; } interface RecentCompletionInfo { label : string ; autoImportText : string ; } interface Edits { format ? : InsertTextFormat | undefined ; textEdit ? : TextEdit | undefined ; additionalTextEdits ? : TextEditAction [ ] | undefined ; } interface SymbolDetail { funcParensDisabled ? : boolean | undefined ; autoImportSource ? : string | undefined ; autoImportAlias ? : string | undefined ; boundObjectOrClass ? : ClassType | undefined ; edits ? : Edits | undefined ; } interface CompletionDetail { funcParensDisabled ? : boolean | undefined ; typeDetail ? : string | undefined ; documentation ? : string | undefined ; autoImportText ? : string | undefined ; edits ? : Edits | undefined ; sortText ? : string | undefined ; itemDetail ? : string | undefined ; } export const autoImportDetail = 'Auto-import' ; export const dictionaryKeyDetail = 'Dictionary key' ; const similarityLimit = 0.25 ; const maxRecentCompletions = 128 ; export class CompletionProvider { private static _mostRecentCompletions : RecentCompletionInfo [ ] = [ ] ; private _itemToResolve : CompletionItem | undefined ; private _execEnv : ExecutionEnvironment ; private _insideStringLiteral = false ; constructor ( private _workspacePath , private _parseResults , private _fileContents , private _importResolver , private _position , private _filePath , private _configOptions , private _importLookup , private _evaluator , private _options , private _sourceMapper , private _autoImportMaps , private _cancellationToken ) { this . _execEnv = this . _configOptions . findExecEnvironment ( this . _filePath ) ; } getCompletionsForPosition ( ) { const offset = convertPositionToOffset ( this . _position , this . _parseResults . tokenizerOutput . lines ) ; if ( offset === undefined ) { return undefined ; } const token = ParseTreeUtils . getTokenOverlapping ( this . _parseResults . tokenizerOutput . tokens , offset ) ; if ( token ?. type === TokenType . String ) { this . _insideStringLiteral = TextRange . contains ( token , offset ) ? true : ( token as StringToken ) . flags & StringTokenFlags . Unterminated ? true : false ; } let node = ParseTreeUtils . findNodeByOffset ( this . _parseResults . parseTree , offset ) ; const initialNode = node ; const initialDepth = node ? ParseTreeUtils . getNodeDepth ( node ) : 0 ; if ( ! initialNode || initialNode . nodeType !== ParseNodeType . Name ) { let curOffset = offset ; let sawComma = false ; while ( curOffset >= 0 ) { curOffset -- ; const curChar = this . _fileContents . substr ( curOffset , 1 ) ; if ( curChar === '(' || curChar === '\n' || curChar === '}' ) { break ; } if ( curChar === ',' ) { sawComma = true ; } const curNode = ParseTreeUtils . findNodeByOffset ( this . _parseResults . parseTree , curOffset ) ; if ( curNode && curNode !== initialNode ) { if ( ParseTreeUtils . getNodeDepth ( curNode ) > initialDepth ) { node = curNode ; if ( sawComma && node . parent ?. nodeType === ParseNodeType . ImportFromAs ) { node = node . parent ; } } break ; } } } if ( node === undefined ) { return undefined ; } const lineTextRange = this . _parseResults . tokenizerOutput . lines . getItemAt ( this . _position . line ) ; const textOnLine = this . _fileContents . substr ( lineTextRange . start , lineTextRange . length ) ; const priorText = textOnLine . substr ( 0 , this . _position . character ) ; const postText = textOnLine . substr ( this . _position . character ) ; const priorWordIndex = priorText . search ( /\w+$/ ) ; const priorWord = priorWordIndex >= 0 ? priorText . substr ( priorWordIndex ) : '' ; if ( this . _isWithinComment ( offset ) ) { return undefined ; } let errorNode = node ; while ( errorNode ) { if ( errorNode . nodeType === ParseNodeType . Error ) { break ; } errorNode = errorNode . parent ; } let curNode = errorNode || node ; while ( true ) { throwIfCancellationRequested ( this . _cancellationToken ) ; if ( curNode . nodeType === ParseNodeType . String ) { return this . _getLiteralCompletions ( curNode , priorWord , priorText , postText ) ; } if ( curNode . nodeType === ParseNodeType . StringList || curNode . nodeType === ParseNodeType . FormatString ) { return undefined ; } if ( curNode . nodeType === ParseNodeType . ModuleName ) { return this . _getImportModuleCompletions ( curNode ) ; } if ( curNode . nodeType === ParseNodeType . Error ) { return this . _getExpressionErrorCompletions ( curNode , priorWord , priorText , postText ) ; } if ( curNode . nodeType === ParseNodeType . MemberAccess ) { return this . _getMemberAccessCompletions ( curNode . leftExpression , priorWord ) ; } if ( curNode . nodeType === ParseNodeType . Dictionary ) { const completionList = CompletionList . create ( ) ; if ( this . _addTypedDictKeys ( curNode , undefined , priorText , postText , completionList ) ) { return { completionList } ; } } const dictionaryEntry = ParseTreeUtils . getFirstAncestorOrSelfOfKind ( curNode , ParseNodeType . DictionaryKeyEntry ) ; if ( dictionaryEntry ) { if ( dictionaryEntry . parent ?. nodeType === ParseNodeType . Dictionary ) { const dictionaryNode = dictionaryEntry . parent ; if ( dictionaryNode . trailingCommaToken && dictionaryNode . trailingCommaToken . start < offset ) { const completionList = CompletionList . create ( ) ; if ( this . _addTypedDictKeys ( dictionaryNode , undefined , priorText , postText , completionList ) ) { return { completionList } ; } } } } if ( curNode . nodeType === ParseNodeType . Name ) { const result = this . _tryGetNameCompletions ( curNode , offset , priorWord ) ; if ( result || result === undefined ) { return result ; } } if ( curNode . nodeType === ParseNodeType . ImportFrom ) { return this . _getImportFromCompletions ( curNode , priorWord ) ; } if ( isExpressionNode ( curNode ) ) { return this . _getExpressionCompletions ( curNode , priorWord , priorText , postText ) ; } if ( curNode . nodeType === ParseNodeType . Suite ) { if ( curNode . parent && curNode . parent . nodeType === ParseNodeType . Except && ! curNode . parent . name && curNode . parent . typeExpression && TextRange . getEnd ( curNode . parent . typeExpression ) < offset && offset <= curNode . parent . exceptSuite . start ) { return undefined ; } if ( curNode . parent && curNode . parent . nodeType === ParseNodeType . Class && ( ! curNode . parent . name || ! curNode . parent . name . value ) && curNode . parent . arguments . length === 0 && offset <= curNode . parent . suite . start ) { return undefined ; } return this . _getStatementCompletions ( curNode , priorWord , priorText , postText ) ; } if ( curNode . nodeType === ParseNodeType . Module ) { return this . _getStatementCompletions ( curNode , priorWord , priorText , postText ) ; } if ( curNode . nodeType === ParseNodeType . Parameter && curNode . length === 0 && curNode . parent && curNode . parent . nodeType === ParseNodeType . Lambda ) { return undefined ; } if ( ! curNode . parent ) { break ; } curNode = curNode . parent ; } return undefined ; } resolveCompletionItem ( completionItem ) { throwIfCancellationRequested ( this . _cancellationToken ) ; const completionItemData = completionItem . data as CompletionItemData ; const label = completionItem . label ; let autoImportText = '' ; if ( completionItemData . autoImportText ) { autoImportText = completionItemData . autoImportText ; } const curIndex = CompletionProvider . _mostRecentCompletions . findIndex ( ( item ) => item . label === label && item . autoImportText === autoImportText ) ; if ( curIndex > 0 ) { CompletionProvider . _mostRecentCompletions = CompletionProvider . _mostRecentCompletions . splice ( curIndex , 1 ) ; } if ( curIndex !== 0 ) { CompletionProvider . _mostRecentCompletions . unshift ( { label , autoImportText } ) ; } if ( CompletionProvider . _mostRecentCompletions . length > maxRecentCompletions ) { CompletionProvider . _mostRecentCompletions . pop ( ) ; } if ( completionItemData . symbolLabel ) { this . _itemToResolve = completionItem ; if ( ! completionItemData . autoImportText ) { this . getCompletionsForPosition ( ) ; } else if ( ! completionItem . additionalTextEdits ) { const completionList = CompletionList . create ( ) ; const completionResults = { completionList } ; this . _addAutoImportCompletions ( completionItemData . symbolLabel , 1 , false , completionResults ) ; } } } private _tryGetNameCompletions ( curNode , offset , priorWord ) { if ( ! curNode . parent ) { return false ; } if ( curNode . parent . nodeType === ParseNodeType . ImportAs && curNode . parent . alias === curNode ) { return undefined ; } if ( curNode . parent . nodeType === ParseNodeType . ModuleName ) { if ( curNode . parent . parent && curNode . parent . parent . nodeType === ParseNodeType . ImportAs && ! curNode . parent . parent . alias && TextRange . getEnd ( curNode . parent . parent ) < offset ) { return undefined ; } return this . _getImportModuleCompletions ( curNode . parent ) ; } if ( curNode . parent . nodeType === ParseNodeType . ImportFromAs ) { if ( curNode . parent . alias === curNode ) { return undefined ; } const parentNode = curNode . parent . parent ; if ( parentNode && parentNode . nodeType === ParseNodeType . ImportFrom ) { if ( ! curNode . parent . alias && TextRange . getEnd ( curNode . parent ) < offset ) { return undefined ; } if ( curNode . parent . name === curNode ) { return this . _getImportFromCompletions ( parentNode , priorWord ) ; } return this . _getImportFromCompletions ( parentNode , '' ) ; } return false ; } if ( curNode . parent . nodeType === ParseNodeType . MemberAccess && curNode === curNode . parent . memberName ) { return this . _getMemberAccessCompletions ( curNode . parent . leftExpression , priorWord ) ; } if ( curNode . parent . nodeType === ParseNodeType . Except && curNode === curNode . parent . name ) { return undefined ; } if ( curNode . parent . nodeType === ParseNodeType . Function && curNode === curNode . parent . name ) { if ( curNode . parent . decorators ?. some ( ( d ) => this . _isOverload ( d ) ) ) { return this . _getMethodOverloadsCompletions ( priorWord , curNode ) ; } return undefined ; } if ( curNode . parent . nodeType === ParseNodeType . Parameter && curNode === curNode . parent . name ) { return undefined ; } if ( curNode . parent . nodeType === ParseNodeType . Class && curNode === curNode . parent . name ) { return undefined ; } if ( curNode . parent . nodeType === ParseNodeType . For && TextRange . contains ( curNode . parent . targetExpression , curNode . start ) ) { return undefined ; } if ( curNode . parent . nodeType === ParseNodeType . ListComprehensionFor && TextRange . contains ( curNode . parent . targetExpression , curNode . start ) ) { return undefined ; } return false ; } private _isWithinComment ( offset ) { const token = getTokenAfter ( offset , this . _parseResults . tokenizerOutput . tokens ) ; if ( ! token ) { return false ; } return token . comments ?. some ( ( c ) => TextRange . overlaps ( c , offset ) ) ?? false ; function getTokenAfter ( offset , tokens ) { const tokenIndex = tokens . getItemAtPosition ( offset ) ; if ( tokenIndex < 0 ) { return undefined ; } let token = tokens . getItemAt ( tokenIndex ) ; if ( offset > token . start && offset < token . start + token . length ) { return undefined ; } for ( let i = tokenIndex - 1 ; i >= 0 ; i -- ) { const prevToken = tokens . getItemAt ( i ) ; if ( token . start !== prevToken . start ) { break ; } token = prevToken ; } if ( offset <= token . start ) { return token ; } debug . assert ( tokenIndex + 1 < tokens . length ) ; return tokens . getItemAt ( tokenIndex + 1 ) ; } } private _getExpressionErrorCompletions ( node , priorWord , priorText , postText ) { switch ( node . category ) { case ErrorExpressionCategory . MissingIn : { return this . _createSingleKeywordCompletionList ( 'in' ) ; } case ErrorExpressionCategory . MissingElse : { return this . _createSingleKeywordCompletionList ( 'else' ) ; } case ErrorExpressionCategory . MissingExpression : case ErrorExpressionCategory . MissingDecoratorCallName : { return this . _getExpressionCompletions ( node , priorWord , priorText , postText ) ; } case ErrorExpressionCategory . MissingIndexOrSlice : { let completionResults = this . _getLiteralCompletions ( node , priorWord , priorText , postText ) ; if ( ! completionResults || ! completionResults . completionList ) { completionResults = this . _getExpressionCompletions ( node , priorWord , priorText , postText ) ; } return completionResults ; } case ErrorExpressionCategory . MissingMemberAccessName : { if ( node . child && isExpressionNode ( node . child ) ) { return this . _getMemberAccessCompletions ( node . child , priorWord ) ; } break ; } case ErrorExpressionCategory . MissingFunctionParameterList : { if ( node . child && node . child . nodeType === ParseNodeType . Name ) { if ( node . decorators ?. some ( ( d ) => this . _isOverload ( d ) ) ) { return this . _getMethodOverloadsCompletions ( priorWord , node . child ) ; } return this . _getMethodOverrideCompletions ( priorWord , node . child , node . decorators ) ; } break ; } } return undefined ; } private _isOverload ( node ) { return this . _checkDecorator ( node , 'overload' ) ; } private _checkDecorator ( node , value ) { return node . expression . nodeType === ParseNodeType . Name && node . expression . value === value ; } private _createSingleKeywordCompletionList ( keyword ) { const completionItem = CompletionItem . create ( keyword ) ; completionItem . kind = CompletionItemKind . Keyword ; completionItem . sortText = this . _makeSortText ( SortCategory . LikelyKeyword , keyword ) ; const completionList = CompletionList . create ( [ completionItem ] ) ; return { completionList } ; } private _getMethodOverloadsCompletions ( priorWord , partialName ) { const symbolTable = getSymbolTable ( this . _evaluator , partialName ) ; if ( ! symbolTable ) { return undefined ; } const funcParensDisabled = partialName . parent ?. nodeType === ParseNodeType . Function ? true : undefined ; const completionList = CompletionList . create ( ) ; const enclosingFunc = ParseTreeUtils . getEnclosingFunction ( partialName ) ; symbolTable . forEach ( ( symbol , name ) => { const decl = getLastTypedDeclaredForSymbol ( symbol ) ; if ( ! decl || decl . type !== DeclarationType . Function ) { return ; } if ( ! decl . node . decorators . some ( ( d ) => this . _isOverload ( d ) ) ) { return ; } const decls = symbol . getDeclarations ( ) ; if ( decls . length === 1 && decls . some ( ( d ) => d . node === enclosingFunc ) ) { return ; } if ( StringUtils . isPatternInSymbol ( partialName . value , name ) ) { const textEdit = this . _createReplaceEdits ( priorWord , partialName , decl . node . name . value ) ; this . _addSymbol ( name , symbol , partialName . value , completionList , { funcParensDisabled , edits : { textEdit } , } ) ; } } ) ; return { completionList } ; function getSymbolTable ( evaluator , partialName ) { const enclosingClass = ParseTreeUtils . getEnclosingClass ( partialName , false ) ; if ( enclosingClass ) { const classResults = evaluator . getTypeOfClass ( enclosingClass ) ; if ( ! classResults ) { return undefined ; } const symbolTable = new Map < string , Symbol > ( ) ; for ( const mroClass of classResults . classType . details . mro ) { if ( isInstantiableClass ( mroClass ) ) { getMembersForClass ( mroClass , symbolTable , false ) ; } } return symbolTable ; } const moduleNode = ParseTreeUtils . getEnclosingModule ( partialName ) ; if ( moduleNode ) { const moduleScope = AnalyzerNodeInfo . getScope ( moduleNode ) ; return moduleScope ?. symbolTable ; } return undefined ; } } private _getMethodOverrideCompletions ( priorWord , partialName , decorators ? ) { const enclosingClass = ParseTreeUtils . getEnclosingClass ( partialName , true ) ; if ( ! enclosingClass ) { return undefined ; } const classResults = this . _evaluator . getTypeOfClass ( enclosingClass ) ; if ( ! classResults ) { return undefined ; } const symbolTable = new Map < string , Symbol > ( ) ; for ( let i = 1 ; i < classResults . classType . details . mro . length ; i ++ ) { const mroClass = classResults . classType . details . mro [ i ] ; if ( isInstantiableClass ( mroClass ) ) { getMembersForClass ( mroClass , symbolTable , false ) ; } } const staticmethod = decorators ?. some ( ( d ) => this . _checkDecorator ( d , 'staticmethod' ) ) ?? false ; const classmethod = decorators ?. some ( ( d ) => this . _checkDecorator ( d , 'classmethod' ) ) ?? false ; const completionList = CompletionList . create ( ) ; symbolTable . forEach ( ( symbol , name ) => { let decl = getLastTypedDeclaredForSymbol ( symbol ) ; if ( decl && decl . type === DeclarationType . Function ) { if ( StringUtils . isPatternInSymbol ( partialName . value , name ) ) { const declaredType = this . _evaluator . getTypeForDeclaration ( decl ) ; if ( ! declaredType ) { return ; } let isProperty = isClassInstance ( declaredType ) && ClassType . isPropertyClass ( declaredType ) ; if ( SymbolNameUtils . isDunderName ( name ) ) { isProperty = false ; } if ( ! isFunction ( declaredType ) && ! isProperty ) { return ; } if ( isProperty ) { const typedDecls = symbol . getTypedDeclarations ( ) ; if ( typedDecls . length > 0 && typedDecls [ 0 ] . type === DeclarationType . Function ) { decl = typedDecls [ 0 ] ; } } const isDeclaredStaticMethod = isFunction ( declaredType ) && FunctionType . isStaticMethod ( declaredType ) ; const isDeclaredClassMethod = isFunction ( declaredType ) && FunctionType . isClassMethod ( declaredType ) && name !== '__init_subclass__' ; if ( staticmethod !== isDeclaredStaticMethod || classmethod !== isDeclaredClassMethod ) { return ; } const methodSignature = this . _printMethodSignature ( classResults . classType , decl ) ; let text ; if ( isStubFile ( this . _filePath ) ) { text = `${ methodSignature }: ...` ; } else { const methodBody = this . _printOverriddenMethodBody ( classResults . classType , isDeclaredStaticMethod , isProperty , decl ) ; text = `${ methodSignature }:\n${ methodBody }` ; } const textEdit = this . _createReplaceEdits ( priorWord , partialName , text ) ; this . _addSymbol ( name , symbol , partialName . value , completionList , { funcParensDisabled : true , edits : { format : this . _options . snippet ? InsertTextFormat . Snippet : undefined , textEdit , } , } ) ; } } } ) ; return { completionList } ; } private _createReplaceEdits ( priorWord , node , text ) { const replaceOrInsertEndChar = node ?. nodeType === ParseNodeType . Name ? this . _position . character - priorWord . length + node . value . length : this . _position . character ; const range = { start : { line : this . _position . line , character : this . _position . character - priorWord . length } , end : { line : this . _position . line , character : replaceOrInsertEndChar } , } ; return TextEdit . replace ( range , text ) ; } private _printMethodSignature ( classType , decl ) { const node = decl . node ; let ellipsisForDefault ; if ( isStubFile ( this . _filePath ) ) { ellipsisForDefault = true ; } else if ( classType . details . moduleName === decl . moduleName ) { ellipsisForDefault = false ; } const printFlags = isStubFile ( this . _filePath ) ? ParseTreeUtils . PrintExpressionFlags . ForwardDeclarations : undefined ; const paramList = node . parameters . map ( ( param , index ) => { let paramString = '' ; if ( param . category === ParameterCategory . VarArgList ) { paramString += '*' ; } else if ( param . category === ParameterCategory . VarArgDictionary ) { paramString += '**' ; } if ( param . name ) { paramString += param . name . value ; } const paramTypeAnnotation = this . _evaluator . getTypeAnnotationForParameter ( node , index ) ; if ( paramTypeAnnotation ) { paramString += ': ' + ParseTreeUtils . printExpression ( paramTypeAnnotation , printFlags ) ; } if ( param . defaultValue ) { paramString += paramTypeAnnotation ? ' = ' : '=' ; const useEllipsis = ellipsisForDefault ?? ! isSimpleDefault ( param . defaultValue ) ; paramString += useEllipsis ? '...' : ParseTreeUtils . printExpression ( param . defaultValue , printFlags ) ; } if ( ! paramString && ! param . name && param . category === ParameterCategory . Simple ) { return '/' ; } return paramString ; } ) . join ( ', ' ) ; let methodSignature = node . name . value + '(' + paramList + ')' ; if ( node . returnTypeAnnotation ) { methodSignature += ' -> ' + ParseTreeUtils . printExpression ( node . returnTypeAnnotation , printFlags ) ; } else if ( node . functionAnnotationComment ) { methodSignature += ' -> ' + ParseTreeUtils . printExpression ( node . functionAnnotationComment . returnTypeAnnotation , printFlags ) ; } return methodSignature ; function isSimpleDefault ( node ) { switch ( node . nodeType ) { case ParseNodeType . Number : case ParseNodeType . Constant : return true ; case ParseNodeType . String : return ( node . token . flags & StringTokenFlags . Format ) === 0 ; case ParseNodeType . StringList : return node . strings . every ( isSimpleDefault ) ; case ParseNodeType . UnaryOperation : return isSimpleDefault ( node . expression ) ; case ParseNodeType . BinaryOperation : return isSimpleDefault ( node . leftExpression ) && isSimpleDefault ( node . rightExpression ) ; default : return false ; } } } private _printOverriddenMethodBody ( classType , isStaticMethod , isProperty , decl ) { let sb = '    ' ; if ( classType . details . baseClasses . length === 1 && isClass ( classType . details . baseClasses [ 0 ] ) && classType . details . baseClasses [ 0 ] . details . fullName === 'builtins.object' ) { sb += this . _options . snippet ? '${0:pass}' : 'pass' ; return sb ; } if ( decl . node . parameters . length === 0 ) { sb += this . _options . snippet ? '${0:pass}' : 'pass' ; return sb ; } const parameters = getParameters ( ) ; if ( decl . node . name . value !== '__init__' ) { sb += 'return ' ; } if ( decl . node . isAsync ) { sb += 'await ' ; } if ( isProperty ) { return sb + `super().${ decl . node . name . value }` ; } return sb + `super().${ decl . node . name . value }(${ parameters . map ( convertToString ) . join ( ', ' ) })` ; function getParameters ( ) { if ( isStaticMethod ) { return decl . node . parameters . filter ( ( p ) => p . name ) ; } return decl . node . parameters . slice ( 1 ) . filter ( ( p ) => p . name ) ; } function convertToString ( parameter ) { const name = parameter . name ?. value ; if ( parameter . category === ParameterCategory . VarArgList ) { return `*${ name }` ; } if ( parameter . category === ParameterCategory . VarArgDictionary ) { return `**${ name }` ; } return parameter . defaultValue ? `${ name }=${ name }` : name ; } } private _getMemberAccessCompletions ( leftExprNode , priorWord ) { const symbolTable = new Map < string , Symbol > ( ) ; const completionList = CompletionList . create ( ) ; let memberAccessInfo = { } ; let leftType = this . _evaluator . getType ( leftExprNode ) ; if ( leftType ) { leftType = this . _evaluator . makeTopLevelTypeVarsConcrete ( leftType ) ; doForEachSubtype ( leftType , ( subtype ) => { subtype = this . _evaluator . makeTopLevelTypeVarsConcrete ( subtype ) ; if ( isClass ( subtype ) ) { getMembersForClass ( subtype , symbolTable , TypeBase . isInstance ( subtype ) ) ; } else if ( isModule ( subtype ) ) { getMembersForModule ( subtype , symbolTable ) ; } else if ( isFunction ( subtype ) || isOverloadedFunction ( subtype ) ) { const functionClass = this . _evaluator . getBuiltInType ( leftExprNode , 'function' ) ; if ( functionClass && isInstantiableClass ( functionClass ) ) { getMembersForClass ( functionClass , symbolTable , true ) ; } } else if ( isNoneInstance ( subtype ) ) { const objectClass = this . _evaluator . getBuiltInType ( leftExprNode , 'object' ) ; if ( objectClass && isInstantiableClass ( objectClass ) ) { getMembersForClass ( objectClass , symbolTable , TypeBase . isInstance ( subtype ) ) ; } } this . _addSymbolsForSymbolTable ( symbolTable , ( _ ) => true , priorWord , false , isClass ( subtype ) ? subtype : undefined , completionList ) ; } ) ; } if ( ! leftType || isUnknown ( leftType ) || isUnbound ( leftType ) ) { memberAccessInfo = this . _getLastKnownModule ( leftExprNode , leftType ) ; } return { completionList , memberAccessInfo } ; } private _getLastKnownModule ( leftExprNode , leftType ) { let curNode = leftExprNode ; let curType = leftType ; let unknownMemberName = leftExprNode . nodeType === ParseNodeType . MemberAccess ? leftExprNode ?. memberName . value : undefined ; while ( curNode ) { if ( curNode . nodeType === ParseNodeType . Call || curNode . nodeType === ParseNodeType . MemberAccess ) { curNode = curNode . leftExpression ; if ( unknownMemberName === undefined ) { unknownMemberName = curNode . nodeType === ParseNodeType . MemberAccess ? curNode ?. memberName . value ?? '' : '' ; } } else { curNode = undefined ; } if ( curNode ) { curType = this . _evaluator . getType ( curNode ) ; if ( curType !== undefined && ! isUnknown ( curType ) && ! isUnbound ( curType ) ) { break ; } } } const memberAccessInfo = { } ; if ( curType && ! isUnknown ( curType ) && ! isUnbound ( curType ) && curNode ) { const moduleNamesForType = getDeclaringModulesForType ( curType ) ; memberAccessInfo . lastKnownModule = moduleNamesForType . find ( ( n ) => n !== 'typing' ) ; if ( curNode . nodeType === ParseNodeType . MemberAccess ) { memberAccessInfo . lastKnownMemberName = curNode . memberName . value ; } else if ( curNode . nodeType === ParseNodeType . Name && isInstantiableClass ( curType ) ) { memberAccessInfo . lastKnownMemberName = curType . details . name ; } else if ( curNode . nodeType === ParseNodeType . Name && isClassInstance ( curType ) ) { memberAccessInfo . lastKnownMemberName = curType . details . name ; } memberAccessInfo . unknownMemberName = unknownMemberName ; } return memberAccessInfo ; } private _getStatementCompletions ( parseNode , priorWord , priorText , postText ) { return this . _getExpressionCompletions ( parseNode , priorWord , priorText , postText ) ; } private _getExpressionCompletions ( parseNode , priorWord , priorText , postText ) { const isIndexArgument = this . _isIndexArgument ( parseNode ) ; if ( ! isIndexArgument && parseNode . nodeType === ParseNodeType . Number ) { return undefined ; } if ( parseNode . parent ?. nodeType === ParseNodeType . WithItem && parseNode . parent === parseNode . parent . target ?. parent ) { return undefined ; } const completionList = CompletionList . create ( ) ; const completionResults = { completionList } ; if ( priorText . slice ( - 2 ) === '..' ) { return completionResults ; } this . _addCallArgumentCompletions ( parseNode , priorWord , priorText , postText , false , completionList ) ; this . _addSymbols ( parseNode , priorWord , completionList ) ; this . _findMatchingKeywords ( Keywords . forVersion ( this . _execEnv . pythonVersion ) , priorWord ) . map ( ( keyword ) => { const completionItem = CompletionItem . create ( keyword ) ; completionItem . kind = CompletionItemKind . Keyword ; completionList . items . push ( completionItem ) ; completionItem . sortText = this . _makeSortText ( SortCategory . Keyword , keyword ) ; } ) ; if ( this . _configOptions . autoImportCompletions && ! priorWord . startsWith ( '_' ) && ! this . _itemToResolve ) { this . _addAutoImportCompletions ( priorWord , similarityLimit , this . _options . lazyEdit , completionResults ) ; } if ( parseNode . nodeType === ParseNodeType . Error ) { if ( parseNode . category === ErrorExpressionCategory . MissingIndexOrSlice && parseNode . parent ?. nodeType === ParseNodeType . Index ) { this . _tryAddTypedDictStringLiteral ( parseNode . parent , undefined , undefined , completionList ) ; } else if ( parseNode . category === ErrorExpressionCategory . MissingExpression ) { if ( parseNode . parent && parseNode . parent . nodeType === ParseNodeType . Assignment ) { const declaredTypeOfTarget = this . _evaluator . getExpectedType ( parseNode ) ?. type ; if ( declaredTypeOfTarget ) { this . _addLiteralValuesForTargetType ( declaredTypeOfTarget , priorText , priorWord , postText , completionList ) ; } } } } if ( isIndexArgument ) { const indexNode = parseNode . parent ! . parent ! as IndexNode ; const excludes = new Set ( completionList . items . map ( ( i ) => i . label ) ) ; this . _getDictionaryKeys ( indexNode , parseNode ) . forEach ( ( key ) => { if ( excludes . has ( key ) ) { return ; } this . _addNameToCompletionList ( key , CompletionItemKind . Constant , priorWord , completionList , { sortText : this . _makeSortText ( SortCategory . LiteralValue , key ) , itemDetail : dictionaryKeyDetail , } ) ; } ) ; } return completionResults ; } private _isIndexArgument ( node ) { const currentNode = node . parent ; return ( currentNode && currentNode . nodeType === ParseNodeType . Argument && currentNode . argumentCategory === ArgumentCategory . Simple && currentNode . parent && currentNode . parent . nodeType === ParseNodeType . Index && currentNode . parent . baseExpression && currentNode . parent . baseExpression . nodeType === ParseNodeType . Name ) ; } private _addCallArgumentCompletions ( parseNode , priorWord , priorText , postText , atArgument , completionList ) { const offset = convertPositionToOffset ( this . _position , this . _parseResults . tokenizerOutput . lines ) ! ; const callInfo = getCallNodeAndActiveParameterIndex ( parseNode , offset , this . _parseResults . tokenizerOutput . tokens ) ; if ( ! callInfo ) { return ; } const signatureInfo = this . _evaluator . getCallSignatureInfo ( callInfo . callNode , callInfo . activeIndex , callInfo . activeOrFake ) ; if ( signatureInfo ) { const callNameEnd = convertOffsetToPosition ( signatureInfo . callNode . leftExpression . start + signatureInfo . callNode . leftExpression . length , this . _parseResults . tokenizerOutput . lines ) ; if ( comparePositions ( this . _position , callNameEnd ) > 0 ) { if ( ! atArgument ) { this . _addNamedParameters ( signatureInfo , priorWord , completionList ) ; } this . _addLiteralValuesForArgument ( signatureInfo , priorText , priorWord , postText , completionList ) ; } } } private _addLiteralValuesForArgument ( signatureInfo , priorText , priorWord , postText , completionList ) { signatureInfo . signatures . forEach ( ( signature ) => { if ( ! signature . activeParam ) { return undefined ; } const type = signature . type ; const paramIndex = type . details . parameters . indexOf ( signature . activeParam ) ; if ( paramIndex < 0 ) { return undefined ; } const paramType = type . details . parameters [ paramIndex ] . type ; this . _addLiteralValuesForTargetType ( paramType , priorText , priorWord , postText , completionList ) ; return undefined ; } ) ; } private _addLiteralValuesForTargetType ( type , priorText , priorWord , postText , completionList ) { const quoteValue = this . _getQuoteValueFromPriorText ( priorText ) ; this . _getSubTypesWithLiteralValues ( type ) . forEach ( ( v ) => { if ( ClassType . isBuiltIn ( v , 'str' ) ) { const value = printLiteralValue ( v , quoteValue . quoteCharacter ) ; if ( quoteValue . stringValue === undefined ) { this . _addNameToCompletionList ( value , CompletionItemKind . Constant , priorWord , completionList , { sortText : this . _makeSortText ( SortCategory . LiteralValue , v . literalValue as string ) , } ) ; } else { this . _addStringLiteralToCompletionList ( value . substr ( 1 , value . length - 2 ) , quoteValue . stringValue , postText , quoteValue . quoteCharacter , completionList ) ; } } } ) ; } private _getDictExpressionStringKeys ( parseNode , excludeIds ? ) { const node = getDictionaryLikeNode ( parseNode ) ; if ( ! node ) { return [ ] ; } return node . entries . flatMap ( ( entry ) => { if ( entry . nodeType !== ParseNodeType . DictionaryKeyEntry || excludeIds ?. has ( entry . keyExpression . id ) ) { return [ ] ; } if ( entry . keyExpression . nodeType === ParseNodeType . StringList ) { return [ entry . keyExpression . strings . map ( ( s ) => s . value ) . join ( '' ) ] ; } return [ ] ; } ) ; function getDictionaryLikeNode ( parseNode ) { if ( parseNode . nodeType === ParseNodeType . Dictionary ) { return parseNode ; } let curNode = parseNode ; while ( curNode && curNode . nodeType !== ParseNodeType . Dictionary && curNode . nodeType !== ParseNodeType . Set ) { curNode = curNode . parent ; if ( ! curNode ) { return ; } } return curNode ; } } private _getSubTypesWithLiteralValues ( type ) { const values = [ ] ; doForEachSubtype ( type , ( subtype ) => { if ( isClassInstance ( subtype ) && isLiteralType ( subtype ) ) { values . push ( subtype ) ; } } ) ; return values ; } private _getDictionaryKeys ( indexNode , invocationNode ) { if ( indexNode . baseExpression . nodeType !== ParseNodeType . Name ) { return [ ] ; } const baseType = this . _evaluator . getType ( indexNode . baseExpression ) ; if ( ! baseType || ! isClassInstance ( baseType ) ) { return [ ] ; } if ( ! ClassType . isBuiltIn ( baseType , 'dict' ) && ! ClassType . isBuiltIn ( baseType , 'Mapping' ) ) { return [ ] ; } if ( baseType . typeArguments ?. length === 2 ) { const keys = [ ] ; this . _getSubTypesWithLiteralValues ( baseType . typeArguments [ 0 ] ) . forEach ( ( v ) => { if ( ! ClassType . isBuiltIn ( v , 'str' ) && ! ClassType . isBuiltIn ( v , 'int' ) && ! ClassType . isBuiltIn ( v , 'bool' ) && ! ClassType . isBuiltIn ( v , 'bytes' ) && ! ClassType . isEnumClass ( v ) ) { return ; } keys . push ( printLiteralValue ( v , this . _parseResults . tokenizerOutput . predominantSingleQuoteCharacter ) ) ; } ) ; if ( keys . length > 0 ) { return keys ; } } const declarations = this . _evaluator . getDeclarationsForNameNode ( indexNode . baseExpression ) ?? [ ] ; const declaration = declarations . length > 0 ? declarations [ 0 ] : undefined ; if ( ! declaration || ( declaration . type !== DeclarationType . Variable && declaration . type !== DeclarationType . Parameter ) ) { return [ ] ; } if ( declaration . path !== this . _filePath ) { return [ ] ; } let startingNode = indexNode . baseExpression ; if ( declaration . node ) { const scopeRoot = ParseTreeUtils . getEvaluationScopeNode ( declaration . node ) ; if ( ParseTreeUtils . getFileInfoFromNode ( startingNode ) ?. filePath === ParseTreeUtils . getFileInfoFromNode ( scopeRoot ) ?. filePath ) { startingNode = scopeRoot ; } } const results = DocumentSymbolCollector . collectFromNode ( indexNode . baseExpression , this . _evaluator , this . _cancellationToken , startingNode ) ; const keys = new Set < string > ( ) ; for ( const result of results ) { const node = result . node . parent ?. nodeType === ParseNodeType . TypeAnnotation ? result . node . parent : result . node ; if ( node . parent ?. nodeType === ParseNodeType . Assignment || node . parent ?. nodeType === ParseNodeType . AssignmentExpression ) { if ( node . parent . rightExpression . nodeType === ParseNodeType . Dictionary ) { const dictionary = node . parent . rightExpression ; for ( const entry of dictionary . entries . filter ( ( e ) => e . nodeType === ParseNodeType . DictionaryKeyEntry ) as DictionaryKeyEntryNode [ ] ) { const key = this . _parseResults . text . substr ( entry . keyExpression . start , entry . keyExpression . length ) . trim ( ) ; if ( key . length > 0 ) keys . add ( key ) ; } } if ( node . parent . rightExpression . nodeType === ParseNodeType . Call ) { const call = node . parent . rightExpression ; const type = this . _evaluator . getType ( call . leftExpression ) ; if ( ! type || ! isInstantiableClass ( type ) || ! ClassType . isBuiltIn ( type , 'dict' ) ) { continue ; } for ( const arg of call . arguments ) { const key = arg . name ?. value . trim ( ) ?? '' ; const quote = this . _parseResults . tokenizerOutput . predominantSingleQuoteCharacter ; if ( key . length > 0 ) { keys . add ( `${ quote }${ key }${ quote }` ) ; } } } } if ( node . parent ?. nodeType === ParseNodeType . Index && node . parent . items . length === 1 && node . parent . items [ 0 ] . valueExpression . nodeType !== ParseNodeType . Error && ! TextRange . containsRange ( node . parent , invocationNode ) ) { const indexArgument = node . parent . items [ 0 ] ; const key = this . _parseResults . text . substr ( indexArgument . valueExpression . start , indexArgument . valueExpression . length ) . trim ( ) ; if ( key . length > 0 ) keys . add ( key ) ; } } return [ ... keys ] ; } private _getLiteralCompletions ( parseNode , priorWord , priorText , postText ) { let parentNode = parseNode . parent ; if ( ! parentNode ) { return undefined ; } const completionList = CompletionList . create ( ) ; if ( isExpressionNode ( parentNode ) ) { const expectedTypeResult = this . _evaluator . getExpectedType ( parentNode ) ; if ( expectedTypeResult && isLiteralTypeOrUnion ( expectedTypeResult . type ) ) { this . _addLiteralValuesForTargetType ( expectedTypeResult . type , priorText , priorWord , postText , completionList ) ; return { completionList } ; } if ( parseNode . nodeType === ParseNodeType . String && parseNode . parent ?. parent ) { const stringParent = parseNode . parent . parent ; let dictOrSet ; if ( stringParent . nodeType === ParseNodeType . DictionaryKeyEntry && stringParent . keyExpression === parseNode . parent && stringParent . parent ?. nodeType === ParseNodeType . Dictionary ) { dictOrSet = stringParent . parent ; } else if ( stringParent ?. nodeType === ParseNodeType . Set ) { dictOrSet = stringParent ; } if ( dictOrSet ) { if ( this . _addTypedDictKeys ( dictOrSet , parseNode , priorText , postText , completionList ) ) { return { completionList } ; } } } } if ( parentNode . nodeType !== ParseNodeType . Argument ) { if ( parentNode . nodeType !== ParseNodeType . StringList || parentNode . strings . length > 1 ) { return undefined ; } parentNode = parentNode . parent ; if ( ! parentNode ) { return undefined ; } } if ( parentNode . nodeType === ParseNodeType . Argument && parentNode . parent ?. nodeType === ParseNodeType . Index ) { if ( ! this . _tryAddTypedDictStringLiteral ( parentNode . parent , parseNode . nodeType === ParseNodeType . String ? priorText : '' , postText , completionList ) ) { const keys = this . _getDictionaryKeys ( parentNode . parent , parseNode ) ; const quoteValue = this . _getQuoteValueFromPriorText ( priorText ) ; for ( const key of keys ) { const stringLiteral = /^["|'].*["|']$/ . test ( key ) ; if ( parseNode . nodeType === ParseNodeType . String && ! stringLiteral ) { continue ; } if ( stringLiteral ) { const keyWithoutQuote = key . substr ( 1 , key . length - 2 ) ; this . _addStringLiteralToCompletionList ( keyWithoutQuote , quoteValue . stringValue , postText , quoteValue . quoteCharacter , completionList , dictionaryKeyDetail ) ; } else { this . _addNameToCompletionList ( key , CompletionItemKind . Constant , priorWord , completionList , { sortText : this . _makeSortText ( SortCategory . LiteralValue , key ) , itemDetail : dictionaryKeyDetail , } ) ; } } if ( completionList . items . length === 0 ) { return undefined ; } } } else { debug . assert ( parseNode . nodeType === ParseNodeType . String ) ; const offset = convertPositionToOffset ( this . _position , this . _parseResults . tokenizerOutput . lines ) ! ; const atArgument = parentNode . start < offset && offset < TextRange . getEnd ( parseNode ) ; this . _addCallArgumentCompletions ( parseNode , priorWord , priorText , postText , atArgument , completionList ) ; } return { completionList } ; } private _addTypedDictKeys ( dictionaryNode , stringNode , priorText , postText , completionList ) { const expectedTypeResult = this . _evaluator . getExpectedType ( dictionaryNode ) ; if ( ! expectedTypeResult ) { return false ; } if ( ParseTreeUtils . getNodeDepth ( expectedTypeResult . node ) < ParseTreeUtils . getNodeDepth ( dictionaryNode ) ) { return false ; } let typedDicts = [ ] ; doForEachSubtype ( expectedTypeResult . type , ( subtype ) => { if ( isClassInstance ( subtype ) && ClassType . isTypedDictClass ( subtype ) ) { typedDicts . push ( subtype ) ; } } ) ; if ( typedDicts . length === 0 ) { return false ; } const keys = this . _getDictExpressionStringKeys ( dictionaryNode , stringNode ? new Set ( [ stringNode . parent ?. id ] ) : undefined ) ; typedDicts = this . _tryNarrowTypedDicts ( typedDicts , keys ) ; const quoteValue = this . _getQuoteValueFromPriorText ( priorText ) ; const excludes = new Set ( completionList . items . map ( ( i ) => i . label ) ) ; keys . forEach ( ( key ) => { excludes . add ( key ) ; } ) ; typedDicts . forEach ( ( typedDict ) => { getTypedDictMembersForClass ( this . _evaluator , typedDict , true ) . forEach ( ( _ , key ) => { if ( excludes . has ( key ) ) { return ; } excludes . add ( key ) ; this . _addStringLiteralToCompletionList ( key , quoteValue ? quoteValue . stringValue : undefined , postText , quoteValue ? quoteValue . quoteCharacter : this . _parseResults . tokenizerOutput . predominantSingleQuoteCharacter , completionList ) ; } ) ; } ) ; return true ; } private _tryNarrowTypedDicts ( types , keys ) { const newTypes = types . flatMap ( ( type ) => { const entries = getTypedDictMembersForClass ( this . _evaluator , type , true ) ; for ( let index = 0 ; index < keys . length ; index ++ ) { if ( ! entries . has ( keys [ index ] ) ) { return [ ] ; } } return [ type ] ; } ) ; if ( newTypes . length === 0 ) { return types ; } return newTypes ; } private _getQuoteValueFromPriorText ( priorText ) { if ( this . _insideStringLiteral ) { const lastSingleQuote = priorText . lastIndexOf ( "'" ) ; const lastDoubleQuote = priorText . lastIndexOf ( '"' ) ; if ( lastSingleQuote > lastDoubleQuote ) { return { quoteCharacter : "'" , stringValue : priorText . substr ( lastSingleQuote + 1 ) , } ; } else if ( lastDoubleQuote > lastSingleQuote ) { return { quoteCharacter : '"' , stringValue : priorText . substr ( lastDoubleQuote + 1 ) } ; } } const quoteCharacter = this . _parseResults . tokenizerOutput . predominantSingleQuoteCharacter ; const stringValue = undefined ; return { stringValue , quoteCharacter } ; } private _tryAddTypedDictStringLiteral ( indexNode , priorText , postText , completionList ) { if ( ! indexNode ) { return false ; } const baseType = this . _evaluator . getType ( indexNode . baseExpression ) ; if ( ! baseType || ! isClassInstance ( baseType ) ) { return false ; } if ( ! ClassType . isTypedDictClass ( baseType ) ) { return false ; } const entries = getTypedDictMembersForClass ( this . _evaluator , baseType , true ) ; const quoteValue = priorText ? this . _getQuoteValueFromPriorText ( priorText ) : undefined ; entries . forEach ( ( _ , key ) => { this . _addStringLiteralToCompletionList ( key , quoteValue ? quoteValue . stringValue : undefined , postText , quoteValue ? quoteValue . quoteCharacter : this . _parseResults . tokenizerOutput . predominantSingleQuoteCharacter , completionList ) ; } ) ; return true ; } private _addStringLiteralToCompletionList ( value , priorString , postText , quoteCharacter , completionList , detail ? ) { if ( StringUtils . isPatternInSymbol ( priorString || '' , value ) ) { const valueWithQuotes = `${ quoteCharacter }${ value }${ quoteCharacter }` ; const completionItem = CompletionItem . create ( valueWithQuotes ) ; completionItem . kind = CompletionItemKind . Constant ; completionItem . sortText = this . _makeSortText ( SortCategory . LiteralValue , valueWithQuotes ) ; let rangeStartCol = this . _position . character ; if ( priorString !== undefined ) { rangeStartCol -= priorString . length + 1 ; } let rangeEndCol = this . _position . character ; if ( postText !== undefined ) { if ( postText . startsWith ( quoteCharacter ) ) { rangeEndCol ++ ; } } const range = { start : { line : this . _position . line , character : rangeStartCol } , end : { line : this . _position . line , character : rangeEndCol } , } ; completionItem . textEdit = TextEdit . replace ( range , valueWithQuotes ) ; completionItem . detail = detail ; completionList . items . push ( completionItem ) ; } } private _addAutoImportCompletions ( priorWord , similarityLimit , lazyEdit , completionResults ) { if ( ! this . _autoImportMaps ) { return ; } const completionList = completionResults . completionList ; if ( ! completionList ) { return ; } const moduleSymbolMap = this . _autoImportMaps . getModuleSymbolsMap ( ) ; const excludes = new Set ( completionList . items . filter ( ( i ) => ! i . data ?. autoImport ) . map ( ( i ) => i . label ) ) ; const autoImporter = new AutoImporter ( this . _execEnv , this . _importResolver , this . _parseResults , this . _position , excludes , moduleSymbolMap , { libraryMap : this . _autoImportMaps . libraryMap , lazyEdit } ) ; const results = [ ] ; const info = this . _autoImportMaps . nameMap ?. get ( priorWord ) ; if ( info && priorWord . length > 1 && ! excludes . has ( priorWord ) ) { results . push ( ... autoImporter . getAutoImportCandidatesForAbbr ( priorWord , info , this . _cancellationToken ) ) ; } results . push ( ... autoImporter . getAutoImportCandidates ( priorWord , similarityLimit , undefined , this . _cancellationToken ) ) ; const perfInfo = autoImporter . getPerfInfo ( ) ; const additionDuration = new Duration ( ) ; for ( const result of results ) { if ( result . symbol ) { this . _addSymbol ( result . name , result . symbol , priorWord , completionList , { autoImportSource : result . source , autoImportAlias : result . alias , edits : { textEdit : this . _createReplaceEdits ( priorWord , undefined , result . insertionText ) , additionalTextEdits : result . edits , } , } ) ; } else { this . _addNameToCompletionList ( result . alias ?? result . name , result . kind ?? CompletionItemKind . Module , priorWord , completionList , { autoImportText : this . _getAutoImportText ( result . name , result . source , result . alias ) , edits : { textEdit : this . _createReplaceEdits ( priorWord , undefined , result . insertionText ) , additionalTextEdits : result . edits , } , } ) ; } } completionResults . autoImportInfo = { indexUsed : perfInfo . indexUsed , totalTimeInMS : perfInfo . totalInMs , moduleTimeInMS : perfInfo . moduleTimeInMS , indexTimeInMS : perfInfo . indexTimeInMS , importAliasTimeInMS : perfInfo . importAliasTimeInMS , itemCount : results . length , symbolCount : perfInfo . symbolCount , indexCount : perfInfo . indexCount , importAliasCount : perfInfo . importAliasCount , additionTimeInMS : additionDuration . getDurationInMilliseconds ( ) , } ; } private _getImportFromCompletions ( importFromNode , priorWord ) { if ( importFromNode . isWildcardImport ) { return undefined ; } const importInfo = AnalyzerNodeInfo . getImportInfo ( importFromNode . module ) ; if ( ! importInfo ) { return undefined ; } const completionList = CompletionList . create ( ) ; const resolvedPath = importInfo . resolvedPaths . length > 0 ? importInfo . resolvedPaths [ importInfo . resolvedPaths . length - 1 ] : '' ; const lookupResults = this . _importLookup ( resolvedPath ) ; if ( lookupResults ) { this . _addSymbolsForSymbolTable ( lookupResults . symbolTable , ( name ) => { return ! importFromNode . imports . find ( ( imp ) => imp . name . value === name ) ; } , priorWord , true , undefined , completionList ) ; } importInfo . implicitImports . forEach ( ( implImport ) => { if ( ! importFromNode . imports . find ( ( imp ) => imp . name . value === implImport . name ) ) { this . _addNameToCompletionList ( implImport . name , CompletionItemKind . Module , priorWord , completionList ) ; } } ) ; return { completionList } ; } private _findMatchingKeywords ( keywordList , partialMatch ) { return keywordList . filter ( ( keyword ) => { if ( partialMatch ) { return StringUtils . isPatternInSymbol ( partialMatch , keyword ) ; } else { return true ; } } ) ; } private _addNamedParameters ( signatureInfo , priorWord , completionList ) { const argNameMap = new Map < string , string > ( ) ; signatureInfo . signatures . forEach ( ( signature ) => { this . _addNamedParametersToMap ( signature . type , argNameMap ) ; } ) ; signatureInfo . callNode . arguments ! . forEach ( ( arg ) => { if ( arg . name ) { argNameMap . delete ( arg . name . value ) ; } } ) ; argNameMap . forEach ( ( argName ) => { if ( StringUtils . isPatternInSymbol ( priorWord , argName ) ) { const completionItem = CompletionItem . create ( argName + '=' ) ; completionItem . kind = CompletionItemKind . Variable ; const completionItemData = { workspacePath : this . _workspacePath , filePath : this . _filePath , position : this . _position , } ; completionItem . data = completionItemData ; completionItem . sortText = this . _makeSortText ( SortCategory . NamedParameter , argName ) ; completionList . items . push ( completionItem ) ; } } ) ; } private _addNamedParametersToMap ( type , paramMap ) { type . details . parameters . forEach ( ( param ) => { if ( param . name && ! param . isNameSynthesized ) { if ( ! SymbolNameUtils . isPrivateOrProtectedName ( param . name ) ) { paramMap . set ( param . name , param . name ) ; } } } ) ; } private _addSymbols ( node , priorWord , completionList ) { let curNode = node ; while ( curNode ) { let scope = getScopeForNode ( curNode ) ; if ( scope ) { while ( scope ) { this . _addSymbolsForSymbolTable ( scope . symbolTable , ( ) => true , priorWord , false , undefined , completionList ) ; scope = scope . parent ; } if ( curNode . nodeType === ParseNodeType . Class ) { const classType = this . _evaluator . getTypeOfClass ( curNode ) ; if ( classType && isInstantiableClass ( classType . classType ) ) { classType . classType . details . mro . forEach ( ( baseClass , index ) => { if ( isInstantiableClass ( baseClass ) ) { this . _addSymbolsForSymbolTable ( baseClass . details . fields , ( name ) => { const symbol = baseClass . details . fields . get ( name ) ; if ( ! symbol || ! symbol . isClassMember ( ) ) { return false ; } return symbol . getDeclarations ( ) . some ( ( decl ) => decl . type === DeclarationType . Variable ) ; } , priorWord , false , undefined , completionList ) ; } } ) ; } } break ; } curNode = curNode . parent ; } } private _addSymbolsForSymbolTable ( symbolTable , includeSymbolCallback , priorWord , isInImport , boundObjectOrClass , completionList ) { symbolTable . forEach ( ( symbol , name ) => { const hidden = symbol . isExternallyHidden ( ) && ! symbol . getDeclarations ( ) . some ( ( d ) => isDefinedInFile ( d , this . _filePath ) ) ; if ( ! hidden && includeSymbolCallback ( name ) ) { if ( ! completionList . items . some ( ( item ) => item . label === name ) ) { this . _addSymbol ( name , symbol , priorWord , completionList , { boundObjectOrClass , funcParensDisabled : isInImport , } ) ; } } } ) ; } private _addSymbol ( name , symbol , priorWord , completionList , detail ) { if ( symbol . isPrivatePyTypedImport ( ) ) { return ; } let primaryDecl = getLastTypedDeclaredForSymbol ( symbol ) ; if ( ! primaryDecl ) { const declarations = symbol . getDeclarations ( ) ; if ( declarations . length > 0 ) { primaryDecl = declarations [ declarations . length - 1 ] ; } } if ( primaryDecl ) { let itemKind = CompletionItemKind . Variable ; primaryDecl = this . _evaluator . resolveAliasDeclaration ( primaryDecl , true ) ; if ( primaryDecl ) { itemKind = this . _convertDeclarationTypeToItemKind ( primaryDecl ) ; if ( primaryDecl . type === DeclarationType . Variable && detail . boundObjectOrClass && isInstantiableClass ( detail . boundObjectOrClass ) && ClassType . isEnumClass ( detail . boundObjectOrClass ) && primaryDecl . node . parent ?. nodeType === ParseNodeType . Assignment ) { itemKind = CompletionItemKind . EnumMember ; } if ( this . _itemToResolve ) { const completionItemData = this . _itemToResolve . data as CompletionItemData ; if ( completionItemData . symbolLabel === name && ! completionItemData . autoImportText ) { const type = this . _evaluator . getEffectiveTypeOfSymbol ( symbol ) ; if ( type ) { let typeDetail ; let documentation ; switch ( primaryDecl . type ) { case DeclarationType . Intrinsic : case DeclarationType . Variable : case DeclarationType . Parameter : { let expandTypeAlias = false ; if ( type && TypeBase . isInstantiable ( type ) ) { const typeAliasInfo = getTypeAliasInfo ( type ) ; if ( typeAliasInfo ) { if ( typeAliasInfo . name === name ) { expandTypeAlias = true ; } } } typeDetail = name + ': ' + this . _evaluator . printType ( type , expandTypeAlias ) ; break ; } case DeclarationType . Function : { const functionType = detail . boundObjectOrClass && ( isFunction ( type ) || isOverloadedFunction ( type ) ) ? this . _evaluator . bindFunctionToClassOrObject ( detail . boundObjectOrClass , type ) : type ; if ( functionType ) { if ( isProperty ( functionType ) && detail . boundObjectOrClass && isClassInstance ( detail . boundObjectOrClass ) ) { const propertyType = this . _evaluator . getGetterTypeFromProperty ( functionType as ClassType , true ) || UnknownType . create ( ) ; typeDetail = name + ': ' + this . _evaluator . printType ( propertyType , false ) + ' (property)' ; } else if ( isOverloadedFunction ( functionType ) ) { typeDetail = getOverloadedFunctionTooltip ( functionType , this . _evaluator , 35 ) ; } else { typeDetail = name + ': ' + this . _evaluator . printType ( functionType , false ) ; } } break ; } case DeclarationType . Class : case DeclarationType . SpecialBuiltInClass : { typeDetail = 'class ' + name + '()' ; break ; } case DeclarationType . Alias : { typeDetail = name ; if ( primaryDecl . path ) { const lookupResults = this . _importLookup ( primaryDecl . path ) ; if ( lookupResults ) { documentation = lookupResults . docString ; } } break ; } default : { typeDetail = name ; break ; } } if ( primaryDecl . type === DeclarationType . Variable && primaryDecl . typeAliasName && primaryDecl . docString ) { documentation = primaryDecl . docString ; } else if ( isModule ( type ) ) { documentation = getModuleDocString ( type , primaryDecl , this . _sourceMapper ) ; } else if ( isInstantiableClass ( type ) ) { documentation = getClassDocString ( type , primaryDecl , this . _sourceMapper ) ; } else if ( isFunction ( type ) ) { const functionType = detail . boundObjectOrClass ? this . _evaluator . bindFunctionToClassOrObject ( detail . boundObjectOrClass , type ) : type ; if ( functionType && isFunction ( functionType ) ) { documentation = getFunctionDocStringFromType ( functionType , this . _sourceMapper , this . _evaluator ) ; } } else if ( isOverloadedFunction ( type ) ) { const enclosingClass = isFunctionDeclaration ( primaryDecl ) ? ParseTreeUtils . getEnclosingClass ( primaryDecl . node . name , false ) : undefined ; const classResults = enclosingClass ? this . _evaluator . getTypeOfClass ( enclosingClass ) : undefined ; const functionType = detail . boundObjectOrClass ? this . _evaluator . bindFunctionToClassOrObject ( detail . boundObjectOrClass , type ) : type ; if ( functionType && isOverloadedFunction ( functionType ) ) { documentation = getOverloadedFunctionDocStringsInherited ( functionType , primaryDecl , this . _sourceMapper , this . _evaluator , classResults ?. classType ) . find ( ( doc ) => doc ) ; } } else if ( primaryDecl ?. type === DeclarationType . Function ) { documentation = getPropertyDocStringInherited ( primaryDecl , this . _sourceMapper , this . _evaluator ) ; } else if ( primaryDecl ?. type === DeclarationType . Variable ) { const decl = ( symbol . getDeclarations ( ) . find ( ( d ) => d . type === DeclarationType . Variable && ! ! d . docString ) ?? primaryDecl ) as VariableDeclaration ; documentation = getVariableDocString ( decl , this . _sourceMapper ) ; } if ( this . _options . format === MarkupKind . Markdown ) { let markdownString = '```python\n' + typeDetail + '\n```\n' ; if ( documentation ) { markdownString += '---\n' ; markdownString += convertDocStringToMarkdown ( documentation ) ; } markdownString = markdownString . trimEnd ( ) ; this . _itemToResolve . documentation = { kind : MarkupKind . Markdown , value : markdownString , } ; } else if ( this . _options . format === MarkupKind . PlainText ) { let plainTextString = typeDetail + '\n' ; if ( documentation ) { plainTextString += '\n' ; plainTextString += convertDocStringToPlainText ( documentation ) ; } plainTextString = plainTextString . trimEnd ( ) ; this . _itemToResolve . documentation = { kind : MarkupKind . PlainText , value : plainTextString , } ; } else { fail ( `Unsupported markup type: ${ this . _options . format }` ) ; } } } } } const autoImportText = detail . autoImportSource ? this . _getAutoImportText ( name , detail . autoImportSource , detail . autoImportAlias ) : undefined ; this . _addNameToCompletionList ( detail . autoImportAlias ?? name , itemKind , priorWord , completionList , { autoImportText , funcParensDisabled : detail . funcParensDisabled , edits : detail . edits , } ) ; } else { const synthesizedType = symbol . getSynthesizedType ( ) ; if ( synthesizedType ) { const itemKind = CompletionItemKind . Variable ; this . _addNameToCompletionList ( name , itemKind , priorWord , completionList , { funcParensDisabled : detail . funcParensDisabled , edits : detail . edits , } ) ; } } } private _getAutoImportText ( importName , importFrom ? , importAlias ? ) { const autoImportText = getAutoImportText ( importName , importFrom , importAlias ) ; if ( this . _options . format === MarkupKind . Markdown ) { return `\`\`\`\n${ autoImportText }\n\`\`\`` ; } else if ( this . _options . format === MarkupKind . PlainText ) { return autoImportText ; } else { fail ( `Unsupported markup type: ${ this . _options . format }` ) ; } } private _addNameToCompletionList ( name , itemKind , filter , completionList , detail ? ) { const similarity = detail ?. autoImportText ? true : StringUtils . isPatternInSymbol ( filter , name ) ; if ( ! similarity ) { return ; } const completionItem = CompletionItem . create ( name ) ; completionItem . kind = itemKind ; const completionItemData = { workspacePath : this . _workspacePath , filePath : this . _filePath , position : this . _position , } ; if ( detail ?. funcParensDisabled ) { completionItemData . funcParensDisabled = true ; } completionItem . data = completionItemData ; if ( detail ?. sortText || detail ?. itemDetail ) { completionItem . sortText = detail . sortText ; completionItem . detail = detail . itemDetail ; } else if ( detail ?. autoImportText ) { completionItem . sortText = this . _makeSortText ( SortCategory . AutoImport , name , detail . autoImportText ) ; completionItemData . autoImportText = detail . autoImportText ; completionItem . detail = autoImportDetail ; } else if ( itemKind === CompletionItemKind . EnumMember ) { completionItem . sortText = this . _makeSortText ( SortCategory . EnumMember , name ) ; } else if ( SymbolNameUtils . isDunderName ( name ) ) { completionItem . sortText = this . _makeSortText ( SortCategory . DunderSymbol , name ) ; } else if ( filter === '' && SymbolNameUtils . isPrivateOrProtectedName ( name ) ) { completionItem . sortText = this . _makeSortText ( SortCategory . PrivateSymbol , name ) ; } else { completionItem . sortText = this . _makeSortText ( SortCategory . NormalSymbol , name ) ; } completionItemData . symbolLabel = name ; if ( this . _options . format === MarkupKind . Markdown ) { let markdownString = '' ; if ( detail ?. autoImportText ) { markdownString += detail . autoImportText ; if ( detail . typeDetail || detail . documentation ) { markdownString += '\n\n' ; } } if ( detail ?. typeDetail ) { markdownString += '```python\n' + detail . typeDetail + '\n```\n' ; } if ( detail ?. documentation ) { markdownString += '---\n' ; markdownString += convertDocStringToMarkdown ( detail . documentation ) ; } markdownString = markdownString . trimEnd ( ) ; if ( markdownString ) { completionItem . documentation = { kind : MarkupKind . Markdown , value : markdownString , } ; } } else if ( this . _options . format === MarkupKind . PlainText ) { let plainTextString = '' ; if ( detail ?. autoImportText ) { plainTextString += detail . autoImportText ; if ( detail . typeDetail || detail . documentation ) { plainTextString += '\n\n' ; } } if ( detail ?. typeDetail ) { plainTextString += detail . typeDetail + '\n' ; } if ( detail ?. documentation ) { plainTextString += '\n' + convertDocStringToPlainText ( detail . documentation ) ; } plainTextString = plainTextString . trimEnd ( ) ; if ( plainTextString ) { completionItem . documentation = { kind : MarkupKind . PlainText , value : plainTextString , } ; } } else { fail ( `Unsupported markup type: ${ this . _options . format }` ) ; } if ( detail ?. edits ?. format ) { completionItem . insertTextFormat = detail . edits . format ; } if ( detail ?. edits ?. textEdit ) { completionItem . textEdit = detail . edits . textEdit ; } if ( detail ?. edits ?. additionalTextEdits ) { completionItem . additionalTextEdits = detail . edits . additionalTextEdits . map ( ( te ) => { const textEdit = { range : { start : { line : te . range . start . line , character : te . range . start . character } , end : { line : te . range . end . line , character : te . range . end . character } , } , newText : te . replacementText , } ; return textEdit ; } ) ; if ( this . _itemToResolve ) { const data = this . _itemToResolve . data as CompletionItemData ; if ( data . autoImportText === completionItemData . autoImportText ) { this . _itemToResolve . additionalTextEdits = completionItem . additionalTextEdits ; } } } completionList . items . push ( completionItem ) ; } private _getRecentListIndex ( name , autoImportText ) { return CompletionProvider . _mostRecentCompletions . findIndex ( ( item ) => item . label === name && item . autoImportText === autoImportText ) ; } private _makeSortText ( sortCategory , name , autoImportText = '' ) { const recentListIndex = this . _getRecentListIndex ( name , autoImportText ) ; if ( recentListIndex >= 0 ) { if ( sortCategory === SortCategory . AutoImport ) { sortCategory = SortCategory . RecentAutoImport ; } else if ( sortCategory === SortCategory . ImportModuleName ) { sortCategory = SortCategory . RecentImportModuleName ; } else if ( sortCategory === SortCategory . Keyword || sortCategory === SortCategory . NormalSymbol || sortCategory === SortCategory . PrivateSymbol || sortCategory === SortCategory . DunderSymbol ) { sortCategory = SortCategory . RecentKeywordOrSymbol ; } } return this . _formatInteger ( sortCategory , 2 ) + '.' + this . _formatInteger ( recentListIndex , 4 ) + '.' + name ; } private _formatInteger ( val , digits ) { const charCodeZero = '0' . charCodeAt ( 0 ) ; let result = '' ; for ( let i = 0 ; i < digits ; i ++ ) { let digit = Math . floor ( val % 10 ) ; if ( digit < 0 ) { digit = 9 ; } result = String . fromCharCode ( digit + charCodeZero ) + result ; val = Math . floor ( val / 10 ) ; } return result ; } private _convertDeclarationTypeToItemKind ( declaration ) { const resolvedDeclaration = this . _evaluator . resolveAliasDeclaration ( declaration , true ) ; if ( ! resolvedDeclaration ) { return CompletionItemKind . Variable ; } switch ( resolvedDeclaration . type ) { case DeclarationType . Intrinsic : return resolvedDeclaration . intrinsicType === 'class' ? CompletionItemKind . Class : CompletionItemKind . Variable ; case DeclarationType . Parameter : return CompletionItemKind . Variable ; case DeclarationType . Variable : return resolvedDeclaration . isConstant || resolvedDeclaration . isFinal ? CompletionItemKind . Constant : CompletionItemKind . Variable ; case DeclarationType . Function : { if ( this . _isPossiblePropertyDeclaration ( resolvedDeclaration ) ) { const functionType = this . _evaluator . getTypeOfFunction ( resolvedDeclaration . node ) ; if ( functionType && isProperty ( functionType . decoratedType ) ) { return CompletionItemKind . Property ; } } return resolvedDeclaration . isMethod ? CompletionItemKind . Method : CompletionItemKind . Function ; } case DeclarationType . Class : case DeclarationType . SpecialBuiltInClass : return CompletionItemKind . Class ; case DeclarationType . Alias : return CompletionItemKind . Module ; } } private _getImportModuleCompletions ( node ) { const moduleDescriptor = { leadingDots : node . leadingDots , hasTrailingDot : node . hasTrailingDot || false , nameParts : node . nameParts . map ( ( part ) => part . value ) , importedSymbols : [ ] , } ; const completions = this . _importResolver . getCompletionSuggestions ( this . _filePath , this . _execEnv , moduleDescriptor ) ; const completionList = CompletionList . create ( ) ; if ( ! node . hasTrailingDot && node . parent && node . parent . nodeType === ParseNodeType . ImportFrom && node . parent . missingImportKeyword ) { const keyword = 'import' ; const completionItem = CompletionItem . create ( keyword ) ; completionItem . kind = CompletionItemKind . Keyword ; completionList . items . push ( completionItem ) ; completionItem . sortText = this . _makeSortText ( SortCategory . Keyword , keyword ) ; } completions . forEach ( ( completionName ) => { const completionItem = CompletionItem . create ( completionName ) ; completionItem . kind = CompletionItemKind . Module ; completionList . items . push ( completionItem ) ; completionItem . sortText = this . _makeSortText ( SortCategory . ImportModuleName , completionName ) ; } ) ; return { completionList } ; } private _isPossiblePropertyDeclaration ( decl ) { return decl . isMethod && decl . node . decorators . length > 0 ; } }