namespace ts . pxtc . service { const COMPLETION_KEYWORD_WEIGHT = 0 ; const COMPLETION_DEFAULT_WEIGHT = 1 ; const COMPLETION_IN_SCOPE_VAR_WEIGHT = 5 ; const COMPLETION_MATCHING_PARAM_TYPE_WEIGHT = 10 ; export function getCallSymbol ( callExp ) { const qName = callExp ?. pxt ?. callInfo ?. qName ; const api = lastApiInfo . apis . byQName [ qName ] ; return api ; } export function getParameter ( callSym , paramIdx , blocksInfo ) { if ( ! callSym || paramIdx < 0 ) return undefined ; const paramDesc = callSym . parameters [ paramIdx ] ; let result = paramDesc ; if ( callSym . attributes . _def ) { const blockParams = callSym . attributes . _def . parameters ; const blockParam = blockParams [ paramIdx ] ; const shadowId = blockParam . shadowBlockId ; if ( shadowId ) { const shadowBlk = blocksInfo . blocksById [ shadowId ] ; const shadowApi = lastApiInfo . apis . byQName [ shadowBlk . qName ] ; const isPassThrough = shadowApi . attributes . shim === "TD_ID" ; if ( isPassThrough && shadowApi . parameters . length === 1 ) { result = shadowApi . parameters [ 0 ] ; } } } return result ; } export function getApisForTsType ( pxtType , location , tc , symbols , isEnum = false ) { const apisByRetType = { } ; symbols . forEach ( i => { let retType = i . symbol . retType ; if ( isEnum ) { if ( i . symbol . kind == SymbolKind . EnumMember ) { retType = i . symbol . namespace ; } else if ( i . symbol . kind == SymbolKind . Variable ) { const enumParts = i . symbol . attributes ?. enumIdentity ?. split ( "." ) ; if ( enumParts ?. length > 1 ) retType = enumParts [ 0 ] ; } } apisByRetType [ retType ] = [ ... ( apisByRetType [ retType ] || [ ] ) , i ] ; } ) ; const retApis = apisByRetType [ pxtType ] || [ ] ; let enumVals = [ ] ; for ( let r of retApis ) { const asTsEnum = getTsSymbolFromPxtSymbol ( r . symbol , location , SymbolFlags . Enum ) ; if ( asTsEnum ) { const enumType = tc . getTypeOfSymbolAtLocation ( asTsEnum , location ) ; const mems = getEnumMembers ( tc , enumType ) ; const enumValQNames = mems . map ( e => enumMemberToQName ( tc , e ) ) ; const symbols = enumValQNames . map ( n => lastApiInfo . apis . byQName [ n ] ) ; enumVals = [ ... enumVals , ... symbols ] ; } } return [ ... retApis , ... completionSymbols ( enumVals , COMPLETION_DEFAULT_WEIGHT ) ] ; } export function getBasicKindDefault ( kind , isPython ) { switch ( kind ) { case SK . StringKeyword : return "\"\"" ; case SK . NumberKeyword : return "0" ; case SK . BooleanKeyword : return isPython ? "False" : "false" ; case SK . ArrayType : return "[]" ; case SK . NullKeyword : return isPython ? "None" : "null" ; default : return undefined ; } } export function tsSymbolToPxtSymbolKind ( ts ) { if ( ts . flags & SymbolFlags . Variable ) return SymbolKind . Variable ; if ( ts . flags & SymbolFlags . Class ) return SymbolKind . Class ; if ( ts . flags & SymbolFlags . Enum ) return SymbolKind . Enum ; if ( ts . flags & SymbolFlags . EnumMember ) return SymbolKind . EnumMember ; if ( ts . flags & SymbolFlags . Method ) return SymbolKind . Method ; if ( ts . flags & SymbolFlags . Module ) return SymbolKind . Module ; if ( ts . flags & SymbolFlags . Property ) return SymbolKind . Property ; return SymbolKind . None ; } export function makePxtSymbolFromKeyword ( keyword ) { let sym = { kind : SymbolKind . None , name : keyword , pyName : keyword , qName : keyword , pyQName : keyword , namespace : "" , attributes : { callingConvention : ir . CallingConvention . Plain , paramDefl : { } , } , fileName : pxt . MAIN_TS , parameters : [ ] , retType : "any" , } ; return sym ; } export function makePxtSymbolFromTsSymbol ( tsSym , tsType ) { let qname = tsSym . getName ( ) ; let match = /(.*)\.(.*)/ . exec ( qname ) ; let name = match ? match [ 2 ] : qname ; let ns = match ? match [ 1 ] : "" ; let typeName = tsType . getSymbol ( ) ?. getName ( ) ?? "any" ; let sym = { kind : tsSymbolToPxtSymbolKind ( tsSym ) , name : name , pyName : name , qName : qname , pyQName : qname , namespace : ns , attributes : { callingConvention : ir . CallingConvention . Plain , paramDefl : { } , } , fileName : pxt . MAIN_TS , parameters : [ ] , retType : typeName , } ; return sym ; } export function getPxtSymbolFromTsSymbol ( tsSym , apiInfo , tc ) { if ( tsSym ) { return apiInfo . byQName [ tc . getFullyQualifiedName ( tsSym ) ] ; } return undefined ; } export function compareCompletionSymbols ( a , b ) { if ( a . weight !== b . weight ) { return b . weight - a . weight ; } return compareSymbols ( a . symbol , b . symbol ) ; } export function completionSymbol ( symbol , weight ) { return { symbol , weight } ; } export function completionSymbols ( symbols , weight ) { return symbols . map ( s => completionSymbol ( s , weight ) ) ; } export function getNodeAndSymbolAtLocation ( program , filename , position , apiInfo ) { const source = program . getSourceFile ( filename ) ; const checker = program . getTypeChecker ( ) ; const node = findInnerMostNodeAtPosition ( source , position ) ; if ( node ) { const symbol = checker . getSymbolAtLocation ( node ) ; if ( symbol ) { let pxtSym = getPxtSymbolFromTsSymbol ( symbol , apiInfo , checker ) ; return [ node , pxtSym ] ; } } return null ; } export function tsTypeToPxtTypeString ( t , tc ) { if ( t . flags & TypeFlags . NumberLiteral ) { return "Number" ; } else if ( t . flags & TypeFlags . StringLiteral ) { return "String" ; } else if ( t . flags & TypeFlags . BooleanLiteral ) { return "Boolean" ; } const tcString = tc . typeToString ( t ) ; const primativeToQname = { "number" : "Number" , "string" : "String" , "boolean" : "Boolean" } ; const pxtString = primativeToQname [ tcString ] ?? tcString ; return pxtString ; } export function filenameWithExtension ( filename , extension ) { if ( extension . charAt ( 0 ) === "." ) extension = extension . substr ( 1 ) ; return filename . substr ( 0 , filename . lastIndexOf ( "." ) + 1 ) + extension ; } /**
     * This function only cares about getting words of the form [a-zA-z]+
     */ export function getWordAtPosition ( text , position ) { let start = position ; let end = position ; while ( start > 0 && isWordCharacter ( start ) ) -- start ; while ( end < text . length - 1 && isWordCharacter ( end ) ) ++ end ; if ( start != end ) { return { text : text . substring ( start + 1 , end ) , start : start + 1 , end : end } ; } return null ; function isWordCharacter ( index ) { const charCode = text . charCodeAt ( index ) ; return charCode >= 65 && charCode <= 90 || charCode >= 97 && charCode <= 122 ; } } export function getTsSymbolFromPxtSymbol ( pxtSym , location , meaning ) { const checker = service && service . getProgram ( ) . getTypeChecker ( ) ; if ( ! checker ) return null ; const tsSymbols = checker . getSymbolsInScope ( location , meaning ) ; for ( let tsSym of tsSymbols ) { if ( tsSym . escapedName . toString ( ) === pxtSym . qName ) return tsSym ; } return null ; } export function getDefaultEnumValue ( t , python ) { const checker = service && service . getProgram ( ) . getTypeChecker ( ) ; const members = getEnumMembers ( checker , t ) ; for ( const member of members ) { if ( member . name . kind === SK . Identifier ) { const fullName = enumMemberToQName ( checker , member ) ; const pxtSym = lastApiInfo . apis . byQName [ fullName ] ; if ( pxtSym ) { if ( pxtSym . attributes . alias ) return ( python && pxtSym . attributes . pyAlias ) || pxtSym . attributes . alias ; return python ? pxtSym . pyQName : pxtSym . qName ; } else return fullName ; } } return "0" ; } export function getCompletions ( v ) { const { fileName , fileContent , position , wordStartPos , wordEndPos , runtime } = v ; let src = fileContent ; if ( fileContent ) { host . setFile ( fileName , fileContent ) ; } const tsFilename = filenameWithExtension ( fileName , "ts" ) ; const span = { startPos : wordStartPos , endPos : wordEndPos } ; const isPython = /\.py$/ . test ( fileName ) ; const r = { entries : [ ] , isMemberCompletion : false , isNewIdentifierLocation : true , isTypeLocation : false , namespace : [ ] , } ; let lastNl = src . lastIndexOf ( "\n" , position - 1 ) ; lastNl = Math . max ( 0 , lastNl ) ; const lineText = src . substring ( lastNl + 1 , position ) ; const lineCommentStr = isPython ? "#" : "//" ; if ( lineText . trim ( ) . startsWith ( lineCommentStr ) ) { return r ; } let dotIdx = - 1 ; let complPosition = - 1 ; for ( let i = position - 1 ; i >= 0 ; -- i ) { if ( src [ i ] == "." ) { dotIdx = i ; break ; } if ( ! /\w/ . test ( src [ i ] ) ) break ; if ( complPosition == - 1 ) complPosition = i ; } if ( dotIdx == position - 1 ) { src = src . slice ( 0 , position ) + "_" + src . slice ( position ) ; } else if ( complPosition == - 1 ) { src = src . slice ( 0 , position ) + "_" + src . slice ( position ) ; complPosition = position ; } const isMemberCompletion = dotIdx !== - 1 ; r . isMemberCompletion = isMemberCompletion ; const partialWord = isMemberCompletion ? src . slice ( dotIdx + 1 , wordEndPos ) : src . slice ( wordStartPos , wordEndPos ) ; const MAX_SYMBOLS_BEFORE_FILTER = 50 ; const MAX_SYMBOLS = 100 ; if ( isMemberCompletion ) complPosition = dotIdx ; const entries = { } ; let opts = cloneCompileOpts ( host . opts ) ; opts . fileSystem [ fileName ] = src ; addApiInfo ( opts ) ; opts . syntaxInfo = { position : complPosition , type : r . isMemberCompletion ? "memberCompletion" : "identifierCompletion" } ; let resultSymbols = [ ] ; let tsPos ; if ( isPython ) { const res = transpile . pyToTs ( opts ) ; if ( res . syntaxInfo && res . syntaxInfo . symbols ) { resultSymbols = completionSymbols ( res . syntaxInfo . symbols , COMPLETION_DEFAULT_WEIGHT ) ; } if ( res . globalNames ) lastGlobalNames = res . globalNames ; if ( ! resultSymbols . length && res . globalNames ) { resultSymbols = completionSymbols ( pxt . U . values ( res . globalNames ) , COMPLETION_DEFAULT_WEIGHT ) ; } Object . keys ( res . outfiles ) . forEach ( k => { if ( k === tsFilename ) { host . setFile ( k , res . outfiles [ k ] ) ; } } ) ; if ( res . sourceMap ) { const pySrc = src ; const tsSrc = res . outfiles [ tsFilename ] || "" ; const srcMap = pxtc . BuildSourceMapHelpers ( res . sourceMap , tsSrc , pySrc ) ; const smallest = srcMap . py . smallestOverlap ( span ) ; if ( smallest ) { tsPos = smallest . ts . startPos ; } } if ( ! isMemberCompletion && resultSymbols . length > MAX_SYMBOLS_BEFORE_FILTER ) { resultSymbols = resultSymbols . filter ( s => ( isPython ? s . symbol . pyQName : s . symbol . qName ) . toLowerCase ( ) . indexOf ( partialWord . toLowerCase ( ) ) >= 0 ) ; } opts . ast = true ; const ts2asm = compile ( opts , service ) ; } else { tsPos = position ; opts . ast = true ; host . setOpts ( opts ) ; const res = runConversionsAndCompileUsingService ( ) ; } const prog = service . getProgram ( ) ; const tsAst = prog . getSourceFile ( tsFilename ) ; const tc = prog . getTypeChecker ( ) ; let tsNode = findInnerMostNodeAtPosition ( tsAst , tsPos ) ; const commentMap = pxtc . decompiler . buildCommentMap ( tsAst ) ; const inComment = commentMap . some ( range => range . start <= position && position <= range . end ) ; if ( inComment ) { return r ; } if ( tsNode ) { const stringLiteralKinds = [ SK . StringLiteral , SK . FirstTemplateToken , SK . NoSubstitutionTemplateLiteral ] ; const inLiteral = stringLiteralKinds . some ( k => tsNode . kind === k ) ; if ( inLiteral ) { return r ; } } r . namespace = getCurrentNamespaces ( tsNode ) ; let didFindMemberCompletions = false ; if ( isMemberCompletion ) { const propertyAccessTarget = findInnerMostNodeAtPosition ( tsAst , isPython ? tsPos : dotIdx - 1 ) ; if ( propertyAccessTarget ) { let type ; const symbol = tc . getSymbolAtLocation ( propertyAccessTarget ) ; if ( symbol ?. members ?. size > 0 ) { type = tc . getDeclaredTypeOfSymbol ( symbol ) ; } else if ( symbol ) { type = tc . getTypeOfSymbolAtLocation ( symbol , propertyAccessTarget ) ; } else { type = tc . getTypeAtLocation ( propertyAccessTarget ) ; } if ( type ) { const qname = type . symbol ? tc . getFullyQualifiedName ( type . symbol ) : tsTypeToPxtTypeString ( type , tc ) ; if ( qname ) { const props = type . getApparentProperties ( ) . map ( prop => qname + "." + prop . getName ( ) ) . map ( propQname => lastApiInfo . apis . byQName [ propQname ] ) . filter ( prop => ! ! prop ) . map ( prop => completionSymbol ( prop , COMPLETION_DEFAULT_WEIGHT ) ) ; resultSymbols = props ; didFindMemberCompletions = true ; } } } } const allSymbols = pxt . U . values ( lastApiInfo . apis . byQName ) ; if ( resultSymbols . length === 0 ) { let wordMatching = allSymbols . filter ( s => ( isPython ? s . pyQName : s . qName ) . toLowerCase ( ) . indexOf ( partialWord . toLowerCase ( ) ) >= 0 ) ; resultSymbols = completionSymbols ( wordMatching , COMPLETION_DEFAULT_WEIGHT ) ; } if ( ! isPython && ! didFindMemberCompletions ) { tsNode = findInnerMostNodeAtPosition ( tsAst , wordStartPos ) ; if ( ! tsNode ) tsNode = tsAst . getSourceFile ( ) ; let symSearch = SymbolFlags . Variable ; let inScopeTsSyms = tc . getSymbolsInScope ( tsNode , symSearch ) ; let matchStr = tsNode . getText ( ) ; if ( matchStr !== "_" ) inScopeTsSyms = inScopeTsSyms . filter ( s => s . name . indexOf ( matchStr ) >= 0 ) ; let inScopePxtSyms = inScopeTsSyms . map ( t => { let pxtSym = getPxtSymbolFromTsSymbol ( t , lastApiInfo . apis , tc ) ; if ( ! pxtSym ) { let tsType = tc . getTypeOfSymbolAtLocation ( t , tsNode ) ; pxtSym = makePxtSymbolFromTsSymbol ( t , tsType ) ; } return pxtSym ; } ) . filter ( s => ! ! s ) . map ( s => completionSymbol ( s , COMPLETION_DEFAULT_WEIGHT ) ) ; inScopePxtSyms . forEach ( s => s . weight += COMPLETION_IN_SCOPE_VAR_WEIGHT ) ; resultSymbols = [ ... resultSymbols , ... inScopePxtSyms ] ; } const call = getParentCallExpression ( tsNode ) ; if ( call ) { let paramIdx = findCurrentCallArgIdx ( call , tsNode , tsPos ) ; if ( paramIdx >= 0 ) { const blocksInfo = blocksInfoOp ( lastApiInfo . apis , runtime . bannedCategories ) ; const callSym = getCallSymbol ( call ) ; if ( callSym ) { if ( paramIdx >= callSym . parameters . length ) paramIdx = callSym . parameters . length - 1 ; const param = getParameter ( callSym , paramIdx , blocksInfo ) ; if ( param ) { const matchingApis = getApisForTsType ( param . type , call , tc , resultSymbols , param . isEnum ) ; matchingApis . forEach ( match => match . weight = COMPLETION_MATCHING_PARAM_TYPE_WEIGHT ) ; } } } } if ( ! isMemberCompletion ) { let keywords ; if ( isPython ) { let keywordsMap = ( pxt as any ) . py . keywords as Map < boolean > ; keywords = Object . keys ( keywordsMap ) ; } else { keywords = [ ... ts . pxtc . reservedWords , ... ts . pxtc . keywordTypes ] ; } let keywordSymbols = keywords . filter ( k => k . indexOf ( partialWord ) >= 0 ) . map ( makePxtSymbolFromKeyword ) . map ( s => completionSymbol ( s , COMPLETION_KEYWORD_WEIGHT ) ) ; resultSymbols = [ ... resultSymbols , ... keywordSymbols ] ; } let takenNames = { } ; if ( isPython && lastGlobalNames ) { takenNames = lastGlobalNames ; } else { takenNames = lastApiInfo . apis . byQName ; } resultSymbols . map ( sym => { if ( sym . symbol . attributes . alias && ! ( isMemberCompletion && sym . symbol . kind === SymbolKind . EnumMember ) ) { return completionSymbol ( lastApiInfo . apis . byQName [ sym . symbol . attributes . alias ] , sym . weight ) ; } else { return sym ; } } ) . filter ( shouldUseSymbol ) . forEach ( sym => { entries [ sym . symbol . qName ] = sym ; } ) ; resultSymbols = pxt . Util . values ( entries ) . filter ( a => ! ! a && ! ! a . symbol ) ; resultSymbols . sort ( compareCompletionSymbols ) ; if ( v . light && resultSymbols . length > MAX_SYMBOLS ) { resultSymbols = resultSymbols . splice ( 0 , MAX_SYMBOLS ) ; } const { bannedCategories , screenSize } = v . runtime ; const blocksInfo = blocksInfoOp ( lastApiInfo . apis , bannedCategories ) ; const context = { takenNames , blocksInfo , screenSize , apis : lastApiInfo . apis , checker : service ?. getProgram ( ) ?. getTypeChecker ( ) } ; resultSymbols . forEach ( sym => patchSymbolWithSnippet ( sym . symbol , isPython , context ) ) ; r . entries = resultSymbols . map ( sym => sym . symbol ) ; return r ; } function shouldUseSymbol ( { symbol : si } ) { let use = ! ( /^__/ . test ( si . name ) || /^__/ . test ( si . namespace ) || si . attributes . hidden || si . attributes . deprecated || si . attributes . shim == "TD_ID" || si . attributes . blockAliasFor ) ; return use ; } function patchSymbolWithSnippet ( si , isPython , context ) { const n = lastApiInfo . decls [ si . qName ] ; if ( isFunctionLike ( n ) ) { if ( si . snippetAddsDefinitions || ( isPython && ! si . pySnippet ) || ( ! isPython && ! si . snippet ) ) { const snippetNode = getSnippet ( context , si , n , isPython ) ; const snippet = snippetStringify ( snippetNode ) ; const snippetWithMarkers = snippetStringify ( snippetNode , true ) ; const addsDefinitions = snippetAddsDefinitions ( snippetNode ) ; if ( isPython ) { si . pySnippet = snippet ; si . pySnippetWithMarkers = snippetWithMarkers ; } else { si . snippet = snippet ; si . snippetWithMarkers = snippetWithMarkers ; } si . snippetAddsDefinitions = addsDefinitions ; } } } }