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