1/* @internal */ 2namespace ts.Completions { 3 // Exported only for tests 4 export const moduleSpecifierResolutionLimit = 100; 5 export const moduleSpecifierResolutionCacheAttemptLimit = 1000; 6 7 export type Log = (message: string) => void; 8 9 export type SortText = string & { __sortText: any }; 10 export const SortText = { 11 // Presets 12 LocalDeclarationPriority: "10" as SortText, 13 LocationPriority: "11" as SortText, 14 OptionalMember: "12" as SortText, 15 MemberDeclaredBySpreadAssignment: "13" as SortText, 16 SuggestedClassMembers: "14" as SortText, 17 GlobalsOrKeywords: "15" as SortText, 18 AutoImportSuggestions: "16" as SortText, 19 ClassMemberSnippets: "17" as SortText, 20 JavascriptIdentifiers: "18" as SortText, 21 22 // Transformations 23 Deprecated(sortText: SortText): SortText { 24 return "z" + sortText as SortText; 25 }, 26 27 ObjectLiteralProperty(presetSortText: SortText, symbolDisplayName: string): SortText { 28 return `${presetSortText}\0${symbolDisplayName}\0` as SortText; 29 }, 30 31 SortBelow(sortText: SortText): SortText { 32 return sortText + "1" as SortText; 33 }, 34 }; 35 36 /** 37 * Special values for `CompletionInfo['source']` used to disambiguate 38 * completion items with the same `name`. (Each completion item must 39 * have a unique name/source combination, because those two fields 40 * comprise `CompletionEntryIdentifier` in `getCompletionEntryDetails`. 41 * 42 * When the completion item is an auto-import suggestion, the source 43 * is the module specifier of the suggestion. To avoid collisions, 44 * the values here should not be a module specifier we would ever 45 * generate for an auto-import. 46 */ 47 export enum CompletionSource { 48 /** Completions that require `this.` insertion text */ 49 ThisProperty = "ThisProperty/", 50 /** Auto-import that comes attached to a class member snippet */ 51 ClassMemberSnippet = "ClassMemberSnippet/", 52 /** A type-only import that needs to be promoted in order to be used at the completion location */ 53 TypeOnlyAlias = "TypeOnlyAlias/", 54 /** Auto-import that comes attached to an object literal method snippet */ 55 ObjectLiteralMethodSnippet = "ObjectLiteralMethodSnippet/", 56 } 57 58 const enum SymbolOriginInfoKind { 59 ThisType = 1 << 0, 60 SymbolMember = 1 << 1, 61 Export = 1 << 2, 62 Promise = 1 << 3, 63 Nullable = 1 << 4, 64 ResolvedExport = 1 << 5, 65 TypeOnlyAlias = 1 << 6, 66 ObjectLiteralMethod = 1 << 7, 67 68 SymbolMemberNoExport = SymbolMember, 69 SymbolMemberExport = SymbolMember | Export, 70 } 71 72 interface SymbolOriginInfo { 73 kind: SymbolOriginInfoKind; 74 isDefaultExport?: boolean; 75 isFromPackageJson?: boolean; 76 fileName?: string; 77 } 78 79 interface SymbolOriginInfoExport extends SymbolOriginInfo { 80 symbolName: string; 81 moduleSymbol: Symbol; 82 isDefaultExport: boolean; 83 exportName: string; 84 exportMapKey: string; 85 } 86 87 interface SymbolOriginInfoResolvedExport extends SymbolOriginInfo { 88 symbolName: string; 89 moduleSymbol: Symbol; 90 exportName: string; 91 moduleSpecifier: string; 92 } 93 94 interface SymbolOriginInfoTypeOnlyAlias extends SymbolOriginInfo { 95 declaration: TypeOnlyAliasDeclaration; 96 } 97 98 interface SymbolOriginInfoObjectLiteralMethod extends SymbolOriginInfo { 99 insertText: string, 100 labelDetails: CompletionEntryLabelDetails, 101 isSnippet?: true, 102 } 103 104 function originIsThisType(origin: SymbolOriginInfo): boolean { 105 return !!(origin.kind & SymbolOriginInfoKind.ThisType); 106 } 107 108 function originIsSymbolMember(origin: SymbolOriginInfo): boolean { 109 return !!(origin.kind & SymbolOriginInfoKind.SymbolMember); 110 } 111 112 function originIsExport(origin: SymbolOriginInfo | undefined): origin is SymbolOriginInfoExport { 113 return !!(origin && origin.kind & SymbolOriginInfoKind.Export); 114 } 115 116 function originIsResolvedExport(origin: SymbolOriginInfo | undefined): origin is SymbolOriginInfoResolvedExport { 117 return !!(origin && origin.kind === SymbolOriginInfoKind.ResolvedExport); 118 } 119 120 function originIncludesSymbolName(origin: SymbolOriginInfo | undefined): origin is SymbolOriginInfoExport | SymbolOriginInfoResolvedExport { 121 return originIsExport(origin) || originIsResolvedExport(origin); 122 } 123 124 function originIsPackageJsonImport(origin: SymbolOriginInfo | undefined): origin is SymbolOriginInfoExport { 125 return (originIsExport(origin) || originIsResolvedExport(origin)) && !!origin.isFromPackageJson; 126 } 127 128 function originIsPromise(origin: SymbolOriginInfo): boolean { 129 return !!(origin.kind & SymbolOriginInfoKind.Promise); 130 } 131 132 function originIsNullableMember(origin: SymbolOriginInfo): boolean { 133 return !!(origin.kind & SymbolOriginInfoKind.Nullable); 134 } 135 136 function originIsTypeOnlyAlias(origin: SymbolOriginInfo | undefined): origin is SymbolOriginInfoTypeOnlyAlias { 137 return !!(origin && origin.kind & SymbolOriginInfoKind.TypeOnlyAlias); 138 } 139 140 function originIsObjectLiteralMethod(origin: SymbolOriginInfo | undefined): origin is SymbolOriginInfoObjectLiteralMethod { 141 return !!(origin && origin.kind & SymbolOriginInfoKind.ObjectLiteralMethod); 142 } 143 144 interface UniqueNameSet { 145 add(name: string): void; 146 has(name: string): boolean; 147 } 148 149 /** 150 * Map from symbol index in `symbols` -> SymbolOriginInfo. 151 */ 152 type SymbolOriginInfoMap = Record<number, SymbolOriginInfo>; 153 154 /** Map from symbol id -> SortText. */ 155 type SymbolSortTextMap = (SortText | undefined)[]; 156 157 const enum KeywordCompletionFilters { 158 None, // No keywords 159 All, // Every possible keyword (TODO: This is never appropriate) 160 ClassElementKeywords, // Keywords inside class body 161 InterfaceElementKeywords, // Keywords inside interface body 162 ConstructorParameterKeywords, // Keywords at constructor parameter 163 FunctionLikeBodyKeywords, // Keywords at function like body 164 TypeAssertionKeywords, 165 TypeKeywords, 166 TypeKeyword, // Literally just `type` 167 Last = TypeKeyword 168 } 169 170 const enum GlobalsSearch { Continue, Success, Fail } 171 172 interface ModuleSpecifierResolutioContext { 173 tryResolve: (exportInfo: readonly SymbolExportInfo[], symbolName: string, isFromAmbientModule: boolean) => ModuleSpecifierResolutionResult; 174 resolvedAny: () => boolean; 175 skippedAny: () => boolean; 176 resolvedBeyondLimit: () => boolean; 177 } 178 179 type ModuleSpecifierResolutionResult = "skipped" | "failed" | { 180 exportInfo?: SymbolExportInfo; 181 moduleSpecifier: string; 182 }; 183 184 function resolvingModuleSpecifiers<TReturn>( 185 logPrefix: string, 186 host: LanguageServiceHost, 187 resolver: codefix.ImportSpecifierResolver, 188 program: Program, 189 position: number, 190 preferences: UserPreferences, 191 isForImportStatementCompletion: boolean, 192 isValidTypeOnlyUseSite: boolean, 193 cb: (context: ModuleSpecifierResolutioContext) => TReturn, 194 ): TReturn { 195 const start = timestamp(); 196 // Under `--moduleResolution nodenext`, we have to resolve module specifiers up front, because 197 // package.json exports can mean we *can't* resolve a module specifier (that doesn't include a 198 // relative path into node_modules), and we want to filter those completions out entirely. 199 // Import statement completions always need specifier resolution because the module specifier is 200 // part of their `insertText`, not the `codeActions` creating edits away from the cursor. 201 const needsFullResolution = isForImportStatementCompletion || moduleResolutionRespectsExports(getEmitModuleResolutionKind(program.getCompilerOptions())); 202 let skippedAny = false; 203 let ambientCount = 0; 204 let resolvedCount = 0; 205 let resolvedFromCacheCount = 0; 206 let cacheAttemptCount = 0; 207 208 const result = cb({ 209 tryResolve, 210 skippedAny: () => skippedAny, 211 resolvedAny: () => resolvedCount > 0, 212 resolvedBeyondLimit: () => resolvedCount > moduleSpecifierResolutionLimit, 213 }); 214 215 const hitRateMessage = cacheAttemptCount ? ` (${(resolvedFromCacheCount / cacheAttemptCount * 100).toFixed(1)}% hit rate)` : ""; 216 host.log?.(`${logPrefix}: resolved ${resolvedCount} module specifiers, plus ${ambientCount} ambient and ${resolvedFromCacheCount} from cache${hitRateMessage}`); 217 host.log?.(`${logPrefix}: response is ${skippedAny ? "incomplete" : "complete"}`); 218 host.log?.(`${logPrefix}: ${timestamp() - start}`); 219 return result; 220 221 function tryResolve(exportInfo: readonly SymbolExportInfo[], symbolName: string, isFromAmbientModule: boolean): ModuleSpecifierResolutionResult { 222 if (isFromAmbientModule) { 223 const result = resolver.getModuleSpecifierForBestExportInfo(exportInfo, symbolName, position, isValidTypeOnlyUseSite); 224 if (result) { 225 ambientCount++; 226 } 227 return result || "failed"; 228 } 229 const shouldResolveModuleSpecifier = needsFullResolution || preferences.allowIncompleteCompletions && resolvedCount < moduleSpecifierResolutionLimit; 230 const shouldGetModuleSpecifierFromCache = !shouldResolveModuleSpecifier && preferences.allowIncompleteCompletions && cacheAttemptCount < moduleSpecifierResolutionCacheAttemptLimit; 231 const result = (shouldResolveModuleSpecifier || shouldGetModuleSpecifierFromCache) 232 ? resolver.getModuleSpecifierForBestExportInfo(exportInfo, symbolName, position, isValidTypeOnlyUseSite, shouldGetModuleSpecifierFromCache) 233 : undefined; 234 235 if (!shouldResolveModuleSpecifier && !shouldGetModuleSpecifierFromCache || shouldGetModuleSpecifierFromCache && !result) { 236 skippedAny = true; 237 } 238 239 resolvedCount += result?.computedWithoutCacheCount || 0; 240 resolvedFromCacheCount += exportInfo.length - (result?.computedWithoutCacheCount || 0); 241 if (shouldGetModuleSpecifierFromCache) { 242 cacheAttemptCount++; 243 } 244 245 return result || (needsFullResolution ? "failed" : "skipped"); 246 } 247 } 248 249 export function getCompletionsAtPosition( 250 host: LanguageServiceHost, 251 program: Program, 252 log: Log, 253 sourceFile: SourceFile, 254 position: number, 255 preferences: UserPreferences, 256 triggerCharacter: CompletionsTriggerCharacter | undefined, 257 completionKind: CompletionTriggerKind | undefined, 258 cancellationToken: CancellationToken, 259 formatContext?: formatting.FormatContext, 260 ): CompletionInfo | undefined { 261 const { previousToken } = getRelevantTokens(position, sourceFile); 262 if (triggerCharacter && !isInString(sourceFile, position, previousToken) && !isValidTrigger(sourceFile, triggerCharacter, previousToken, position)) { 263 return undefined; 264 } 265 266 if (triggerCharacter === " ") { 267 // `isValidTrigger` ensures we are at `import |` 268 if (preferences.includeCompletionsForImportStatements && preferences.includeCompletionsWithInsertText) { 269 return { isGlobalCompletion: true, isMemberCompletion: false, isNewIdentifierLocation: true, isIncomplete: true, entries: [] }; 270 } 271 return undefined; 272 273 } 274 275 // If the request is a continuation of an earlier `isIncomplete` response, 276 // we can continue it from the cached previous response. 277 const compilerOptions = program.getCompilerOptions(); 278 const incompleteCompletionsCache = preferences.allowIncompleteCompletions ? host.getIncompleteCompletionsCache?.() : undefined; 279 if (incompleteCompletionsCache && completionKind === CompletionTriggerKind.TriggerForIncompleteCompletions && previousToken && isIdentifier(previousToken)) { 280 const incompleteContinuation = continuePreviousIncompleteResponse(incompleteCompletionsCache, sourceFile, previousToken, program, host, preferences, cancellationToken); 281 if (incompleteContinuation) { 282 return incompleteContinuation; 283 } 284 } 285 else { 286 incompleteCompletionsCache?.clear(); 287 } 288 289 const stringCompletions = StringCompletions.getStringLiteralCompletions(sourceFile, position, previousToken, compilerOptions, host, program, log, preferences); 290 if (stringCompletions) { 291 return stringCompletions; 292 } 293 294 if (previousToken && isBreakOrContinueStatement(previousToken.parent) 295 && (previousToken.kind === SyntaxKind.BreakKeyword || previousToken.kind === SyntaxKind.ContinueKeyword || previousToken.kind === SyntaxKind.Identifier)) { 296 return getLabelCompletionAtPosition(previousToken.parent); 297 } 298 299 const completionData = getCompletionData(program, log, sourceFile, compilerOptions, position, preferences, /*detailsEntryId*/ undefined, host, formatContext, cancellationToken); 300 if (!completionData) { 301 return undefined; 302 } 303 304 switch (completionData.kind) { 305 case CompletionDataKind.Data: 306 const response = completionInfoFromData(sourceFile, host, program, compilerOptions, log, completionData, preferences, formatContext, position); 307 if (response?.isIncomplete) { 308 incompleteCompletionsCache?.set(response); 309 } 310 return response; 311 case CompletionDataKind.JsDocTagName: 312 // If the current position is a jsDoc tag name, only tag names should be provided for completion 313 return jsdocCompletionInfo(JsDoc.getJSDocTagNameCompletions()); 314 case CompletionDataKind.JsDocTag: 315 // If the current position is a jsDoc tag, only tags should be provided for completion 316 return jsdocCompletionInfo(JsDoc.getJSDocTagCompletions()); 317 case CompletionDataKind.JsDocParameterName: 318 return jsdocCompletionInfo(JsDoc.getJSDocParameterNameCompletions(completionData.tag)); 319 case CompletionDataKind.Keywords: 320 return specificKeywordCompletionInfo(completionData.keywordCompletions, completionData.isNewIdentifierLocation); 321 default: 322 return Debug.assertNever(completionData); 323 } 324 } 325 326 // Editors will use the `sortText` and then fall back to `name` for sorting, but leave ties in response order. 327 // So, it's important that we sort those ties in the order we want them displayed if it matters. We don't 328 // strictly need to sort by name or SortText here since clients are going to do it anyway, but we have to 329 // do the work of comparing them so we can sort those ties appropriately; plus, it makes the order returned 330 // by the language service consistent with what TS Server does and what editors typically do. This also makes 331 // completions tests make more sense. We used to sort only alphabetically and only in the server layer, but 332 // this made tests really weird, since most fourslash tests don't use the server. 333 function compareCompletionEntries(entryInArray: CompletionEntry, entryToInsert: CompletionEntry): Comparison { 334 let result = compareStringsCaseSensitiveUI(entryInArray.sortText, entryToInsert.sortText); 335 if (result === Comparison.EqualTo) { 336 result = compareStringsCaseSensitiveUI(entryInArray.name, entryToInsert.name); 337 } 338 if (result === Comparison.EqualTo && entryInArray.data?.moduleSpecifier && entryToInsert.data?.moduleSpecifier) { 339 // Sort same-named auto-imports by module specifier 340 result = compareNumberOfDirectorySeparators( 341 (entryInArray.data as CompletionEntryDataResolved).moduleSpecifier, 342 (entryToInsert.data as CompletionEntryDataResolved).moduleSpecifier, 343 ); 344 } 345 if (result === Comparison.EqualTo) { 346 // Fall back to symbol order - if we return `EqualTo`, `insertSorted` will put later symbols first. 347 return Comparison.LessThan; 348 } 349 return result; 350 } 351 352 function completionEntryDataIsResolved(data: CompletionEntryDataAutoImport | undefined): data is CompletionEntryDataResolved { 353 return !!data?.moduleSpecifier; 354 } 355 356 function continuePreviousIncompleteResponse( 357 cache: IncompleteCompletionsCache, 358 file: SourceFile, 359 location: Identifier, 360 program: Program, 361 host: LanguageServiceHost, 362 preferences: UserPreferences, 363 cancellationToken: CancellationToken, 364 ): CompletionInfo | undefined { 365 const previousResponse = cache.get(); 366 if (!previousResponse) return undefined; 367 368 const lowerCaseTokenText = location.text.toLowerCase(); 369 const exportMap = getExportInfoMap(file, host, program, preferences, cancellationToken); 370 const newEntries = resolvingModuleSpecifiers( 371 "continuePreviousIncompleteResponse", 372 host, 373 codefix.createImportSpecifierResolver(file, program, host, preferences), 374 program, 375 location.getStart(), 376 preferences, 377 /*isForImportStatementCompletion*/ false, 378 isValidTypeOnlyAliasUseSite(location), 379 context => { 380 const entries = mapDefined(previousResponse.entries, entry => { 381 if (!entry.hasAction || !entry.source || !entry.data || completionEntryDataIsResolved(entry.data)) { 382 // Not an auto import or already resolved; keep as is 383 return entry; 384 } 385 if (!charactersFuzzyMatchInString(entry.name, lowerCaseTokenText)) { 386 // No longer matches typed characters; filter out 387 return undefined; 388 } 389 390 const { origin } = Debug.checkDefined(getAutoImportSymbolFromCompletionEntryData(entry.name, entry.data, program, host)); 391 const info = exportMap.get(file.path, entry.data.exportMapKey); 392 393 const result = info && context.tryResolve(info, entry.name, !isExternalModuleNameRelative(stripQuotes(origin.moduleSymbol.name))); 394 if (result === "skipped") return entry; 395 if (!result || result === "failed") { 396 host.log?.(`Unexpected failure resolving auto import for '${entry.name}' from '${entry.source}'`); 397 return undefined; 398 } 399 400 const newOrigin: SymbolOriginInfoResolvedExport = { 401 ...origin, 402 kind: SymbolOriginInfoKind.ResolvedExport, 403 moduleSpecifier: result.moduleSpecifier, 404 }; 405 // Mutating for performance... feels sketchy but nobody else uses the cache, 406 // so why bother allocating a bunch of new objects? 407 entry.data = originToCompletionEntryData(newOrigin); 408 entry.source = getSourceFromOrigin(newOrigin); 409 entry.sourceDisplay = [textPart(newOrigin.moduleSpecifier)]; 410 return entry; 411 }); 412 413 if (!context.skippedAny()) { 414 previousResponse.isIncomplete = undefined; 415 } 416 417 return entries; 418 }, 419 ); 420 421 previousResponse.entries = newEntries; 422 previousResponse.flags = (previousResponse.flags || 0) | CompletionInfoFlags.IsContinuation; 423 return previousResponse; 424 } 425 426 function jsdocCompletionInfo(entries: CompletionEntry[]): CompletionInfo { 427 return { isGlobalCompletion: false, isMemberCompletion: false, isNewIdentifierLocation: false, entries }; 428 } 429 430 function keywordToCompletionEntry(keyword: TokenSyntaxKind) { 431 return { 432 name: tokenToString(keyword)!, 433 kind: ScriptElementKind.keyword, 434 kindModifiers: ScriptElementKindModifier.none, 435 sortText: SortText.GlobalsOrKeywords, 436 }; 437 } 438 439 function specificKeywordCompletionInfo(entries: readonly CompletionEntry[], isNewIdentifierLocation: boolean): CompletionInfo { 440 return { 441 isGlobalCompletion: false, 442 isMemberCompletion: false, 443 isNewIdentifierLocation, 444 entries: entries.slice(), 445 }; 446 } 447 448 function keywordCompletionData(keywordFilters: KeywordCompletionFilters, filterOutTsOnlyKeywords: boolean, isNewIdentifierLocation: boolean): Request { 449 return { 450 kind: CompletionDataKind.Keywords, 451 keywordCompletions: getKeywordCompletions(keywordFilters, filterOutTsOnlyKeywords), 452 isNewIdentifierLocation, 453 }; 454 } 455 456 function keywordFiltersFromSyntaxKind(keywordCompletion: TokenSyntaxKind): KeywordCompletionFilters { 457 switch (keywordCompletion) { 458 case SyntaxKind.TypeKeyword: return KeywordCompletionFilters.TypeKeyword; 459 default: Debug.fail("Unknown mapping from SyntaxKind to KeywordCompletionFilters"); 460 } 461 } 462 463 function getOptionalReplacementSpan(location: Node | undefined) { 464 // StringLiteralLike locations are handled separately in stringCompletions.ts 465 return location?.kind === SyntaxKind.Identifier ? createTextSpanFromNode(location) : undefined; 466 } 467 468 function completionInfoFromData( 469 sourceFile: SourceFile, 470 host: LanguageServiceHost, 471 program: Program, 472 compilerOptions: CompilerOptions, 473 log: Log, 474 completionData: CompletionData, 475 preferences: UserPreferences, 476 formatContext: formatting.FormatContext | undefined, 477 position: number 478 ): CompletionInfo | undefined { 479 const { 480 symbols, 481 contextToken, 482 completionKind, 483 isInSnippetScope, 484 isNewIdentifierLocation, 485 location, 486 propertyAccessToConvert, 487 keywordFilters, 488 literals, 489 symbolToOriginInfoMap, 490 recommendedCompletion, 491 isJsxInitializer, 492 isTypeOnlyLocation, 493 isJsxIdentifierExpected, 494 isRightOfOpenTag, 495 importStatementCompletion, 496 insideJsDocTagTypeExpression, 497 symbolToSortTextMap: symbolToSortTextMap, 498 hasUnresolvedAutoImports, 499 } = completionData; 500 501 // Verify if the file is JSX language variant 502 if (getLanguageVariant(sourceFile.scriptKind) === LanguageVariant.JSX) { 503 const completionInfo = getJsxClosingTagCompletion(location, sourceFile); 504 if (completionInfo) { 505 return completionInfo; 506 } 507 } 508 509 const entries = createSortedArray<CompletionEntry>(); 510 const isChecked = isCheckedFile(sourceFile, compilerOptions); 511 if (isChecked && !isNewIdentifierLocation && (!symbols || symbols.length === 0) && keywordFilters === KeywordCompletionFilters.None) { 512 return undefined; 513 } 514 const uniqueNames = getCompletionEntriesFromSymbols( 515 symbols, 516 entries, 517 /*replacementToken*/ undefined, 518 contextToken, 519 location, 520 sourceFile, 521 host, 522 program, 523 getEmitScriptTarget(compilerOptions), 524 log, 525 completionKind, 526 preferences, 527 compilerOptions, 528 formatContext, 529 isTypeOnlyLocation, 530 propertyAccessToConvert, 531 isJsxIdentifierExpected, 532 isJsxInitializer, 533 importStatementCompletion, 534 recommendedCompletion, 535 symbolToOriginInfoMap, 536 symbolToSortTextMap, 537 isJsxIdentifierExpected, 538 isRightOfOpenTag, 539 ); 540 541 if (keywordFilters !== KeywordCompletionFilters.None) { 542 for (const keywordEntry of getKeywordCompletions(keywordFilters, !insideJsDocTagTypeExpression && isSourceFileJS(sourceFile))) { 543 if (isTypeOnlyLocation && isTypeKeyword(stringToToken(keywordEntry.name)!) || !uniqueNames.has(keywordEntry.name)) { 544 uniqueNames.add(keywordEntry.name); 545 insertSorted(entries, keywordEntry, compareCompletionEntries, /*allowDuplicates*/ true); 546 } 547 } 548 } 549 550 for (const keywordEntry of getContextualKeywords(contextToken, position)) { 551 if (!uniqueNames.has(keywordEntry.name)) { 552 uniqueNames.add(keywordEntry.name); 553 insertSorted(entries, keywordEntry, compareCompletionEntries, /*allowDuplicates*/ true); 554 } 555 } 556 557 for (const literal of literals) { 558 const literalEntry = createCompletionEntryForLiteral(sourceFile, preferences, literal); 559 uniqueNames.add(literalEntry.name); 560 insertSorted(entries, literalEntry, compareCompletionEntries, /*allowDuplicates*/ true); 561 } 562 563 if (!isChecked) { 564 getJSCompletionEntries(sourceFile, location.pos, uniqueNames, getEmitScriptTarget(compilerOptions), entries); 565 } 566 567 return { 568 flags: completionData.flags, 569 isGlobalCompletion: isInSnippetScope, 570 isIncomplete: preferences.allowIncompleteCompletions && hasUnresolvedAutoImports ? true : undefined, 571 isMemberCompletion: isMemberCompletionKind(completionKind), 572 isNewIdentifierLocation, 573 optionalReplacementSpan: getOptionalReplacementSpan(location), 574 entries, 575 }; 576 } 577 578 function isCheckedFile(sourceFile: SourceFile, compilerOptions: CompilerOptions): boolean { 579 return !isSourceFileJS(sourceFile) || !!isCheckJsEnabledForFile(sourceFile, compilerOptions); 580 } 581 582 function isMemberCompletionKind(kind: CompletionKind): boolean { 583 switch (kind) { 584 case CompletionKind.ObjectPropertyDeclaration: 585 case CompletionKind.MemberLike: 586 case CompletionKind.PropertyAccess: 587 return true; 588 default: 589 return false; 590 } 591 } 592 593 function getJsxClosingTagCompletion(location: Node | undefined, sourceFile: SourceFile): CompletionInfo | undefined { 594 // We wanna walk up the tree till we find a JSX closing element 595 const jsxClosingElement = findAncestor(location, node => { 596 switch (node.kind) { 597 case SyntaxKind.JsxClosingElement: 598 return true; 599 case SyntaxKind.SlashToken: 600 case SyntaxKind.GreaterThanToken: 601 case SyntaxKind.Identifier: 602 case SyntaxKind.PropertyAccessExpression: 603 return false; 604 default: 605 return "quit"; 606 } 607 }) as JsxClosingElement | undefined; 608 609 if (jsxClosingElement) { 610 // In the TypeScript JSX element, if such element is not defined. When users query for completion at closing tag, 611 // instead of simply giving unknown value, the completion will return the tag-name of an associated opening-element. 612 // For example: 613 // var x = <div> </ /*1*/ 614 // The completion list at "1" will contain "div>" with type any 615 // And at `<div> </ /*1*/ >` (with a closing `>`), the completion list will contain "div". 616 // And at property access expressions `<MainComponent.Child> </MainComponent. /*1*/ >` the completion will 617 // return full closing tag with an optional replacement span 618 // For example: 619 // var x = <MainComponent.Child> </ MainComponent /*1*/ > 620 // var y = <MainComponent.Child> </ /*2*/ MainComponent > 621 // the completion list at "1" and "2" will contain "MainComponent.Child" with a replacement span of closing tag name 622 const hasClosingAngleBracket = !!findChildOfKind(jsxClosingElement, SyntaxKind.GreaterThanToken, sourceFile); 623 const tagName = jsxClosingElement.parent.openingElement.tagName; 624 const closingTag = tagName.getText(sourceFile); 625 const fullClosingTag = closingTag + (hasClosingAngleBracket ? "" : ">"); 626 const replacementSpan = createTextSpanFromNode(jsxClosingElement.tagName); 627 628 const entry: CompletionEntry = { 629 name: fullClosingTag, 630 kind: ScriptElementKind.classElement, 631 kindModifiers: undefined, 632 sortText: SortText.LocationPriority, 633 }; 634 return { isGlobalCompletion: false, isMemberCompletion: true, isNewIdentifierLocation: false, optionalReplacementSpan: replacementSpan, entries: [entry] }; 635 } 636 return; 637 } 638 639 function getJSCompletionEntries( 640 sourceFile: SourceFile, 641 position: number, 642 uniqueNames: UniqueNameSet, 643 target: ScriptTarget, 644 entries: SortedArray<CompletionEntry>): void { 645 getNameTable(sourceFile).forEach((pos, name) => { 646 // Skip identifiers produced only from the current location 647 if (pos === position) { 648 return; 649 } 650 const realName = unescapeLeadingUnderscores(name); 651 if (!uniqueNames.has(realName) && isIdentifierText(realName, target)) { 652 uniqueNames.add(realName); 653 insertSorted(entries, { 654 name: realName, 655 kind: ScriptElementKind.warning, 656 kindModifiers: "", 657 sortText: SortText.JavascriptIdentifiers, 658 isFromUncheckedFile: true 659 }, compareCompletionEntries); 660 } 661 }); 662 } 663 664 function completionNameForLiteral(sourceFile: SourceFile, preferences: UserPreferences, literal: string | number | PseudoBigInt): string { 665 return typeof literal === "object" ? pseudoBigIntToString(literal) + "n" : 666 isString(literal) ? quote(sourceFile, preferences, literal) : JSON.stringify(literal); 667 } 668 669 function createCompletionEntryForLiteral(sourceFile: SourceFile, preferences: UserPreferences, literal: string | number | PseudoBigInt): CompletionEntry { 670 return { name: completionNameForLiteral(sourceFile, preferences, literal), kind: ScriptElementKind.string, kindModifiers: ScriptElementKindModifier.none, sortText: SortText.LocationPriority }; 671 } 672 673 function createCompletionEntry( 674 symbol: Symbol, 675 sortText: SortText, 676 replacementToken: Node | undefined, 677 contextToken: Node | undefined, 678 location: Node, 679 sourceFile: SourceFile, 680 host: LanguageServiceHost, 681 program: Program, 682 name: string, 683 needsConvertPropertyAccess: boolean, 684 origin: SymbolOriginInfo | undefined, 685 recommendedCompletion: Symbol | undefined, 686 propertyAccessToConvert: PropertyAccessExpression | undefined, 687 isJsxInitializer: IsJsxInitializer | undefined, 688 importStatementCompletion: ImportStatementCompletionInfo | undefined, 689 useSemicolons: boolean, 690 options: CompilerOptions, 691 preferences: UserPreferences, 692 completionKind: CompletionKind, 693 formatContext: formatting.FormatContext | undefined, 694 isJsxIdentifierExpected: boolean | undefined, 695 isRightOfOpenTag: boolean | undefined, 696 ): CompletionEntry | undefined { 697 let insertText: string | undefined; 698 let replacementSpan = getReplacementSpanForContextToken(replacementToken); 699 let data: CompletionEntryData | undefined; 700 let isSnippet: true | undefined; 701 let source = getSourceFromOrigin(origin); 702 let sourceDisplay; 703 let hasAction; 704 let labelDetails; 705 706 const typeChecker = program.getTypeChecker(); 707 const insertQuestionDot = origin && originIsNullableMember(origin); 708 const useBraces = origin && originIsSymbolMember(origin) || needsConvertPropertyAccess; 709 if (origin && originIsThisType(origin)) { 710 insertText = needsConvertPropertyAccess 711 ? `this${insertQuestionDot ? "?." : ""}[${quotePropertyName(sourceFile, preferences, name)}]` 712 : `this${insertQuestionDot ? "?." : "."}${name}`; 713 } 714 // We should only have needsConvertPropertyAccess if there's a property access to convert. But see #21790. 715 // Somehow there was a global with a non-identifier name. Hopefully someone will complain about getting a "foo bar" global completion and provide a repro. 716 else if ((useBraces || insertQuestionDot) && propertyAccessToConvert) { 717 insertText = useBraces ? needsConvertPropertyAccess ? `[${quotePropertyName(sourceFile, preferences, name)}]` : `[${name}]` : name; 718 if (insertQuestionDot || propertyAccessToConvert.questionDotToken) { 719 insertText = `?.${insertText}`; 720 } 721 722 const dot = findChildOfKind(propertyAccessToConvert, SyntaxKind.DotToken, sourceFile) || 723 findChildOfKind(propertyAccessToConvert, SyntaxKind.QuestionDotToken, sourceFile); 724 if (!dot) { 725 return undefined; 726 } 727 // If the text after the '.' starts with this name, write over it. Else, add new text. 728 const end = startsWith(name, propertyAccessToConvert.name.text) ? propertyAccessToConvert.name.end : dot.end; 729 replacementSpan = createTextSpanFromBounds(dot.getStart(sourceFile), end); 730 } 731 732 if (isJsxInitializer) { 733 if (insertText === undefined) insertText = name; 734 insertText = `{${insertText}}`; 735 if (typeof isJsxInitializer !== "boolean") { 736 replacementSpan = createTextSpanFromNode(isJsxInitializer, sourceFile); 737 } 738 } 739 if (origin && originIsPromise(origin) && propertyAccessToConvert) { 740 if (insertText === undefined) insertText = name; 741 const precedingToken = findPrecedingToken(propertyAccessToConvert.pos, sourceFile); 742 let awaitText = ""; 743 if (precedingToken && positionIsASICandidate(precedingToken.end, precedingToken.parent, sourceFile)) { 744 awaitText = ";"; 745 } 746 747 awaitText += `(await ${propertyAccessToConvert.expression.getText()})`; 748 insertText = needsConvertPropertyAccess ? `${awaitText}${insertText}` : `${awaitText}${insertQuestionDot ? "?." : "."}${insertText}`; 749 replacementSpan = createTextSpanFromBounds(propertyAccessToConvert.getStart(sourceFile), propertyAccessToConvert.end); 750 } 751 752 if (originIsResolvedExport(origin)) { 753 sourceDisplay = [textPart(origin.moduleSpecifier)]; 754 if (importStatementCompletion) { 755 ({ insertText, replacementSpan } = getInsertTextAndReplacementSpanForImportCompletion(name, importStatementCompletion, origin, useSemicolons, sourceFile, options, preferences)); 756 isSnippet = preferences.includeCompletionsWithSnippetText ? true : undefined; 757 } 758 } 759 760 if (origin?.kind === SymbolOriginInfoKind.TypeOnlyAlias) { 761 hasAction = true; 762 } 763 764 if (preferences.includeCompletionsWithClassMemberSnippets && 765 preferences.includeCompletionsWithInsertText && 766 completionKind === CompletionKind.MemberLike && 767 isClassLikeMemberCompletion(symbol, location, sourceFile)) { 768 let importAdder; 769 ({ insertText, isSnippet, importAdder, replacementSpan } = getEntryForMemberCompletion(host, program, options, preferences, name, symbol, location, contextToken, formatContext)); 770 sortText = SortText.ClassMemberSnippets; // sortText has to be lower priority than the sortText for keywords. See #47852. 771 if (importAdder?.hasFixes()) { 772 hasAction = true; 773 source = CompletionSource.ClassMemberSnippet; 774 } 775 } 776 777 if (origin && originIsObjectLiteralMethod(origin)) { 778 ({ insertText, isSnippet, labelDetails } = origin); 779 if (!preferences.useLabelDetailsInCompletionEntries) { 780 name = name + labelDetails.detail; 781 labelDetails = undefined; 782 } 783 source = CompletionSource.ObjectLiteralMethodSnippet; 784 sortText = SortText.SortBelow(sortText); 785 } 786 787 if (isJsxIdentifierExpected && !isRightOfOpenTag && preferences.includeCompletionsWithSnippetText && preferences.jsxAttributeCompletionStyle && preferences.jsxAttributeCompletionStyle !== "none") { 788 let useBraces = preferences.jsxAttributeCompletionStyle === "braces"; 789 const type = typeChecker.getTypeOfSymbolAtLocation(symbol, location); 790 791 // If is boolean like or undefined, don't return a snippet we want just to return the completion. 792 if (preferences.jsxAttributeCompletionStyle === "auto" 793 && !(type.flags & TypeFlags.BooleanLike) 794 && !(type.flags & TypeFlags.Union && find((type as UnionType).types, type => !!(type.flags & TypeFlags.BooleanLike))) 795 ) { 796 if (type.flags & TypeFlags.StringLike || (type.flags & TypeFlags.Union && every((type as UnionType).types, type => !!(type.flags & (TypeFlags.StringLike | TypeFlags.Undefined))))) { 797 // If is string like or undefined use quotes 798 insertText = `${escapeSnippetText(name)}=${quote(sourceFile, preferences, "$1")}`; 799 isSnippet = true; 800 } 801 else { 802 // Use braces for everything else 803 useBraces = true; 804 } 805 } 806 807 if (useBraces) { 808 insertText = `${escapeSnippetText(name)}={$1}`; 809 isSnippet = true; 810 } 811 } 812 813 if (insertText !== undefined && !preferences.includeCompletionsWithInsertText) { 814 return undefined; 815 } 816 817 if (originIsExport(origin) || originIsResolvedExport(origin)) { 818 data = originToCompletionEntryData(origin); 819 hasAction = !importStatementCompletion; 820 } 821 822 // TODO(drosen): Right now we just permit *all* semantic meanings when calling 823 // 'getSymbolKind' which is permissible given that it is backwards compatible; but 824 // really we should consider passing the meaning for the node so that we don't report 825 // that a suggestion for a value is an interface. We COULD also just do what 826 // 'getSymbolModifiers' does, which is to use the first declaration. 827 828 // Use a 'sortText' of 0' so that all symbol completion entries come before any other 829 // entries (like JavaScript identifier entries). 830 return { 831 name, 832 kind: SymbolDisplay.getSymbolKind(typeChecker, symbol, location), 833 kindModifiers: SymbolDisplay.getSymbolModifiers(typeChecker, symbol), 834 sortText, 835 source, 836 hasAction: hasAction ? true : undefined, 837 isRecommended: isRecommendedCompletionMatch(symbol, recommendedCompletion, typeChecker) || undefined, 838 insertText, 839 replacementSpan, 840 sourceDisplay, 841 labelDetails, 842 isSnippet, 843 isPackageJsonImport: originIsPackageJsonImport(origin) || undefined, 844 isImportStatementCompletion: !!importStatementCompletion || undefined, 845 data, 846 }; 847 } 848 849 function isClassLikeMemberCompletion(symbol: Symbol, location: Node, sourceFile: SourceFile): boolean { 850 // TODO: support JS files. 851 if (isInJSFile(location)) { 852 return false; 853 } 854 855 // Completion symbol must be for a class member. 856 const memberFlags = 857 SymbolFlags.ClassMember 858 & SymbolFlags.EnumMemberExcludes; 859 /* In 860 `class C { 861 | 862 }` 863 `location` is a class-like declaration. 864 In 865 `class C { 866 m| 867 }` 868 `location` is an identifier, 869 `location.parent` is a class element declaration, 870 and `location.parent.parent` is a class-like declaration. 871 In 872 `abstract class C { 873 abstract 874 abstract m| 875 }` 876 `location` is a syntax list (with modifiers as children), 877 and `location.parent` is a class-like declaration. 878 */ 879 return !!(symbol.flags & memberFlags) && 880 ( 881 isClassLike(location) || 882 ( 883 location.parent && 884 location.parent.parent && 885 isClassElement(location.parent) && 886 location === location.parent.name && 887 location.parent.getLastToken(sourceFile) === location.parent.name && 888 isClassLike(location.parent.parent) 889 ) || 890 ( 891 location.parent && 892 isSyntaxList(location) && 893 isClassLike(location.parent) 894 ) 895 ); 896 } 897 898 function getEntryForMemberCompletion( 899 host: LanguageServiceHost, 900 program: Program, 901 options: CompilerOptions, 902 preferences: UserPreferences, 903 name: string, 904 symbol: Symbol, 905 location: Node, 906 contextToken: Node | undefined, 907 formatContext: formatting.FormatContext | undefined, 908 ): { insertText: string, isSnippet?: true, importAdder?: codefix.ImportAdder, replacementSpan?: TextSpan } { 909 const classLikeDeclaration = findAncestor(location, isClassLike); 910 if (!classLikeDeclaration) { 911 return { insertText: name }; 912 } 913 914 let isSnippet: true | undefined; 915 let replacementSpan: TextSpan | undefined; 916 let insertText: string = name; 917 918 const checker = program.getTypeChecker(); 919 const sourceFile = location.getSourceFile(); 920 const printer = createSnippetPrinter({ 921 removeComments: true, 922 module: options.module, 923 target: options.target, 924 omitTrailingSemicolon: false, 925 newLine: getNewLineKind(getNewLineCharacter(options, maybeBind(host, host.getNewLine))), 926 }); 927 const importAdder = codefix.createImportAdder(sourceFile, program, preferences, host); 928 929 // Create empty body for possible method implementation. 930 let body; 931 if (preferences.includeCompletionsWithSnippetText) { 932 isSnippet = true; 933 // We are adding a tabstop (i.e. `$0`) in the body of the suggested member, 934 // if it has one, so that the cursor ends up in the body once the completion is inserted. 935 // Note: this assumes we won't have more than one body in the completion nodes, which should be the case. 936 const emptyStmt = factory.createEmptyStatement(); 937 body = factory.createBlock([emptyStmt], /* multiline */ true); 938 setSnippetElement(emptyStmt, { kind: SnippetKind.TabStop, order: 0 }); 939 } 940 else { 941 body = factory.createBlock([], /* multiline */ true); 942 } 943 944 let modifiers = ModifierFlags.None; 945 // Whether the suggested member should be abstract. 946 // e.g. in `abstract class C { abstract | }`, we should offer abstract method signatures at position `|`. 947 const { modifiers: presentModifiers, span: modifiersSpan } = getPresentModifiers(contextToken); 948 const isAbstract = !!(presentModifiers & ModifierFlags.Abstract); 949 const completionNodes: Node[] = []; 950 codefix.addNewNodeForMemberSymbol( 951 symbol, 952 classLikeDeclaration, 953 sourceFile, 954 { program, host }, 955 preferences, 956 importAdder, 957 // `addNewNodeForMemberSymbol` calls this callback function for each new member node 958 // it adds for the given member symbol. 959 // We store these member nodes in the `completionNodes` array. 960 // Note: there might be: 961 // - No nodes if `addNewNodeForMemberSymbol` cannot figure out a node for the member; 962 // - One node; 963 // - More than one node if the member is overloaded (e.g. a method with overload signatures). 964 node => { 965 let requiredModifiers = ModifierFlags.None; 966 if (isAbstract) { 967 requiredModifiers |= ModifierFlags.Abstract; 968 } 969 if (isClassElement(node) 970 && checker.getMemberOverrideModifierStatus(classLikeDeclaration, node) === MemberOverrideStatus.NeedsOverride) { 971 requiredModifiers |= ModifierFlags.Override; 972 } 973 974 if (!completionNodes.length) { 975 // Keep track of added missing required modifiers and modifiers already present. 976 // This is needed when we have overloaded signatures, 977 // so this callback will be called for multiple nodes/signatures, 978 // and we need to make sure the modifiers are uniform for all nodes/signatures. 979 modifiers = node.modifierFlagsCache | requiredModifiers | presentModifiers; 980 } 981 node = factory.updateModifiers(node, modifiers); 982 completionNodes.push(node); 983 }, 984 body, 985 codefix.PreserveOptionalFlags.Property, 986 isAbstract); 987 988 if (completionNodes.length) { 989 const format = ListFormat.MultiLine | ListFormat.NoTrailingNewLine; 990 replacementSpan = modifiersSpan; 991 // If we have access to formatting settings, we print the nodes using the emitter, 992 // and then format the printed text. 993 if (formatContext) { 994 insertText = printer.printAndFormatSnippetList( 995 format, 996 factory.createNodeArray(completionNodes), 997 sourceFile, 998 formatContext); 999 } 1000 else { // Otherwise, just use emitter to print the new nodes. 1001 insertText = printer.printSnippetList( 1002 format, 1003 factory.createNodeArray(completionNodes), 1004 sourceFile); 1005 } 1006 } 1007 1008 return { insertText, isSnippet, importAdder, replacementSpan }; 1009 } 1010 1011 function getPresentModifiers(contextToken: Node | undefined): { modifiers: ModifierFlags, span?: TextSpan } { 1012 if (!contextToken) { 1013 return { modifiers: ModifierFlags.None }; 1014 } 1015 let modifiers = ModifierFlags.None; 1016 let span; 1017 let contextMod; 1018 /* 1019 Cases supported: 1020 In 1021 `class C { 1022 public abstract | 1023 }` 1024 `contextToken` is ``abstract`` (as an identifier), 1025 `contextToken.parent` is property declaration, 1026 `location` is class declaration ``class C { ... }``. 1027 In 1028 `class C { 1029 protected override m| 1030 }` 1031 `contextToken` is ``override`` (as a keyword), 1032 `contextToken.parent` is property declaration, 1033 `location` is identifier ``m``, 1034 `location.parent` is property declaration ``protected override m``, 1035 `location.parent.parent` is class declaration ``class C { ... }``. 1036 */ 1037 if (contextMod = isModifierLike(contextToken)) { 1038 modifiers |= modifierToFlag(contextMod); 1039 span = createTextSpanFromNode(contextToken); 1040 } 1041 if (isPropertyDeclaration(contextToken.parent)) { 1042 modifiers |= modifiersToFlags(contextToken.parent.modifiers) & ModifierFlags.Modifier; 1043 span = createTextSpanFromNode(contextToken.parent); 1044 } 1045 return { modifiers, span }; 1046 } 1047 1048 function isModifierLike(node: Node): ModifierSyntaxKind | undefined { 1049 if (isModifier(node)) { 1050 return node.kind; 1051 } 1052 if (isIdentifier(node) && node.originalKeywordKind && isModifierKind(node.originalKeywordKind)) { 1053 return node.originalKeywordKind; 1054 } 1055 return undefined; 1056 } 1057 1058 function getEntryForObjectLiteralMethodCompletion( 1059 symbol: Symbol, 1060 name: string, 1061 enclosingDeclaration: ObjectLiteralExpression, 1062 program: Program, 1063 host: LanguageServiceHost, 1064 options: CompilerOptions, 1065 preferences: UserPreferences, 1066 formatContext: formatting.FormatContext | undefined, 1067 ): { insertText: string, isSnippet?: true, labelDetails: CompletionEntryLabelDetails } | undefined { 1068 const isSnippet = preferences.includeCompletionsWithSnippetText || undefined; 1069 let insertText: string = name; 1070 1071 const sourceFile = enclosingDeclaration.getSourceFile(); 1072 1073 const method = createObjectLiteralMethod(symbol, enclosingDeclaration, sourceFile, program, host, preferences); 1074 if (!method) { 1075 return undefined; 1076 } 1077 1078 const printer = createSnippetPrinter({ 1079 removeComments: true, 1080 module: options.module, 1081 target: options.target, 1082 omitTrailingSemicolon: false, 1083 newLine: getNewLineKind(getNewLineCharacter(options, maybeBind(host, host.getNewLine))), 1084 }); 1085 if (formatContext) { 1086 insertText = printer.printAndFormatSnippetList(ListFormat.CommaDelimited | ListFormat.AllowTrailingComma, factory.createNodeArray([method], /*hasTrailingComma*/ true), sourceFile, formatContext); 1087 } 1088 else { 1089 insertText = printer.printSnippetList(ListFormat.CommaDelimited | ListFormat.AllowTrailingComma, factory.createNodeArray([method], /*hasTrailingComma*/ true), sourceFile); 1090 } 1091 1092 const signaturePrinter = createPrinter({ 1093 removeComments: true, 1094 module: options.module, 1095 target: options.target, 1096 omitTrailingSemicolon: true, 1097 }); 1098 // The `labelDetails.detail` will be displayed right beside the method name, 1099 // so we drop the name (and modifiers) from the signature. 1100 const methodSignature = factory.createMethodSignature( 1101 /*modifiers*/ undefined, 1102 /*name*/ "", 1103 method.questionToken, 1104 method.typeParameters, 1105 method.parameters, 1106 method.type); 1107 const labelDetails = { detail: signaturePrinter.printNode(EmitHint.Unspecified, methodSignature, sourceFile) }; 1108 1109 return { isSnippet, insertText, labelDetails }; 1110 1111 } 1112 1113 function createObjectLiteralMethod( 1114 symbol: Symbol, 1115 enclosingDeclaration: ObjectLiteralExpression, 1116 sourceFile: SourceFile, 1117 program: Program, 1118 host: LanguageServiceHost, 1119 preferences: UserPreferences, 1120 ): MethodDeclaration | undefined { 1121 const declarations = symbol.getDeclarations(); 1122 if (!(declarations && declarations.length)) { 1123 return undefined; 1124 } 1125 const checker = program.getTypeChecker(); 1126 const declaration = declarations[0]; 1127 const name = getSynthesizedDeepClone(getNameOfDeclaration(declaration), /*includeTrivia*/ false) as PropertyName; 1128 const type = checker.getWidenedType(checker.getTypeOfSymbolAtLocation(symbol, enclosingDeclaration)); 1129 const quotePreference = getQuotePreference(sourceFile, preferences); 1130 const builderFlags = NodeBuilderFlags.OmitThisParameter | (quotePreference === QuotePreference.Single ? NodeBuilderFlags.UseSingleQuotesForStringLiteralType : NodeBuilderFlags.None); 1131 1132 switch (declaration.kind) { 1133 case SyntaxKind.PropertySignature: 1134 case SyntaxKind.PropertyDeclaration: 1135 case SyntaxKind.MethodSignature: 1136 case SyntaxKind.MethodDeclaration: { 1137 let effectiveType = type.flags & TypeFlags.Union && (type as UnionType).types.length < 10 1138 ? checker.getUnionType((type as UnionType).types, UnionReduction.Subtype) 1139 : type; 1140 if (effectiveType.flags & TypeFlags.Union) { 1141 // Only offer the completion if there's a single function type component. 1142 const functionTypes = filter((effectiveType as UnionType).types, type => checker.getSignaturesOfType(type, SignatureKind.Call).length > 0); 1143 if (functionTypes.length === 1) { 1144 effectiveType = functionTypes[0]; 1145 } 1146 else { 1147 return undefined; 1148 } 1149 } 1150 const signatures = checker.getSignaturesOfType(effectiveType, SignatureKind.Call); 1151 if (signatures.length !== 1) { 1152 // We don't support overloads in object literals. 1153 return undefined; 1154 } 1155 const typeNode = checker.typeToTypeNode(effectiveType, enclosingDeclaration, builderFlags, codefix.getNoopSymbolTrackerWithResolver({ program, host })); 1156 if (!typeNode || !isFunctionTypeNode(typeNode)) { 1157 return undefined; 1158 } 1159 1160 let body; 1161 if (preferences.includeCompletionsWithSnippetText) { 1162 const emptyStmt = factory.createEmptyStatement(); 1163 body = factory.createBlock([emptyStmt], /* multiline */ true); 1164 setSnippetElement(emptyStmt, { kind: SnippetKind.TabStop, order: 0 }); 1165 } 1166 else { 1167 body = factory.createBlock([], /* multiline */ true); 1168 } 1169 1170 const parameters = typeNode.parameters.map(typedParam => 1171 factory.createParameterDeclaration( 1172 /*modifiers*/ undefined, 1173 typedParam.dotDotDotToken, 1174 typedParam.name, 1175 /*questionToken*/ undefined, 1176 /*type*/ undefined, 1177 typedParam.initializer, 1178 )); 1179 return factory.createMethodDeclaration( 1180 /*modifiers*/ undefined, 1181 /*asteriskToken*/ undefined, 1182 name, 1183 /*questionToken*/ undefined, 1184 /*typeParameters*/ undefined, 1185 parameters, 1186 /*type*/ undefined, 1187 body); 1188 } 1189 default: 1190 return undefined; 1191 } 1192 } 1193 1194 function createSnippetPrinter( 1195 printerOptions: PrinterOptions, 1196 ) { 1197 let escapes: TextChange[] | undefined; 1198 const baseWriter = textChanges.createWriter(getNewLineCharacter(printerOptions)); 1199 const printer = createPrinter(printerOptions, baseWriter); 1200 const writer: EmitTextWriter = { 1201 ...baseWriter, 1202 write: s => escapingWrite(s, () => baseWriter.write(s)), 1203 nonEscapingWrite: baseWriter.write, 1204 writeLiteral: s => escapingWrite(s, () => baseWriter.writeLiteral(s)), 1205 writeStringLiteral: s => escapingWrite(s, () => baseWriter.writeStringLiteral(s)), 1206 writeSymbol: (s, symbol) => escapingWrite(s, () => baseWriter.writeSymbol(s, symbol)), 1207 writeParameter: s => escapingWrite(s, () => baseWriter.writeParameter(s)), 1208 writeComment: s => escapingWrite(s, () => baseWriter.writeComment(s)), 1209 writeProperty: s => escapingWrite(s, () => baseWriter.writeProperty(s)), 1210 }; 1211 1212 return { 1213 printSnippetList, 1214 printAndFormatSnippetList, 1215 }; 1216 1217 // The formatter/scanner will have issues with snippet-escaped text, 1218 // so instead of writing the escaped text directly to the writer, 1219 // generate a set of changes that can be applied to the unescaped text 1220 // to escape it post-formatting. 1221 function escapingWrite(s: string, write: () => void) { 1222 const escaped = escapeSnippetText(s); 1223 if (escaped !== s) { 1224 const start = baseWriter.getTextPos(); 1225 write(); 1226 const end = baseWriter.getTextPos(); 1227 escapes = append(escapes ||= [], { newText: escaped, span: { start, length: end - start } }); 1228 } 1229 else { 1230 write(); 1231 } 1232 } 1233 1234 /* Snippet-escaping version of `printer.printList`. */ 1235 function printSnippetList( 1236 format: ListFormat, 1237 list: NodeArray<Node>, 1238 sourceFile: SourceFile | undefined, 1239 ): string { 1240 const unescaped = printUnescapedSnippetList(format, list, sourceFile); 1241 return escapes ? textChanges.applyChanges(unescaped, escapes) : unescaped; 1242 } 1243 1244 function printUnescapedSnippetList( 1245 format: ListFormat, 1246 list: NodeArray<Node>, 1247 sourceFile: SourceFile | undefined, 1248 ): string { 1249 escapes = undefined; 1250 writer.clear(); 1251 printer.writeList(format, list, sourceFile, writer); 1252 return writer.getText(); 1253 } 1254 1255 function printAndFormatSnippetList( 1256 format: ListFormat, 1257 list: NodeArray<Node>, 1258 sourceFile: SourceFile, 1259 formatContext: formatting.FormatContext, 1260 ): string { 1261 const syntheticFile = { 1262 text: printUnescapedSnippetList( 1263 format, 1264 list, 1265 sourceFile), 1266 getLineAndCharacterOfPosition(pos: number) { 1267 return getLineAndCharacterOfPosition(this, pos); 1268 }, 1269 }; 1270 1271 const formatOptions = getFormatCodeSettingsForWriting(formatContext, sourceFile); 1272 const changes = flatMap(list, node => { 1273 const nodeWithPos = textChanges.assignPositionsToNode(node); 1274 return formatting.formatNodeGivenIndentation( 1275 nodeWithPos, 1276 syntheticFile, 1277 sourceFile.languageVariant, 1278 /* indentation */ 0, 1279 /* delta */ 0, 1280 { ...formatContext, options: formatOptions }); 1281 }); 1282 1283 const allChanges = escapes 1284 ? stableSort(concatenate(changes, escapes), (a, b) => compareTextSpans(a.span, b.span)) 1285 : changes; 1286 return textChanges.applyChanges(syntheticFile.text, allChanges); 1287 } 1288 } 1289 1290 function originToCompletionEntryData(origin: SymbolOriginInfoExport | SymbolOriginInfoResolvedExport): CompletionEntryData | undefined { 1291 const ambientModuleName = origin.fileName ? undefined : stripQuotes(origin.moduleSymbol.name); 1292 const isPackageJsonImport = origin.isFromPackageJson ? true : undefined; 1293 if (originIsResolvedExport(origin)) { 1294 const resolvedData: CompletionEntryDataResolved = { 1295 exportName: origin.exportName, 1296 moduleSpecifier: origin.moduleSpecifier, 1297 ambientModuleName, 1298 fileName: origin.fileName, 1299 isPackageJsonImport, 1300 }; 1301 return resolvedData; 1302 } 1303 const unresolvedData: CompletionEntryDataUnresolved = { 1304 exportName: origin.exportName, 1305 exportMapKey: origin.exportMapKey, 1306 fileName: origin.fileName, 1307 ambientModuleName: origin.fileName ? undefined : stripQuotes(origin.moduleSymbol.name), 1308 isPackageJsonImport: origin.isFromPackageJson ? true : undefined, 1309 }; 1310 return unresolvedData; 1311 } 1312 1313 function completionEntryDataToSymbolOriginInfo(data: CompletionEntryData, completionName: string, moduleSymbol: Symbol): SymbolOriginInfoExport | SymbolOriginInfoResolvedExport { 1314 const isDefaultExport = data.exportName === InternalSymbolName.Default; 1315 const isFromPackageJson = !!data.isPackageJsonImport; 1316 if (completionEntryDataIsResolved(data)) { 1317 const resolvedOrigin: SymbolOriginInfoResolvedExport = { 1318 kind: SymbolOriginInfoKind.ResolvedExport, 1319 exportName: data.exportName, 1320 moduleSpecifier: data.moduleSpecifier, 1321 symbolName: completionName, 1322 fileName: data.fileName, 1323 moduleSymbol, 1324 isDefaultExport, 1325 isFromPackageJson, 1326 }; 1327 return resolvedOrigin; 1328 } 1329 const unresolvedOrigin: SymbolOriginInfoExport = { 1330 kind: SymbolOriginInfoKind.Export, 1331 exportName: data.exportName, 1332 exportMapKey: data.exportMapKey, 1333 symbolName: completionName, 1334 fileName: data.fileName, 1335 moduleSymbol, 1336 isDefaultExport, 1337 isFromPackageJson, 1338 }; 1339 return unresolvedOrigin; 1340 } 1341 1342 function getInsertTextAndReplacementSpanForImportCompletion(name: string, importStatementCompletion: ImportStatementCompletionInfo, origin: SymbolOriginInfoResolvedExport, useSemicolons: boolean, sourceFile: SourceFile, options: CompilerOptions, preferences: UserPreferences) { 1343 const replacementSpan = importStatementCompletion.replacementSpan; 1344 const quotedModuleSpecifier = quote(sourceFile, preferences, origin.moduleSpecifier); 1345 const exportKind = 1346 origin.isDefaultExport ? ExportKind.Default : 1347 origin.exportName === InternalSymbolName.ExportEquals ? ExportKind.ExportEquals : 1348 ExportKind.Named; 1349 const tabStop = preferences.includeCompletionsWithSnippetText ? "$1" : ""; 1350 const importKind = codefix.getImportKind(sourceFile, exportKind, options, /*forceImportKeyword*/ true); 1351 const isImportSpecifierTypeOnly = importStatementCompletion.couldBeTypeOnlyImportSpecifier; 1352 const topLevelTypeOnlyText = importStatementCompletion.isTopLevelTypeOnly ? ` ${tokenToString(SyntaxKind.TypeKeyword)} ` : " "; 1353 const importSpecifierTypeOnlyText = isImportSpecifierTypeOnly ? `${tokenToString(SyntaxKind.TypeKeyword)} ` : ""; 1354 const suffix = useSemicolons ? ";" : ""; 1355 switch (importKind) { 1356 case ImportKind.CommonJS: return { replacementSpan, insertText: `import${topLevelTypeOnlyText}${escapeSnippetText(name)}${tabStop} = require(${quotedModuleSpecifier})${suffix}` }; 1357 case ImportKind.Default: return { replacementSpan, insertText: `import${topLevelTypeOnlyText}${escapeSnippetText(name)}${tabStop} from ${quotedModuleSpecifier}${suffix}` }; 1358 case ImportKind.Namespace: return { replacementSpan, insertText: `import${topLevelTypeOnlyText}* as ${escapeSnippetText(name)} from ${quotedModuleSpecifier}${suffix}` }; 1359 case ImportKind.Named: return { replacementSpan, insertText: `import${topLevelTypeOnlyText}{ ${importSpecifierTypeOnlyText}${escapeSnippetText(name)}${tabStop} } from ${quotedModuleSpecifier}${suffix}` }; 1360 } 1361 } 1362 1363 function quotePropertyName(sourceFile: SourceFile, preferences: UserPreferences, name: string,): string { 1364 if (/^\d+$/.test(name)) { 1365 return name; 1366 } 1367 1368 return quote(sourceFile, preferences, name); 1369 } 1370 1371 function isRecommendedCompletionMatch(localSymbol: Symbol, recommendedCompletion: Symbol | undefined, checker: TypeChecker): boolean { 1372 return localSymbol === recommendedCompletion || 1373 !!(localSymbol.flags & SymbolFlags.ExportValue) && checker.getExportSymbolOfSymbol(localSymbol) === recommendedCompletion; 1374 } 1375 1376 function getSourceFromOrigin(origin: SymbolOriginInfo | undefined): string | undefined { 1377 if (originIsExport(origin)) { 1378 return stripQuotes(origin.moduleSymbol.name); 1379 } 1380 if (originIsResolvedExport(origin)) { 1381 return origin.moduleSpecifier; 1382 } 1383 if (origin?.kind === SymbolOriginInfoKind.ThisType) { 1384 return CompletionSource.ThisProperty; 1385 } 1386 if (origin?.kind === SymbolOriginInfoKind.TypeOnlyAlias) { 1387 return CompletionSource.TypeOnlyAlias; 1388 } 1389 } 1390 1391 export function getCompletionEntriesFromSymbols( 1392 symbols: readonly Symbol[], 1393 entries: SortedArray<CompletionEntry>, 1394 replacementToken: Node | undefined, 1395 contextToken: Node | undefined, 1396 location: Node, 1397 sourceFile: SourceFile, 1398 host: LanguageServiceHost, 1399 program: Program, 1400 target: ScriptTarget, 1401 log: Log, 1402 kind: CompletionKind, 1403 preferences: UserPreferences, 1404 compilerOptions: CompilerOptions, 1405 formatContext: formatting.FormatContext | undefined, 1406 isTypeOnlyLocation?: boolean, 1407 propertyAccessToConvert?: PropertyAccessExpression, 1408 jsxIdentifierExpected?: boolean, 1409 isJsxInitializer?: IsJsxInitializer, 1410 importStatementCompletion?: ImportStatementCompletionInfo, 1411 recommendedCompletion?: Symbol, 1412 symbolToOriginInfoMap?: SymbolOriginInfoMap, 1413 symbolToSortTextMap?: SymbolSortTextMap, 1414 isJsxIdentifierExpected?: boolean, 1415 isRightOfOpenTag?: boolean, 1416 ): UniqueNameSet { 1417 const start = timestamp(); 1418 const variableDeclaration = getVariableDeclaration(location); 1419 const useSemicolons = probablyUsesSemicolons(sourceFile); 1420 const typeChecker = program.getTypeChecker(); 1421 // Tracks unique names. 1422 // Value is set to false for global variables or completions from external module exports, because we can have multiple of those; 1423 // true otherwise. Based on the order we add things we will always see locals first, then globals, then module exports. 1424 // So adding a completion for a local will prevent us from adding completions for external module exports sharing the same name. 1425 const uniques = new Map<string, boolean>(); 1426 for (let i = 0; i < symbols.length; i++) { 1427 const symbol = symbols[i]; 1428 const origin = symbolToOriginInfoMap?.[i]; 1429 const info = getCompletionEntryDisplayNameForSymbol(symbol, target, origin, kind, !!jsxIdentifierExpected); 1430 if (!info || (uniques.get(info.name) && (!origin || !originIsObjectLiteralMethod(origin))) || kind === CompletionKind.Global && symbolToSortTextMap && !shouldIncludeSymbol(symbol, symbolToSortTextMap)) { 1431 continue; 1432 } 1433 1434 const { name, needsConvertPropertyAccess } = info; 1435 const originalSortText = symbolToSortTextMap?.[getSymbolId(symbol)] ?? SortText.LocationPriority; 1436 const sortText = (isDeprecated(symbol, typeChecker) ? SortText.Deprecated(originalSortText) : originalSortText); 1437 const entry = createCompletionEntry( 1438 symbol, 1439 sortText, 1440 replacementToken, 1441 contextToken, 1442 location, 1443 sourceFile, 1444 host, 1445 program, 1446 name, 1447 needsConvertPropertyAccess, 1448 origin, 1449 recommendedCompletion, 1450 propertyAccessToConvert, 1451 isJsxInitializer, 1452 importStatementCompletion, 1453 useSemicolons, 1454 compilerOptions, 1455 preferences, 1456 kind, 1457 formatContext, 1458 isJsxIdentifierExpected, 1459 isRightOfOpenTag, 1460 ); 1461 if (!entry) { 1462 continue; 1463 } 1464 1465 /** True for locals; false for globals, module exports from other files, `this.` completions. */ 1466 const shouldShadowLaterSymbols = (!origin || originIsTypeOnlyAlias(origin)) && !(symbol.parent === undefined && !some(symbol.declarations, d => d.getSourceFile() === location.getSourceFile())); 1467 uniques.set(name, shouldShadowLaterSymbols); 1468 // add jsDoc info at interface getCompletionsAtPosition 1469 if (symbol.getJsDocTags().length > 0) { 1470 entry.jsDoc = symbol.getJsDocTags(); 1471 } 1472 if (symbol.declarations && i < 50) { 1473 const symbolDisplayPartsDocumentationAndSymbolKind = SymbolDisplay.getSymbolDisplayPartsDocumentationAndSymbolKind(typeChecker, symbol, sourceFile, location, location, SemanticMeaning.All); 1474 const container = getContaningConstructorDeclaration(symbol.valueDeclaration); 1475 const regex = /(Missing)/g; 1476 entry.displayParts = container && container.virtual ? symbolDisplayPartsDocumentationAndSymbolKind.displayParts.map(part => { 1477 if (part.text.match(regex)) { 1478 part.text = part.text.replace(regex, entry.name); 1479 } 1480 return part; 1481 }) : symbolDisplayPartsDocumentationAndSymbolKind.displayParts; 1482 } 1483 insertSorted(entries, entry, compareCompletionEntries, /*allowDuplicates*/ true); 1484 } 1485 1486 log("getCompletionsAtPosition: getCompletionEntriesFromSymbols: " + (timestamp() - start)); 1487 1488 // Prevent consumers of this map from having to worry about 1489 // the boolean value. Externally, it should be seen as the 1490 // set of all names. 1491 return { 1492 has: name => uniques.has(name), 1493 add: name => uniques.set(name, true), 1494 }; 1495 1496 function shouldIncludeSymbol(symbol: Symbol, symbolToSortTextMap: SymbolSortTextMap): boolean { 1497 let allFlags = symbol.flags; 1498 if (!isSourceFile(location)) { 1499 // export = /**/ here we want to get all meanings, so any symbol is ok 1500 if (isExportAssignment(location.parent)) { 1501 return true; 1502 } 1503 // Filter out variables from their own initializers 1504 // `const a = /* no 'a' here */` 1505 if (variableDeclaration && symbol.valueDeclaration === variableDeclaration) { 1506 return false; 1507 } 1508 1509 // External modules can have global export declarations that will be 1510 // available as global keywords in all scopes. But if the external module 1511 // already has an explicit export and user only wants to user explicit 1512 // module imports then the global keywords will be filtered out so auto 1513 // import suggestions will win in the completion 1514 const symbolOrigin = skipAlias(symbol, typeChecker); 1515 // We only want to filter out the global keywords 1516 // Auto Imports are not available for scripts so this conditional is always false 1517 if (!!sourceFile.externalModuleIndicator 1518 && !compilerOptions.allowUmdGlobalAccess 1519 && symbolToSortTextMap[getSymbolId(symbol)] === SortText.GlobalsOrKeywords 1520 && (symbolToSortTextMap[getSymbolId(symbolOrigin)] === SortText.AutoImportSuggestions 1521 || symbolToSortTextMap[getSymbolId(symbolOrigin)] === SortText.LocationPriority)) { 1522 return false; 1523 } 1524 1525 allFlags |= getCombinedLocalAndExportSymbolFlags(symbolOrigin); 1526 1527 // import m = /**/ <-- It can only access namespace (if typing import = x. this would get member symbols and not namespace) 1528 if (isInRightSideOfInternalImportEqualsDeclaration(location)) { 1529 return !!(allFlags & SymbolFlags.Namespace); 1530 } 1531 1532 if (isTypeOnlyLocation) { 1533 // It's a type, but you can reach it by namespace.type as well 1534 return symbolCanBeReferencedAtTypeLocation(symbol, typeChecker); 1535 } 1536 } 1537 1538 // expressions are value space (which includes the value namespaces) 1539 return !!(allFlags & SymbolFlags.Value); 1540 } 1541 } 1542 1543 function getLabelCompletionAtPosition(node: BreakOrContinueStatement): CompletionInfo | undefined { 1544 const entries = getLabelStatementCompletions(node); 1545 if (entries.length) { 1546 return { isGlobalCompletion: false, isMemberCompletion: false, isNewIdentifierLocation: false, entries }; 1547 } 1548 } 1549 1550 function getLabelStatementCompletions(node: Node): CompletionEntry[] { 1551 const entries: CompletionEntry[] = []; 1552 const uniques = new Map<string, true>(); 1553 let current = node; 1554 1555 while (current) { 1556 if (isFunctionLike(current)) { 1557 break; 1558 } 1559 if (isLabeledStatement(current)) { 1560 const name = current.label.text; 1561 if (!uniques.has(name)) { 1562 uniques.set(name, true); 1563 entries.push({ 1564 name, 1565 kindModifiers: ScriptElementKindModifier.none, 1566 kind: ScriptElementKind.label, 1567 sortText: SortText.LocationPriority 1568 }); 1569 } 1570 } 1571 current = current.parent; 1572 } 1573 return entries; 1574 } 1575 1576 interface SymbolCompletion { 1577 type: "symbol"; 1578 symbol: Symbol; 1579 location: Node; 1580 origin: SymbolOriginInfo | SymbolOriginInfoExport | SymbolOriginInfoResolvedExport | undefined; 1581 previousToken: Node | undefined; 1582 contextToken: Node | undefined; 1583 readonly isJsxInitializer: IsJsxInitializer; 1584 readonly isTypeOnlyLocation: boolean; 1585 } 1586 function getSymbolCompletionFromEntryId( 1587 program: Program, 1588 log: Log, 1589 sourceFile: SourceFile, 1590 position: number, 1591 entryId: CompletionEntryIdentifier, 1592 host: LanguageServiceHost, 1593 preferences: UserPreferences, 1594 ): SymbolCompletion | { type: "request", request: Request } | { type: "literal", literal: string | number | PseudoBigInt } | { type: "none" } { 1595 if (entryId.data) { 1596 const autoImport = getAutoImportSymbolFromCompletionEntryData(entryId.name, entryId.data, program, host); 1597 if (autoImport) { 1598 const { contextToken, previousToken } = getRelevantTokens(position, sourceFile); 1599 return { 1600 type: "symbol", 1601 symbol: autoImport.symbol, 1602 location: getTouchingPropertyName(sourceFile, position), 1603 previousToken, 1604 contextToken, 1605 isJsxInitializer: false, 1606 isTypeOnlyLocation: false, 1607 origin: autoImport.origin, 1608 }; 1609 } 1610 } 1611 1612 const compilerOptions = program.getCompilerOptions(); 1613 const completionData = getCompletionData(program, log, sourceFile, compilerOptions, position, { includeCompletionsForModuleExports: true, includeCompletionsWithInsertText: true }, entryId, host, /*formatContext*/ undefined); 1614 if (!completionData) { 1615 return { type: "none" }; 1616 } 1617 if (completionData.kind !== CompletionDataKind.Data) { 1618 return { type: "request", request: completionData }; 1619 } 1620 1621 const { symbols, literals, location, completionKind, symbolToOriginInfoMap, contextToken, previousToken, isJsxInitializer, isTypeOnlyLocation } = completionData; 1622 1623 const literal = find(literals, l => completionNameForLiteral(sourceFile, preferences, l) === entryId.name); 1624 if (literal !== undefined) return { type: "literal", literal }; 1625 1626 // Find the symbol with the matching entry name. 1627 // We don't need to perform character checks here because we're only comparing the 1628 // name against 'entryName' (which is known to be good), not building a new 1629 // completion entry. 1630 return firstDefined(symbols, (symbol, index): SymbolCompletion | undefined => { 1631 const origin = symbolToOriginInfoMap[index]; 1632 const info = getCompletionEntryDisplayNameForSymbol(symbol, getEmitScriptTarget(compilerOptions), origin, completionKind, completionData.isJsxIdentifierExpected); 1633 return info && info.name === entryId.name && ( 1634 entryId.source === CompletionSource.ClassMemberSnippet && symbol.flags & SymbolFlags.ClassMember 1635 || entryId.source === CompletionSource.ObjectLiteralMethodSnippet && symbol.flags & (SymbolFlags.Property | SymbolFlags.Method) 1636 || getSourceFromOrigin(origin) === entryId.source) 1637 ? { type: "symbol" as const, symbol, location, origin, contextToken, previousToken, isJsxInitializer, isTypeOnlyLocation } 1638 : undefined; 1639 }) || { type: "none" }; 1640 } 1641 1642 export interface CompletionEntryIdentifier { 1643 name: string; 1644 source?: string; 1645 data?: CompletionEntryData; 1646 } 1647 1648 export function getCompletionEntryDetails( 1649 program: Program, 1650 log: Log, 1651 sourceFile: SourceFile, 1652 position: number, 1653 entryId: CompletionEntryIdentifier, 1654 host: LanguageServiceHost, 1655 formatContext: formatting.FormatContext, 1656 preferences: UserPreferences, 1657 cancellationToken: CancellationToken, 1658 ): CompletionEntryDetails | undefined { 1659 const typeChecker = program.getTypeChecker(); 1660 const compilerOptions = program.getCompilerOptions(); 1661 const { name, source, data } = entryId; 1662 1663 const contextToken = findPrecedingToken(position, sourceFile); 1664 if (isInString(sourceFile, position, contextToken)) { 1665 return StringCompletions.getStringLiteralCompletionDetails(name, sourceFile, position, contextToken, typeChecker, compilerOptions, host, cancellationToken, preferences); 1666 } 1667 1668 // Compute all the completion symbols again. 1669 const symbolCompletion = getSymbolCompletionFromEntryId(program, log, sourceFile, position, entryId, host, preferences); 1670 switch (symbolCompletion.type) { 1671 case "request": { 1672 const { request } = symbolCompletion; 1673 switch (request.kind) { 1674 case CompletionDataKind.JsDocTagName: 1675 return JsDoc.getJSDocTagNameCompletionDetails(name); 1676 case CompletionDataKind.JsDocTag: 1677 return JsDoc.getJSDocTagCompletionDetails(name); 1678 case CompletionDataKind.JsDocParameterName: 1679 return JsDoc.getJSDocParameterNameCompletionDetails(name); 1680 case CompletionDataKind.Keywords: 1681 return some(request.keywordCompletions, c => c.name === name) ? createSimpleDetails(name, ScriptElementKind.keyword, SymbolDisplayPartKind.keyword) : undefined; 1682 default: 1683 return Debug.assertNever(request); 1684 } 1685 } 1686 case "symbol": { 1687 const { symbol, location, contextToken, origin, previousToken } = symbolCompletion; 1688 const { codeActions, sourceDisplay } = getCompletionEntryCodeActionsAndSourceDisplay(name, location, contextToken, origin, symbol, program, host, compilerOptions, sourceFile, position, previousToken, formatContext, preferences, data, source, cancellationToken); 1689 return createCompletionDetailsForSymbol(symbol, typeChecker, sourceFile, location, cancellationToken, codeActions, sourceDisplay); // TODO: GH#18217 1690 } 1691 case "literal": { 1692 const { literal } = symbolCompletion; 1693 return createSimpleDetails(completionNameForLiteral(sourceFile, preferences, literal), ScriptElementKind.string, typeof literal === "string" ? SymbolDisplayPartKind.stringLiteral : SymbolDisplayPartKind.numericLiteral); 1694 } 1695 case "none": 1696 // Didn't find a symbol with this name. See if we can find a keyword instead. 1697 return allKeywordsCompletions().some(c => c.name === name) ? createSimpleDetails(name, ScriptElementKind.keyword, SymbolDisplayPartKind.keyword) : undefined; 1698 default: 1699 Debug.assertNever(symbolCompletion); 1700 } 1701 } 1702 1703 function createSimpleDetails(name: string, kind: ScriptElementKind, kind2: SymbolDisplayPartKind): CompletionEntryDetails { 1704 return createCompletionDetails(name, ScriptElementKindModifier.none, kind, [displayPart(name, kind2)]); 1705 } 1706 1707 export function createCompletionDetailsForSymbol(symbol: Symbol, checker: TypeChecker, sourceFile: SourceFile, location: Node, cancellationToken: CancellationToken, codeActions?: CodeAction[], sourceDisplay?: SymbolDisplayPart[]): CompletionEntryDetails { 1708 const { displayParts, documentation, symbolKind, tags } = 1709 checker.runWithCancellationToken(cancellationToken, checker => 1710 SymbolDisplay.getSymbolDisplayPartsDocumentationAndSymbolKind(checker, symbol, sourceFile, location, location, SemanticMeaning.All) 1711 ); 1712 return createCompletionDetails(symbol.name, SymbolDisplay.getSymbolModifiers(checker, symbol), symbolKind, displayParts, documentation, tags, codeActions, sourceDisplay); 1713 } 1714 1715 export function createCompletionDetails(name: string, kindModifiers: string, kind: ScriptElementKind, displayParts: SymbolDisplayPart[], documentation?: SymbolDisplayPart[], tags?: JSDocTagInfo[], codeActions?: CodeAction[], source?: SymbolDisplayPart[]): CompletionEntryDetails { 1716 return { name, kindModifiers, kind, displayParts, documentation, tags, codeActions, source, sourceDisplay: source }; 1717 } 1718 1719 interface CodeActionsAndSourceDisplay { 1720 readonly codeActions: CodeAction[] | undefined; 1721 readonly sourceDisplay: SymbolDisplayPart[] | undefined; 1722 } 1723 function getCompletionEntryCodeActionsAndSourceDisplay( 1724 name: string, 1725 location: Node, 1726 contextToken: Node | undefined, 1727 origin: SymbolOriginInfo | SymbolOriginInfoExport | SymbolOriginInfoResolvedExport | undefined, 1728 symbol: Symbol, 1729 program: Program, 1730 host: LanguageServiceHost, 1731 compilerOptions: CompilerOptions, 1732 sourceFile: SourceFile, 1733 position: number, 1734 previousToken: Node | undefined, 1735 formatContext: formatting.FormatContext, 1736 preferences: UserPreferences, 1737 data: CompletionEntryData | undefined, 1738 source: string | undefined, 1739 cancellationToken: CancellationToken, 1740 ): CodeActionsAndSourceDisplay { 1741 if (data?.moduleSpecifier) { 1742 if (previousToken && getImportStatementCompletionInfo(contextToken || previousToken).replacementSpan) { 1743 // Import statement completion: 'import c|' 1744 return { codeActions: undefined, sourceDisplay: [textPart(data.moduleSpecifier)] }; 1745 } 1746 } 1747 1748 if (source === CompletionSource.ClassMemberSnippet) { 1749 const { importAdder } = getEntryForMemberCompletion( 1750 host, 1751 program, 1752 compilerOptions, 1753 preferences, 1754 name, 1755 symbol, 1756 location, 1757 contextToken, 1758 formatContext); 1759 if (importAdder) { 1760 const changes = textChanges.ChangeTracker.with( 1761 { host, formatContext, preferences }, 1762 importAdder.writeFixes); 1763 return { 1764 sourceDisplay: undefined, 1765 codeActions: [{ 1766 changes, 1767 description: diagnosticToString([Diagnostics.Includes_imports_of_types_referenced_by_0, name]), 1768 }], 1769 }; 1770 } 1771 } 1772 1773 if (originIsTypeOnlyAlias(origin)) { 1774 const codeAction = codefix.getPromoteTypeOnlyCompletionAction( 1775 sourceFile, 1776 origin.declaration.name, 1777 program, 1778 host, 1779 formatContext, 1780 preferences); 1781 1782 Debug.assertIsDefined(codeAction, "Expected to have a code action for promoting type-only alias"); 1783 return { codeActions: [codeAction], sourceDisplay: undefined }; 1784 } 1785 1786 if (!origin || !(originIsExport(origin) || originIsResolvedExport(origin))) { 1787 return { codeActions: undefined, sourceDisplay: undefined }; 1788 } 1789 1790 const checker = origin.isFromPackageJson ? host.getPackageJsonAutoImportProvider!()!.getTypeChecker() : program.getTypeChecker(); 1791 const { moduleSymbol } = origin; 1792 const targetSymbol = checker.getMergedSymbol(skipAlias(symbol.exportSymbol || symbol, checker)); 1793 const isJsxOpeningTagName = contextToken?.kind === SyntaxKind.LessThanToken && isJsxOpeningLikeElement(contextToken.parent); 1794 const { moduleSpecifier, codeAction } = codefix.getImportCompletionAction( 1795 targetSymbol, 1796 moduleSymbol, 1797 sourceFile, 1798 getNameForExportedSymbol(symbol, getEmitScriptTarget(compilerOptions), isJsxOpeningTagName), 1799 isJsxOpeningTagName, 1800 host, 1801 program, 1802 formatContext, 1803 previousToken && isIdentifier(previousToken) ? previousToken.getStart(sourceFile) : position, 1804 preferences, 1805 cancellationToken); 1806 Debug.assert(!data?.moduleSpecifier || moduleSpecifier === data.moduleSpecifier); 1807 return { sourceDisplay: [textPart(moduleSpecifier)], codeActions: [codeAction] }; 1808 } 1809 1810 export function getCompletionEntrySymbol( 1811 program: Program, 1812 log: Log, 1813 sourceFile: SourceFile, 1814 position: number, 1815 entryId: CompletionEntryIdentifier, 1816 host: LanguageServiceHost, 1817 preferences: UserPreferences, 1818 ): Symbol | undefined { 1819 const completion = getSymbolCompletionFromEntryId(program, log, sourceFile, position, entryId, host, preferences); 1820 return completion.type === "symbol" ? completion.symbol : undefined; 1821 } 1822 1823 const enum CompletionDataKind { Data, JsDocTagName, JsDocTag, JsDocParameterName, Keywords } 1824 /** true: after the `=` sign but no identifier has been typed yet. Else is the Identifier after the initializer. */ 1825 type IsJsxInitializer = boolean | Identifier; 1826 interface CompletionData { 1827 readonly kind: CompletionDataKind.Data; 1828 readonly symbols: readonly Symbol[]; 1829 readonly completionKind: CompletionKind; 1830 readonly isInSnippetScope: boolean; 1831 /** Note that the presence of this alone doesn't mean that we need a conversion. Only do that if the completion is not an ordinary identifier. */ 1832 readonly propertyAccessToConvert: PropertyAccessExpression | undefined; 1833 readonly isNewIdentifierLocation: boolean; 1834 readonly location: Node; 1835 readonly keywordFilters: KeywordCompletionFilters; 1836 readonly literals: readonly (string | number | PseudoBigInt)[]; 1837 readonly symbolToOriginInfoMap: SymbolOriginInfoMap; 1838 readonly recommendedCompletion: Symbol | undefined; 1839 readonly previousToken: Node | undefined; 1840 readonly contextToken: Node | undefined; 1841 readonly isJsxInitializer: IsJsxInitializer; 1842 readonly insideJsDocTagTypeExpression: boolean; 1843 readonly symbolToSortTextMap: SymbolSortTextMap; 1844 readonly isTypeOnlyLocation: boolean; 1845 /** In JSX tag name and attribute names, identifiers like "my-tag" or "aria-name" is valid identifier. */ 1846 readonly isJsxIdentifierExpected: boolean; 1847 readonly isRightOfOpenTag: boolean; 1848 readonly importStatementCompletion?: ImportStatementCompletionInfo; 1849 readonly hasUnresolvedAutoImports?: boolean; 1850 readonly flags: CompletionInfoFlags; 1851 } 1852 type Request = 1853 | { readonly kind: CompletionDataKind.JsDocTagName | CompletionDataKind.JsDocTag } 1854 | { readonly kind: CompletionDataKind.JsDocParameterName, tag: JSDocParameterTag } 1855 | { readonly kind: CompletionDataKind.Keywords, keywordCompletions: readonly CompletionEntry[], isNewIdentifierLocation: boolean }; 1856 1857 export const enum CompletionKind { 1858 ObjectPropertyDeclaration, 1859 Global, 1860 PropertyAccess, 1861 MemberLike, 1862 String, 1863 None, 1864 } 1865 1866 function getRecommendedCompletion(previousToken: Node, contextualType: Type, checker: TypeChecker): Symbol | undefined { 1867 // For a union, return the first one with a recommended completion. 1868 return firstDefined(contextualType && (contextualType.isUnion() ? contextualType.types : [contextualType]), type => { 1869 const symbol = type && type.symbol; 1870 // Don't include make a recommended completion for an abstract class 1871 return symbol && (symbol.flags & (SymbolFlags.EnumMember | SymbolFlags.Enum | SymbolFlags.Class) && !isAbstractConstructorSymbol(symbol)) 1872 ? getFirstSymbolInChain(symbol, previousToken, checker) 1873 : undefined; 1874 }); 1875 } 1876 1877 function getContextualType(previousToken: Node, position: number, sourceFile: SourceFile, checker: TypeChecker): Type | undefined { 1878 const { parent } = previousToken; 1879 switch (previousToken.kind) { 1880 case SyntaxKind.Identifier: 1881 return getContextualTypeFromParent(previousToken as Identifier, checker); 1882 case SyntaxKind.EqualsToken: 1883 switch (parent.kind) { 1884 case SyntaxKind.VariableDeclaration: 1885 return checker.getContextualType((parent as VariableDeclaration).initializer!); // TODO: GH#18217 1886 case SyntaxKind.BinaryExpression: 1887 return checker.getTypeAtLocation((parent as BinaryExpression).left); 1888 case SyntaxKind.JsxAttribute: 1889 return checker.getContextualTypeForJsxAttribute(parent as JsxAttribute); 1890 default: 1891 return undefined; 1892 } 1893 case SyntaxKind.NewKeyword: 1894 return checker.getContextualType(parent as Expression); 1895 case SyntaxKind.CaseKeyword: 1896 const caseClause = tryCast(parent, isCaseClause); 1897 return caseClause ? getSwitchedType(caseClause, checker) : undefined; 1898 case SyntaxKind.OpenBraceToken: 1899 return isJsxExpression(parent) && !isJsxElement(parent.parent) && !isJsxFragment(parent.parent) ? checker.getContextualTypeForJsxAttribute(parent.parent) : undefined; 1900 default: 1901 const argInfo = SignatureHelp.getArgumentInfoForCompletions(previousToken, position, sourceFile); 1902 return argInfo ? 1903 // At `,`, treat this as the next argument after the comma. 1904 checker.getContextualTypeForArgumentAtIndex(argInfo.invocation, argInfo.argumentIndex + (previousToken.kind === SyntaxKind.CommaToken ? 1 : 0)) : 1905 isEqualityOperatorKind(previousToken.kind) && isBinaryExpression(parent) && isEqualityOperatorKind(parent.operatorToken.kind) ? 1906 // completion at `x ===/**/` should be for the right side 1907 checker.getTypeAtLocation(parent.left) : 1908 checker.getContextualType(previousToken as Expression); 1909 } 1910 } 1911 1912 function getFirstSymbolInChain(symbol: Symbol, enclosingDeclaration: Node, checker: TypeChecker): Symbol | undefined { 1913 const chain = checker.getAccessibleSymbolChain(symbol, enclosingDeclaration, /*meaning*/ SymbolFlags.All, /*useOnlyExternalAliasing*/ false); 1914 if (chain) return first(chain); 1915 return symbol.parent && (isModuleSymbol(symbol.parent) ? symbol : getFirstSymbolInChain(symbol.parent, enclosingDeclaration, checker)); 1916 } 1917 1918 function isModuleSymbol(symbol: Symbol): boolean { 1919 return !!symbol.declarations?.some(d => d.kind === SyntaxKind.SourceFile); 1920 } 1921 1922 function getCompletionData( 1923 program: Program, 1924 log: (message: string) => void, 1925 sourceFile: SourceFile, 1926 compilerOptions: CompilerOptions, 1927 position: number, 1928 preferences: UserPreferences, 1929 detailsEntryId: CompletionEntryIdentifier | undefined, 1930 host: LanguageServiceHost, 1931 formatContext: formatting.FormatContext | undefined, 1932 cancellationToken?: CancellationToken, 1933 ): CompletionData | Request | undefined { 1934 const isEtsFile = sourceFile.scriptKind === ScriptKind.ETS; 1935 const typeChecker = program.getTypeChecker(); 1936 const inCheckedFile = isCheckedFile(sourceFile, compilerOptions); 1937 let start = timestamp(); 1938 let currentToken = getTokenAtPosition(sourceFile, position); // TODO: GH#15853 1939 // We will check for jsdoc comments with insideComment and getJsDocTagAtPosition. (TODO: that seems rather inefficient to check the same thing so many times.) 1940 1941 log("getCompletionData: Get current token: " + (timestamp() - start)); 1942 1943 start = timestamp(); 1944 const insideComment = isInComment(sourceFile, position, currentToken); 1945 log("getCompletionData: Is inside comment: " + (timestamp() - start)); 1946 1947 let insideJsDocTagTypeExpression = false; 1948 let isInSnippetScope = false; 1949 if (insideComment) { 1950 if (hasDocComment(sourceFile, position)) { 1951 if (sourceFile.text.charCodeAt(position - 1) === CharacterCodes.at) { 1952 // The current position is next to the '@' sign, when no tag name being provided yet. 1953 // Provide a full list of tag names 1954 return { kind: CompletionDataKind.JsDocTagName }; 1955 } 1956 else { 1957 // When completion is requested without "@", we will have check to make sure that 1958 // there are no comments prefix the request position. We will only allow "*" and space. 1959 // e.g 1960 // /** |c| /* 1961 // 1962 // /** 1963 // |c| 1964 // */ 1965 // 1966 // /** 1967 // * |c| 1968 // */ 1969 // 1970 // /** 1971 // * |c| 1972 // */ 1973 const lineStart = getLineStartPositionForPosition(position, sourceFile); 1974 if (!/[^\*|\s(/)]/.test(sourceFile.text.substring(lineStart, position))) { 1975 return { kind: CompletionDataKind.JsDocTag }; 1976 } 1977 } 1978 } 1979 1980 // Completion should work inside certain JsDoc tags. For example: 1981 // /** @type {number | string} */ 1982 // Completion should work in the brackets 1983 const tag = getJsDocTagAtPosition(currentToken, position); 1984 if (tag) { 1985 if (tag.tagName.pos <= position && position <= tag.tagName.end) { 1986 return { kind: CompletionDataKind.JsDocTagName }; 1987 } 1988 const typeExpression = tryGetTypeExpressionFromTag(tag); 1989 if (typeExpression) { 1990 currentToken = getTokenAtPosition(sourceFile, position); 1991 if (!currentToken || 1992 (!isDeclarationName(currentToken) && 1993 (currentToken.parent.kind !== SyntaxKind.JSDocPropertyTag || 1994 (currentToken.parent as JSDocPropertyTag).name !== currentToken))) { 1995 // Use as type location if inside tag's type expression 1996 insideJsDocTagTypeExpression = isCurrentlyEditingNode(typeExpression); 1997 } 1998 } 1999 if (!insideJsDocTagTypeExpression && isJSDocParameterTag(tag) && (nodeIsMissing(tag.name) || tag.name.pos <= position && position <= tag.name.end)) { 2000 return { kind: CompletionDataKind.JsDocParameterName, tag }; 2001 } 2002 } 2003 2004 if (!insideJsDocTagTypeExpression) { 2005 // Proceed if the current position is in jsDoc tag expression; otherwise it is a normal 2006 // comment or the plain text part of a jsDoc comment, so no completion should be available 2007 log("Returning an empty list because completion was inside a regular comment or plain text part of a JsDoc comment."); 2008 return undefined; 2009 } 2010 } 2011 2012 start = timestamp(); 2013 // The decision to provide completion depends on the contextToken, which is determined through the previousToken. 2014 // Note: 'previousToken' (and thus 'contextToken') can be undefined if we are the beginning of the file 2015 const isJsOnlyLocation = !insideJsDocTagTypeExpression && isSourceFileJS(sourceFile); 2016 const tokens = getRelevantTokens(position, sourceFile); 2017 const previousToken = tokens.previousToken!; 2018 let contextToken = tokens.contextToken!; 2019 log("getCompletionData: Get previous token: " + (timestamp() - start)); 2020 2021 // Find the node where completion is requested on. 2022 // Also determine whether we are trying to complete with members of that node 2023 // or attributes of a JSX tag. 2024 let node = currentToken; 2025 let propertyAccessToConvert: PropertyAccessExpression | undefined; 2026 let isRightOfDot = false; 2027 let isRightOfQuestionDot = false; 2028 let isRightOfOpenTag = false; 2029 let isStartingCloseTag = false; 2030 let isJsxInitializer: IsJsxInitializer = false; 2031 let isJsxIdentifierExpected = false; 2032 let importStatementCompletion: ImportStatementCompletionInfo | undefined; 2033 let location = getTouchingPropertyName(sourceFile, position); 2034 let keywordFilters = KeywordCompletionFilters.None; 2035 let isNewIdentifierLocation = false; 2036 let flags = CompletionInfoFlags.None; 2037 2038 if (contextToken) { 2039 const importStatementCompletionInfo = getImportStatementCompletionInfo(contextToken); 2040 if (importStatementCompletionInfo.keywordCompletion) { 2041 if (importStatementCompletionInfo.isKeywordOnlyCompletion) { 2042 return { 2043 kind: CompletionDataKind.Keywords, 2044 keywordCompletions: [keywordToCompletionEntry(importStatementCompletionInfo.keywordCompletion)], 2045 isNewIdentifierLocation: importStatementCompletionInfo.isNewIdentifierLocation, 2046 }; 2047 } 2048 keywordFilters = keywordFiltersFromSyntaxKind(importStatementCompletionInfo.keywordCompletion); 2049 } 2050 if (importStatementCompletionInfo.replacementSpan && preferences.includeCompletionsForImportStatements && preferences.includeCompletionsWithInsertText) { 2051 // Import statement completions use `insertText`, and also require the `data` property of `CompletionEntryIdentifier` 2052 // added in TypeScript 4.3 to be sent back from the client during `getCompletionEntryDetails`. Since this feature 2053 // is not backward compatible with older clients, the language service defaults to disabling it, allowing newer clients 2054 // to opt in with the `includeCompletionsForImportStatements` user preference. 2055 flags |= CompletionInfoFlags.IsImportStatementCompletion; 2056 importStatementCompletion = importStatementCompletionInfo; 2057 isNewIdentifierLocation = importStatementCompletionInfo.isNewIdentifierLocation; 2058 } 2059 // Bail out if this is a known invalid completion location 2060 if (!importStatementCompletionInfo.replacementSpan && isCompletionListBlocker(contextToken)) { 2061 log("Returning an empty list because completion was requested in an invalid position."); 2062 return keywordFilters 2063 ? keywordCompletionData(keywordFilters, isJsOnlyLocation, isNewIdentifierDefinitionLocation()) 2064 : undefined; 2065 } 2066 2067 let parent = contextToken.parent; 2068 if (contextToken.kind === SyntaxKind.DotToken || contextToken.kind === SyntaxKind.QuestionDotToken) { 2069 isRightOfDot = contextToken.kind === SyntaxKind.DotToken; 2070 isRightOfQuestionDot = contextToken.kind === SyntaxKind.QuestionDotToken; 2071 switch (parent.kind) { 2072 case SyntaxKind.PropertyAccessExpression: 2073 propertyAccessToConvert = parent as PropertyAccessExpression; 2074 node = propertyAccessToConvert.expression; 2075 const leftmostAccessExpression = getLeftmostAccessExpression(propertyAccessToConvert); 2076 if (nodeIsMissing(leftmostAccessExpression) || 2077 ((isCallExpression(node) || isFunctionLike(node) || isEtsComponentExpression(node)) && 2078 node.end === contextToken.pos && 2079 node.getChildCount(sourceFile) && 2080 last(node.getChildren(sourceFile)).kind !== SyntaxKind.CloseParenToken && !node.getLastToken(sourceFile))) { 2081 // This is likely dot from incorrectly parsed expression and user is starting to write spread 2082 // eg: Math.min(./**/) 2083 // const x = function (./**/) {} 2084 // ({./**/}) 2085 return undefined; 2086 } 2087 if (node.virtual && findPrecedingToken(node.pos, sourceFile)?.kind === SyntaxKind.OpenParenToken) { 2088 return undefined; 2089 } 2090 break; 2091 case SyntaxKind.QualifiedName: 2092 node = (parent as QualifiedName).left; 2093 break; 2094 case SyntaxKind.ModuleDeclaration: 2095 node = (parent as ModuleDeclaration).name; 2096 break; 2097 case SyntaxKind.ImportType: 2098 node = parent; 2099 break; 2100 case SyntaxKind.MetaProperty: 2101 node = parent.getFirstToken(sourceFile)!; 2102 Debug.assert(node.kind === SyntaxKind.ImportKeyword || node.kind === SyntaxKind.NewKeyword); 2103 break; 2104 default: 2105 // There is nothing that precedes the dot, so this likely just a stray character 2106 // or leading into a '...' token. Just bail out instead. 2107 return undefined; 2108 } 2109 } 2110 else if (!importStatementCompletion) { 2111 // <UI.Test /* completion position */ /> 2112 // If the tagname is a property access expression, we will then walk up to the top most of property access expression. 2113 // Then, try to get a JSX container and its associated attributes type. 2114 if (parent && parent.kind === SyntaxKind.PropertyAccessExpression) { 2115 contextToken = parent; 2116 parent = parent.parent; 2117 } 2118 2119 // Fix location 2120 if (currentToken.parent === location) { 2121 switch (currentToken.kind) { 2122 case SyntaxKind.GreaterThanToken: 2123 if (currentToken.parent.kind === SyntaxKind.JsxElement || currentToken.parent.kind === SyntaxKind.JsxOpeningElement) { 2124 location = currentToken; 2125 } 2126 break; 2127 2128 case SyntaxKind.SlashToken: 2129 if (currentToken.parent.kind === SyntaxKind.JsxSelfClosingElement) { 2130 location = currentToken; 2131 } 2132 break; 2133 } 2134 } 2135 2136 switch (parent.kind) { 2137 case SyntaxKind.JsxClosingElement: 2138 if (contextToken.kind === SyntaxKind.SlashToken) { 2139 isStartingCloseTag = true; 2140 location = contextToken; 2141 } 2142 break; 2143 2144 case SyntaxKind.BinaryExpression: 2145 if (!binaryExpressionMayBeOpenTag(parent as BinaryExpression)) { 2146 break; 2147 } 2148 // falls through 2149 2150 case SyntaxKind.JsxSelfClosingElement: 2151 case SyntaxKind.JsxElement: 2152 case SyntaxKind.JsxOpeningElement: 2153 isJsxIdentifierExpected = true; 2154 if (contextToken.kind === SyntaxKind.LessThanToken) { 2155 isRightOfOpenTag = true; 2156 location = contextToken; 2157 } 2158 break; 2159 2160 case SyntaxKind.JsxExpression: 2161 case SyntaxKind.JsxSpreadAttribute: 2162 // For `<div foo={true} [||] ></div>`, `parent` will be `{true}` and `previousToken` will be `}` 2163 if (previousToken.kind === SyntaxKind.CloseBraceToken && currentToken.kind === SyntaxKind.GreaterThanToken) { 2164 isJsxIdentifierExpected = true; 2165 } 2166 break; 2167 2168 case SyntaxKind.JsxAttribute: 2169 // For `<div className="x" [||] ></div>`, `parent` will be JsxAttribute and `previousToken` will be its initializer 2170 if ((parent as JsxAttribute).initializer === previousToken && 2171 previousToken.end < position) { 2172 isJsxIdentifierExpected = true; 2173 break; 2174 } 2175 switch (previousToken.kind) { 2176 case SyntaxKind.EqualsToken: 2177 isJsxInitializer = true; 2178 break; 2179 case SyntaxKind.Identifier: 2180 isJsxIdentifierExpected = true; 2181 // For `<div x=[|f/**/|]`, `parent` will be `x` and `previousToken.parent` will be `f` (which is its own JsxAttribute) 2182 // Note for `<div someBool f>` we don't want to treat this as a jsx inializer, instead it's the attribute name. 2183 if (parent !== previousToken.parent && 2184 !(parent as JsxAttribute).initializer && 2185 findChildOfKind(parent, SyntaxKind.EqualsToken, sourceFile)) { 2186 isJsxInitializer = previousToken as Identifier; 2187 } 2188 } 2189 break; 2190 } 2191 } 2192 } 2193 2194 const semanticStart = timestamp(); 2195 let completionKind = CompletionKind.None; 2196 let isNonContextualObjectLiteral = false; 2197 let hasUnresolvedAutoImports = false; 2198 // This also gets mutated in nested-functions after the return 2199 let symbols: Symbol[] = []; 2200 let importSpecifierResolver: codefix.ImportSpecifierResolver | undefined; 2201 const symbolToOriginInfoMap: SymbolOriginInfoMap = []; 2202 const symbolToSortTextMap: SymbolSortTextMap = []; 2203 const seenPropertySymbols = new Map<SymbolId, true>(); 2204 const isTypeOnlyLocation = isTypeOnlyCompletion(); 2205 const getModuleSpecifierResolutionHost = memoizeOne((isFromPackageJson: boolean) => { 2206 return createModuleSpecifierResolutionHost(isFromPackageJson ? host.getPackageJsonAutoImportProvider!()! : program, host); 2207 }); 2208 2209 if (isRightOfDot || isRightOfQuestionDot) { 2210 getTypeScriptMemberSymbols(); 2211 } 2212 else if (isRightOfOpenTag) { 2213 symbols = typeChecker.getJsxIntrinsicTagNamesAt(location); 2214 Debug.assertEachIsDefined(symbols, "getJsxIntrinsicTagNames() should all be defined"); 2215 tryGetGlobalSymbols(); 2216 completionKind = CompletionKind.Global; 2217 keywordFilters = KeywordCompletionFilters.None; 2218 } 2219 else if (isStartingCloseTag) { 2220 const tagName = (contextToken.parent.parent as JsxElement).openingElement.tagName; 2221 const tagSymbol = typeChecker.getSymbolAtLocation(tagName); 2222 if (tagSymbol) { 2223 symbols = [tagSymbol]; 2224 } 2225 completionKind = CompletionKind.Global; 2226 keywordFilters = KeywordCompletionFilters.None; 2227 } 2228 else { 2229 // For JavaScript or TypeScript, if we're not after a dot, then just try to get the 2230 // global symbols in scope. These results should be valid for either language as 2231 // the set of symbols that can be referenced from this location. 2232 if (!tryGetGlobalSymbols()) { 2233 return keywordFilters 2234 ? keywordCompletionData(keywordFilters, isJsOnlyLocation, isNewIdentifierLocation) 2235 : undefined; 2236 } 2237 } 2238 2239 const etsLibFilesNames = program.getEtsLibSFromProgram(); 2240 symbols = symbols.filter(symbol => { 2241 if(!symbol.declarations || !symbol.declarations.length) { 2242 return true; 2243 } 2244 const declaration = (symbol.declarations??[]).filter(declaration =>{ 2245 if(!declaration.getSourceFile().fileName) { 2246 return true; 2247 } 2248 const symbolFileName = sys.resolvePath(declaration.getSourceFile().fileName); 2249 if(!isEtsFile && etsLibFilesNames.indexOf(symbolFileName) !== -1) { 2250 return false; 2251 } 2252 return true; 2253 }); 2254 return declaration.length; 2255 }); 2256 2257 log("getCompletionData: Semantic work: " + (timestamp() - semanticStart)); 2258 const contextualType = previousToken && getContextualType(previousToken, position, sourceFile, typeChecker); 2259 2260 const literals = mapDefined( 2261 contextualType && (contextualType.isUnion() ? contextualType.types : [contextualType]), 2262 t => t.isLiteral() && !(t.flags & TypeFlags.EnumLiteral) ? t.value : undefined); 2263 2264 const recommendedCompletion = previousToken && contextualType && getRecommendedCompletion(previousToken, contextualType, typeChecker); 2265 return { 2266 kind: CompletionDataKind.Data, 2267 symbols, 2268 completionKind, 2269 isInSnippetScope, 2270 propertyAccessToConvert, 2271 isNewIdentifierLocation, 2272 location, 2273 keywordFilters, 2274 literals, 2275 symbolToOriginInfoMap, 2276 recommendedCompletion, 2277 previousToken, 2278 contextToken, 2279 isJsxInitializer, 2280 insideJsDocTagTypeExpression, 2281 symbolToSortTextMap, 2282 isTypeOnlyLocation, 2283 isJsxIdentifierExpected, 2284 isRightOfOpenTag, 2285 importStatementCompletion, 2286 hasUnresolvedAutoImports, 2287 flags, 2288 }; 2289 2290 type JSDocTagWithTypeExpression = JSDocParameterTag | JSDocPropertyTag | JSDocReturnTag | JSDocTypeTag | JSDocTypedefTag | JSDocTemplateTag; 2291 2292 function isTagWithTypeExpression(tag: JSDocTag): tag is JSDocTagWithTypeExpression { 2293 switch (tag.kind) { 2294 case SyntaxKind.JSDocParameterTag: 2295 case SyntaxKind.JSDocPropertyTag: 2296 case SyntaxKind.JSDocReturnTag: 2297 case SyntaxKind.JSDocTypeTag: 2298 case SyntaxKind.JSDocTypedefTag: 2299 return true; 2300 case SyntaxKind.JSDocTemplateTag: 2301 return !!(tag as JSDocTemplateTag).constraint; 2302 default: 2303 return false; 2304 } 2305 } 2306 2307 function tryGetTypeExpressionFromTag(tag: JSDocTag): JSDocTypeExpression | undefined { 2308 if (isTagWithTypeExpression(tag)) { 2309 const typeExpression = isJSDocTemplateTag(tag) ? tag.constraint : tag.typeExpression; 2310 return typeExpression && typeExpression.kind === SyntaxKind.JSDocTypeExpression ? typeExpression : undefined; 2311 } 2312 return undefined; 2313 } 2314 2315 function getTypeScriptMemberSymbols(): void { 2316 // Right of dot member completion list 2317 completionKind = CompletionKind.PropertyAccess; 2318 2319 // Since this is qualified name check it's a type node location 2320 const isImportType = isLiteralImportTypeNode(node); 2321 const isTypeLocation = insideJsDocTagTypeExpression 2322 || (isImportType && !(node as ImportTypeNode).isTypeOf) 2323 || isPartOfTypeNode(node.parent) 2324 || isPossiblyTypeArgumentPosition(contextToken, sourceFile, typeChecker); 2325 const isRhsOfImportDeclaration = isInRightSideOfInternalImportEqualsDeclaration(node); 2326 if (isEntityName(node) || isImportType || isPropertyAccessExpression(node)) { 2327 const isNamespaceName = isModuleDeclaration(node.parent); 2328 if (isNamespaceName) isNewIdentifierLocation = true; 2329 let symbol = typeChecker.getSymbolAtLocation(node); 2330 if (symbol) { 2331 symbol = skipAlias(symbol, typeChecker); 2332 if (symbol.flags & (SymbolFlags.Module | SymbolFlags.Enum)) { 2333 // Extract module or enum members 2334 const exportedSymbols = typeChecker.getExportsOfModule(symbol); 2335 Debug.assertEachIsDefined(exportedSymbols, "getExportsOfModule() should all be defined"); 2336 const isValidValueAccess = (symbol: Symbol) => typeChecker.isValidPropertyAccess(isImportType ? node as ImportTypeNode : (node.parent as PropertyAccessExpression), symbol.name); 2337 const isValidTypeAccess = (symbol: Symbol) => symbolCanBeReferencedAtTypeLocation(symbol, typeChecker); 2338 const isValidAccess: (symbol: Symbol) => boolean = 2339 isNamespaceName 2340 // At `namespace N.M/**/`, if this is the only declaration of `M`, don't include `M` as a completion. 2341 ? symbol => !!(symbol.flags & SymbolFlags.Namespace) && !symbol.declarations?.every(d => d.parent === node.parent) 2342 : isRhsOfImportDeclaration ? 2343 // Any kind is allowed when dotting off namespace in internal import equals declaration 2344 symbol => isValidTypeAccess(symbol) || isValidValueAccess(symbol) : 2345 isTypeLocation ? isValidTypeAccess : isValidValueAccess; 2346 for (const exportedSymbol of exportedSymbols) { 2347 if (isValidAccess(exportedSymbol)) { 2348 symbols.push(exportedSymbol); 2349 } 2350 } 2351 2352 // If the module is merged with a value, we must get the type of the class and add its propertes (for inherited static methods). 2353 if (!isTypeLocation && 2354 symbol.declarations && 2355 symbol.declarations.some(d => d.kind !== SyntaxKind.SourceFile && d.kind !== SyntaxKind.ModuleDeclaration && d.kind !== SyntaxKind.EnumDeclaration)) { 2356 let type = typeChecker.getTypeOfSymbolAtLocation(symbol, node).getNonOptionalType(); 2357 let insertQuestionDot = false; 2358 if (type.isNullableType()) { 2359 const canCorrectToQuestionDot = 2360 isRightOfDot && 2361 !isRightOfQuestionDot && 2362 preferences.includeAutomaticOptionalChainCompletions !== false; 2363 2364 if (canCorrectToQuestionDot || isRightOfQuestionDot) { 2365 type = type.getNonNullableType(); 2366 if (canCorrectToQuestionDot) { 2367 insertQuestionDot = true; 2368 } 2369 } 2370 } 2371 addTypeProperties(type, !!(node.flags & NodeFlags.AwaitContext), insertQuestionDot); 2372 } 2373 2374 return; 2375 } 2376 } 2377 } 2378 2379 if (!isTypeLocation) { 2380 // GH#39946. Pulling on the type of a node inside of a function with a contextual `this` parameter can result in a circularity 2381 // if the `node` is part of the exprssion of a `yield` or `return`. This circularity doesn't exist at compile time because 2382 // we will check (and cache) the type of `this` *before* checking the type of the node. 2383 typeChecker.tryGetThisTypeAt(node, /*includeGlobalThis*/ false); 2384 2385 let type = typeChecker.getTypeAtLocation(node).getNonOptionalType(); 2386 let insertQuestionDot = false; 2387 if (type.isNullableType()) { 2388 const canCorrectToQuestionDot = 2389 isRightOfDot && 2390 !isRightOfQuestionDot && 2391 preferences.includeAutomaticOptionalChainCompletions !== false; 2392 2393 if (canCorrectToQuestionDot || isRightOfQuestionDot) { 2394 type = type.getNonNullableType(); 2395 if (canCorrectToQuestionDot) { 2396 insertQuestionDot = true; 2397 } 2398 } 2399 } 2400 addTypeProperties(type, !!(node.flags & NodeFlags.AwaitContext), insertQuestionDot); 2401 } 2402 } 2403 2404 function addTypeProperties(type: Type, insertAwait: boolean, insertQuestionDot: boolean): void { 2405 isNewIdentifierLocation = !!type.getStringIndexType(); 2406 if (isRightOfQuestionDot && some(type.getCallSignatures())) { 2407 isNewIdentifierLocation = true; 2408 } 2409 2410 const propertyAccess = node.kind === SyntaxKind.ImportType ? node as ImportTypeNode : node.parent as PropertyAccessExpression | QualifiedName; 2411 if (inCheckedFile) { 2412 const typeSymbols = type.getApparentProperties(); 2413 for (const symbol of typeSymbols) { 2414 if (typeChecker.isValidPropertyAccessForCompletions(propertyAccess, type, symbol)) { 2415 addPropertySymbol(symbol, /* insertAwait */ false, insertQuestionDot); 2416 } 2417 } 2418 2419 // The extension method on the ETS depends on whether the type is correctly parsed. 2420 if (typeSymbols.length) { 2421 // if complete expression is ets component expression, then complete data need add extend properties and styles properties. 2422 const etsComponentExpressionNode = getEtsComponentExpressionInnerExpressionStatementNode(node) 2423 || getRootEtsComponentInnerCallExpressionNode(node); 2424 const returnType = typeChecker.getTypeAtLocation(node); 2425 if (etsComponentExpressionNode && shouldAddExtendOrStylesProperties(node, returnType)) { 2426 addEtsExtendPropertySymbol(etsComponentExpressionNode, insertQuestionDot); 2427 addEtsStylesPropertySymbol(etsComponentExpressionNode, insertQuestionDot); 2428 } 2429 } 2430 } 2431 else { 2432 // In javascript files, for union types, we don't just get the members that 2433 // the individual types have in common, we also include all the members that 2434 // each individual type has. This is because we're going to add all identifiers 2435 // anyways. So we might as well elevate the members that were at least part 2436 // of the individual types to a higher status since we know what they are. 2437 symbols.push(...filter(getPropertiesForCompletion(type, typeChecker), s => typeChecker.isValidPropertyAccessForCompletions(propertyAccess, type, s))); 2438 } 2439 2440 if (insertAwait && preferences.includeCompletionsWithInsertText) { 2441 const promiseType = typeChecker.getPromisedTypeOfPromise(type); 2442 if (promiseType) { 2443 for (const symbol of promiseType.getApparentProperties()) { 2444 if (typeChecker.isValidPropertyAccessForCompletions(propertyAccess, promiseType, symbol)) { 2445 addPropertySymbol(symbol, /* insertAwait */ true, insertQuestionDot); 2446 } 2447 } 2448 } 2449 } 2450 } 2451 2452 function shouldAddExtendOrStylesProperties(node: Node, returnType: Type) { 2453 return isCallExpressionOrEtsComponentExpressionKind(node, returnType) && 2454 !!returnType.symbol.declarations?.length && 2455 !isStructDeclaration(returnType.symbol.declarations[0]); 2456 } 2457 2458 function isCallExpressionOrEtsComponentExpressionKind(node: Node, returnType: Type):boolean { 2459 if ((isCallExpression(node) || isEtsComponentExpression(node)) && returnType.symbol) { 2460 return !!returnType.symbol.getName().match("Attribute") && isVirtualAttributeTypeArgument(node); 2461 } 2462 return !!node.virtual && isIdentifier(node) && !!node.escapedText.toString().match("Instance"); 2463 } 2464 2465 function addPropertySymbol(symbol: Symbol, insertAwait: boolean, insertQuestionDot: boolean) { 2466 // For a computed property with an accessible name like `Symbol.iterator`, 2467 // we'll add a completion for the *name* `Symbol` instead of for the property. 2468 // If this is e.g. [Symbol.iterator], add a completion for `Symbol`. 2469 const computedPropertyName = firstDefined(symbol.declarations, decl => tryCast(getNameOfDeclaration(decl), isComputedPropertyName)); 2470 if (computedPropertyName) { 2471 const leftMostName = getLeftMostName(computedPropertyName.expression); // The completion is for `Symbol`, not `iterator`. 2472 const nameSymbol = leftMostName && typeChecker.getSymbolAtLocation(leftMostName); 2473 // If this is nested like for `namespace N { export const sym = Symbol(); }`, we'll add the completion for `N`. 2474 const firstAccessibleSymbol = nameSymbol && getFirstSymbolInChain(nameSymbol, contextToken, typeChecker); 2475 if (firstAccessibleSymbol && addToSeen(seenPropertySymbols, getSymbolId(firstAccessibleSymbol))) { 2476 const index = symbols.length; 2477 symbols.push(firstAccessibleSymbol); 2478 const moduleSymbol = firstAccessibleSymbol.parent; 2479 if (!moduleSymbol || 2480 !isExternalModuleSymbol(moduleSymbol) || 2481 typeChecker.tryGetMemberInModuleExportsAndProperties(firstAccessibleSymbol.name, moduleSymbol) !== firstAccessibleSymbol 2482 ) { 2483 symbolToOriginInfoMap[index] = { kind: getNullableSymbolOriginInfoKind(SymbolOriginInfoKind.SymbolMemberNoExport) }; 2484 } 2485 else { 2486 const fileName = isExternalModuleNameRelative(stripQuotes(moduleSymbol.name)) ? getSourceFileOfModule(moduleSymbol)?.fileName : undefined; 2487 const { moduleSpecifier } = (importSpecifierResolver ||= codefix.createImportSpecifierResolver(sourceFile, program, host, preferences)).getModuleSpecifierForBestExportInfo([{ 2488 exportKind: ExportKind.Named, 2489 moduleFileName: fileName, 2490 isFromPackageJson: false, 2491 moduleSymbol, 2492 symbol: firstAccessibleSymbol, 2493 targetFlags: skipAlias(firstAccessibleSymbol, typeChecker).flags, 2494 }], firstAccessibleSymbol.name, position, isValidTypeOnlyAliasUseSite(location)) || {}; 2495 2496 if (moduleSpecifier) { 2497 const origin: SymbolOriginInfoResolvedExport = { 2498 kind: getNullableSymbolOriginInfoKind(SymbolOriginInfoKind.SymbolMemberExport), 2499 moduleSymbol, 2500 isDefaultExport: false, 2501 symbolName: firstAccessibleSymbol.name, 2502 exportName: firstAccessibleSymbol.name, 2503 fileName, 2504 moduleSpecifier, 2505 }; 2506 symbolToOriginInfoMap[index] = origin; 2507 } 2508 } 2509 } 2510 else if (preferences.includeCompletionsWithInsertText) { 2511 addSymbolOriginInfo(symbol); 2512 addSymbolSortInfo(symbol); 2513 symbols.push(symbol); 2514 } 2515 } 2516 else { 2517 addSymbolOriginInfo(symbol); 2518 addSymbolSortInfo(symbol); 2519 symbols.push(symbol); 2520 } 2521 2522 function addSymbolSortInfo(symbol: Symbol) { 2523 if (isStaticProperty(symbol)) { 2524 symbolToSortTextMap[getSymbolId(symbol)] = SortText.LocalDeclarationPriority; 2525 } 2526 } 2527 2528 function addSymbolOriginInfo(symbol: Symbol) { 2529 if (preferences.includeCompletionsWithInsertText) { 2530 if (insertAwait && addToSeen(seenPropertySymbols, getSymbolId(symbol))) { 2531 symbolToOriginInfoMap[symbols.length] = { kind: getNullableSymbolOriginInfoKind(SymbolOriginInfoKind.Promise) }; 2532 } 2533 else if (insertQuestionDot) { 2534 symbolToOriginInfoMap[symbols.length] = { kind: SymbolOriginInfoKind.Nullable }; 2535 } 2536 } 2537 } 2538 2539 function getNullableSymbolOriginInfoKind(kind: SymbolOriginInfoKind) { 2540 return insertQuestionDot ? kind | SymbolOriginInfoKind.Nullable : kind; 2541 } 2542 } 2543 2544 function addEtsExtendPropertySymbol(node: EtsComponentExpression | CallExpression | PropertyAccessExpression | Identifier, insertQuestionDot: boolean) { 2545 const locals = getSourceFileOfNode(node).locals; 2546 if (!locals) { 2547 return; 2548 } 2549 const etsComponentName = isIdentifier(node) ? node.escapedText : isIdentifier(node.expression) ? node.expression.escapedText : undefined; 2550 const extendComponentSymbolMap: UnderscoreEscapedMap<Symbol[]> = new Map<__String, Symbol[]>(); 2551 locals.forEach(local => { 2552 const declaration = getDeclarationFromSymbol(local); 2553 if (!declaration) { 2554 return; 2555 } 2556 const currDeclaration = isVariableDeclaration(declaration) && isVariableDeclarationList(declaration.parent) && isVariableStatement(declaration.parent.parent) ? declaration.parent.parent : declaration; 2557 getEtsExtendDecoratorsComponentNames(getAllDecorators(currDeclaration), compilerOptions).forEach((extendName) => { 2558 if (extendComponentSymbolMap.has(extendName)) { 2559 extendComponentSymbolMap.get(extendName)!.push(local); 2560 } 2561 else { 2562 extendComponentSymbolMap.set(extendName, [local]); 2563 } 2564 }); 2565 }); 2566 if (!etsComponentName) { 2567 return; 2568 } 2569 const name = extendComponentSymbolMap.has(etsComponentName) ? 2570 etsComponentName : extendComponentSymbolMap.has(etsComponentName.toString().slice(0, -8) as __String) ? 2571 etsComponentName.toString().slice(0, -8) as __String : undefined; 2572 if (!name) { 2573 return; 2574 } 2575 extendComponentSymbolMap.get(name)!.forEach(local => { 2576 addPropertySymbol(local, /* insertAwait */ false, insertQuestionDot); 2577 }); 2578 } 2579 2580 function addEtsStylesPropertySymbol(node: EtsComponentExpression | CallExpression | PropertyAccessExpression | Identifier , insertQuestionDot: boolean) { 2581 const locals = getSourceFileOfNode(node).locals; 2582 if (!locals) { 2583 return; 2584 } 2585 const etsComponentName = isIdentifier(node) ? node.escapedText : isIdentifier(node.expression) ? node.expression.escapedText : undefined; 2586 const stylesComponentSymbolMap: UnderscoreEscapedMap<Symbol[]> = new Map<__String, Symbol[]>(); 2587 locals.forEach(local => { 2588 const declaration = getDeclarationFromSymbol(local); 2589 if (!declaration) { 2590 return; 2591 } 2592 const currDeclaration = isVariableDeclaration(declaration) && isVariableDeclarationList(declaration.parent) && isVariableStatement(declaration.parent.parent) ? declaration.parent.parent : declaration; 2593 getEtsStylesDecoratorComponentNames(getAllDecorators(currDeclaration), compilerOptions).forEach((stylesName) => { 2594 if (stylesComponentSymbolMap.has(stylesName)) { 2595 stylesComponentSymbolMap.get(stylesName)!.push(local); 2596 } 2597 else { 2598 stylesComponentSymbolMap.set(stylesName, [local]); 2599 } 2600 }); 2601 }); 2602 // If it's a '@Styles' method inside StructDeclaration, 2603 // we will find container StructDeclaration of current node first, 2604 // and then find method decorated with '@Styles' 2605 getContainingStruct(node)?.symbol.members?.forEach(member => { 2606 getEtsStylesDecoratorComponentNames(getAllDecorators(member.valueDeclaration), compilerOptions).forEach((stylesName) => { 2607 if (stylesComponentSymbolMap.has(stylesName)) { 2608 stylesComponentSymbolMap.get(stylesName)!.push(member); 2609 } 2610 else { 2611 stylesComponentSymbolMap.set(stylesName, [member]); 2612 } 2613 }); 2614 }); 2615 if (etsComponentName && stylesComponentSymbolMap.size > 0) { 2616 stylesComponentSymbolMap.forEach(symbols => { 2617 symbols.forEach(symbol => { 2618 addPropertySymbol(symbol, /* insertAwait */ false, insertQuestionDot); 2619 }); 2620 }); 2621 } 2622 } 2623 2624 /** Given 'a.b.c', returns 'a'. */ 2625 function getLeftMostName(e: Expression): Identifier | undefined { 2626 return isIdentifier(e) ? e : isPropertyAccessExpression(e) ? getLeftMostName(e.expression) : undefined; 2627 } 2628 2629 function tryGetGlobalSymbols(): boolean { 2630 const result: GlobalsSearch = tryGetObjectTypeLiteralInTypeArgumentCompletionSymbols() 2631 || tryGetObjectLikeCompletionSymbols() 2632 || tryGetImportCompletionSymbols() 2633 || tryGetImportOrExportClauseCompletionSymbols() 2634 || tryGetLocalNamedExportCompletionSymbols() 2635 || tryGetConstructorCompletion() 2636 || tryGetClassLikeCompletionSymbols() 2637 || tryGetJsxCompletionSymbols() 2638 || (getGlobalCompletions(), GlobalsSearch.Success); 2639 return result === GlobalsSearch.Success; 2640 } 2641 2642 function tryGetConstructorCompletion(): GlobalsSearch { 2643 if (!tryGetConstructorLikeCompletionContainer(contextToken)) return GlobalsSearch.Continue; 2644 // no members, only keywords 2645 completionKind = CompletionKind.None; 2646 // Declaring new property/method/accessor 2647 isNewIdentifierLocation = true; 2648 // Has keywords for constructor parameter 2649 keywordFilters = KeywordCompletionFilters.ConstructorParameterKeywords; 2650 return GlobalsSearch.Success; 2651 } 2652 2653 function tryGetJsxCompletionSymbols(): GlobalsSearch { 2654 const jsxContainer = tryGetContainingJsxElement(contextToken); 2655 // Cursor is inside a JSX self-closing element or opening element 2656 const attrsType = jsxContainer && typeChecker.getContextualType(jsxContainer.attributes); 2657 if (!attrsType) return GlobalsSearch.Continue; 2658 const completionsType = jsxContainer && typeChecker.getContextualType(jsxContainer.attributes, ContextFlags.Completions); 2659 symbols = concatenate(symbols, filterJsxAttributes(getPropertiesForObjectExpression(attrsType, completionsType, jsxContainer.attributes, typeChecker), jsxContainer.attributes.properties)); 2660 setSortTextToOptionalMember(); 2661 completionKind = CompletionKind.MemberLike; 2662 isNewIdentifierLocation = false; 2663 return GlobalsSearch.Success; 2664 } 2665 2666 function tryGetImportCompletionSymbols(): GlobalsSearch { 2667 if (!importStatementCompletion) return GlobalsSearch.Continue; 2668 isNewIdentifierLocation = true; 2669 collectAutoImports(); 2670 return GlobalsSearch.Success; 2671 } 2672 2673 function getGlobalCompletions(): void { 2674 keywordFilters = tryGetFunctionLikeBodyCompletionContainer(contextToken) ? KeywordCompletionFilters.FunctionLikeBodyKeywords : KeywordCompletionFilters.All; 2675 2676 // Get all entities in the current scope. 2677 completionKind = CompletionKind.Global; 2678 isNewIdentifierLocation = isNewIdentifierDefinitionLocation(); 2679 2680 if (previousToken !== contextToken) { 2681 Debug.assert(!!previousToken, "Expected 'contextToken' to be defined when different from 'previousToken'."); 2682 } 2683 // We need to find the node that will give us an appropriate scope to begin 2684 // aggregating completion candidates. This is achieved in 'getScopeNode' 2685 // by finding the first node that encompasses a position, accounting for whether a node 2686 // is "complete" to decide whether a position belongs to the node. 2687 // 2688 // However, at the end of an identifier, we are interested in the scope of the identifier 2689 // itself, but fall outside of the identifier. For instance: 2690 // 2691 // xyz => x$ 2692 // 2693 // the cursor is outside of both the 'x' and the arrow function 'xyz => x', 2694 // so 'xyz' is not returned in our results. 2695 // 2696 // We define 'adjustedPosition' so that we may appropriately account for 2697 // being at the end of an identifier. The intention is that if requesting completion 2698 // at the end of an identifier, it should be effectively equivalent to requesting completion 2699 // anywhere inside/at the beginning of the identifier. So in the previous case, the 2700 // 'adjustedPosition' will work as if requesting completion in the following: 2701 // 2702 // xyz => $x 2703 // 2704 // If previousToken !== contextToken, then 2705 // - 'contextToken' was adjusted to the token prior to 'previousToken' 2706 // because we were at the end of an identifier. 2707 // - 'previousToken' is defined. 2708 const adjustedPosition = previousToken !== contextToken ? 2709 previousToken.getStart() : 2710 position; 2711 2712 const scopeNode = getScopeNode(contextToken, adjustedPosition, sourceFile) || sourceFile; 2713 isInSnippetScope = isSnippetScope(scopeNode); 2714 2715 const symbolMeanings = (isTypeOnlyLocation ? SymbolFlags.None : SymbolFlags.Value) | SymbolFlags.Type | SymbolFlags.Namespace | SymbolFlags.Alias; 2716 const typeOnlyAliasNeedsPromotion = previousToken && !isValidTypeOnlyAliasUseSite(previousToken); 2717 2718 symbols = concatenate(symbols, typeChecker.getSymbolsInScope(scopeNode, symbolMeanings)); 2719 Debug.assertEachIsDefined(symbols, "getSymbolsInScope() should all be defined"); 2720 for (let i = 0; i < symbols.length; i++) { 2721 const symbol = symbols[i]; 2722 if (!typeChecker.isArgumentsSymbol(symbol) && 2723 !some(symbol.declarations, d => d.getSourceFile() === sourceFile)) { 2724 symbolToSortTextMap[getSymbolId(symbol)] = SortText.GlobalsOrKeywords; 2725 } 2726 if (typeOnlyAliasNeedsPromotion && !(symbol.flags & SymbolFlags.Value)) { 2727 const typeOnlyAliasDeclaration = symbol.declarations && find(symbol.declarations, isTypeOnlyImportOrExportDeclaration); 2728 if (typeOnlyAliasDeclaration) { 2729 const origin: SymbolOriginInfoTypeOnlyAlias = { kind: SymbolOriginInfoKind.TypeOnlyAlias, declaration: typeOnlyAliasDeclaration }; 2730 symbolToOriginInfoMap[i] = origin; 2731 } 2732 } 2733 } 2734 2735 // Need to insert 'this.' before properties of `this` type, so only do that if `includeInsertTextCompletions` 2736 if (preferences.includeCompletionsWithInsertText && scopeNode.kind !== SyntaxKind.SourceFile) { 2737 const thisType = typeChecker.tryGetThisTypeAt(scopeNode, /*includeGlobalThis*/ false, isClassLike(scopeNode.parent) ? scopeNode : undefined); 2738 if (thisType && !isProbablyGlobalType(thisType, sourceFile, typeChecker)) { 2739 for (const symbol of getPropertiesForCompletion(thisType, typeChecker)) { 2740 symbolToOriginInfoMap[symbols.length] = { kind: SymbolOriginInfoKind.ThisType }; 2741 symbols.push(symbol); 2742 symbolToSortTextMap[getSymbolId(symbol)] = SortText.SuggestedClassMembers; 2743 } 2744 } 2745 } 2746 collectAutoImports(); 2747 if (isTypeOnlyLocation) { 2748 keywordFilters = contextToken && isAssertionExpression(contextToken.parent) 2749 ? KeywordCompletionFilters.TypeAssertionKeywords 2750 : KeywordCompletionFilters.TypeKeywords; 2751 } 2752 } 2753 2754 function shouldOfferImportCompletions(): boolean { 2755 // If already typing an import statement, provide completions for it. 2756 if (importStatementCompletion) return true; 2757 // If current completion is for non-contextual Object literal shortahands, ignore auto-import symbols 2758 if (isNonContextualObjectLiteral) return false; 2759 // If not already a module, must have modules enabled. 2760 if (!preferences.includeCompletionsForModuleExports) return false; 2761 // If already using ES modules, OK to continue using them. 2762 if (sourceFile.externalModuleIndicator || sourceFile.commonJsModuleIndicator) return true; 2763 // If module transpilation is enabled or we're targeting es6 or above, or not emitting, OK. 2764 if (compilerOptionsIndicateEsModules(program.getCompilerOptions())) return true; 2765 // If some file is using ES6 modules, assume that it's OK to add more. 2766 return programContainsModules(program); 2767 } 2768 2769 function isSnippetScope(scopeNode: Node): boolean { 2770 switch (scopeNode.kind) { 2771 case SyntaxKind.SourceFile: 2772 case SyntaxKind.TemplateExpression: 2773 case SyntaxKind.JsxExpression: 2774 case SyntaxKind.Block: 2775 return true; 2776 default: 2777 return isStatement(scopeNode); 2778 } 2779 } 2780 2781 function isTypeOnlyCompletion(): boolean { 2782 return insideJsDocTagTypeExpression 2783 || !!importStatementCompletion && isTypeOnlyImportOrExportDeclaration(location.parent) 2784 || !isContextTokenValueLocation(contextToken) && 2785 (isPossiblyTypeArgumentPosition(contextToken, sourceFile, typeChecker) 2786 || isPartOfTypeNode(location) 2787 || isContextTokenTypeLocation(contextToken)); 2788 } 2789 2790 function isContextTokenValueLocation(contextToken: Node) { 2791 return contextToken && 2792 ((contextToken.kind === SyntaxKind.TypeOfKeyword && 2793 (contextToken.parent.kind === SyntaxKind.TypeQuery || isTypeOfExpression(contextToken.parent))) || 2794 (contextToken.kind === SyntaxKind.AssertsKeyword && contextToken.parent.kind === SyntaxKind.TypePredicate)); 2795 } 2796 2797 function isContextTokenTypeLocation(contextToken: Node): boolean { 2798 if (contextToken) { 2799 const parentKind = contextToken.parent.kind; 2800 switch (contextToken.kind) { 2801 case SyntaxKind.ColonToken: 2802 return parentKind === SyntaxKind.PropertyDeclaration || 2803 parentKind === SyntaxKind.PropertySignature || 2804 parentKind === SyntaxKind.Parameter || 2805 parentKind === SyntaxKind.VariableDeclaration || 2806 isFunctionLikeKind(parentKind); 2807 2808 case SyntaxKind.EqualsToken: 2809 return parentKind === SyntaxKind.TypeAliasDeclaration; 2810 2811 case SyntaxKind.AsKeyword: 2812 return parentKind === SyntaxKind.AsExpression; 2813 2814 case SyntaxKind.LessThanToken: 2815 return parentKind === SyntaxKind.TypeReference || 2816 parentKind === SyntaxKind.TypeAssertionExpression; 2817 2818 case SyntaxKind.ExtendsKeyword: 2819 return parentKind === SyntaxKind.TypeParameter; 2820 2821 case SyntaxKind.SatisfiesKeyword: 2822 return parentKind === SyntaxKind.SatisfiesExpression; 2823 } 2824 } 2825 return false; 2826 } 2827 2828 /** Mutates `symbols`, `symbolToOriginInfoMap`, and `symbolToSortTextMap` */ 2829 function collectAutoImports() { 2830 if (!shouldOfferImportCompletions()) return; 2831 Debug.assert(!detailsEntryId?.data, "Should not run 'collectAutoImports' when faster path is available via `data`"); 2832 if (detailsEntryId && !detailsEntryId.source) { 2833 // Asking for completion details for an item that is not an auto-import 2834 return; 2835 } 2836 2837 flags |= CompletionInfoFlags.MayIncludeAutoImports; 2838 // import { type | -> token text should be blank 2839 const isAfterTypeOnlyImportSpecifierModifier = previousToken === contextToken 2840 && importStatementCompletion; 2841 2842 const lowerCaseTokenText = 2843 isAfterTypeOnlyImportSpecifierModifier ? "" : 2844 previousToken && isIdentifier(previousToken) ? previousToken.text.toLowerCase() : 2845 ""; 2846 2847 const moduleSpecifierCache = host.getModuleSpecifierCache?.(); 2848 const exportInfo = getExportInfoMap(sourceFile, host, program, preferences, cancellationToken); 2849 const packageJsonAutoImportProvider = host.getPackageJsonAutoImportProvider?.(); 2850 const packageJsonFilter = detailsEntryId ? undefined : createPackageJsonImportFilter(sourceFile, preferences, host); 2851 resolvingModuleSpecifiers( 2852 "collectAutoImports", 2853 host, 2854 importSpecifierResolver ||= codefix.createImportSpecifierResolver(sourceFile, program, host, preferences), 2855 program, 2856 position, 2857 preferences, 2858 !!importStatementCompletion, 2859 isValidTypeOnlyAliasUseSite(location), 2860 context => { 2861 exportInfo.search( 2862 sourceFile.path, 2863 /*preferCapitalized*/ isRightOfOpenTag, 2864 (symbolName, targetFlags) => { 2865 if (!isIdentifierText(symbolName, getEmitScriptTarget(host.getCompilationSettings()))) return false; 2866 if (!detailsEntryId && isStringANonContextualKeyword(symbolName)) return false; 2867 if (!isTypeOnlyLocation && !importStatementCompletion && !(targetFlags & SymbolFlags.Value)) return false; 2868 if (isTypeOnlyLocation && !(targetFlags & (SymbolFlags.Module | SymbolFlags.Type))) return false; 2869 // Do not try to auto-import something with a lowercase first letter for a JSX tag 2870 const firstChar = symbolName.charCodeAt(0); 2871 if (isRightOfOpenTag && (firstChar < CharacterCodes.A || firstChar > CharacterCodes.Z)) return false; 2872 2873 if (detailsEntryId) return true; 2874 return charactersFuzzyMatchInString(symbolName, lowerCaseTokenText); 2875 }, 2876 (info, symbolName, isFromAmbientModule, exportMapKey) => { 2877 if (detailsEntryId && !some(info, i => detailsEntryId.source === stripQuotes(i.moduleSymbol.name))) { 2878 return; 2879 } 2880 2881 // Do a relatively cheap check to bail early if all re-exports are non-importable 2882 // due to file location or package.json dependency filtering. For non-node16+ 2883 // module resolution modes, getting past this point guarantees that we'll be 2884 // able to generate a suitable module specifier, so we can safely show a completion, 2885 // even if we defer computing the module specifier. 2886 const firstImportableExportInfo = find(info, isImportableExportInfo); 2887 if (!firstImportableExportInfo) { 2888 return; 2889 } 2890 2891 // In node16+, module specifier resolution can fail due to modules being blocked 2892 // by package.json `exports`. If that happens, don't show a completion item. 2893 // N.B. in this resolution mode we always try to resolve module specifiers here, 2894 // because we have to know now if it's going to fail so we can omit the completion 2895 // from the list. 2896 const result = context.tryResolve(info, symbolName, isFromAmbientModule) || {}; 2897 if (result === "failed") return; 2898 2899 // If we skipped resolving module specifiers, our selection of which ExportInfo 2900 // to use here is arbitrary, since the info shown in the completion list derived from 2901 // it should be identical regardless of which one is used. During the subsequent 2902 // `CompletionEntryDetails` request, we'll get all the ExportInfos again and pick 2903 // the best one based on the module specifier it produces. 2904 let exportInfo = firstImportableExportInfo, moduleSpecifier; 2905 if (result !== "skipped") { 2906 ({ exportInfo = firstImportableExportInfo, moduleSpecifier } = result); 2907 } 2908 2909 const isDefaultExport = exportInfo.exportKind === ExportKind.Default; 2910 const symbol = isDefaultExport && getLocalSymbolForExportDefault(exportInfo.symbol) || exportInfo.symbol; 2911 2912 pushAutoImportSymbol(symbol, { 2913 kind: moduleSpecifier ? SymbolOriginInfoKind.ResolvedExport : SymbolOriginInfoKind.Export, 2914 moduleSpecifier, 2915 symbolName, 2916 exportMapKey, 2917 exportName: exportInfo.exportKind === ExportKind.ExportEquals ? InternalSymbolName.ExportEquals : exportInfo.symbol.name, 2918 fileName: exportInfo.moduleFileName, 2919 isDefaultExport, 2920 moduleSymbol: exportInfo.moduleSymbol, 2921 isFromPackageJson: exportInfo.isFromPackageJson, 2922 }); 2923 } 2924 ); 2925 2926 hasUnresolvedAutoImports = context.skippedAny(); 2927 flags |= context.resolvedAny() ? CompletionInfoFlags.ResolvedModuleSpecifiers : 0; 2928 flags |= context.resolvedBeyondLimit() ? CompletionInfoFlags.ResolvedModuleSpecifiersBeyondLimit : 0; 2929 } 2930 ); 2931 2932 function isImportableExportInfo(info: SymbolExportInfo) { 2933 const moduleFile = tryCast(info.moduleSymbol.valueDeclaration, isSourceFile); 2934 if (!moduleFile) { 2935 const moduleName = stripQuotes(info.moduleSymbol.name); 2936 if (JsTyping.nodeCoreModules.has(moduleName) && startsWith(moduleName, "node:") !== shouldUseUriStyleNodeCoreModules(sourceFile, program)) { 2937 return false; 2938 } 2939 return packageJsonFilter 2940 ? packageJsonFilter.allowsImportingAmbientModule(info.moduleSymbol, getModuleSpecifierResolutionHost(info.isFromPackageJson)) 2941 : true; 2942 } 2943 return isImportableFile( 2944 info.isFromPackageJson ? packageJsonAutoImportProvider! : program, 2945 sourceFile, 2946 moduleFile, 2947 preferences, 2948 packageJsonFilter, 2949 getModuleSpecifierResolutionHost(info.isFromPackageJson), 2950 moduleSpecifierCache); 2951 } 2952 } 2953 2954 function pushAutoImportSymbol(symbol: Symbol, origin: SymbolOriginInfoResolvedExport | SymbolOriginInfoExport) { 2955 const symbolId = getSymbolId(symbol); 2956 if (symbolToSortTextMap[symbolId] === SortText.GlobalsOrKeywords) { 2957 // If an auto-importable symbol is available as a global, don't add the auto import 2958 return; 2959 } 2960 symbolToOriginInfoMap[symbols.length] = origin; 2961 symbolToSortTextMap[symbolId] = importStatementCompletion ? SortText.LocationPriority : SortText.AutoImportSuggestions; 2962 symbols.push(symbol); 2963 } 2964 2965 /* Mutates `symbols` and `symbolToOriginInfoMap`. */ 2966 function collectObjectLiteralMethodSymbols(members: Symbol[], enclosingDeclaration: ObjectLiteralExpression): void { 2967 // TODO: support JS files. 2968 if (isInJSFile(location)) { 2969 return; 2970 } 2971 members.forEach(member => { 2972 if (!isObjectLiteralMethodSymbol(member)) { 2973 return; 2974 } 2975 const displayName = getCompletionEntryDisplayNameForSymbol( 2976 member, 2977 getEmitScriptTarget(compilerOptions), 2978 /*origin*/ undefined, 2979 CompletionKind.ObjectPropertyDeclaration, 2980 /*jsxIdentifierExpected*/ false); 2981 if (!displayName) { 2982 return; 2983 } 2984 const { name } = displayName; 2985 const entryProps = getEntryForObjectLiteralMethodCompletion( 2986 member, 2987 name, 2988 enclosingDeclaration, 2989 program, 2990 host, 2991 compilerOptions, 2992 preferences, 2993 formatContext); 2994 if (!entryProps) { 2995 return; 2996 } 2997 const origin: SymbolOriginInfoObjectLiteralMethod = { kind: SymbolOriginInfoKind.ObjectLiteralMethod, ...entryProps }; 2998 flags |= CompletionInfoFlags.MayIncludeMethodSnippets; 2999 symbolToOriginInfoMap[symbols.length] = origin; 3000 symbols.push(member); 3001 }); 3002 } 3003 3004 function isObjectLiteralMethodSymbol(symbol: Symbol): boolean { 3005 /* 3006 For an object type 3007 `type Foo = { 3008 bar(x: number): void; 3009 foo: (x: string) => string; 3010 }`, 3011 `bar` will have symbol flag `Method`, 3012 `foo` will have symbol flag `Property`. 3013 */ 3014 if (!(symbol.flags & (SymbolFlags.Property | SymbolFlags.Method))) { 3015 return false; 3016 } 3017 return true; 3018 } 3019 3020 /** 3021 * Finds the first node that "embraces" the position, so that one may 3022 * accurately aggregate locals from the closest containing scope. 3023 */ 3024 function getScopeNode(initialToken: Node | undefined, position: number, sourceFile: SourceFile) { 3025 let scope: Node | undefined = initialToken; 3026 while (scope && !positionBelongsToNode(scope, position, sourceFile)) { 3027 scope = scope.parent; 3028 } 3029 return scope; 3030 } 3031 3032 function isCompletionListBlocker(contextToken: Node): boolean { 3033 const start = timestamp(); 3034 const result = isInStringOrRegularExpressionOrTemplateLiteral(contextToken) || 3035 isSolelyIdentifierDefinitionLocation(contextToken) || 3036 isDotOfNumericLiteral(contextToken) || 3037 isInJsxText(contextToken) || 3038 isBigIntLiteral(contextToken); 3039 log("getCompletionsAtPosition: isCompletionListBlocker: " + (timestamp() - start)); 3040 return result; 3041 } 3042 3043 function isInJsxText(contextToken: Node): boolean { 3044 if (contextToken.kind === SyntaxKind.JsxText) { 3045 return true; 3046 } 3047 3048 if (contextToken.kind === SyntaxKind.GreaterThanToken && contextToken.parent) { 3049 // <Component<string> /**/ /> 3050 // <Component<string> /**/ ><Component> 3051 // - contextToken: GreaterThanToken (before cursor) 3052 // - location: JsxSelfClosingElement or JsxOpeningElement 3053 // - contextToken.parent === location 3054 if (location === contextToken.parent && (location.kind === SyntaxKind.JsxOpeningElement || location.kind === SyntaxKind.JsxSelfClosingElement)) { 3055 return false; 3056 } 3057 3058 if (contextToken.parent.kind === SyntaxKind.JsxOpeningElement) { 3059 // <div>/**/ 3060 // - contextToken: GreaterThanToken (before cursor) 3061 // - location: JSXElement 3062 // - different parents (JSXOpeningElement, JSXElement) 3063 return location.parent.kind !== SyntaxKind.JsxOpeningElement; 3064 } 3065 3066 if (contextToken.parent.kind === SyntaxKind.JsxClosingElement || contextToken.parent.kind === SyntaxKind.JsxSelfClosingElement) { 3067 return !!contextToken.parent.parent && contextToken.parent.parent.kind === SyntaxKind.JsxElement; 3068 } 3069 } 3070 return false; 3071 } 3072 3073 function isNewIdentifierDefinitionLocation(): boolean { 3074 if (contextToken) { 3075 const containingNodeKind = contextToken.parent.kind; 3076 const tokenKind = keywordForNode(contextToken); 3077 // Previous token may have been a keyword that was converted to an identifier. 3078 switch (tokenKind) { 3079 case SyntaxKind.CommaToken: 3080 return containingNodeKind === SyntaxKind.CallExpression // func( a, | 3081 || containingNodeKind === SyntaxKind.Constructor // constructor( a, | /* public, protected, private keywords are allowed here, so show completion */ 3082 || containingNodeKind === SyntaxKind.NewExpression // new C(a, | 3083 || containingNodeKind === SyntaxKind.ArrayLiteralExpression // [a, | 3084 || containingNodeKind === SyntaxKind.BinaryExpression // const x = (a, | 3085 || containingNodeKind === SyntaxKind.FunctionType // var x: (s: string, list| 3086 || containingNodeKind === SyntaxKind.ObjectLiteralExpression; // const obj = { x, | 3087 3088 case SyntaxKind.OpenParenToken: 3089 return containingNodeKind === SyntaxKind.CallExpression // func( | 3090 || containingNodeKind === SyntaxKind.Constructor // constructor( | 3091 || containingNodeKind === SyntaxKind.NewExpression // new C(a| 3092 || containingNodeKind === SyntaxKind.ParenthesizedExpression // const x = (a| 3093 || containingNodeKind === SyntaxKind.ParenthesizedType; // function F(pred: (a| /* this can become an arrow function, where 'a' is the argument */ 3094 3095 case SyntaxKind.OpenBracketToken: 3096 return containingNodeKind === SyntaxKind.ArrayLiteralExpression // [ | 3097 || containingNodeKind === SyntaxKind.IndexSignature // [ | : string ] 3098 || containingNodeKind === SyntaxKind.ComputedPropertyName; // [ | /* this can become an index signature */ 3099 3100 case SyntaxKind.ModuleKeyword: // module | 3101 case SyntaxKind.NamespaceKeyword: // namespace | 3102 case SyntaxKind.ImportKeyword: // import | 3103 return true; 3104 3105 case SyntaxKind.DotToken: 3106 return containingNodeKind === SyntaxKind.ModuleDeclaration; // module A.| 3107 3108 case SyntaxKind.OpenBraceToken: 3109 return containingNodeKind === SyntaxKind.ClassDeclaration // class A { | 3110 || containingNodeKind === SyntaxKind.StructDeclaration // struct A { | 3111 || containingNodeKind === SyntaxKind.ObjectLiteralExpression; // const obj = { | 3112 3113 case SyntaxKind.EqualsToken: 3114 return containingNodeKind === SyntaxKind.VariableDeclaration // const x = a| 3115 || containingNodeKind === SyntaxKind.BinaryExpression; // x = a| 3116 3117 case SyntaxKind.TemplateHead: 3118 return containingNodeKind === SyntaxKind.TemplateExpression; // `aa ${| 3119 3120 case SyntaxKind.TemplateMiddle: 3121 return containingNodeKind === SyntaxKind.TemplateSpan; // `aa ${10} dd ${| 3122 3123 case SyntaxKind.AsyncKeyword: 3124 return containingNodeKind === SyntaxKind.MethodDeclaration // const obj = { async c|() 3125 || containingNodeKind === SyntaxKind.ShorthandPropertyAssignment; // const obj = { async c| 3126 3127 case SyntaxKind.AsteriskToken: 3128 return containingNodeKind === SyntaxKind.MethodDeclaration; // const obj = { * c| 3129 } 3130 3131 if (isClassMemberCompletionKeyword(tokenKind)) { 3132 return true; 3133 } 3134 } 3135 3136 return false; 3137 } 3138 3139 function isInStringOrRegularExpressionOrTemplateLiteral(contextToken: Node): boolean { 3140 // To be "in" one of these literals, the position has to be: 3141 // 1. entirely within the token text. 3142 // 2. at the end position of an unterminated token. 3143 // 3. at the end of a regular expression (due to trailing flags like '/foo/g'). 3144 return (isRegularExpressionLiteral(contextToken) || isStringTextContainingNode(contextToken)) && ( 3145 rangeContainsPositionExclusive(createTextRangeFromSpan(createTextSpanFromNode(contextToken)), position) || 3146 position === contextToken.end && (!!contextToken.isUnterminated || isRegularExpressionLiteral(contextToken))); 3147 } 3148 3149 function tryGetObjectTypeLiteralInTypeArgumentCompletionSymbols(): GlobalsSearch | undefined { 3150 const typeLiteralNode = tryGetTypeLiteralNode(contextToken); 3151 if (!typeLiteralNode) return GlobalsSearch.Continue; 3152 3153 const intersectionTypeNode = isIntersectionTypeNode(typeLiteralNode.parent) ? typeLiteralNode.parent : undefined; 3154 const containerTypeNode = intersectionTypeNode || typeLiteralNode; 3155 3156 const containerExpectedType = getConstraintOfTypeArgumentProperty(containerTypeNode, typeChecker); 3157 if (!containerExpectedType) return GlobalsSearch.Continue; 3158 3159 const containerActualType = typeChecker.getTypeFromTypeNode(containerTypeNode); 3160 3161 const members = getPropertiesForCompletion(containerExpectedType, typeChecker); 3162 const existingMembers = getPropertiesForCompletion(containerActualType, typeChecker); 3163 3164 const existingMemberEscapedNames: Set<__String> = new Set(); 3165 existingMembers.forEach(s => existingMemberEscapedNames.add(s.escapedName)); 3166 3167 symbols = concatenate(symbols, filter(members, s => !existingMemberEscapedNames.has(s.escapedName))); 3168 3169 completionKind = CompletionKind.ObjectPropertyDeclaration; 3170 isNewIdentifierLocation = true; 3171 3172 return GlobalsSearch.Success; 3173 } 3174 3175 /** 3176 * Aggregates relevant symbols for completion in object literals and object binding patterns. 3177 * Relevant symbols are stored in the captured 'symbols' variable. 3178 * 3179 * @returns true if 'symbols' was successfully populated; false otherwise. 3180 */ 3181 function tryGetObjectLikeCompletionSymbols(): GlobalsSearch | undefined { 3182 const symbolsStartIndex = symbols.length; 3183 const objectLikeContainer = tryGetObjectLikeCompletionContainer(contextToken); 3184 if (!objectLikeContainer) return GlobalsSearch.Continue; 3185 3186 // We're looking up possible property names from contextual/inferred/declared type. 3187 completionKind = CompletionKind.ObjectPropertyDeclaration; 3188 3189 let typeMembers: Symbol[] | undefined; 3190 let existingMembers: readonly Declaration[] | undefined; 3191 3192 if (objectLikeContainer.kind === SyntaxKind.ObjectLiteralExpression) { 3193 const instantiatedType = tryGetObjectLiteralContextualType(objectLikeContainer, typeChecker); 3194 3195 // Check completions for Object property value shorthand 3196 if (instantiatedType === undefined) { 3197 if (objectLikeContainer.flags & NodeFlags.InWithStatement) { 3198 return GlobalsSearch.Fail; 3199 } 3200 isNonContextualObjectLiteral = true; 3201 return GlobalsSearch.Continue; 3202 } 3203 const completionsType = typeChecker.getContextualType(objectLikeContainer, ContextFlags.Completions); 3204 const hasStringIndexType = (completionsType || instantiatedType).getStringIndexType(); 3205 const hasNumberIndextype = (completionsType || instantiatedType).getNumberIndexType(); 3206 isNewIdentifierLocation = !!hasStringIndexType || !!hasNumberIndextype; 3207 typeMembers = getPropertiesForObjectExpression(instantiatedType, completionsType, objectLikeContainer, typeChecker); 3208 existingMembers = objectLikeContainer.properties; 3209 3210 if (typeMembers.length === 0) { 3211 // Edge case: If NumberIndexType exists 3212 if (!hasNumberIndextype) { 3213 isNonContextualObjectLiteral = true; 3214 return GlobalsSearch.Continue; 3215 } 3216 } 3217 } 3218 else { 3219 Debug.assert(objectLikeContainer.kind === SyntaxKind.ObjectBindingPattern); 3220 // We are *only* completing on properties from the type being destructured. 3221 isNewIdentifierLocation = false; 3222 3223 const rootDeclaration = getRootDeclaration(objectLikeContainer.parent); 3224 if (!isVariableLike(rootDeclaration)) return Debug.fail("Root declaration is not variable-like."); 3225 3226 // We don't want to complete using the type acquired by the shape 3227 // of the binding pattern; we are only interested in types acquired 3228 // through type declaration or inference. 3229 // Also proceed if rootDeclaration is a parameter and if its containing function expression/arrow function is contextually typed - 3230 // type of parameter will flow in from the contextual type of the function 3231 let canGetType = hasInitializer(rootDeclaration) || !!getEffectiveTypeAnnotationNode(rootDeclaration) || rootDeclaration.parent.parent.kind === SyntaxKind.ForOfStatement; 3232 if (!canGetType && rootDeclaration.kind === SyntaxKind.Parameter) { 3233 if (isExpression(rootDeclaration.parent)) { 3234 canGetType = !!typeChecker.getContextualType(rootDeclaration.parent as Expression); 3235 } 3236 else if (rootDeclaration.parent.kind === SyntaxKind.MethodDeclaration || rootDeclaration.parent.kind === SyntaxKind.SetAccessor) { 3237 canGetType = isExpression(rootDeclaration.parent.parent) && !!typeChecker.getContextualType(rootDeclaration.parent.parent as Expression); 3238 } 3239 } 3240 if (canGetType) { 3241 const typeForObject = typeChecker.getTypeAtLocation(objectLikeContainer); 3242 if (!typeForObject) return GlobalsSearch.Fail; 3243 typeMembers = typeChecker.getPropertiesOfType(typeForObject).filter(propertySymbol => { 3244 return typeChecker.isPropertyAccessible(objectLikeContainer, /*isSuper*/ false, /*writing*/ false, typeForObject, propertySymbol); 3245 }); 3246 existingMembers = objectLikeContainer.elements; 3247 } 3248 } 3249 3250 if (typeMembers && typeMembers.length > 0) { 3251 // Add filtered items to the completion list 3252 const filteredMembers = filterObjectMembersList(typeMembers, Debug.checkDefined(existingMembers)); 3253 symbols = concatenate(symbols, filteredMembers); 3254 setSortTextToOptionalMember(); 3255 if (objectLikeContainer.kind === SyntaxKind.ObjectLiteralExpression 3256 && preferences.includeCompletionsWithObjectLiteralMethodSnippets 3257 && preferences.includeCompletionsWithInsertText) { 3258 transformObjectLiteralMembersSortText(symbolsStartIndex); 3259 collectObjectLiteralMethodSymbols(filteredMembers, objectLikeContainer); 3260 } 3261 } 3262 3263 return GlobalsSearch.Success; 3264 } 3265 3266 /** 3267 * Aggregates relevant symbols for completion in import clauses and export clauses 3268 * whose declarations have a module specifier; for instance, symbols will be aggregated for 3269 * 3270 * import { | } from "moduleName"; 3271 * export { a as foo, | } from "moduleName"; 3272 * 3273 * but not for 3274 * 3275 * export { | }; 3276 * 3277 * Relevant symbols are stored in the captured 'symbols' variable. 3278 */ 3279 function tryGetImportOrExportClauseCompletionSymbols(): GlobalsSearch { 3280 if (!contextToken) return GlobalsSearch.Continue; 3281 3282 // `import { |` or `import { a as 0, | }` or `import { type | }` 3283 const namedImportsOrExports = 3284 contextToken.kind === SyntaxKind.OpenBraceToken || contextToken.kind === SyntaxKind.CommaToken ? tryCast(contextToken.parent, isNamedImportsOrExports) : 3285 isTypeKeywordTokenOrIdentifier(contextToken) ? tryCast(contextToken.parent.parent, isNamedImportsOrExports) : undefined; 3286 3287 if (!namedImportsOrExports) return GlobalsSearch.Continue; 3288 3289 // We can at least offer `type` at `import { |` 3290 if (!isTypeKeywordTokenOrIdentifier(contextToken)) { 3291 keywordFilters = KeywordCompletionFilters.TypeKeyword; 3292 } 3293 3294 // try to show exported member for imported/re-exported module 3295 const { moduleSpecifier } = namedImportsOrExports.kind === SyntaxKind.NamedImports ? namedImportsOrExports.parent.parent : namedImportsOrExports.parent; 3296 if (!moduleSpecifier) { 3297 isNewIdentifierLocation = true; 3298 return namedImportsOrExports.kind === SyntaxKind.NamedImports ? GlobalsSearch.Fail : GlobalsSearch.Continue; 3299 } 3300 const moduleSpecifierSymbol = typeChecker.getSymbolAtLocation(moduleSpecifier); // TODO: GH#18217 3301 if (!moduleSpecifierSymbol) { 3302 isNewIdentifierLocation = true; 3303 return GlobalsSearch.Fail; 3304 } 3305 3306 completionKind = CompletionKind.MemberLike; 3307 isNewIdentifierLocation = false; 3308 const exports = typeChecker.getExportsAndPropertiesOfModule(moduleSpecifierSymbol); 3309 const existing = new Set((namedImportsOrExports.elements as NodeArray<ImportOrExportSpecifier>).filter(n => !isCurrentlyEditingNode(n)).map(n => (n.propertyName || n.name).escapedText)); 3310 const uniques = exports.filter(e => e.escapedName !== InternalSymbolName.Default && !existing.has(e.escapedName)); 3311 symbols = concatenate(symbols, uniques); 3312 if (!uniques.length) { 3313 // If there's nothing else to import, don't offer `type` either 3314 keywordFilters = KeywordCompletionFilters.None; 3315 } 3316 return GlobalsSearch.Success; 3317 } 3318 3319 /** 3320 * Adds local declarations for completions in named exports: 3321 * 3322 * export { | }; 3323 * 3324 * Does not check for the absence of a module specifier (`export {} from "./other"`) 3325 * because `tryGetImportOrExportClauseCompletionSymbols` runs first and handles that, 3326 * preventing this function from running. 3327 */ 3328 function tryGetLocalNamedExportCompletionSymbols(): GlobalsSearch { 3329 const namedExports = contextToken && (contextToken.kind === SyntaxKind.OpenBraceToken || contextToken.kind === SyntaxKind.CommaToken) 3330 ? tryCast(contextToken.parent, isNamedExports) 3331 : undefined; 3332 3333 if (!namedExports) { 3334 return GlobalsSearch.Continue; 3335 } 3336 3337 const localsContainer = findAncestor(namedExports, or(isSourceFile, isModuleDeclaration))!; 3338 completionKind = CompletionKind.None; 3339 isNewIdentifierLocation = false; 3340 localsContainer.locals?.forEach((symbol, name) => { 3341 symbols.push(symbol); 3342 if (localsContainer.symbol?.exports?.has(name)) { 3343 symbolToSortTextMap[getSymbolId(symbol)] = SortText.OptionalMember; 3344 } 3345 }); 3346 return GlobalsSearch.Success; 3347 } 3348 3349 /** 3350 * Aggregates relevant symbols for completion in class declaration 3351 * Relevant symbols are stored in the captured 'symbols' variable. 3352 */ 3353 function tryGetClassLikeCompletionSymbols(): GlobalsSearch { 3354 const decl = tryGetObjectTypeDeclarationCompletionContainer(sourceFile, contextToken, location, position); 3355 if (!decl) return GlobalsSearch.Continue; 3356 3357 // We're looking up possible property names from parent type. 3358 completionKind = CompletionKind.MemberLike; 3359 // Declaring new property/method/accessor 3360 isNewIdentifierLocation = true; 3361 keywordFilters = contextToken.kind === SyntaxKind.AsteriskToken ? KeywordCompletionFilters.None : 3362 isClassLike(decl) ? KeywordCompletionFilters.ClassElementKeywords : KeywordCompletionFilters.InterfaceElementKeywords; 3363 3364 // If you're in an interface you don't want to repeat things from super-interface. So just stop here. 3365 if (!isClassLike(decl)) return GlobalsSearch.Success; 3366 3367 const classElement = contextToken.kind === SyntaxKind.SemicolonToken ? contextToken.parent.parent : contextToken.parent; 3368 let classElementModifierFlags = isClassElement(classElement) ? getEffectiveModifierFlags(classElement) : ModifierFlags.None; 3369 // If this is context token is not something we are editing now, consider if this would lead to be modifier 3370 if (contextToken.kind === SyntaxKind.Identifier && !isCurrentlyEditingNode(contextToken)) { 3371 switch (contextToken.getText()) { 3372 case "private": 3373 classElementModifierFlags = classElementModifierFlags | ModifierFlags.Private; 3374 break; 3375 case "static": 3376 classElementModifierFlags = classElementModifierFlags | ModifierFlags.Static; 3377 break; 3378 case "override": 3379 classElementModifierFlags = classElementModifierFlags | ModifierFlags.Override; 3380 break; 3381 } 3382 } 3383 if (isClassStaticBlockDeclaration(classElement)) { 3384 classElementModifierFlags |= ModifierFlags.Static; 3385 } 3386 3387 // No member list for private methods 3388 if (!(classElementModifierFlags & ModifierFlags.Private)) { 3389 // List of property symbols of base type that are not private and already implemented 3390 const baseTypeNodes = isClassLike(decl) && classElementModifierFlags & ModifierFlags.Override ? singleElementArray(getEffectiveBaseTypeNode(decl)) : getAllSuperTypeNodes(decl); 3391 const baseSymbols = flatMap(baseTypeNodes, baseTypeNode => { 3392 const type = typeChecker.getTypeAtLocation(baseTypeNode); 3393 return classElementModifierFlags & ModifierFlags.Static ? 3394 type?.symbol && typeChecker.getPropertiesOfType(typeChecker.getTypeOfSymbolAtLocation(type.symbol, decl)) : 3395 type && typeChecker.getPropertiesOfType(type); 3396 }); 3397 symbols = concatenate(symbols, filterClassMembersList(baseSymbols, decl.members, classElementModifierFlags)); 3398 } 3399 3400 return GlobalsSearch.Success; 3401 } 3402 3403 function isConstructorParameterCompletion(node: Node): boolean { 3404 return !!node.parent && isParameter(node.parent) && isConstructorDeclaration(node.parent.parent) 3405 && (isParameterPropertyModifier(node.kind) || isDeclarationName(node)); 3406 } 3407 3408 /** 3409 * Returns the immediate owning class declaration of a context token, 3410 * on the condition that one exists and that the context implies completion should be given. 3411 */ 3412 function tryGetConstructorLikeCompletionContainer(contextToken: Node): ConstructorDeclaration | undefined { 3413 if (contextToken) { 3414 const parent = contextToken.parent; 3415 switch (contextToken.kind) { 3416 case SyntaxKind.OpenParenToken: 3417 case SyntaxKind.CommaToken: 3418 return isConstructorDeclaration(contextToken.parent) ? contextToken.parent : undefined; 3419 3420 default: 3421 if (isConstructorParameterCompletion(contextToken)) { 3422 return parent.parent as ConstructorDeclaration; 3423 } 3424 } 3425 } 3426 return undefined; 3427 } 3428 3429 function tryGetFunctionLikeBodyCompletionContainer(contextToken: Node): FunctionLikeDeclaration | undefined { 3430 if (contextToken) { 3431 let prev: Node; 3432 const container = findAncestor(contextToken.parent, (node: Node) => { 3433 if (isClassLike(node)) { 3434 return "quit"; 3435 } 3436 if (isFunctionLikeDeclaration(node) && prev === node.body) { 3437 return true; 3438 } 3439 prev = node; 3440 return false; 3441 }); 3442 return container && container as FunctionLikeDeclaration; 3443 } 3444 } 3445 3446 function tryGetContainingJsxElement(contextToken: Node): JsxOpeningLikeElement | undefined { 3447 if (contextToken) { 3448 const parent = contextToken.parent; 3449 switch (contextToken.kind) { 3450 case SyntaxKind.GreaterThanToken: // End of a type argument list 3451 case SyntaxKind.LessThanSlashToken: 3452 case SyntaxKind.SlashToken: 3453 case SyntaxKind.Identifier: 3454 case SyntaxKind.PropertyAccessExpression: 3455 case SyntaxKind.JsxAttributes: 3456 case SyntaxKind.JsxAttribute: 3457 case SyntaxKind.JsxSpreadAttribute: 3458 if (parent && (parent.kind === SyntaxKind.JsxSelfClosingElement || parent.kind === SyntaxKind.JsxOpeningElement)) { 3459 if (contextToken.kind === SyntaxKind.GreaterThanToken) { 3460 const precedingToken = findPrecedingToken(contextToken.pos, sourceFile, /*startNode*/ undefined); 3461 if (!(parent as JsxOpeningLikeElement).typeArguments || (precedingToken && precedingToken.kind === SyntaxKind.SlashToken)) break; 3462 } 3463 return parent as JsxOpeningLikeElement; 3464 } 3465 else if (parent.kind === SyntaxKind.JsxAttribute) { 3466 // Currently we parse JsxOpeningLikeElement as: 3467 // JsxOpeningLikeElement 3468 // attributes: JsxAttributes 3469 // properties: NodeArray<JsxAttributeLike> 3470 return parent.parent.parent as JsxOpeningLikeElement; 3471 } 3472 break; 3473 3474 // The context token is the closing } or " of an attribute, which means 3475 // its parent is a JsxExpression, whose parent is a JsxAttribute, 3476 // whose parent is a JsxOpeningLikeElement 3477 case SyntaxKind.StringLiteral: 3478 if (parent && ((parent.kind === SyntaxKind.JsxAttribute) || (parent.kind === SyntaxKind.JsxSpreadAttribute))) { 3479 // Currently we parse JsxOpeningLikeElement as: 3480 // JsxOpeningLikeElement 3481 // attributes: JsxAttributes 3482 // properties: NodeArray<JsxAttributeLike> 3483 return parent.parent.parent as JsxOpeningLikeElement; 3484 } 3485 3486 break; 3487 3488 case SyntaxKind.CloseBraceToken: 3489 if (parent && 3490 parent.kind === SyntaxKind.JsxExpression && 3491 parent.parent && parent.parent.kind === SyntaxKind.JsxAttribute) { 3492 // Currently we parse JsxOpeningLikeElement as: 3493 // JsxOpeningLikeElement 3494 // attributes: JsxAttributes 3495 // properties: NodeArray<JsxAttributeLike> 3496 // each JsxAttribute can have initializer as JsxExpression 3497 return parent.parent.parent.parent as JsxOpeningLikeElement; 3498 } 3499 3500 if (parent && parent.kind === SyntaxKind.JsxSpreadAttribute) { 3501 // Currently we parse JsxOpeningLikeElement as: 3502 // JsxOpeningLikeElement 3503 // attributes: JsxAttributes 3504 // properties: NodeArray<JsxAttributeLike> 3505 return parent.parent.parent as JsxOpeningLikeElement; 3506 } 3507 3508 break; 3509 } 3510 } 3511 return undefined; 3512 } 3513 3514 /** 3515 * @returns true if we are certain that the currently edited location must define a new location; false otherwise. 3516 */ 3517 function isSolelyIdentifierDefinitionLocation(contextToken: Node): boolean { 3518 const parent = contextToken.parent; 3519 const containingNodeKind = parent.kind; 3520 switch (contextToken.kind) { 3521 case SyntaxKind.CommaToken: 3522 return containingNodeKind === SyntaxKind.VariableDeclaration || 3523 isVariableDeclarationListButNotTypeArgument(contextToken) || 3524 containingNodeKind === SyntaxKind.VariableStatement || 3525 containingNodeKind === SyntaxKind.EnumDeclaration || // enum a { foo, | 3526 isFunctionLikeButNotConstructor(containingNodeKind) || 3527 containingNodeKind === SyntaxKind.InterfaceDeclaration || // interface A<T, | 3528 containingNodeKind === SyntaxKind.ArrayBindingPattern || // var [x, y| 3529 containingNodeKind === SyntaxKind.TypeAliasDeclaration || // type Map, K, | 3530 // class A<T, | 3531 // var C = class D<T, | 3532 (isClassLike(parent) && 3533 !!parent.typeParameters && 3534 parent.typeParameters.end >= contextToken.pos); 3535 3536 case SyntaxKind.DotToken: 3537 return containingNodeKind === SyntaxKind.ArrayBindingPattern; // var [.| 3538 3539 case SyntaxKind.ColonToken: 3540 return containingNodeKind === SyntaxKind.BindingElement; // var {x :html| 3541 3542 case SyntaxKind.OpenBracketToken: 3543 return containingNodeKind === SyntaxKind.ArrayBindingPattern; // var [x| 3544 3545 case SyntaxKind.OpenParenToken: 3546 return containingNodeKind === SyntaxKind.CatchClause || 3547 isFunctionLikeButNotConstructor(containingNodeKind); 3548 3549 case SyntaxKind.OpenBraceToken: 3550 return containingNodeKind === SyntaxKind.EnumDeclaration; // enum a { | 3551 3552 case SyntaxKind.LessThanToken: 3553 return containingNodeKind === SyntaxKind.ClassDeclaration || // class A< | 3554 containingNodeKind === SyntaxKind.ClassExpression || // var C = class D< | 3555 containingNodeKind === SyntaxKind.InterfaceDeclaration || // interface A< | 3556 containingNodeKind === SyntaxKind.TypeAliasDeclaration || // type List< | 3557 isFunctionLikeKind(containingNodeKind); 3558 3559 case SyntaxKind.StaticKeyword: 3560 return containingNodeKind === SyntaxKind.PropertyDeclaration && !isClassLike(parent.parent); 3561 3562 case SyntaxKind.DotDotDotToken: 3563 return containingNodeKind === SyntaxKind.Parameter || 3564 (!!parent.parent && parent.parent.kind === SyntaxKind.ArrayBindingPattern); // var [...z| 3565 3566 case SyntaxKind.PublicKeyword: 3567 case SyntaxKind.PrivateKeyword: 3568 case SyntaxKind.ProtectedKeyword: 3569 return containingNodeKind === SyntaxKind.Parameter && !isConstructorDeclaration(parent.parent); 3570 3571 case SyntaxKind.AsKeyword: 3572 return containingNodeKind === SyntaxKind.ImportSpecifier || 3573 containingNodeKind === SyntaxKind.ExportSpecifier || 3574 containingNodeKind === SyntaxKind.NamespaceImport; 3575 3576 case SyntaxKind.GetKeyword: 3577 case SyntaxKind.SetKeyword: 3578 return !isFromObjectTypeDeclaration(contextToken); 3579 3580 case SyntaxKind.Identifier: 3581 if (containingNodeKind === SyntaxKind.ImportSpecifier && 3582 contextToken === (parent as ImportSpecifier).name && 3583 (contextToken as Identifier).text === "type" 3584 ) { 3585 // import { type | } 3586 return false; 3587 } 3588 break; 3589 3590 case SyntaxKind.ClassKeyword: 3591 case SyntaxKind.StructKeyword: 3592 case SyntaxKind.EnumKeyword: 3593 case SyntaxKind.InterfaceKeyword: 3594 case SyntaxKind.FunctionKeyword: 3595 case SyntaxKind.VarKeyword: 3596 case SyntaxKind.ImportKeyword: 3597 case SyntaxKind.LetKeyword: 3598 case SyntaxKind.ConstKeyword: 3599 case SyntaxKind.InferKeyword: 3600 return true; 3601 3602 case SyntaxKind.TypeKeyword: 3603 // import { type foo| } 3604 return containingNodeKind !== SyntaxKind.ImportSpecifier; 3605 3606 case SyntaxKind.AsteriskToken: 3607 return isFunctionLike(contextToken.parent) && !isMethodDeclaration(contextToken.parent); 3608 } 3609 3610 // If the previous token is keyword corresponding to class member completion keyword 3611 // there will be completion available here 3612 if (isClassMemberCompletionKeyword(keywordForNode(contextToken)) && isFromObjectTypeDeclaration(contextToken)) { 3613 return false; 3614 } 3615 3616 if (isConstructorParameterCompletion(contextToken)) { 3617 // constructor parameter completion is available only if 3618 // - its modifier of the constructor parameter or 3619 // - its name of the parameter and not being edited 3620 // eg. constructor(a |<- this shouldnt show completion 3621 if (!isIdentifier(contextToken) || 3622 isParameterPropertyModifier(keywordForNode(contextToken)) || 3623 isCurrentlyEditingNode(contextToken)) { 3624 return false; 3625 } 3626 } 3627 3628 // Previous token may have been a keyword that was converted to an identifier. 3629 switch (keywordForNode(contextToken)) { 3630 case SyntaxKind.AbstractKeyword: 3631 case SyntaxKind.ClassKeyword: 3632 case SyntaxKind.StructKeyword: 3633 case SyntaxKind.ConstKeyword: 3634 case SyntaxKind.DeclareKeyword: 3635 case SyntaxKind.EnumKeyword: 3636 case SyntaxKind.FunctionKeyword: 3637 case SyntaxKind.InterfaceKeyword: 3638 case SyntaxKind.LetKeyword: 3639 case SyntaxKind.PrivateKeyword: 3640 case SyntaxKind.ProtectedKeyword: 3641 case SyntaxKind.PublicKeyword: 3642 case SyntaxKind.StaticKeyword: 3643 case SyntaxKind.VarKeyword: 3644 return true; 3645 case SyntaxKind.AsyncKeyword: 3646 return isPropertyDeclaration(contextToken.parent); 3647 } 3648 3649 // If we are inside a class declaration, and `constructor` is totally not present, 3650 // but we request a completion manually at a whitespace... 3651 const ancestorClassLike = findAncestor(contextToken.parent, isClassLike); 3652 if (ancestorClassLike && contextToken === previousToken && isPreviousPropertyDeclarationTerminated(contextToken, position)) { 3653 return false; // Don't block completions. 3654 } 3655 3656 const ancestorPropertyDeclaraion = getAncestor(contextToken.parent, SyntaxKind.PropertyDeclaration); 3657 // If we are inside a class declaration and typing `constructor` after property declaration... 3658 if (ancestorPropertyDeclaraion 3659 && contextToken !== previousToken 3660 && isClassLike(previousToken.parent.parent) 3661 // And the cursor is at the token... 3662 && position <= previousToken.end) { 3663 // If we are sure that the previous property declaration is terminated according to newline or semicolon... 3664 if (isPreviousPropertyDeclarationTerminated(contextToken, previousToken.end)) { 3665 return false; // Don't block completions. 3666 } 3667 else if (contextToken.kind !== SyntaxKind.EqualsToken 3668 // Should not block: `class C { blah = c/**/ }` 3669 // But should block: `class C { blah = somewhat c/**/ }` and `class C { blah: SomeType c/**/ }` 3670 && (isInitializedProperty(ancestorPropertyDeclaraion as PropertyDeclaration) 3671 || hasType(ancestorPropertyDeclaraion))) { 3672 return true; 3673 } 3674 } 3675 3676 return isDeclarationName(contextToken) 3677 && !isShorthandPropertyAssignment(contextToken.parent) 3678 && !isJsxAttribute(contextToken.parent) 3679 // Don't block completions if we're in `class C /**/`, because we're *past* the end of the identifier and might want to complete `extends`. 3680 // If `contextToken !== previousToken`, this is `class C ex/**/`. 3681 && !(isClassLike(contextToken.parent) && (contextToken !== previousToken || position > previousToken.end)); 3682 } 3683 3684 function isPreviousPropertyDeclarationTerminated(contextToken: Node, position: number) { 3685 return contextToken.kind !== SyntaxKind.EqualsToken && 3686 (contextToken.kind === SyntaxKind.SemicolonToken 3687 || !positionsAreOnSameLine(contextToken.end, position, sourceFile)); 3688 } 3689 3690 function isFunctionLikeButNotConstructor(kind: SyntaxKind) { 3691 return isFunctionLikeKind(kind) && kind !== SyntaxKind.Constructor; 3692 } 3693 3694 function isDotOfNumericLiteral(contextToken: Node): boolean { 3695 if (contextToken.kind === SyntaxKind.NumericLiteral) { 3696 const text = contextToken.getFullText(); 3697 return text.charAt(text.length - 1) === "."; 3698 } 3699 3700 return false; 3701 } 3702 3703 function isVariableDeclarationListButNotTypeArgument(node: Node): boolean { 3704 return node.parent.kind === SyntaxKind.VariableDeclarationList 3705 && !isPossiblyTypeArgumentPosition(node, sourceFile, typeChecker); 3706 } 3707 3708 /** 3709 * Filters out completion suggestions for named imports or exports. 3710 * 3711 * @returns Symbols to be suggested in an object binding pattern or object literal expression, barring those whose declarations 3712 * do not occur at the current position and have not otherwise been typed. 3713 */ 3714 function filterObjectMembersList(contextualMemberSymbols: Symbol[], existingMembers: readonly Declaration[]): Symbol[] { 3715 if (existingMembers.length === 0) { 3716 return contextualMemberSymbols; 3717 } 3718 3719 const membersDeclaredBySpreadAssignment = new Set<string>(); 3720 const existingMemberNames = new Set<__String>(); 3721 for (const m of existingMembers) { 3722 // Ignore omitted expressions for missing members 3723 if (m.kind !== SyntaxKind.PropertyAssignment && 3724 m.kind !== SyntaxKind.ShorthandPropertyAssignment && 3725 m.kind !== SyntaxKind.BindingElement && 3726 m.kind !== SyntaxKind.MethodDeclaration && 3727 m.kind !== SyntaxKind.GetAccessor && 3728 m.kind !== SyntaxKind.SetAccessor && 3729 m.kind !== SyntaxKind.SpreadAssignment) { 3730 continue; 3731 } 3732 3733 // If this is the current item we are editing right now, do not filter it out 3734 if (isCurrentlyEditingNode(m)) { 3735 continue; 3736 } 3737 3738 let existingName: __String | undefined; 3739 3740 if (isSpreadAssignment(m)) { 3741 setMembersDeclaredBySpreadAssignment(m, membersDeclaredBySpreadAssignment); 3742 } 3743 else if (isBindingElement(m) && m.propertyName) { 3744 // include only identifiers in completion list 3745 if (m.propertyName.kind === SyntaxKind.Identifier) { 3746 existingName = m.propertyName.escapedText; 3747 } 3748 } 3749 else { 3750 // TODO: Account for computed property name 3751 // NOTE: if one only performs this step when m.name is an identifier, 3752 // things like '__proto__' are not filtered out. 3753 const name = getNameOfDeclaration(m); 3754 existingName = name && isPropertyNameLiteral(name) ? getEscapedTextOfIdentifierOrLiteral(name) : undefined; 3755 } 3756 3757 if (existingName !== undefined) { 3758 existingMemberNames.add(existingName); 3759 } 3760 } 3761 3762 const filteredSymbols = contextualMemberSymbols.filter(m => !existingMemberNames.has(m.escapedName)); 3763 setSortTextToMemberDeclaredBySpreadAssignment(membersDeclaredBySpreadAssignment, filteredSymbols); 3764 3765 return filteredSymbols; 3766 } 3767 3768 function setMembersDeclaredBySpreadAssignment(declaration: SpreadAssignment | JsxSpreadAttribute, membersDeclaredBySpreadAssignment: Set<string>) { 3769 const expression = declaration.expression; 3770 const symbol = typeChecker.getSymbolAtLocation(expression); 3771 const type = symbol && typeChecker.getTypeOfSymbolAtLocation(symbol, expression); 3772 const properties = type && (type as ObjectType).properties; 3773 if (properties) { 3774 properties.forEach(property => { 3775 membersDeclaredBySpreadAssignment.add(property.name); 3776 }); 3777 } 3778 } 3779 3780 // Set SortText to OptionalMember if it is an optional member 3781 function setSortTextToOptionalMember() { 3782 symbols.forEach(m => { 3783 if (m.flags & SymbolFlags.Optional) { 3784 const symbolId = getSymbolId(m); 3785 symbolToSortTextMap[symbolId] = symbolToSortTextMap[symbolId] ?? SortText.OptionalMember; 3786 } 3787 }); 3788 } 3789 3790 // Set SortText to MemberDeclaredBySpreadAssignment if it is fulfilled by spread assignment 3791 function setSortTextToMemberDeclaredBySpreadAssignment(membersDeclaredBySpreadAssignment: Set<string>, contextualMemberSymbols: Symbol[]): void { 3792 if (membersDeclaredBySpreadAssignment.size === 0) { 3793 return; 3794 } 3795 for (const contextualMemberSymbol of contextualMemberSymbols) { 3796 if (membersDeclaredBySpreadAssignment.has(contextualMemberSymbol.name)) { 3797 symbolToSortTextMap[getSymbolId(contextualMemberSymbol)] = SortText.MemberDeclaredBySpreadAssignment; 3798 } 3799 } 3800 } 3801 3802 function transformObjectLiteralMembersSortText(start: number): void { 3803 for (let i = start; i < symbols.length; i++) { 3804 const symbol = symbols[i]; 3805 const symbolId = getSymbolId(symbol); 3806 const origin = symbolToOriginInfoMap?.[i]; 3807 const target = getEmitScriptTarget(compilerOptions); 3808 const displayName = getCompletionEntryDisplayNameForSymbol( 3809 symbol, 3810 target, 3811 origin, 3812 CompletionKind.ObjectPropertyDeclaration, 3813 /*jsxIdentifierExpected*/ false); 3814 if (displayName) { 3815 const originalSortText = symbolToSortTextMap[symbolId] ?? SortText.LocationPriority; 3816 const { name } = displayName; 3817 symbolToSortTextMap[symbolId] = SortText.ObjectLiteralProperty(originalSortText, name); 3818 } 3819 } 3820 } 3821 3822 /** 3823 * Filters out completion suggestions for class elements. 3824 * 3825 * @returns Symbols to be suggested in an class element depending on existing memebers and symbol flags 3826 */ 3827 function filterClassMembersList(baseSymbols: readonly Symbol[], existingMembers: readonly ClassElement[], currentClassElementModifierFlags: ModifierFlags): Symbol[] { 3828 const existingMemberNames = new Set<__String>(); 3829 for (const m of existingMembers) { 3830 // Ignore omitted expressions for missing members 3831 if (m.kind !== SyntaxKind.PropertyDeclaration && 3832 m.kind !== SyntaxKind.MethodDeclaration && 3833 m.kind !== SyntaxKind.GetAccessor && 3834 m.kind !== SyntaxKind.SetAccessor) { 3835 continue; 3836 } 3837 3838 // If this is the current item we are editing right now, do not filter it out 3839 if (isCurrentlyEditingNode(m)) { 3840 continue; 3841 } 3842 3843 // Dont filter member even if the name matches if it is declared private in the list 3844 if (hasEffectiveModifier(m, ModifierFlags.Private)) { 3845 continue; 3846 } 3847 3848 // do not filter it out if the static presence doesnt match 3849 if (isStatic(m) !== !!(currentClassElementModifierFlags & ModifierFlags.Static)) { 3850 continue; 3851 } 3852 3853 const existingName = getPropertyNameForPropertyNameNode(m.name!); 3854 if (existingName) { 3855 existingMemberNames.add(existingName); 3856 } 3857 } 3858 3859 return baseSymbols.filter(propertySymbol => 3860 !existingMemberNames.has(propertySymbol.escapedName) && 3861 !!propertySymbol.declarations && 3862 !(getDeclarationModifierFlagsFromSymbol(propertySymbol) & ModifierFlags.Private) && 3863 !(propertySymbol.valueDeclaration && isPrivateIdentifierClassElementDeclaration(propertySymbol.valueDeclaration))); 3864 } 3865 3866 /** 3867 * Filters out completion suggestions from 'symbols' according to existing JSX attributes. 3868 * 3869 * @returns Symbols to be suggested in a JSX element, barring those whose attributes 3870 * do not occur at the current position and have not otherwise been typed. 3871 */ 3872 function filterJsxAttributes(symbols: Symbol[], attributes: NodeArray<JsxAttribute | JsxSpreadAttribute>): Symbol[] { 3873 const seenNames = new Set<__String>(); 3874 const membersDeclaredBySpreadAssignment = new Set<string>(); 3875 for (const attr of attributes) { 3876 // If this is the current item we are editing right now, do not filter it out 3877 if (isCurrentlyEditingNode(attr)) { 3878 continue; 3879 } 3880 3881 if (attr.kind === SyntaxKind.JsxAttribute) { 3882 seenNames.add(attr.name.escapedText); 3883 } 3884 else if (isJsxSpreadAttribute(attr)) { 3885 setMembersDeclaredBySpreadAssignment(attr, membersDeclaredBySpreadAssignment); 3886 } 3887 } 3888 const filteredSymbols = symbols.filter(a => !seenNames.has(a.escapedName)); 3889 3890 setSortTextToMemberDeclaredBySpreadAssignment(membersDeclaredBySpreadAssignment, filteredSymbols); 3891 3892 return filteredSymbols; 3893 } 3894 3895 function isCurrentlyEditingNode(node: Node): boolean { 3896 return node.getStart(sourceFile) <= position && position <= node.getEnd(); 3897 } 3898 } 3899 3900 /** 3901 * Returns the immediate owning object literal or binding pattern of a context token, 3902 * on the condition that one exists and that the context implies completion should be given. 3903 */ 3904 function tryGetObjectLikeCompletionContainer(contextToken: Node | undefined): ObjectLiteralExpression | ObjectBindingPattern | undefined { 3905 if (contextToken) { 3906 const { parent } = contextToken; 3907 switch (contextToken.kind) { 3908 case SyntaxKind.OpenBraceToken: // const x = { | 3909 case SyntaxKind.CommaToken: // const x = { a: 0, | 3910 if (isObjectLiteralExpression(parent) || isObjectBindingPattern(parent)) { 3911 return parent; 3912 } 3913 break; 3914 case SyntaxKind.AsteriskToken: 3915 return isMethodDeclaration(parent) ? tryCast(parent.parent, isObjectLiteralExpression) : undefined; 3916 case SyntaxKind.Identifier: 3917 return (contextToken as Identifier).text === "async" && isShorthandPropertyAssignment(contextToken.parent) 3918 ? contextToken.parent.parent : undefined; 3919 } 3920 } 3921 3922 return undefined; 3923 } 3924 3925 function getRelevantTokens(position: number, sourceFile: SourceFile): { contextToken: Node, previousToken: Node } | { contextToken: undefined, previousToken: undefined } { 3926 const previousToken = findPrecedingToken(position, sourceFile); 3927 if (previousToken && position <= previousToken.end && (isMemberName(previousToken) || isKeyword(previousToken.kind))) { 3928 const contextToken = findPrecedingToken(previousToken.getFullStart(), sourceFile, /*startNode*/ undefined)!; // TODO: GH#18217 3929 return { contextToken, previousToken }; 3930 } 3931 return { contextToken: previousToken as Node, previousToken: previousToken as Node }; 3932 } 3933 3934 function getAutoImportSymbolFromCompletionEntryData(name: string, data: CompletionEntryData, program: Program, host: LanguageServiceHost): { symbol: Symbol, origin: SymbolOriginInfoExport | SymbolOriginInfoResolvedExport } | undefined { 3935 const containingProgram = data.isPackageJsonImport ? host.getPackageJsonAutoImportProvider!()! : program; 3936 const checker = containingProgram.getTypeChecker(); 3937 const moduleSymbol = 3938 data.ambientModuleName ? checker.tryFindAmbientModule(data.ambientModuleName) : 3939 data.fileName ? checker.getMergedSymbol(Debug.checkDefined(containingProgram.getSourceFile(data.fileName)).symbol) : 3940 undefined; 3941 3942 if (!moduleSymbol) return undefined; 3943 let symbol = data.exportName === InternalSymbolName.ExportEquals 3944 ? checker.resolveExternalModuleSymbol(moduleSymbol) 3945 : checker.tryGetMemberInModuleExportsAndProperties(data.exportName, moduleSymbol); 3946 if (!symbol) return undefined; 3947 const isDefaultExport = data.exportName === InternalSymbolName.Default; 3948 symbol = isDefaultExport && getLocalSymbolForExportDefault(symbol) || symbol; 3949 return { symbol, origin: completionEntryDataToSymbolOriginInfo(data, name, moduleSymbol) }; 3950 } 3951 3952 interface CompletionEntryDisplayNameForSymbol { 3953 readonly name: string; 3954 readonly needsConvertPropertyAccess: boolean; 3955 } 3956 function getCompletionEntryDisplayNameForSymbol( 3957 symbol: Symbol, 3958 target: ScriptTarget, 3959 origin: SymbolOriginInfo | undefined, 3960 kind: CompletionKind, 3961 jsxIdentifierExpected: boolean, 3962 ): CompletionEntryDisplayNameForSymbol | undefined { 3963 const name = originIncludesSymbolName(origin) ? origin.symbolName : symbol.name; 3964 if (name === undefined 3965 // If the symbol is external module, don't show it in the completion list 3966 // (i.e declare module "http" { const x; } | // <= request completion here, "http" should not be there) 3967 || symbol.flags & SymbolFlags.Module && isSingleOrDoubleQuote(name.charCodeAt(0)) 3968 // If the symbol is the internal name of an ES symbol, it is not a valid entry. Internal names for ES symbols start with "__@" 3969 || isKnownSymbol(symbol)) { 3970 return undefined; 3971 } 3972 3973 const validNameResult: CompletionEntryDisplayNameForSymbol = { name, needsConvertPropertyAccess: false }; 3974 if (isIdentifierText(name, target, jsxIdentifierExpected ? LanguageVariant.JSX : LanguageVariant.Standard) || symbol.valueDeclaration && isPrivateIdentifierClassElementDeclaration(symbol.valueDeclaration)) { 3975 return validNameResult; 3976 } 3977 switch (kind) { 3978 case CompletionKind.MemberLike: 3979 return undefined; 3980 case CompletionKind.ObjectPropertyDeclaration: 3981 // TODO: GH#18169 3982 return { name: JSON.stringify(name), needsConvertPropertyAccess: false }; 3983 case CompletionKind.PropertyAccess: 3984 case CompletionKind.Global: // For a 'this.' completion it will be in a global context, but may have a non-identifier name. 3985 // Don't add a completion for a name starting with a space. See https://github.com/Microsoft/TypeScript/pull/20547 3986 return name.charCodeAt(0) === CharacterCodes.space ? undefined : { name, needsConvertPropertyAccess: true }; 3987 case CompletionKind.None: 3988 case CompletionKind.String: 3989 return validNameResult; 3990 default: 3991 Debug.assertNever(kind); 3992 } 3993 } 3994 3995 // A cache of completion entries for keywords, these do not change between sessions 3996 const _keywordCompletions: CompletionEntry[][] = []; 3997 const allKeywordsCompletions: () => readonly CompletionEntry[] = memoize(() => { 3998 const res: CompletionEntry[] = []; 3999 for (let i = SyntaxKind.FirstKeyword; i <= SyntaxKind.LastKeyword; i++) { 4000 res.push({ 4001 name: tokenToString(i)!, 4002 kind: ScriptElementKind.keyword, 4003 kindModifiers: ScriptElementKindModifier.none, 4004 sortText: SortText.GlobalsOrKeywords 4005 }); 4006 } 4007 return res; 4008 }); 4009 4010 function getKeywordCompletions(keywordFilter: KeywordCompletionFilters, filterOutTsOnlyKeywords: boolean): readonly CompletionEntry[] { 4011 if (!filterOutTsOnlyKeywords) return getTypescriptKeywordCompletions(keywordFilter); 4012 4013 const index = keywordFilter + KeywordCompletionFilters.Last + 1; 4014 return _keywordCompletions[index] || 4015 (_keywordCompletions[index] = getTypescriptKeywordCompletions(keywordFilter) 4016 .filter(entry => !isTypeScriptOnlyKeyword(stringToToken(entry.name)!)) 4017 ); 4018 } 4019 4020 function getTypescriptKeywordCompletions(keywordFilter: KeywordCompletionFilters): readonly CompletionEntry[] { 4021 return _keywordCompletions[keywordFilter] || (_keywordCompletions[keywordFilter] = allKeywordsCompletions().filter(entry => { 4022 const kind = stringToToken(entry.name)!; 4023 switch (keywordFilter) { 4024 case KeywordCompletionFilters.None: 4025 return false; 4026 case KeywordCompletionFilters.All: 4027 return isFunctionLikeBodyKeyword(kind) 4028 || kind === SyntaxKind.DeclareKeyword 4029 || kind === SyntaxKind.ModuleKeyword 4030 || kind === SyntaxKind.TypeKeyword 4031 || kind === SyntaxKind.NamespaceKeyword 4032 || kind === SyntaxKind.AbstractKeyword 4033 || isTypeKeyword(kind) && kind !== SyntaxKind.UndefinedKeyword; 4034 case KeywordCompletionFilters.FunctionLikeBodyKeywords: 4035 return isFunctionLikeBodyKeyword(kind); 4036 case KeywordCompletionFilters.ClassElementKeywords: 4037 return isClassMemberCompletionKeyword(kind); 4038 case KeywordCompletionFilters.InterfaceElementKeywords: 4039 return isInterfaceOrTypeLiteralCompletionKeyword(kind); 4040 case KeywordCompletionFilters.ConstructorParameterKeywords: 4041 return isParameterPropertyModifier(kind); 4042 case KeywordCompletionFilters.TypeAssertionKeywords: 4043 return isTypeKeyword(kind) || kind === SyntaxKind.ConstKeyword; 4044 case KeywordCompletionFilters.TypeKeywords: 4045 return isTypeKeyword(kind); 4046 case KeywordCompletionFilters.TypeKeyword: 4047 return kind === SyntaxKind.TypeKeyword; 4048 default: 4049 return Debug.assertNever(keywordFilter); 4050 } 4051 })); 4052 } 4053 4054 function isTypeScriptOnlyKeyword(kind: SyntaxKind) { 4055 switch (kind) { 4056 case SyntaxKind.AbstractKeyword: 4057 case SyntaxKind.AnyKeyword: 4058 case SyntaxKind.BigIntKeyword: 4059 case SyntaxKind.BooleanKeyword: 4060 case SyntaxKind.DeclareKeyword: 4061 case SyntaxKind.EnumKeyword: 4062 case SyntaxKind.GlobalKeyword: 4063 case SyntaxKind.ImplementsKeyword: 4064 case SyntaxKind.InferKeyword: 4065 case SyntaxKind.InterfaceKeyword: 4066 case SyntaxKind.IsKeyword: 4067 case SyntaxKind.KeyOfKeyword: 4068 case SyntaxKind.ModuleKeyword: 4069 case SyntaxKind.NamespaceKeyword: 4070 case SyntaxKind.NeverKeyword: 4071 case SyntaxKind.NumberKeyword: 4072 case SyntaxKind.ObjectKeyword: 4073 case SyntaxKind.OverrideKeyword: 4074 case SyntaxKind.PrivateKeyword: 4075 case SyntaxKind.ProtectedKeyword: 4076 case SyntaxKind.PublicKeyword: 4077 case SyntaxKind.ReadonlyKeyword: 4078 case SyntaxKind.StringKeyword: 4079 case SyntaxKind.SymbolKeyword: 4080 case SyntaxKind.TypeKeyword: 4081 case SyntaxKind.UniqueKeyword: 4082 case SyntaxKind.UnknownKeyword: 4083 return true; 4084 default: 4085 return false; 4086 } 4087 } 4088 4089 function isInterfaceOrTypeLiteralCompletionKeyword(kind: SyntaxKind): boolean { 4090 return kind === SyntaxKind.ReadonlyKeyword; 4091 } 4092 4093 function isClassMemberCompletionKeyword(kind: SyntaxKind) { 4094 switch (kind) { 4095 case SyntaxKind.AbstractKeyword: 4096 case SyntaxKind.AccessorKeyword: 4097 case SyntaxKind.ConstructorKeyword: 4098 case SyntaxKind.GetKeyword: 4099 case SyntaxKind.SetKeyword: 4100 case SyntaxKind.AsyncKeyword: 4101 case SyntaxKind.DeclareKeyword: 4102 case SyntaxKind.OverrideKeyword: 4103 return true; 4104 default: 4105 return isClassMemberModifier(kind); 4106 } 4107 } 4108 4109 function isFunctionLikeBodyKeyword(kind: SyntaxKind) { 4110 return kind === SyntaxKind.AsyncKeyword 4111 || kind === SyntaxKind.AwaitKeyword 4112 || kind === SyntaxKind.AsKeyword 4113 || kind === SyntaxKind.SatisfiesKeyword 4114 || kind === SyntaxKind.TypeKeyword 4115 || !isContextualKeyword(kind) && !isClassMemberCompletionKeyword(kind); 4116 } 4117 4118 function keywordForNode(node: Node): SyntaxKind { 4119 return isIdentifier(node) ? node.originalKeywordKind || SyntaxKind.Unknown : node.kind; 4120 } 4121 4122 function getContextualKeywords( 4123 contextToken: Node | undefined, 4124 position: number, 4125 ): readonly CompletionEntry[] { 4126 const entries = []; 4127 /** 4128 * An `AssertClause` can come after an import declaration: 4129 * import * from "foo" | 4130 * import "foo" | 4131 * or after a re-export declaration that has a module specifier: 4132 * export { foo } from "foo" | 4133 * Source: https://tc39.es/proposal-import-assertions/ 4134 */ 4135 if (contextToken) { 4136 const file = contextToken.getSourceFile(); 4137 const parent = contextToken.parent; 4138 const tokenLine = file.getLineAndCharacterOfPosition(contextToken.end).line; 4139 const currentLine = file.getLineAndCharacterOfPosition(position).line; 4140 if ((isImportDeclaration(parent) || isExportDeclaration(parent) && parent.moduleSpecifier) 4141 && contextToken === parent.moduleSpecifier 4142 && tokenLine === currentLine) { 4143 entries.push({ 4144 name: tokenToString(SyntaxKind.AssertKeyword)!, 4145 kind: ScriptElementKind.keyword, 4146 kindModifiers: ScriptElementKindModifier.none, 4147 sortText: SortText.GlobalsOrKeywords, 4148 }); 4149 } 4150 } 4151 return entries; 4152 } 4153 4154 /** Get the corresponding JSDocTag node if the position is in a jsDoc comment */ 4155 function getJsDocTagAtPosition(node: Node, position: number): JSDocTag | undefined { 4156 return findAncestor(node, n => 4157 isJSDocTag(n) && rangeContainsPosition(n, position) ? true : 4158 isJSDoc(n) ? "quit" : false) as JSDocTag | undefined; 4159 } 4160 4161 export function getPropertiesForObjectExpression(contextualType: Type, completionsType: Type | undefined, obj: ObjectLiteralExpression | JsxAttributes, checker: TypeChecker): Symbol[] { 4162 const hasCompletionsType = completionsType && completionsType !== contextualType; 4163 const type = hasCompletionsType && !(completionsType.flags & TypeFlags.AnyOrUnknown) 4164 ? checker.getUnionType([contextualType, completionsType]) 4165 : contextualType; 4166 4167 const properties = getApparentProperties(type, obj, checker); 4168 return type.isClass() && containsNonPublicProperties(properties) ? [] : 4169 hasCompletionsType ? filter(properties, hasDeclarationOtherThanSelf) : properties; 4170 4171 // Filter out members whose only declaration is the object literal itself to avoid 4172 // self-fulfilling completions like: 4173 // 4174 // function f<T>(x: T) {} 4175 // f({ abc/**/: "" }) // `abc` is a member of `T` but only because it declares itself 4176 function hasDeclarationOtherThanSelf(member: Symbol) { 4177 if (!length(member.declarations)) return true; 4178 return some(member.declarations, decl => decl.parent !== obj); 4179 } 4180 } 4181 4182 function getApparentProperties(type: Type, node: ObjectLiteralExpression | JsxAttributes, checker: TypeChecker) { 4183 if (!type.isUnion()) return type.getApparentProperties(); 4184 return checker.getAllPossiblePropertiesOfTypes(filter(type.types, memberType => 4185 !(memberType.flags & TypeFlags.Primitive 4186 || checker.isArrayLikeType(memberType) 4187 || checker.isTypeInvalidDueToUnionDiscriminant(memberType, node) 4188 || typeHasCallOrConstructSignatures(memberType, checker) 4189 || memberType.isClass() && containsNonPublicProperties(memberType.getApparentProperties())))); 4190 } 4191 4192 function containsNonPublicProperties(props: Symbol[]) { 4193 return some(props, p => !!(getDeclarationModifierFlagsFromSymbol(p) & ModifierFlags.NonPublicAccessibilityModifier)); 4194 } 4195 4196 /** 4197 * Gets all properties on a type, but if that type is a union of several types, 4198 * excludes array-like types or callable/constructable types. 4199 */ 4200 function getPropertiesForCompletion(type: Type, checker: TypeChecker): Symbol[] { 4201 return type.isUnion() 4202 ? Debug.checkEachDefined(checker.getAllPossiblePropertiesOfTypes(type.types), "getAllPossiblePropertiesOfTypes() should all be defined") 4203 : Debug.checkEachDefined(type.getApparentProperties(), "getApparentProperties() should all be defined"); 4204 } 4205 4206 /** 4207 * Returns the immediate owning class declaration of a context token, 4208 * on the condition that one exists and that the context implies completion should be given. 4209 */ 4210 function tryGetObjectTypeDeclarationCompletionContainer(sourceFile: SourceFile, contextToken: Node | undefined, location: Node, position: number): ObjectTypeDeclaration | undefined { 4211 // class c { method() { } | method2() { } } 4212 switch (location.kind) { 4213 case SyntaxKind.SyntaxList: 4214 return tryCast(location.parent, isObjectTypeDeclaration); 4215 case SyntaxKind.EndOfFileToken: 4216 const cls = tryCast(lastOrUndefined(cast(location.parent, isSourceFile).statements), isObjectTypeDeclaration); 4217 if (cls && !findChildOfKind(cls, SyntaxKind.CloseBraceToken, sourceFile)) { 4218 return cls; 4219 } 4220 break; 4221 case SyntaxKind.Identifier: { 4222 const originalKeywordKind = (location as Identifier).originalKeywordKind; 4223 if (originalKeywordKind && isKeyword(originalKeywordKind)) { 4224 return undefined; 4225 } 4226 // class c { public prop = c| } 4227 if (isPropertyDeclaration(location.parent) && location.parent.initializer === location) { 4228 return undefined; 4229 } 4230 // class c extends React.Component { a: () => 1\n compon| } 4231 if (isFromObjectTypeDeclaration(location)) { 4232 return findAncestor(location, isObjectTypeDeclaration); 4233 } 4234 } 4235 } 4236 4237 if (!contextToken) return undefined; 4238 4239 // class C { blah; constructor/**/ } and so on 4240 if (location.kind === SyntaxKind.ConstructorKeyword 4241 // class C { blah \n constructor/**/ } 4242 || (isIdentifier(contextToken) && isPropertyDeclaration(contextToken.parent) && isClassLike(location))) { 4243 return findAncestor(contextToken, isClassLike) as ObjectTypeDeclaration; 4244 } 4245 4246 switch (contextToken.kind) { 4247 case SyntaxKind.EqualsToken: // class c { public prop = | /* global completions */ } 4248 return undefined; 4249 4250 case SyntaxKind.SemicolonToken: // class c {getValue(): number; | } 4251 case SyntaxKind.CloseBraceToken: // class c { method() { } | } 4252 // class c { method() { } b| } 4253 return isFromObjectTypeDeclaration(location) && (location.parent as ClassElement | TypeElement).name === location 4254 ? location.parent.parent as ObjectTypeDeclaration 4255 : tryCast(location, isObjectTypeDeclaration); 4256 case SyntaxKind.OpenBraceToken: // class c { | 4257 case SyntaxKind.CommaToken: // class c {getValue(): number, | } 4258 return tryCast(contextToken.parent, isObjectTypeDeclaration); 4259 default: 4260 if (!isFromObjectTypeDeclaration(contextToken)) { 4261 // class c extends React.Component { a: () => 1\n| } 4262 if (getLineAndCharacterOfPosition(sourceFile, contextToken.getEnd()).line !== getLineAndCharacterOfPosition(sourceFile, position).line && isObjectTypeDeclaration(location)) { 4263 return location; 4264 } 4265 return undefined; 4266 } 4267 const isValidKeyword = isClassLike(contextToken.parent.parent) ? isClassMemberCompletionKeyword : isInterfaceOrTypeLiteralCompletionKeyword; 4268 return (isValidKeyword(contextToken.kind) || contextToken.kind === SyntaxKind.AsteriskToken || isIdentifier(contextToken) && isValidKeyword(stringToToken(contextToken.text)!)) // TODO: GH#18217 4269 ? contextToken.parent.parent as ObjectTypeDeclaration : undefined; 4270 } 4271 } 4272 4273 function tryGetTypeLiteralNode(node: Node): TypeLiteralNode | undefined { 4274 if (!node) return undefined; 4275 4276 const parent = node.parent; 4277 4278 switch (node.kind) { 4279 case SyntaxKind.OpenBraceToken: 4280 if (isTypeLiteralNode(parent)) { 4281 return parent; 4282 } 4283 break; 4284 case SyntaxKind.SemicolonToken: 4285 case SyntaxKind.CommaToken: 4286 case SyntaxKind.Identifier: 4287 if (parent.kind === SyntaxKind.PropertySignature && isTypeLiteralNode(parent.parent)) { 4288 return parent.parent; 4289 } 4290 break; 4291 } 4292 4293 return undefined; 4294 } 4295 4296 function getConstraintOfTypeArgumentProperty(node: Node, checker: TypeChecker): Type | undefined { 4297 if (!node) return undefined; 4298 4299 if (isTypeNode(node) && isTypeReferenceType(node.parent)) { 4300 return checker.getTypeArgumentConstraint(node); 4301 } 4302 4303 const t = getConstraintOfTypeArgumentProperty(node.parent, checker); 4304 if (!t) return undefined; 4305 4306 switch (node.kind) { 4307 case SyntaxKind.PropertySignature: 4308 return checker.getTypeOfPropertyOfContextualType(t, node.symbol.escapedName); 4309 case SyntaxKind.IntersectionType: 4310 case SyntaxKind.TypeLiteral: 4311 case SyntaxKind.UnionType: 4312 return t; 4313 } 4314 } 4315 4316 // TODO: GH#19856 Would like to return `node is Node & { parent: (ClassElement | TypeElement) & { parent: ObjectTypeDeclaration } }` but then compilation takes > 10 minutes 4317 function isFromObjectTypeDeclaration(node: Node): boolean { 4318 return node.parent && isClassOrTypeElement(node.parent) && isObjectTypeDeclaration(node.parent.parent); 4319 } 4320 4321 function isValidTrigger(sourceFile: SourceFile, triggerCharacter: CompletionsTriggerCharacter, contextToken: Node | undefined, position: number): boolean { 4322 switch (triggerCharacter) { 4323 case ".": 4324 case "@": 4325 return true; 4326 case '"': 4327 case "'": 4328 case "`": 4329 // Only automatically bring up completions if this is an opening quote. 4330 return !!contextToken && isStringLiteralOrTemplate(contextToken) && position === contextToken.getStart(sourceFile) + 1; 4331 case "#": 4332 return !!contextToken && isPrivateIdentifier(contextToken) && !!getContainingClass(contextToken); 4333 case "<": 4334 // Opening JSX tag 4335 return !!contextToken && contextToken.kind === SyntaxKind.LessThanToken && (!isBinaryExpression(contextToken.parent) || binaryExpressionMayBeOpenTag(contextToken.parent)); 4336 case "/": 4337 return !!contextToken && (isStringLiteralLike(contextToken) 4338 ? !!tryGetImportFromModuleSpecifier(contextToken) 4339 : contextToken.kind === SyntaxKind.SlashToken && isJsxClosingElement(contextToken.parent)); 4340 case " ": 4341 return !!contextToken && isImportKeyword(contextToken) && contextToken.parent.kind === SyntaxKind.SourceFile; 4342 default: 4343 return Debug.assertNever(triggerCharacter); 4344 } 4345 } 4346 4347 function binaryExpressionMayBeOpenTag({ left }: BinaryExpression): boolean { 4348 return nodeIsMissing(left); 4349 } 4350 4351 /** Determines if a type is exactly the same type resolved by the global 'self', 'global', or 'globalThis'. */ 4352 function isProbablyGlobalType(type: Type, sourceFile: SourceFile, checker: TypeChecker) { 4353 // The type of `self` and `window` is the same in lib.dom.d.ts, but `window` does not exist in 4354 // lib.webworker.d.ts, so checking against `self` is also a check against `window` when it exists. 4355 const selfSymbol = checker.resolveName("self", /*location*/ undefined, SymbolFlags.Value, /*excludeGlobals*/ false); 4356 if (selfSymbol && checker.getTypeOfSymbolAtLocation(selfSymbol, sourceFile) === type) { 4357 return true; 4358 } 4359 const globalSymbol = checker.resolveName("global", /*location*/ undefined, SymbolFlags.Value, /*excludeGlobals*/ false); 4360 if (globalSymbol && checker.getTypeOfSymbolAtLocation(globalSymbol, sourceFile) === type) { 4361 return true; 4362 } 4363 const globalThisSymbol = checker.resolveName("globalThis", /*location*/ undefined, SymbolFlags.Value, /*excludeGlobals*/ false); 4364 if (globalThisSymbol && checker.getTypeOfSymbolAtLocation(globalThisSymbol, sourceFile) === type) { 4365 return true; 4366 } 4367 return false; 4368 } 4369 4370 function isStaticProperty(symbol: Symbol) { 4371 return !!(symbol.valueDeclaration && getEffectiveModifierFlags(symbol.valueDeclaration) & ModifierFlags.Static && isClassLike(symbol.valueDeclaration.parent)); 4372 } 4373 4374 function tryGetObjectLiteralContextualType(node: ObjectLiteralExpression, typeChecker: TypeChecker) { 4375 const type = typeChecker.getContextualType(node); 4376 if (type) { 4377 return type; 4378 } 4379 const parent = walkUpParenthesizedExpressions(node.parent); 4380 if (isBinaryExpression(parent) && parent.operatorToken.kind === SyntaxKind.EqualsToken && node === parent.left) { 4381 // Object literal is assignment pattern: ({ | } = x) 4382 return typeChecker.getTypeAtLocation(parent); 4383 } 4384 if (isExpression(parent)) { 4385 // f(() => (({ | }))); 4386 return typeChecker.getContextualType(parent); 4387 } 4388 return undefined; 4389 } 4390 4391 interface ImportStatementCompletionInfo { 4392 isKeywordOnlyCompletion: boolean; 4393 keywordCompletion: TokenSyntaxKind | undefined; 4394 isNewIdentifierLocation: boolean; 4395 isTopLevelTypeOnly: boolean; 4396 couldBeTypeOnlyImportSpecifier: boolean; 4397 replacementSpan: TextSpan | undefined; 4398 } 4399 4400 function getImportStatementCompletionInfo(contextToken: Node): ImportStatementCompletionInfo { 4401 let keywordCompletion: TokenSyntaxKind | undefined; 4402 let isKeywordOnlyCompletion = false; 4403 const candidate = getCandidate(); 4404 return { 4405 isKeywordOnlyCompletion, 4406 keywordCompletion, 4407 isNewIdentifierLocation: !!(candidate || keywordCompletion === SyntaxKind.TypeKeyword), 4408 isTopLevelTypeOnly: !!tryCast(candidate, isImportDeclaration)?.importClause?.isTypeOnly || !!tryCast(candidate, isImportEqualsDeclaration)?.isTypeOnly, 4409 couldBeTypeOnlyImportSpecifier: !!candidate && couldBeTypeOnlyImportSpecifier(candidate, contextToken), 4410 replacementSpan: getSingleLineReplacementSpanForImportCompletionNode(candidate), 4411 }; 4412 4413 function getCandidate() { 4414 const parent = contextToken.parent; 4415 if (isImportEqualsDeclaration(parent)) { 4416 keywordCompletion = contextToken.kind === SyntaxKind.TypeKeyword ? undefined : SyntaxKind.TypeKeyword; 4417 return isModuleSpecifierMissingOrEmpty(parent.moduleReference) ? parent : undefined; 4418 } 4419 if (couldBeTypeOnlyImportSpecifier(parent, contextToken) && canCompleteFromNamedBindings(parent.parent)) { 4420 return parent; 4421 } 4422 if (isNamedImports(parent) || isNamespaceImport(parent)) { 4423 if (!parent.parent.isTypeOnly && ( 4424 contextToken.kind === SyntaxKind.OpenBraceToken || 4425 contextToken.kind === SyntaxKind.ImportKeyword || 4426 contextToken.kind === SyntaxKind.CommaToken 4427 )) { 4428 keywordCompletion = SyntaxKind.TypeKeyword; 4429 } 4430 4431 if (canCompleteFromNamedBindings(parent)) { 4432 // At `import { ... } |` or `import * as Foo |`, the only possible completion is `from` 4433 if (contextToken.kind === SyntaxKind.CloseBraceToken || contextToken.kind === SyntaxKind.Identifier) { 4434 isKeywordOnlyCompletion = true; 4435 keywordCompletion = SyntaxKind.FromKeyword; 4436 } 4437 else { 4438 return parent.parent.parent; 4439 } 4440 } 4441 return undefined; 4442 } 4443 if (isImportKeyword(contextToken) && isSourceFile(parent)) { 4444 // A lone import keyword with nothing following it does not parse as a statement at all 4445 keywordCompletion = SyntaxKind.TypeKeyword; 4446 return contextToken as Token<SyntaxKind.ImportKeyword>; 4447 } 4448 if (isImportKeyword(contextToken) && isImportDeclaration(parent)) { 4449 // `import s| from` 4450 keywordCompletion = SyntaxKind.TypeKeyword; 4451 return isModuleSpecifierMissingOrEmpty(parent.moduleSpecifier) ? parent : undefined; 4452 } 4453 return undefined; 4454 } 4455 } 4456 4457 function getSingleLineReplacementSpanForImportCompletionNode(node: ImportDeclaration | ImportEqualsDeclaration | ImportSpecifier | Token<SyntaxKind.ImportKeyword> | undefined) { 4458 if (!node) return undefined; 4459 const top = findAncestor(node, or(isImportDeclaration, isImportEqualsDeclaration)) ?? node; 4460 const sourceFile = top.getSourceFile(); 4461 if (rangeIsOnSingleLine(top, sourceFile)) { 4462 return createTextSpanFromNode(top, sourceFile); 4463 } 4464 // ImportKeyword was necessarily on one line; ImportSpecifier was necessarily parented in an ImportDeclaration 4465 Debug.assert(top.kind !== SyntaxKind.ImportKeyword && top.kind !== SyntaxKind.ImportSpecifier); 4466 // Guess which point in the import might actually be a later statement parsed as part of the import 4467 // during parser recovery - either in the middle of named imports, or the module specifier. 4468 const potentialSplitPoint = top.kind === SyntaxKind.ImportDeclaration 4469 ? getPotentiallyInvalidImportSpecifier(top.importClause?.namedBindings) ?? top.moduleSpecifier 4470 : top.moduleReference; 4471 const withoutModuleSpecifier: TextRange = { 4472 pos: top.getFirstToken()!.getStart(), 4473 end: potentialSplitPoint.pos, 4474 }; 4475 // The module specifier/reference was previously found to be missing, empty, or 4476 // not a string literal - in this last case, it's likely that statement on a following 4477 // line was parsed as the module specifier of a partially-typed import, e.g. 4478 // import Foo| 4479 // interface Blah {} 4480 // This appears to be a multiline-import, and editors can't replace multiple lines. 4481 // But if everything but the "module specifier" is on one line, by this point we can 4482 // assume that the "module specifier" is actually just another statement, and return 4483 // the single-line range of the import excluding that probable statement. 4484 if (rangeIsOnSingleLine(withoutModuleSpecifier, sourceFile)) { 4485 return createTextSpanFromRange(withoutModuleSpecifier); 4486 } 4487 } 4488 4489 // Tries to identify the first named import that is not really a named import, but rather 4490 // just parser recovery for a situation like: 4491 // import { Foo| 4492 // interface Bar {} 4493 // in which `Foo`, `interface`, and `Bar` are all parsed as import specifiers. The caller 4494 // will also check if this token is on a separate line from the rest of the import. 4495 function getPotentiallyInvalidImportSpecifier(namedBindings: NamedImportBindings | undefined) { 4496 return find( 4497 tryCast(namedBindings, isNamedImports)?.elements, 4498 e => !e.propertyName && 4499 isStringANonContextualKeyword(e.name.text) && 4500 findPrecedingToken(e.name.pos, namedBindings!.getSourceFile(), namedBindings)?.kind !== SyntaxKind.CommaToken); 4501 } 4502 4503 function couldBeTypeOnlyImportSpecifier(importSpecifier: Node, contextToken: Node | undefined): importSpecifier is ImportSpecifier { 4504 return isImportSpecifier(importSpecifier) 4505 && (importSpecifier.isTypeOnly || contextToken === importSpecifier.name && isTypeKeywordTokenOrIdentifier(contextToken)); 4506 } 4507 4508 function canCompleteFromNamedBindings(namedBindings: NamedImportBindings) { 4509 if (!isModuleSpecifierMissingOrEmpty(namedBindings.parent.parent.moduleSpecifier) || namedBindings.parent.name) { 4510 return false; 4511 } 4512 if (isNamedImports(namedBindings)) { 4513 // We can only complete on named imports if there are no other named imports already, 4514 // but parser recovery sometimes puts later statements in the named imports list, so 4515 // we try to only consider the probably-valid ones. 4516 const invalidNamedImport = getPotentiallyInvalidImportSpecifier(namedBindings); 4517 const validImports = invalidNamedImport ? namedBindings.elements.indexOf(invalidNamedImport) : namedBindings.elements.length; 4518 return validImports < 2; 4519 } 4520 return true; 4521 } 4522 4523 function isModuleSpecifierMissingOrEmpty(specifier: ModuleReference | Expression) { 4524 if (nodeIsMissing(specifier)) return true; 4525 return !tryCast(isExternalModuleReference(specifier) ? specifier.expression : specifier, isStringLiteralLike)?.text; 4526 } 4527 4528 function getVariableDeclaration(property: Node): VariableDeclaration | undefined { 4529 const variableDeclaration = findAncestor(property, node => 4530 isFunctionBlock(node) || isArrowFunctionBody(node) || isBindingPattern(node) 4531 ? "quit" 4532 : isVariableDeclaration(node)); 4533 4534 return variableDeclaration as VariableDeclaration | undefined; 4535 } 4536 4537 function isArrowFunctionBody(node: Node) { 4538 return node.parent && isArrowFunction(node.parent) && node.parent.body === node; 4539 } 4540 4541 /** True if symbol is a type or a module containing at least one type. */ 4542 function symbolCanBeReferencedAtTypeLocation(symbol: Symbol, checker: TypeChecker, seenModules = new Map<SymbolId, true>()): boolean { 4543 // Since an alias can be merged with a local declaration, we need to test both the alias and its target. 4544 // This code used to just test the result of `skipAlias`, but that would ignore any locally introduced meanings. 4545 return nonAliasCanBeReferencedAtTypeLocation(symbol) || nonAliasCanBeReferencedAtTypeLocation(skipAlias(symbol.exportSymbol || symbol, checker)); 4546 4547 function nonAliasCanBeReferencedAtTypeLocation(symbol: Symbol): boolean { 4548 return !!(symbol.flags & SymbolFlags.Type) || checker.isUnknownSymbol(symbol) || 4549 !!(symbol.flags & SymbolFlags.Module) && addToSeen(seenModules, getSymbolId(symbol)) && 4550 checker.getExportsOfModule(symbol).some(e => symbolCanBeReferencedAtTypeLocation(e, checker, seenModules)); 4551 } 4552 } 4553 4554 function isDeprecated(symbol: Symbol, checker: TypeChecker) { 4555 const declarations = skipAlias(symbol, checker).declarations; 4556 return !!length(declarations) && every(declarations, isDeprecatedDeclaration); 4557 } 4558 4559 /** 4560 * True if the first character of `lowercaseCharacters` is the first character 4561 * of some "word" in `identiferString` (where the string is split into "words" 4562 * by camelCase and snake_case segments), then if the remaining characters of 4563 * `lowercaseCharacters` appear, in order, in the rest of `identifierString`. 4564 * 4565 * True: 4566 * 'state' in 'useState' 4567 * 'sae' in 'useState' 4568 * 'viable' in 'ENVIRONMENT_VARIABLE' 4569 * 4570 * False: 4571 * 'staet' in 'useState' 4572 * 'tate' in 'useState' 4573 * 'ment' in 'ENVIRONMENT_VARIABLE' 4574 */ 4575 function charactersFuzzyMatchInString(identifierString: string, lowercaseCharacters: string): boolean { 4576 if (lowercaseCharacters.length === 0) { 4577 return true; 4578 } 4579 4580 let matchedFirstCharacter = false; 4581 let prevChar: number | undefined; 4582 let characterIndex = 0; 4583 const len = identifierString.length; 4584 for (let strIndex = 0; strIndex < len; strIndex++) { 4585 const strChar = identifierString.charCodeAt(strIndex); 4586 const testChar = lowercaseCharacters.charCodeAt(characterIndex); 4587 if (strChar === testChar || strChar === toUpperCharCode(testChar)) { 4588 matchedFirstCharacter ||= 4589 prevChar === undefined || // Beginning of word 4590 CharacterCodes.a <= prevChar && prevChar <= CharacterCodes.z && CharacterCodes.A <= strChar && strChar <= CharacterCodes.Z || // camelCase transition 4591 prevChar === CharacterCodes._ && strChar !== CharacterCodes._; // snake_case transition 4592 if (matchedFirstCharacter) { 4593 characterIndex++; 4594 } 4595 if (characterIndex === lowercaseCharacters.length) { 4596 return true; 4597 } 4598 } 4599 prevChar = strChar; 4600 } 4601 4602 // Did not find all characters 4603 return false; 4604 } 4605 4606 function toUpperCharCode(charCode: number) { 4607 if (CharacterCodes.a <= charCode && charCode <= CharacterCodes.z) { 4608 return charCode - 32; 4609 } 4610 return charCode; 4611 } 4612 4613} 4614 4615