1/* @internal */ 2namespace ts.Completions { 3 export enum SortText { 4 LocalDeclarationPriority = "0", 5 LocationPriority = "1", 6 OptionalMember = "2", 7 MemberDeclaredBySpreadAssignment = "3", 8 SuggestedClassMembers = "4", 9 GlobalsOrKeywords = "5", 10 AutoImportSuggestions = "6", 11 JavascriptIdentifiers = "7" 12 } 13 export type Log = (message: string) => void; 14 15 /** 16 * Special values for `CompletionInfo['source']` used to disambiguate 17 * completion items with the same `name`. (Each completion item must 18 * have a unique name/source combination, because those two fields 19 * comprise `CompletionEntryIdentifier` in `getCompletionEntryDetails`. 20 * 21 * When the completion item is an auto-import suggestion, the source 22 * is the module specifier of the suggestion. To avoid collisions, 23 * the values here should not be a module specifier we would ever 24 * generate for an auto-import. 25 */ 26 export enum CompletionSource { 27 /** Completions that require `this.` insertion text */ 28 ThisProperty = "ThisProperty/" 29 } 30 31 const enum SymbolOriginInfoKind { 32 ThisType = 1 << 0, 33 SymbolMember = 1 << 1, 34 Export = 1 << 2, 35 Promise = 1 << 3, 36 Nullable = 1 << 4, 37 38 SymbolMemberNoExport = SymbolMember, 39 SymbolMemberExport = SymbolMember | Export, 40 } 41 42 interface SymbolOriginInfo { 43 kind: SymbolOriginInfoKind; 44 } 45 46 interface SymbolOriginInfoExport extends SymbolOriginInfo { 47 kind: SymbolOriginInfoKind; 48 moduleSymbol: Symbol; 49 isDefaultExport: boolean; 50 isFromPackageJson?: boolean; 51 } 52 53 function originIsThisType(origin: SymbolOriginInfo): boolean { 54 return !!(origin.kind & SymbolOriginInfoKind.ThisType); 55 } 56 57 function originIsSymbolMember(origin: SymbolOriginInfo): boolean { 58 return !!(origin.kind & SymbolOriginInfoKind.SymbolMember); 59 } 60 61 function originIsExport(origin: SymbolOriginInfo | undefined): origin is SymbolOriginInfoExport { 62 return !!(origin && origin.kind & SymbolOriginInfoKind.Export); 63 } 64 65 function originIsPackageJsonImport(origin: SymbolOriginInfo | undefined): origin is SymbolOriginInfoExport { 66 return originIsExport(origin) && !!origin.isFromPackageJson; 67 } 68 69 function originIsPromise(origin: SymbolOriginInfo): boolean { 70 return !!(origin.kind & SymbolOriginInfoKind.Promise); 71 } 72 73 function originIsNullableMember(origin: SymbolOriginInfo): boolean { 74 return !!(origin.kind & SymbolOriginInfoKind.Nullable); 75 } 76 77 interface UniqueNameSet { 78 add(name: string): void; 79 has(name: string): boolean; 80 } 81 82 /** 83 * Map from symbol id -> SymbolOriginInfo. 84 * Only populated for symbols that come from other modules. 85 */ 86 type SymbolOriginInfoMap = (SymbolOriginInfo | SymbolOriginInfoExport | undefined)[]; 87 88 type SymbolSortTextMap = (SortText | undefined)[]; 89 90 const enum KeywordCompletionFilters { 91 None, // No keywords 92 All, // Every possible keyword (TODO: This is never appropriate) 93 ClassElementKeywords, // Keywords inside class body 94 InterfaceElementKeywords, // Keywords inside interface body 95 ConstructorParameterKeywords, // Keywords at constructor parameter 96 FunctionLikeBodyKeywords, // Keywords at function like body 97 TypeAssertionKeywords, 98 TypeKeywords, 99 Last = TypeKeywords 100 } 101 102 const enum GlobalsSearch { Continue, Success, Fail } 103 104 export interface AutoImportSuggestion { 105 symbol: Symbol; 106 symbolName: string; 107 skipFilter: boolean; 108 origin: SymbolOriginInfoExport; 109 } 110 export interface ImportSuggestionsForFileCache { 111 clear(): void; 112 get(fileName: string, checker: TypeChecker, projectVersion?: string): readonly AutoImportSuggestion[] | undefined; 113 set(fileName: string, suggestions: readonly AutoImportSuggestion[], projectVersion?: string): void; 114 isEmpty(): boolean; 115 } 116 export function createImportSuggestionsForFileCache(): ImportSuggestionsForFileCache { 117 let cache: readonly AutoImportSuggestion[] | undefined; 118 let projectVersion: string | undefined; 119 let fileName: string | undefined; 120 return { 121 isEmpty() { 122 return !cache; 123 }, 124 clear: () => { 125 cache = undefined; 126 fileName = undefined; 127 projectVersion = undefined; 128 }, 129 set: (file, suggestions, version) => { 130 cache = suggestions; 131 fileName = file; 132 if (version) { 133 projectVersion = version; 134 } 135 }, 136 get: (file, checker, version) => { 137 if (file !== fileName) { 138 return undefined; 139 } 140 if (version) { 141 return projectVersion === version ? cache : undefined; 142 } 143 forEach(cache, suggestion => { 144 // If the symbol/moduleSymbol was a merged symbol, it will have a new identity 145 // in the checker, even though the symbols to merge are the same (guaranteed by 146 // cache invalidation in synchronizeHostData). 147 if (suggestion.symbol.declarations?.length) { 148 suggestion.symbol = checker.getMergedSymbol(suggestion.origin.isDefaultExport 149 ? suggestion.symbol.declarations[0].localSymbol ?? suggestion.symbol.declarations[0].symbol 150 : suggestion.symbol.declarations[0].symbol); 151 } 152 if (suggestion.origin.moduleSymbol.declarations?.length) { 153 suggestion.origin.moduleSymbol = checker.getMergedSymbol(suggestion.origin.moduleSymbol.declarations[0].symbol); 154 } 155 }); 156 return cache; 157 }, 158 }; 159 } 160 161 export function getCompletionsAtPosition( 162 host: LanguageServiceHost, 163 program: Program, 164 log: Log, 165 sourceFile: SourceFile, 166 position: number, 167 preferences: UserPreferences, 168 triggerCharacter: CompletionsTriggerCharacter | undefined 169 ): CompletionInfo | undefined { 170 const typeChecker = program.getTypeChecker(); 171 const compilerOptions = program.getCompilerOptions(); 172 173 const contextToken = findPrecedingToken(position, sourceFile); 174 if (triggerCharacter && !isInString(sourceFile, position, contextToken) && !isValidTrigger(sourceFile, triggerCharacter, contextToken, position)) { 175 return undefined; 176 } 177 178 const stringCompletions = StringCompletions.getStringLiteralCompletions(sourceFile, position, contextToken, typeChecker, compilerOptions, host, log, preferences); 179 if (stringCompletions) { 180 return stringCompletions; 181 } 182 183 if (contextToken && isBreakOrContinueStatement(contextToken.parent) 184 && (contextToken.kind === SyntaxKind.BreakKeyword || contextToken.kind === SyntaxKind.ContinueKeyword || contextToken.kind === SyntaxKind.Identifier)) { 185 return getLabelCompletionAtPosition(contextToken.parent); 186 } 187 188 const completionData = getCompletionData(program, log, sourceFile, isUncheckedFile(sourceFile, compilerOptions), position, preferences, /*detailsEntryId*/ undefined, host); 189 if (!completionData) { 190 return undefined; 191 } 192 193 switch (completionData.kind) { 194 case CompletionDataKind.Data: 195 return completionInfoFromData(sourceFile, typeChecker, compilerOptions, log, completionData, preferences); 196 case CompletionDataKind.JsDocTagName: 197 // If the current position is a jsDoc tag name, only tag names should be provided for completion 198 return jsdocCompletionInfo(JsDoc.getJSDocTagNameCompletions()); 199 case CompletionDataKind.JsDocTag: 200 // If the current position is a jsDoc tag, only tags should be provided for completion 201 return jsdocCompletionInfo(JsDoc.getJSDocTagCompletions()); 202 case CompletionDataKind.JsDocParameterName: 203 return jsdocCompletionInfo(JsDoc.getJSDocParameterNameCompletions(completionData.tag)); 204 default: 205 return Debug.assertNever(completionData); 206 } 207 } 208 209 function jsdocCompletionInfo(entries: CompletionEntry[]): CompletionInfo { 210 return { isGlobalCompletion: false, isMemberCompletion: false, isNewIdentifierLocation: false, entries }; 211 } 212 213 function getOptionalReplacementSpan(location: Node | undefined) { 214 // StringLiteralLike locations are handled separately in stringCompletions.ts 215 return location?.kind === SyntaxKind.Identifier ? createTextSpanFromNode(location) : undefined; 216 } 217 218 function completionInfoFromData(sourceFile: SourceFile, typeChecker: TypeChecker, compilerOptions: CompilerOptions, log: Log, completionData: CompletionData, preferences: UserPreferences): CompletionInfo | undefined { 219 const { 220 symbols, 221 completionKind, 222 isInSnippetScope, 223 isNewIdentifierLocation, 224 location, 225 propertyAccessToConvert, 226 keywordFilters, 227 literals, 228 symbolToOriginInfoMap, 229 recommendedCompletion, 230 isJsxInitializer, 231 insideJsDocTagTypeExpression, 232 symbolToSortTextMap, 233 } = completionData; 234 235 if (location && location.parent && isJsxClosingElement(location.parent)) { 236 // In the TypeScript JSX element, if such element is not defined. When users query for completion at closing tag, 237 // instead of simply giving unknown value, the completion will return the tag-name of an associated opening-element. 238 // For example: 239 // var x = <div> </ /*1*/ 240 // The completion list at "1" will contain "div>" with type any 241 // And at `<div> </ /*1*/ >` (with a closing `>`), the completion list will contain "div". 242 const tagName = location.parent.parent.openingElement.tagName; 243 const hasClosingAngleBracket = !!findChildOfKind(location.parent, SyntaxKind.GreaterThanToken, sourceFile); 244 const entry: CompletionEntry = { 245 name: tagName.getFullText(sourceFile) + (hasClosingAngleBracket ? "" : ">"), 246 kind: ScriptElementKind.classElement, 247 kindModifiers: undefined, 248 sortText: SortText.LocationPriority, 249 }; 250 return { isGlobalCompletion: false, isMemberCompletion: true, isNewIdentifierLocation: false, optionalReplacementSpan: getOptionalReplacementSpan(location), entries: [entry] }; 251 } 252 253 const entries: CompletionEntry[] = []; 254 255 if (isUncheckedFile(sourceFile, compilerOptions)) { 256 const uniqueNames = getCompletionEntriesFromSymbols( 257 symbols, 258 entries, 259 /* contextToken */ undefined, 260 location, 261 sourceFile, 262 typeChecker, 263 compilerOptions.target!, 264 log, 265 completionKind, 266 preferences, 267 propertyAccessToConvert, 268 completionData.isJsxIdentifierExpected, 269 isJsxInitializer, 270 recommendedCompletion, 271 symbolToOriginInfoMap, 272 symbolToSortTextMap 273 ); 274 getJSCompletionEntries(sourceFile, location!.pos, uniqueNames, compilerOptions.target!, entries); // TODO: GH#18217 275 } 276 else { 277 if (!isNewIdentifierLocation && (!symbols || symbols.length === 0) && keywordFilters === KeywordCompletionFilters.None) { 278 return undefined; 279 } 280 281 getCompletionEntriesFromSymbols( 282 symbols, 283 entries, 284 /* contextToken */ undefined, 285 location, 286 sourceFile, 287 typeChecker, 288 compilerOptions.target!, 289 log, 290 completionKind, 291 preferences, 292 propertyAccessToConvert, 293 completionData.isJsxIdentifierExpected, 294 isJsxInitializer, 295 recommendedCompletion, 296 symbolToOriginInfoMap, 297 symbolToSortTextMap 298 ); 299 } 300 301 if (keywordFilters !== KeywordCompletionFilters.None) { 302 const entryNames = new Set(entries.map(e => e.name)); 303 for (const keywordEntry of getKeywordCompletions(keywordFilters, !insideJsDocTagTypeExpression && isSourceFileJS(sourceFile))) { 304 if (!entryNames.has(keywordEntry.name)) { 305 entries.push(keywordEntry); 306 } 307 } 308 } 309 310 for (const literal of literals) { 311 entries.push(createCompletionEntryForLiteral(sourceFile, preferences, literal)); 312 } 313 314 return { 315 isGlobalCompletion: isInSnippetScope, 316 isMemberCompletion: isMemberCompletionKind(completionKind), 317 isNewIdentifierLocation, 318 optionalReplacementSpan: getOptionalReplacementSpan(location), 319 entries 320 }; 321 } 322 323 function isUncheckedFile(sourceFile: SourceFile, compilerOptions: CompilerOptions): boolean { 324 return isSourceFileJS(sourceFile) && !isCheckJsEnabledForFile(sourceFile, compilerOptions); 325 } 326 327 function isMemberCompletionKind(kind: CompletionKind): boolean { 328 switch (kind) { 329 case CompletionKind.ObjectPropertyDeclaration: 330 case CompletionKind.MemberLike: 331 case CompletionKind.PropertyAccess: 332 return true; 333 default: 334 return false; 335 } 336 } 337 338 function getJSCompletionEntries( 339 sourceFile: SourceFile, 340 position: number, 341 uniqueNames: UniqueNameSet, 342 target: ScriptTarget, 343 entries: Push<CompletionEntry>): void { 344 getNameTable(sourceFile).forEach((pos, name) => { 345 // Skip identifiers produced only from the current location 346 if (pos === position) { 347 return; 348 } 349 const realName = unescapeLeadingUnderscores(name); 350 if (!uniqueNames.has(realName) && isIdentifierText(realName, target)) { 351 uniqueNames.add(realName); 352 entries.push({ 353 name: realName, 354 kind: ScriptElementKind.warning, 355 kindModifiers: "", 356 sortText: SortText.JavascriptIdentifiers, 357 isFromUncheckedFile: true 358 }); 359 } 360 }); 361 } 362 363 function completionNameForLiteral(sourceFile: SourceFile, preferences: UserPreferences, literal: string | number | PseudoBigInt): string { 364 return typeof literal === "object" ? pseudoBigIntToString(literal) + "n" : 365 isString(literal) ? quote(sourceFile, preferences, literal) : JSON.stringify(literal); 366 } 367 368 function createCompletionEntryForLiteral(sourceFile: SourceFile, preferences: UserPreferences, literal: string | number | PseudoBigInt): CompletionEntry { 369 return { name: completionNameForLiteral(sourceFile, preferences, literal), kind: ScriptElementKind.string, kindModifiers: ScriptElementKindModifier.none, sortText: SortText.LocationPriority }; 370 } 371 372 function createCompletionEntry( 373 symbol: Symbol, 374 sortText: SortText, 375 contextToken: Node | undefined, 376 location: Node | undefined, 377 sourceFile: SourceFile, 378 typeChecker: TypeChecker, 379 name: string, 380 needsConvertPropertyAccess: boolean, 381 origin: SymbolOriginInfo | undefined, 382 recommendedCompletion: Symbol | undefined, 383 propertyAccessToConvert: PropertyAccessExpression | undefined, 384 isJsxInitializer: IsJsxInitializer | undefined, 385 preferences: UserPreferences, 386 ): CompletionEntry | undefined { 387 let insertText: string | undefined; 388 let replacementSpan = getReplacementSpanForContextToken(contextToken); 389 390 const insertQuestionDot = origin && originIsNullableMember(origin); 391 const useBraces = origin && originIsSymbolMember(origin) || needsConvertPropertyAccess; 392 if (origin && originIsThisType(origin)) { 393 insertText = needsConvertPropertyAccess 394 ? `this${insertQuestionDot ? "?." : ""}[${quotePropertyName(sourceFile, preferences, name)}]` 395 : `this${insertQuestionDot ? "?." : "."}${name}`; 396 } 397 // We should only have needsConvertPropertyAccess if there's a property access to convert. But see #21790. 398 // 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. 399 else if ((useBraces || insertQuestionDot) && propertyAccessToConvert) { 400 insertText = useBraces ? needsConvertPropertyAccess ? `[${quotePropertyName(sourceFile, preferences, name)}]` : `[${name}]` : name; 401 if (insertQuestionDot || propertyAccessToConvert.questionDotToken) { 402 insertText = `?.${insertText}`; 403 } 404 405 const dot = findChildOfKind(propertyAccessToConvert, SyntaxKind.DotToken, sourceFile) || 406 findChildOfKind(propertyAccessToConvert, SyntaxKind.QuestionDotToken, sourceFile); 407 if (!dot) { 408 return undefined; 409 } 410 // If the text after the '.' starts with this name, write over it. Else, add new text. 411 const end = startsWith(name, propertyAccessToConvert.name.text) ? propertyAccessToConvert.name.end : dot.end; 412 replacementSpan = createTextSpanFromBounds(dot.getStart(sourceFile), end); 413 } 414 415 if (isJsxInitializer) { 416 if (insertText === undefined) insertText = name; 417 insertText = `{${insertText}}`; 418 if (typeof isJsxInitializer !== "boolean") { 419 replacementSpan = createTextSpanFromNode(isJsxInitializer, sourceFile); 420 } 421 } 422 if (origin && originIsPromise(origin) && propertyAccessToConvert) { 423 if (insertText === undefined) insertText = name; 424 const precedingToken = findPrecedingToken(propertyAccessToConvert.pos, sourceFile); 425 let awaitText = ""; 426 if (precedingToken && positionIsASICandidate(precedingToken.end, precedingToken.parent, sourceFile)) { 427 awaitText = ";"; 428 } 429 430 awaitText += `(await ${propertyAccessToConvert.expression.getText()})`; 431 insertText = needsConvertPropertyAccess ? `${awaitText}${insertText}` : `${awaitText}${insertQuestionDot ? "?." : "."}${insertText}`; 432 replacementSpan = createTextSpanFromBounds(propertyAccessToConvert.getStart(sourceFile), propertyAccessToConvert.end); 433 } 434 435 if (insertText !== undefined && !preferences.includeCompletionsWithInsertText) { 436 return undefined; 437 } 438 439 // TODO(drosen): Right now we just permit *all* semantic meanings when calling 440 // 'getSymbolKind' which is permissible given that it is backwards compatible; but 441 // really we should consider passing the meaning for the node so that we don't report 442 // that a suggestion for a value is an interface. We COULD also just do what 443 // 'getSymbolModifiers' does, which is to use the first declaration. 444 445 // Use a 'sortText' of 0' so that all symbol completion entries come before any other 446 // entries (like JavaScript identifier entries). 447 return { 448 name, 449 kind: SymbolDisplay.getSymbolKind(typeChecker, symbol, location!), // TODO: GH#18217 450 kindModifiers: SymbolDisplay.getSymbolModifiers(typeChecker, symbol), 451 sortText, 452 source: getSourceFromOrigin(origin), 453 hasAction: origin && originIsExport(origin) || undefined, 454 isRecommended: isRecommendedCompletionMatch(symbol, recommendedCompletion, typeChecker) || undefined, 455 insertText, 456 replacementSpan, 457 isPackageJsonImport: originIsPackageJsonImport(origin) || undefined, 458 }; 459 } 460 461 function quotePropertyName(sourceFile: SourceFile, preferences: UserPreferences, name: string,): string { 462 if (/^\d+$/.test(name)) { 463 return name; 464 } 465 466 return quote(sourceFile, preferences, name); 467 } 468 469 function isRecommendedCompletionMatch(localSymbol: Symbol, recommendedCompletion: Symbol | undefined, checker: TypeChecker): boolean { 470 return localSymbol === recommendedCompletion || 471 !!(localSymbol.flags & SymbolFlags.ExportValue) && checker.getExportSymbolOfSymbol(localSymbol) === recommendedCompletion; 472 } 473 474 function getSourceFromOrigin(origin: SymbolOriginInfo | undefined): string | undefined { 475 if (originIsExport(origin)) { 476 return stripQuotes(origin.moduleSymbol.name); 477 } 478 if (origin?.kind === SymbolOriginInfoKind.ThisType) { 479 return CompletionSource.ThisProperty; 480 } 481 } 482 483 export function getCompletionEntriesFromSymbols( 484 symbols: readonly Symbol[], 485 entries: Push<CompletionEntry>, 486 contextToken: Node | undefined, 487 location: Node | undefined, 488 sourceFile: SourceFile, 489 typeChecker: TypeChecker, 490 target: ScriptTarget, 491 log: Log, 492 kind: CompletionKind, 493 preferences: UserPreferences, 494 propertyAccessToConvert?: PropertyAccessExpression, 495 jsxIdentifierExpected?: boolean, 496 isJsxInitializer?: IsJsxInitializer, 497 recommendedCompletion?: Symbol, 498 symbolToOriginInfoMap?: SymbolOriginInfoMap, 499 symbolToSortTextMap?: SymbolSortTextMap, 500 ): UniqueNameSet { 501 const start = timestamp(); 502 // Tracks unique names. 503 // Value is set to false for global variables or completions from external module exports, because we can have multiple of those; 504 // true otherwise. Based on the order we add things we will always see locals first, then globals, then module exports. 505 // So adding a completion for a local will prevent us from adding completions for external module exports sharing the same name. 506 const uniques = new Map<string, boolean>(); 507 for (const symbol of symbols) { 508 const origin = symbolToOriginInfoMap ? symbolToOriginInfoMap[getSymbolId(symbol)] : undefined; 509 const info = getCompletionEntryDisplayNameForSymbol(symbol, target, origin, kind, !!jsxIdentifierExpected); 510 if (!info) { 511 continue; 512 } 513 const { name, needsConvertPropertyAccess } = info; 514 if (uniques.get(name)) { 515 continue; 516 } 517 518 const entry = createCompletionEntry( 519 symbol, 520 symbolToSortTextMap && symbolToSortTextMap[getSymbolId(symbol)] || SortText.LocationPriority, 521 contextToken, 522 location, 523 sourceFile, 524 typeChecker, 525 name, 526 needsConvertPropertyAccess, 527 origin, 528 recommendedCompletion, 529 propertyAccessToConvert, 530 isJsxInitializer, 531 preferences 532 ); 533 if (!entry) { 534 continue; 535 } 536 537 /** True for locals; false for globals, module exports from other files, `this.` completions. */ 538 const shouldShadowLaterSymbols = !origin && !(symbol.parent === undefined && !some(symbol.declarations, d => d.getSourceFile() === location!.getSourceFile())); 539 uniques.set(name, shouldShadowLaterSymbols); 540 // add jsDoc info at interface getCompletionsAtPosition 541 if (symbol.getJsDocTags().length > 0) { 542 entry.jsDoc = symbol.getJsDocTags(); 543 } 544 // add displayParts info at interface getCompletionsAtPosition 545 if (symbol.declarations) { 546 const symbolDisplayPartsDocumentationAndSymbolKind = SymbolDisplay.getSymbolDisplayPartsDocumentationAndSymbolKind(typeChecker, symbol, sourceFile, location, location!, SemanticMeaning.All); 547 entry.displayParts = symbolDisplayPartsDocumentationAndSymbolKind.displayParts; 548 } 549 entries.push(entry); 550 } 551 552 log("getCompletionsAtPosition: getCompletionEntriesFromSymbols: " + (timestamp() - start)); 553 554 // Prevent consumers of this map from having to worry about 555 // the boolean value. Externally, it should be seen as the 556 // set of all names. 557 return { 558 has: name => uniques.has(name), 559 add: name => uniques.set(name, true), 560 }; 561 } 562 563 function getLabelCompletionAtPosition(node: BreakOrContinueStatement): CompletionInfo | undefined { 564 const entries = getLabelStatementCompletions(node); 565 if (entries.length) { 566 return { isGlobalCompletion: false, isMemberCompletion: false, isNewIdentifierLocation: false, entries }; 567 } 568 } 569 570 function getLabelStatementCompletions(node: Node): CompletionEntry[] { 571 const entries: CompletionEntry[] = []; 572 const uniques = new Map<string, true>(); 573 let current = node; 574 575 while (current) { 576 if (isFunctionLike(current)) { 577 break; 578 } 579 if (isLabeledStatement(current)) { 580 const name = current.label.text; 581 if (!uniques.has(name)) { 582 uniques.set(name, true); 583 entries.push({ 584 name, 585 kindModifiers: ScriptElementKindModifier.none, 586 kind: ScriptElementKind.label, 587 sortText: SortText.LocationPriority 588 }); 589 } 590 } 591 current = current.parent; 592 } 593 return entries; 594 } 595 596 interface SymbolCompletion { 597 type: "symbol"; 598 symbol: Symbol; 599 location: Node | undefined; 600 symbolToOriginInfoMap: SymbolOriginInfoMap; 601 previousToken: Node | undefined; 602 readonly isJsxInitializer: IsJsxInitializer; 603 readonly isTypeOnlyLocation: boolean; 604 } 605 function getSymbolCompletionFromEntryId( 606 program: Program, 607 log: Log, 608 sourceFile: SourceFile, 609 position: number, 610 entryId: CompletionEntryIdentifier, 611 host: LanguageServiceHost, 612 preferences: UserPreferences, 613 ): SymbolCompletion | { type: "request", request: Request } | { type: "literal", literal: string | number | PseudoBigInt } | { type: "none" } { 614 const compilerOptions = program.getCompilerOptions(); 615 const completionData = getCompletionData(program, log, sourceFile, isUncheckedFile(sourceFile, compilerOptions), position, { includeCompletionsForModuleExports: true, includeCompletionsWithInsertText: true }, entryId, host); 616 if (!completionData) { 617 return { type: "none" }; 618 } 619 if (completionData.kind !== CompletionDataKind.Data) { 620 return { type: "request", request: completionData }; 621 } 622 623 const { symbols, literals, location, completionKind, symbolToOriginInfoMap, previousToken, isJsxInitializer, isTypeOnlyLocation } = completionData; 624 625 const literal = find(literals, l => completionNameForLiteral(sourceFile, preferences, l) === entryId.name); 626 if (literal !== undefined) return { type: "literal", literal }; 627 628 // Find the symbol with the matching entry name. 629 // We don't need to perform character checks here because we're only comparing the 630 // name against 'entryName' (which is known to be good), not building a new 631 // completion entry. 632 return firstDefined(symbols, (symbol): SymbolCompletion | undefined => { 633 const origin = symbolToOriginInfoMap[getSymbolId(symbol)]; 634 const info = getCompletionEntryDisplayNameForSymbol(symbol, compilerOptions.target!, origin, completionKind, completionData.isJsxIdentifierExpected); 635 return info && info.name === entryId.name && getSourceFromOrigin(origin) === entryId.source 636 ? { type: "symbol" as const, symbol, location, symbolToOriginInfoMap, previousToken, isJsxInitializer, isTypeOnlyLocation } 637 : undefined; 638 }) || { type: "none" }; 639 } 640 641 export interface CompletionEntryIdentifier { 642 name: string; 643 source?: string; 644 } 645 646 export function getCompletionEntryDetails( 647 program: Program, 648 log: Log, 649 sourceFile: SourceFile, 650 position: number, 651 entryId: CompletionEntryIdentifier, 652 host: LanguageServiceHost, 653 formatContext: formatting.FormatContext, 654 preferences: UserPreferences, 655 cancellationToken: CancellationToken, 656 ): CompletionEntryDetails | undefined { 657 const typeChecker = program.getTypeChecker(); 658 const compilerOptions = program.getCompilerOptions(); 659 const { name } = entryId; 660 661 const contextToken = findPrecedingToken(position, sourceFile); 662 if (isInString(sourceFile, position, contextToken)) { 663 return StringCompletions.getStringLiteralCompletionDetails(name, sourceFile, position, contextToken, typeChecker, compilerOptions, host, cancellationToken); 664 } 665 666 // Compute all the completion symbols again. 667 const symbolCompletion = getSymbolCompletionFromEntryId(program, log, sourceFile, position, entryId, host, preferences); 668 switch (symbolCompletion.type) { 669 case "request": { 670 const { request } = symbolCompletion; 671 switch (request.kind) { 672 case CompletionDataKind.JsDocTagName: 673 return JsDoc.getJSDocTagNameCompletionDetails(name); 674 case CompletionDataKind.JsDocTag: 675 return JsDoc.getJSDocTagCompletionDetails(name); 676 case CompletionDataKind.JsDocParameterName: 677 return JsDoc.getJSDocParameterNameCompletionDetails(name); 678 default: 679 return Debug.assertNever(request); 680 } 681 } 682 case "symbol": { 683 const { symbol, location, symbolToOriginInfoMap, previousToken } = symbolCompletion; 684 const { codeActions, sourceDisplay } = getCompletionEntryCodeActionsAndSourceDisplay(symbolToOriginInfoMap, symbol, program, typeChecker, host, compilerOptions, sourceFile, position, previousToken, formatContext, preferences); 685 return createCompletionDetailsForSymbol(symbol, typeChecker, sourceFile, location!, cancellationToken, codeActions, sourceDisplay); // TODO: GH#18217 686 } 687 case "literal": { 688 const { literal } = symbolCompletion; 689 return createSimpleDetails(completionNameForLiteral(sourceFile, preferences, literal), ScriptElementKind.string, typeof literal === "string" ? SymbolDisplayPartKind.stringLiteral : SymbolDisplayPartKind.numericLiteral); 690 } 691 case "none": 692 // Didn't find a symbol with this name. See if we can find a keyword instead. 693 return allKeywordsCompletions().some(c => c.name === name) ? createSimpleDetails(name, ScriptElementKind.keyword, SymbolDisplayPartKind.keyword) : undefined; 694 default: 695 Debug.assertNever(symbolCompletion); 696 } 697 } 698 699 function createSimpleDetails(name: string, kind: ScriptElementKind, kind2: SymbolDisplayPartKind): CompletionEntryDetails { 700 return createCompletionDetails(name, ScriptElementKindModifier.none, kind, [displayPart(name, kind2)]); 701 } 702 703 export function createCompletionDetailsForSymbol(symbol: Symbol, checker: TypeChecker, sourceFile: SourceFile, location: Node, cancellationToken: CancellationToken, codeActions?: CodeAction[], sourceDisplay?: SymbolDisplayPart[]): CompletionEntryDetails { 704 const { displayParts, documentation, symbolKind, tags } = 705 checker.runWithCancellationToken(cancellationToken, checker => 706 SymbolDisplay.getSymbolDisplayPartsDocumentationAndSymbolKind(checker, symbol, sourceFile, location, location, SemanticMeaning.All) 707 ); 708 return createCompletionDetails(symbol.name, SymbolDisplay.getSymbolModifiers(checker, symbol), symbolKind, displayParts, documentation, tags, codeActions, sourceDisplay); 709 } 710 711 export function createCompletionDetails(name: string, kindModifiers: string, kind: ScriptElementKind, displayParts: SymbolDisplayPart[], documentation?: SymbolDisplayPart[], tags?: JSDocTagInfo[], codeActions?: CodeAction[], source?: SymbolDisplayPart[]): CompletionEntryDetails { 712 return { name, kindModifiers, kind, displayParts, documentation, tags, codeActions, source }; 713 } 714 715 interface CodeActionsAndSourceDisplay { 716 readonly codeActions: CodeAction[] | undefined; 717 readonly sourceDisplay: SymbolDisplayPart[] | undefined; 718 } 719 function getCompletionEntryCodeActionsAndSourceDisplay( 720 symbolToOriginInfoMap: SymbolOriginInfoMap, 721 symbol: Symbol, 722 program: Program, 723 checker: TypeChecker, 724 host: LanguageServiceHost, 725 compilerOptions: CompilerOptions, 726 sourceFile: SourceFile, 727 position: number, 728 previousToken: Node | undefined, 729 formatContext: formatting.FormatContext, 730 preferences: UserPreferences, 731 ): CodeActionsAndSourceDisplay { 732 const symbolOriginInfo = symbolToOriginInfoMap[getSymbolId(symbol)]; 733 if (!symbolOriginInfo || !originIsExport(symbolOriginInfo)) { 734 return { codeActions: undefined, sourceDisplay: undefined }; 735 } 736 737 const { moduleSymbol } = symbolOriginInfo; 738 const exportedSymbol = checker.getMergedSymbol(skipAlias(symbol.exportSymbol || symbol, checker)); 739 const { moduleSpecifier, codeAction } = codefix.getImportCompletionAction( 740 exportedSymbol, 741 moduleSymbol, 742 sourceFile, 743 getNameForExportedSymbol(symbol, compilerOptions.target), 744 host, 745 program, 746 formatContext, 747 previousToken && isIdentifier(previousToken) ? previousToken.getStart(sourceFile) : position, 748 preferences); 749 return { sourceDisplay: [textPart(moduleSpecifier)], codeActions: [codeAction] }; 750 } 751 752 export function getCompletionEntrySymbol( 753 program: Program, 754 log: Log, 755 sourceFile: SourceFile, 756 position: number, 757 entryId: CompletionEntryIdentifier, 758 host: LanguageServiceHost, 759 preferences: UserPreferences, 760 ): Symbol | undefined { 761 const completion = getSymbolCompletionFromEntryId(program, log, sourceFile, position, entryId, host, preferences); 762 return completion.type === "symbol" ? completion.symbol : undefined; 763 } 764 765 const enum CompletionDataKind { Data, JsDocTagName, JsDocTag, JsDocParameterName } 766 /** true: after the `=` sign but no identifier has been typed yet. Else is the Identifier after the initializer. */ 767 type IsJsxInitializer = boolean | Identifier; 768 interface CompletionData { 769 readonly kind: CompletionDataKind.Data; 770 readonly symbols: readonly Symbol[]; 771 readonly completionKind: CompletionKind; 772 readonly isInSnippetScope: boolean; 773 /** 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. */ 774 readonly propertyAccessToConvert: PropertyAccessExpression | undefined; 775 readonly isNewIdentifierLocation: boolean; 776 readonly location: Node | undefined; 777 readonly keywordFilters: KeywordCompletionFilters; 778 readonly literals: readonly (string | number | PseudoBigInt)[]; 779 readonly symbolToOriginInfoMap: SymbolOriginInfoMap; 780 readonly recommendedCompletion: Symbol | undefined; 781 readonly previousToken: Node | undefined; 782 readonly isJsxInitializer: IsJsxInitializer; 783 readonly insideJsDocTagTypeExpression: boolean; 784 readonly symbolToSortTextMap: SymbolSortTextMap; 785 readonly isTypeOnlyLocation: boolean; 786 /** In JSX tag name and attribute names, identifiers like "my-tag" or "aria-name" is valid identifier. */ 787 readonly isJsxIdentifierExpected: boolean; 788 } 789 type Request = { readonly kind: CompletionDataKind.JsDocTagName | CompletionDataKind.JsDocTag } | { readonly kind: CompletionDataKind.JsDocParameterName, tag: JSDocParameterTag }; 790 791 export const enum CompletionKind { 792 ObjectPropertyDeclaration, 793 Global, 794 PropertyAccess, 795 MemberLike, 796 String, 797 None, 798 } 799 800 function getRecommendedCompletion(previousToken: Node, contextualType: Type, checker: TypeChecker): Symbol | undefined { 801 // For a union, return the first one with a recommended completion. 802 return firstDefined(contextualType && (contextualType.isUnion() ? contextualType.types : [contextualType]), type => { 803 const symbol = type && type.symbol; 804 // Don't include make a recommended completion for an abstract class 805 return symbol && (symbol.flags & (SymbolFlags.EnumMember | SymbolFlags.Enum | SymbolFlags.Class) && !isAbstractConstructorSymbol(symbol)) 806 ? getFirstSymbolInChain(symbol, previousToken, checker) 807 : undefined; 808 }); 809 } 810 811 function getContextualType(previousToken: Node, position: number, sourceFile: SourceFile, checker: TypeChecker): Type | undefined { 812 const { parent } = previousToken; 813 switch (previousToken.kind) { 814 case SyntaxKind.Identifier: 815 return getContextualTypeFromParent(previousToken as Identifier, checker); 816 case SyntaxKind.EqualsToken: 817 switch (parent.kind) { 818 case SyntaxKind.VariableDeclaration: 819 return checker.getContextualType((parent as VariableDeclaration).initializer!); // TODO: GH#18217 820 case SyntaxKind.BinaryExpression: 821 return checker.getTypeAtLocation((parent as BinaryExpression).left); 822 case SyntaxKind.JsxAttribute: 823 return checker.getContextualTypeForJsxAttribute(parent as JsxAttribute); 824 default: 825 return undefined; 826 } 827 case SyntaxKind.NewKeyword: 828 return checker.getContextualType(parent as Expression); 829 case SyntaxKind.CaseKeyword: 830 return getSwitchedType(cast(parent, isCaseClause), checker); 831 case SyntaxKind.OpenBraceToken: 832 return isJsxExpression(parent) && parent.parent.kind !== SyntaxKind.JsxElement ? checker.getContextualTypeForJsxAttribute(parent.parent) : undefined; 833 default: 834 const argInfo = SignatureHelp.getArgumentInfoForCompletions(previousToken, position, sourceFile); 835 return argInfo ? 836 // At `,`, treat this as the next argument after the comma. 837 checker.getContextualTypeForArgumentAtIndex(argInfo.invocation, argInfo.argumentIndex + (previousToken.kind === SyntaxKind.CommaToken ? 1 : 0)) : 838 isEqualityOperatorKind(previousToken.kind) && isBinaryExpression(parent) && isEqualityOperatorKind(parent.operatorToken.kind) ? 839 // completion at `x ===/**/` should be for the right side 840 checker.getTypeAtLocation(parent.left) : 841 checker.getContextualType(previousToken as Expression); 842 } 843 } 844 845 function getFirstSymbolInChain(symbol: Symbol, enclosingDeclaration: Node, checker: TypeChecker): Symbol | undefined { 846 const chain = checker.getAccessibleSymbolChain(symbol, enclosingDeclaration, /*meaning*/ SymbolFlags.All, /*useOnlyExternalAliasing*/ false); 847 if (chain) return first(chain); 848 return symbol.parent && (isModuleSymbol(symbol.parent) ? symbol : getFirstSymbolInChain(symbol.parent, enclosingDeclaration, checker)); 849 } 850 851 function isModuleSymbol(symbol: Symbol): boolean { 852 return symbol.declarations.some(d => d.kind === SyntaxKind.SourceFile); 853 } 854 855 function getCompletionData( 856 program: Program, 857 log: (message: string) => void, 858 sourceFile: SourceFile, 859 isUncheckedFile: boolean, 860 position: number, 861 preferences: Pick<UserPreferences, "includeCompletionsForModuleExports" | "includeCompletionsWithInsertText" | "includeAutomaticOptionalChainCompletions">, 862 detailsEntryId: CompletionEntryIdentifier | undefined, 863 host: LanguageServiceHost 864 ): CompletionData | Request | undefined { 865 const isEtsFile = sourceFile.scriptKind === ScriptKind.ETS; 866 const typeChecker = program.getTypeChecker(); 867 const compilerOptions = program.getCompilerOptions(); 868 869 let start = timestamp(); 870 let currentToken = getTokenAtPosition(sourceFile, position); // TODO: GH#15853 871 // We will check for jsdoc comments with insideComment and getJsDocTagAtPosition. (TODO: that seems rather inefficient to check the same thing so many times.) 872 873 log("getCompletionData: Get current token: " + (timestamp() - start)); 874 875 start = timestamp(); 876 const insideComment = isInComment(sourceFile, position, currentToken); 877 log("getCompletionData: Is inside comment: " + (timestamp() - start)); 878 879 let insideJsDocTagTypeExpression = false; 880 let isInSnippetScope = false; 881 if (insideComment) { 882 if (hasDocComment(sourceFile, position)) { 883 if (sourceFile.text.charCodeAt(position - 1) === CharacterCodes.at) { 884 // The current position is next to the '@' sign, when no tag name being provided yet. 885 // Provide a full list of tag names 886 return { kind: CompletionDataKind.JsDocTagName }; 887 } 888 else { 889 // When completion is requested without "@", we will have check to make sure that 890 // there are no comments prefix the request position. We will only allow "*" and space. 891 // e.g 892 // /** |c| /* 893 // 894 // /** 895 // |c| 896 // */ 897 // 898 // /** 899 // * |c| 900 // */ 901 // 902 // /** 903 // * |c| 904 // */ 905 const lineStart = getLineStartPositionForPosition(position, sourceFile); 906 if (!/[^\*|\s(/)]/.test(sourceFile.text.substring(lineStart, position))) { 907 return { kind: CompletionDataKind.JsDocTag }; 908 } 909 } 910 } 911 912 // Completion should work inside certain JsDoc tags. For example: 913 // /** @type {number | string} */ 914 // Completion should work in the brackets 915 const tag = getJsDocTagAtPosition(currentToken, position); 916 if (tag) { 917 if (tag.tagName.pos <= position && position <= tag.tagName.end) { 918 return { kind: CompletionDataKind.JsDocTagName }; 919 } 920 if (isTagWithTypeExpression(tag) && tag.typeExpression && tag.typeExpression.kind === SyntaxKind.JSDocTypeExpression) { 921 currentToken = getTokenAtPosition(sourceFile, position); 922 if (!currentToken || 923 (!isDeclarationName(currentToken) && 924 (currentToken.parent.kind !== SyntaxKind.JSDocPropertyTag || 925 (<JSDocPropertyTag>currentToken.parent).name !== currentToken))) { 926 // Use as type location if inside tag's type expression 927 insideJsDocTagTypeExpression = isCurrentlyEditingNode(tag.typeExpression); 928 } 929 } 930 if (!insideJsDocTagTypeExpression && isJSDocParameterTag(tag) && (nodeIsMissing(tag.name) || tag.name.pos <= position && position <= tag.name.end)) { 931 return { kind: CompletionDataKind.JsDocParameterName, tag }; 932 } 933 } 934 935 if (!insideJsDocTagTypeExpression) { 936 // Proceed if the current position is in jsDoc tag expression; otherwise it is a normal 937 // comment or the plain text part of a jsDoc comment, so no completion should be available 938 log("Returning an empty list because completion was inside a regular comment or plain text part of a JsDoc comment."); 939 return undefined; 940 } 941 } 942 943 start = timestamp(); 944 const previousToken = findPrecedingToken(position, sourceFile, /*startNode*/ undefined)!; // TODO: GH#18217 945 log("getCompletionData: Get previous token 1: " + (timestamp() - start)); 946 947 // The decision to provide completion depends on the contextToken, which is determined through the previousToken. 948 // Note: 'previousToken' (and thus 'contextToken') can be undefined if we are the beginning of the file 949 let contextToken = previousToken; 950 951 // Check if the caret is at the end of an identifier; this is a partial identifier that we want to complete: e.g. a.toS| 952 // Skip this partial identifier and adjust the contextToken to the token that precedes it. 953 if (contextToken && position <= contextToken.end && (isIdentifierOrPrivateIdentifier(contextToken) || isKeyword(contextToken.kind))) { 954 const start = timestamp(); 955 contextToken = findPrecedingToken(contextToken.getFullStart(), sourceFile, /*startNode*/ undefined)!; // TODO: GH#18217 956 log("getCompletionData: Get previous token 2: " + (timestamp() - start)); 957 } 958 959 // Find the node where completion is requested on. 960 // Also determine whether we are trying to complete with members of that node 961 // or attributes of a JSX tag. 962 let node = currentToken; 963 let propertyAccessToConvert: PropertyAccessExpression | undefined; 964 let isRightOfDot = false; 965 let isRightOfQuestionDot = false; 966 let isRightOfOpenTag = false; 967 let isStartingCloseTag = false; 968 let isJsxInitializer: IsJsxInitializer = false; 969 let isJsxIdentifierExpected = false; 970 971 let location = getTouchingPropertyName(sourceFile, position); 972 if (contextToken) { 973 // Bail out if this is a known invalid completion location 974 if (isCompletionListBlocker(contextToken)) { 975 log("Returning an empty list because completion was requested in an invalid position."); 976 return undefined; 977 } 978 979 let parent = contextToken.parent; 980 if (contextToken.kind === SyntaxKind.DotToken || contextToken.kind === SyntaxKind.QuestionDotToken) { 981 isRightOfDot = contextToken.kind === SyntaxKind.DotToken; 982 isRightOfQuestionDot = contextToken.kind === SyntaxKind.QuestionDotToken; 983 switch (parent.kind) { 984 case SyntaxKind.PropertyAccessExpression: 985 propertyAccessToConvert = parent as PropertyAccessExpression; 986 node = propertyAccessToConvert.expression; 987 if ((isCallExpression(node) || isFunctionLike(node) || isEtsComponentExpression(node)) && 988 node.end === contextToken.pos && 989 node.getChildCount(sourceFile) && 990 last(node.getChildren(sourceFile)).kind !== SyntaxKind.CloseParenToken && !node.getLastToken(sourceFile)) { 991 // This is likely dot from incorrectly parsed expression and user is starting to write spread 992 // eg: Math.min(./**/) 993 // const x = function (./**/) {} 994 return undefined; 995 } 996 if (node.virtual && findPrecedingToken(node.pos, sourceFile)?.kind === SyntaxKind.OpenParenToken) { 997 return undefined; 998 } 999 break; 1000 case SyntaxKind.QualifiedName: 1001 node = (parent as QualifiedName).left; 1002 break; 1003 case SyntaxKind.ModuleDeclaration: 1004 node = (parent as ModuleDeclaration).name; 1005 break; 1006 case SyntaxKind.ImportType: 1007 case SyntaxKind.MetaProperty: 1008 node = parent; 1009 break; 1010 default: 1011 // There is nothing that precedes the dot, so this likely just a stray character 1012 // or leading into a '...' token. Just bail out instead. 1013 return undefined; 1014 } 1015 } 1016 else if (sourceFile.languageVariant === LanguageVariant.JSX) { 1017 // <UI.Test /* completion position */ /> 1018 // If the tagname is a property access expression, we will then walk up to the top most of property access expression. 1019 // Then, try to get a JSX container and its associated attributes type. 1020 if (parent && parent.kind === SyntaxKind.PropertyAccessExpression) { 1021 contextToken = parent; 1022 parent = parent.parent; 1023 } 1024 1025 // Fix location 1026 if (currentToken.parent === location) { 1027 switch (currentToken.kind) { 1028 case SyntaxKind.GreaterThanToken: 1029 if (currentToken.parent.kind === SyntaxKind.JsxElement || currentToken.parent.kind === SyntaxKind.JsxOpeningElement) { 1030 location = currentToken; 1031 } 1032 break; 1033 1034 case SyntaxKind.SlashToken: 1035 if (currentToken.parent.kind === SyntaxKind.JsxSelfClosingElement) { 1036 location = currentToken; 1037 } 1038 break; 1039 } 1040 } 1041 1042 switch (parent.kind) { 1043 case SyntaxKind.JsxClosingElement: 1044 if (contextToken.kind === SyntaxKind.SlashToken) { 1045 isStartingCloseTag = true; 1046 location = contextToken; 1047 } 1048 break; 1049 1050 case SyntaxKind.BinaryExpression: 1051 if (!binaryExpressionMayBeOpenTag(parent as BinaryExpression)) { 1052 break; 1053 } 1054 // falls through 1055 1056 case SyntaxKind.JsxSelfClosingElement: 1057 case SyntaxKind.JsxElement: 1058 case SyntaxKind.JsxOpeningElement: 1059 isJsxIdentifierExpected = true; 1060 if (contextToken.kind === SyntaxKind.LessThanToken) { 1061 isRightOfOpenTag = true; 1062 location = contextToken; 1063 } 1064 break; 1065 1066 case SyntaxKind.JsxExpression: 1067 // For `<div foo={true} [||] ></div>`, `parent` will be `{true}` and `previousToken` will be `}` 1068 if (previousToken.kind === SyntaxKind.CloseBraceToken && currentToken.kind === SyntaxKind.GreaterThanToken) { 1069 isJsxIdentifierExpected = true; 1070 } 1071 break; 1072 1073 case SyntaxKind.JsxAttribute: 1074 // For `<div className="x" [||] ></div>`, `parent` will be JsxAttribute and `previousToken` will be its initializer 1075 if ((parent as JsxAttribute).initializer === previousToken && 1076 previousToken.end < position) { 1077 isJsxIdentifierExpected = true; 1078 break; 1079 } 1080 switch (previousToken.kind) { 1081 case SyntaxKind.EqualsToken: 1082 isJsxInitializer = true; 1083 break; 1084 case SyntaxKind.Identifier: 1085 isJsxIdentifierExpected = true; 1086 // For `<div x=[|f/**/|]`, `parent` will be `x` and `previousToken.parent` will be `f` (which is its own JsxAttribute) 1087 // Note for `<div someBool f>` we don't want to treat this as a jsx inializer, instead it's the attribute name. 1088 if (parent !== previousToken.parent && 1089 !(parent as JsxAttribute).initializer && 1090 findChildOfKind(parent, SyntaxKind.EqualsToken, sourceFile)) { 1091 isJsxInitializer = previousToken as Identifier; 1092 } 1093 } 1094 break; 1095 } 1096 } 1097 } 1098 1099 const semanticStart = timestamp(); 1100 let completionKind = CompletionKind.None; 1101 let isNewIdentifierLocation = false; 1102 let isNonContextualObjectLiteral = false; 1103 let keywordFilters = KeywordCompletionFilters.None; 1104 // This also gets mutated in nested-functions after the return 1105 let symbols: Symbol[] = []; 1106 const symbolToOriginInfoMap: SymbolOriginInfoMap = []; 1107 const symbolToSortTextMap: SymbolSortTextMap = []; 1108 const importSuggestionsCache = host.getImportSuggestionsCache && host.getImportSuggestionsCache(); 1109 const isTypeOnly = isTypeOnlyCompletion(); 1110 1111 if (isRightOfDot || isRightOfQuestionDot) { 1112 getTypeScriptMemberSymbols(); 1113 } 1114 else if (isRightOfOpenTag) { 1115 const tagSymbols = typeChecker.getJsxIntrinsicTagNamesAt(location); 1116 Debug.assertEachIsDefined(tagSymbols, "getJsxIntrinsicTagNames() should all be defined"); 1117 tryGetGlobalSymbols(); 1118 symbols = tagSymbols.concat(symbols); 1119 completionKind = CompletionKind.MemberLike; 1120 keywordFilters = KeywordCompletionFilters.None; 1121 } 1122 else if (isStartingCloseTag) { 1123 const tagName = (<JsxElement>contextToken.parent.parent).openingElement.tagName; 1124 const tagSymbol = typeChecker.getSymbolAtLocation(tagName); 1125 if (tagSymbol) { 1126 symbols = [tagSymbol]; 1127 } 1128 completionKind = CompletionKind.MemberLike; 1129 keywordFilters = KeywordCompletionFilters.None; 1130 } 1131 else { 1132 // For JavaScript or TypeScript, if we're not after a dot, then just try to get the 1133 // global symbols in scope. These results should be valid for either language as 1134 // the set of symbols that can be referenced from this location. 1135 if (!tryGetGlobalSymbols()) { 1136 return undefined; 1137 } 1138 } 1139 1140 const etsLibFilesNames = program.getEtsLibSFromProgram(); 1141 symbols = symbols.filter(symbol => { 1142 if(!symbol.declarations || !symbol.declarations.length) { 1143 return true; 1144 } 1145 const declaration = (symbol.declarations??[]).filter(declaration =>{ 1146 if(!declaration.getSourceFile().fileName) { 1147 return true; 1148 } 1149 const symbolFileName = sys.resolvePath(declaration.getSourceFile().fileName); 1150 if(!isEtsFile && etsLibFilesNames.indexOf(symbolFileName) !== -1) { 1151 return false; 1152 } 1153 return true; 1154 }); 1155 return declaration.length; 1156 }); 1157 1158 log("getCompletionData: Semantic work: " + (timestamp() - semanticStart)); 1159 const contextualType = previousToken && getContextualType(previousToken, position, sourceFile, typeChecker); 1160 const literals = mapDefined(contextualType && (contextualType.isUnion() ? contextualType.types : [contextualType]), t => t.isLiteral() ? t.value : undefined); 1161 1162 const recommendedCompletion = previousToken && contextualType && getRecommendedCompletion(previousToken, contextualType, typeChecker); 1163 return { 1164 kind: CompletionDataKind.Data, 1165 symbols, 1166 completionKind, 1167 isInSnippetScope, 1168 propertyAccessToConvert, 1169 isNewIdentifierLocation, 1170 location, 1171 keywordFilters, 1172 literals, 1173 symbolToOriginInfoMap, 1174 recommendedCompletion, 1175 previousToken, 1176 isJsxInitializer, 1177 insideJsDocTagTypeExpression, 1178 symbolToSortTextMap, 1179 isTypeOnlyLocation: isTypeOnly, 1180 isJsxIdentifierExpected, 1181 }; 1182 1183 type JSDocTagWithTypeExpression = JSDocParameterTag | JSDocPropertyTag | JSDocReturnTag | JSDocTypeTag | JSDocTypedefTag; 1184 1185 function isTagWithTypeExpression(tag: JSDocTag): tag is JSDocTagWithTypeExpression { 1186 switch (tag.kind) { 1187 case SyntaxKind.JSDocParameterTag: 1188 case SyntaxKind.JSDocPropertyTag: 1189 case SyntaxKind.JSDocReturnTag: 1190 case SyntaxKind.JSDocTypeTag: 1191 case SyntaxKind.JSDocTypedefTag: 1192 return true; 1193 default: 1194 return false; 1195 } 1196 } 1197 1198 function getTypeScriptMemberSymbols(): void { 1199 // Right of dot member completion list 1200 completionKind = CompletionKind.PropertyAccess; 1201 1202 // Since this is qualified name check it's a type node location 1203 const isImportType = isLiteralImportTypeNode(node); 1204 const isTypeLocation = insideJsDocTagTypeExpression 1205 || (isImportType && !(node as ImportTypeNode).isTypeOf) 1206 || isPartOfTypeNode(node.parent) 1207 || isPossiblyTypeArgumentPosition(contextToken, sourceFile, typeChecker); 1208 const isRhsOfImportDeclaration = isInRightSideOfInternalImportEqualsDeclaration(node); 1209 if (isEntityName(node) || isImportType || isPropertyAccessExpression(node)) { 1210 const isNamespaceName = isModuleDeclaration(node.parent); 1211 if (isNamespaceName) isNewIdentifierLocation = true; 1212 let symbol = typeChecker.getSymbolAtLocation(node); 1213 if (symbol) { 1214 symbol = skipAlias(symbol, typeChecker); 1215 if (symbol.flags & (SymbolFlags.Module | SymbolFlags.Enum)) { 1216 // Extract module or enum members 1217 const exportedSymbols = typeChecker.getExportsOfModule(symbol); 1218 Debug.assertEachIsDefined(exportedSymbols, "getExportsOfModule() should all be defined"); 1219 const isValidValueAccess = (symbol: Symbol) => typeChecker.isValidPropertyAccess(isImportType ? <ImportTypeNode>node : <PropertyAccessExpression>(node.parent), symbol.name); 1220 const isValidTypeAccess = (symbol: Symbol) => symbolCanBeReferencedAtTypeLocation(symbol); 1221 const isValidAccess: (symbol: Symbol) => boolean = 1222 isNamespaceName 1223 // At `namespace N.M/**/`, if this is the only declaration of `M`, don't include `M` as a completion. 1224 ? symbol => !!(symbol.flags & SymbolFlags.Namespace) && !symbol.declarations.every(d => d.parent === node.parent) 1225 : isRhsOfImportDeclaration ? 1226 // Any kind is allowed when dotting off namespace in internal import equals declaration 1227 symbol => isValidTypeAccess(symbol) || isValidValueAccess(symbol) : 1228 isTypeLocation ? isValidTypeAccess : isValidValueAccess; 1229 for (const exportedSymbol of exportedSymbols) { 1230 if (isValidAccess(exportedSymbol)) { 1231 symbols.push(exportedSymbol); 1232 } 1233 } 1234 1235 // If the module is merged with a value, we must get the type of the class and add its propertes (for inherited static methods). 1236 if (!isTypeLocation && 1237 symbol.declarations && 1238 symbol.declarations.some(d => d.kind !== SyntaxKind.SourceFile && d.kind !== SyntaxKind.ModuleDeclaration && d.kind !== SyntaxKind.EnumDeclaration)) { 1239 let type = typeChecker.getTypeOfSymbolAtLocation(symbol, node).getNonOptionalType(); 1240 let insertQuestionDot = false; 1241 if (type.isNullableType()) { 1242 const canCorrectToQuestionDot = 1243 isRightOfDot && 1244 !isRightOfQuestionDot && 1245 preferences.includeAutomaticOptionalChainCompletions !== false; 1246 1247 if (canCorrectToQuestionDot || isRightOfQuestionDot) { 1248 type = type.getNonNullableType(); 1249 if (canCorrectToQuestionDot) { 1250 insertQuestionDot = true; 1251 } 1252 } 1253 } 1254 addTypeProperties(type, !!(node.flags & NodeFlags.AwaitContext), insertQuestionDot); 1255 } 1256 1257 return; 1258 } 1259 } 1260 } 1261 1262 if (isMetaProperty(node) && (node.keywordToken === SyntaxKind.NewKeyword || node.keywordToken === SyntaxKind.ImportKeyword) && contextToken === node.getChildAt(1)) { 1263 const completion = (node.keywordToken === SyntaxKind.NewKeyword) ? "target" : "meta"; 1264 symbols.push(typeChecker.createSymbol(SymbolFlags.Property, escapeLeadingUnderscores(completion))); 1265 return; 1266 } 1267 1268 if (!isTypeLocation) { 1269 let type = typeChecker.tryGetTypeAtLocationWithoutCheck(node).getNonOptionalType(); 1270 let insertQuestionDot = false; 1271 if (type.isNullableType()) { 1272 const canCorrectToQuestionDot = 1273 isRightOfDot && 1274 !isRightOfQuestionDot && 1275 preferences.includeAutomaticOptionalChainCompletions !== false; 1276 1277 if (canCorrectToQuestionDot || isRightOfQuestionDot) { 1278 type = type.getNonNullableType(); 1279 if (canCorrectToQuestionDot) { 1280 insertQuestionDot = true; 1281 } 1282 } 1283 } 1284 addTypeProperties(type, !!(node.flags & NodeFlags.AwaitContext), insertQuestionDot); 1285 } 1286 } 1287 1288 function addTypeProperties(type: Type, insertAwait: boolean, insertQuestionDot: boolean): void { 1289 isNewIdentifierLocation = !!type.getStringIndexType(); 1290 if (isRightOfQuestionDot && some(type.getCallSignatures())) { 1291 isNewIdentifierLocation = true; 1292 } 1293 1294 const propertyAccess = node.kind === SyntaxKind.ImportType ? <ImportTypeNode>node : <PropertyAccessExpression | QualifiedName>node.parent; 1295 if (isUncheckedFile) { 1296 // In javascript files, for union types, we don't just get the members that 1297 // the individual types have in common, we also include all the members that 1298 // each individual type has. This is because we're going to add all identifiers 1299 // anyways. So we might as well elevate the members that were at least part 1300 // of the individual types to a higher status since we know what they are. 1301 symbols.push(...filter(getPropertiesForCompletion(type, typeChecker), s => typeChecker.isValidPropertyAccessForCompletions(propertyAccess, type, s))); 1302 } 1303 else { 1304 const typeSymbols = type.getApparentProperties(); 1305 1306 for (const symbol of typeSymbols) { 1307 if (typeChecker.isValidPropertyAccessForCompletions(propertyAccess, type, symbol)) { 1308 addPropertySymbol(symbol, /* insertAwait */ false, insertQuestionDot); 1309 } 1310 } 1311 1312 // The extension method on the ETS depends on whether the type is correctly parsed. 1313 if (typeSymbols.length) { 1314 // if complete expression is ets component expression, then complete data need add extend properties and styles properties. 1315 const etsComponentExpressionNode = getEtsComponentExpressionInnerCallExpressionNode(node) 1316 || getRootEtsComponentInnerCallExpressionNode(node); 1317 if (etsComponentExpressionNode) { 1318 addEtsExtendPropertySymbol(etsComponentExpressionNode, insertQuestionDot); 1319 addEtsStylesPropertySymbol(etsComponentExpressionNode, insertQuestionDot); 1320 } 1321 } 1322 } 1323 1324 if (insertAwait && preferences.includeCompletionsWithInsertText) { 1325 const promiseType = typeChecker.getPromisedTypeOfPromise(type); 1326 if (promiseType) { 1327 for (const symbol of promiseType.getApparentProperties()) { 1328 if (typeChecker.isValidPropertyAccessForCompletions(propertyAccess, promiseType, symbol)) { 1329 addPropertySymbol(symbol, /* insertAwait */ true, insertQuestionDot); 1330 } 1331 } 1332 } 1333 } 1334 } 1335 1336 function addPropertySymbol(symbol: Symbol, insertAwait: boolean, insertQuestionDot: boolean) { 1337 // For a computed property with an accessible name like `Symbol.iterator`, 1338 // we'll add a completion for the *name* `Symbol` instead of for the property. 1339 // If this is e.g. [Symbol.iterator], add a completion for `Symbol`. 1340 const computedPropertyName = firstDefined(symbol.declarations, decl => tryCast(getNameOfDeclaration(decl), isComputedPropertyName)); 1341 if (computedPropertyName) { 1342 const leftMostName = getLeftMostName(computedPropertyName.expression); // The completion is for `Symbol`, not `iterator`. 1343 const nameSymbol = leftMostName && typeChecker.getSymbolAtLocation(leftMostName); 1344 // If this is nested like for `namespace N { export const sym = Symbol(); }`, we'll add the completion for `N`. 1345 const firstAccessibleSymbol = nameSymbol && getFirstSymbolInChain(nameSymbol, contextToken, typeChecker); 1346 if (firstAccessibleSymbol && !symbolToOriginInfoMap[getSymbolId(firstAccessibleSymbol)]) { 1347 symbols.push(firstAccessibleSymbol); 1348 const moduleSymbol = firstAccessibleSymbol.parent; 1349 symbolToOriginInfoMap[getSymbolId(firstAccessibleSymbol)] = 1350 !moduleSymbol || !isExternalModuleSymbol(moduleSymbol) 1351 ? { kind: getNullableSymbolOriginInfoKind(SymbolOriginInfoKind.SymbolMemberNoExport) } 1352 : { kind: getNullableSymbolOriginInfoKind(SymbolOriginInfoKind.SymbolMemberExport), moduleSymbol, isDefaultExport: false }; 1353 } 1354 else if (preferences.includeCompletionsWithInsertText) { 1355 addSymbolOriginInfo(symbol); 1356 addSymbolSortInfo(symbol); 1357 symbols.push(symbol); 1358 } 1359 } 1360 else { 1361 addSymbolOriginInfo(symbol); 1362 addSymbolSortInfo(symbol); 1363 symbols.push(symbol); 1364 } 1365 1366 function addSymbolSortInfo(symbol: Symbol) { 1367 if (isStaticProperty(symbol)) { 1368 symbolToSortTextMap[getSymbolId(symbol)] = SortText.LocalDeclarationPriority; 1369 } 1370 } 1371 1372 function addSymbolOriginInfo(symbol: Symbol) { 1373 if (preferences.includeCompletionsWithInsertText) { 1374 if (insertAwait && !symbolToOriginInfoMap[getSymbolId(symbol)]) { 1375 symbolToOriginInfoMap[getSymbolId(symbol)] = { kind: getNullableSymbolOriginInfoKind(SymbolOriginInfoKind.Promise) }; 1376 } 1377 else if (insertQuestionDot) { 1378 symbolToOriginInfoMap[getSymbolId(symbol)] = { kind: SymbolOriginInfoKind.Nullable }; 1379 } 1380 } 1381 } 1382 1383 function getNullableSymbolOriginInfoKind(kind: SymbolOriginInfoKind) { 1384 return insertQuestionDot ? kind | SymbolOriginInfoKind.Nullable : kind; 1385 } 1386 } 1387 1388 function addEtsExtendPropertySymbol(node: EtsComponentExpression, insertQuestionDot: boolean) { 1389 const locals = getSourceFileOfNode(node).locals; 1390 if (!locals) { 1391 return; 1392 } 1393 const etsComponentName = node.expression.kind === SyntaxKind.Identifier ? (<Identifier>node.expression).escapedText : undefined; 1394 const extendComponentSymbolMap: UnderscoreEscapedMap<Symbol[]> = new Map<__String, Symbol[]>(); 1395 locals.forEach(local => { 1396 getEtsExtendDecoratorComponentNames(local.valueDeclaration?.decorators, compilerOptions).forEach((extendName) => { 1397 if (extendComponentSymbolMap.has(extendName)) { 1398 extendComponentSymbolMap.get(extendName)!.push(local); 1399 } 1400 else { 1401 extendComponentSymbolMap.set(extendName, [local]); 1402 } 1403 }); 1404 }); 1405 1406 if (etsComponentName && extendComponentSymbolMap.has(etsComponentName)) { 1407 extendComponentSymbolMap.get(etsComponentName)!.forEach(local => { 1408 addPropertySymbol(local, /* insertAwait */ false, insertQuestionDot); 1409 }); 1410 } 1411 } 1412 1413 function addEtsStylesPropertySymbol(node: EtsComponentExpression, insertQuestionDot: boolean) { 1414 const locals = getSourceFileOfNode(node).locals; 1415 if (!locals) { 1416 return; 1417 } 1418 const etsComponentName = isIdentifier(node.expression) ? node.expression.escapedText : undefined; 1419 const stylesComponentSymbolMap: UnderscoreEscapedMap<Symbol[]> = new Map<__String, Symbol[]>(); 1420 locals.forEach(local => { 1421 if (hasEtsStylesDecoratorNames(local.valueDeclaration?.decorators, compilerOptions)) { 1422 if (stylesComponentSymbolMap.has(local.escapedName)) { 1423 stylesComponentSymbolMap.get(local.escapedName)!.push(local); 1424 } 1425 else { 1426 stylesComponentSymbolMap.set(local.escapedName, [local]); 1427 } 1428 } 1429 }); 1430 1431 // If it's a '@Styles' method inside StructDeclaration, 1432 // we will find container StructDeclaration of current node first, 1433 // and then find method decorated with '@Styles' 1434 getContainingStruct(node)?.symbol.members?.forEach(member => { 1435 if (hasEtsStylesDecoratorNames(member.valueDeclaration?.decorators, compilerOptions)) { 1436 if (stylesComponentSymbolMap.has(member.escapedName)) { 1437 stylesComponentSymbolMap.get(member.escapedName)!.push(member); 1438 } 1439 else { 1440 stylesComponentSymbolMap.set(member.escapedName, [member]); 1441 } 1442 } 1443 }); 1444 1445 if (etsComponentName && stylesComponentSymbolMap.size > 0) { 1446 stylesComponentSymbolMap.forEach(symbols => { 1447 symbols.forEach(symbol => { 1448 addPropertySymbol(symbol, /* insertAwait */ false, insertQuestionDot); 1449 }); 1450 }); 1451 } 1452 } 1453 1454 /** Given 'a.b.c', returns 'a'. */ 1455 function getLeftMostName(e: Expression): Identifier | undefined { 1456 return isIdentifier(e) ? e : isPropertyAccessExpression(e) ? getLeftMostName(e.expression) : undefined; 1457 } 1458 1459 function tryGetGlobalSymbols(): boolean { 1460 const result: GlobalsSearch = tryGetObjectLikeCompletionSymbols() 1461 || tryGetImportOrExportClauseCompletionSymbols() 1462 || tryGetLocalNamedExportCompletionSymbols() 1463 || tryGetConstructorCompletion() 1464 || tryGetClassLikeCompletionSymbols() 1465 || tryGetJsxCompletionSymbols() 1466 || (getGlobalCompletions(), GlobalsSearch.Success); 1467 return result === GlobalsSearch.Success; 1468 } 1469 1470 function tryGetConstructorCompletion(): GlobalsSearch { 1471 if (!tryGetConstructorLikeCompletionContainer(contextToken)) return GlobalsSearch.Continue; 1472 // no members, only keywords 1473 completionKind = CompletionKind.None; 1474 // Declaring new property/method/accessor 1475 isNewIdentifierLocation = true; 1476 // Has keywords for constructor parameter 1477 keywordFilters = KeywordCompletionFilters.ConstructorParameterKeywords; 1478 return GlobalsSearch.Success; 1479 } 1480 1481 function tryGetJsxCompletionSymbols(): GlobalsSearch { 1482 const jsxContainer = tryGetContainingJsxElement(contextToken); 1483 // Cursor is inside a JSX self-closing element or opening element 1484 const attrsType = jsxContainer && typeChecker.getContextualType(jsxContainer.attributes); 1485 if (!attrsType) return GlobalsSearch.Continue; 1486 const completionsType = jsxContainer && typeChecker.getContextualType(jsxContainer.attributes, ContextFlags.Completions); 1487 symbols = filterJsxAttributes(getPropertiesForObjectExpression(attrsType, completionsType, jsxContainer!.attributes, typeChecker), jsxContainer!.attributes.properties); 1488 setSortTextToOptionalMember(); 1489 completionKind = CompletionKind.MemberLike; 1490 isNewIdentifierLocation = false; 1491 return GlobalsSearch.Success; 1492 } 1493 1494 function getGlobalCompletions(): void { 1495 keywordFilters = tryGetFunctionLikeBodyCompletionContainer(contextToken) ? KeywordCompletionFilters.FunctionLikeBodyKeywords : KeywordCompletionFilters.All; 1496 1497 // Get all entities in the current scope. 1498 completionKind = CompletionKind.Global; 1499 isNewIdentifierLocation = isNewIdentifierDefinitionLocation(); 1500 1501 if (previousToken !== contextToken) { 1502 Debug.assert(!!previousToken, "Expected 'contextToken' to be defined when different from 'previousToken'."); 1503 } 1504 // We need to find the node that will give us an appropriate scope to begin 1505 // aggregating completion candidates. This is achieved in 'getScopeNode' 1506 // by finding the first node that encompasses a position, accounting for whether a node 1507 // is "complete" to decide whether a position belongs to the node. 1508 // 1509 // However, at the end of an identifier, we are interested in the scope of the identifier 1510 // itself, but fall outside of the identifier. For instance: 1511 // 1512 // xyz => x$ 1513 // 1514 // the cursor is outside of both the 'x' and the arrow function 'xyz => x', 1515 // so 'xyz' is not returned in our results. 1516 // 1517 // We define 'adjustedPosition' so that we may appropriately account for 1518 // being at the end of an identifier. The intention is that if requesting completion 1519 // at the end of an identifier, it should be effectively equivalent to requesting completion 1520 // anywhere inside/at the beginning of the identifier. So in the previous case, the 1521 // 'adjustedPosition' will work as if requesting completion in the following: 1522 // 1523 // xyz => $x 1524 // 1525 // If previousToken !== contextToken, then 1526 // - 'contextToken' was adjusted to the token prior to 'previousToken' 1527 // because we were at the end of an identifier. 1528 // - 'previousToken' is defined. 1529 const adjustedPosition = previousToken !== contextToken ? 1530 previousToken.getStart() : 1531 position; 1532 1533 const scopeNode = getScopeNode(contextToken, adjustedPosition, sourceFile) || sourceFile; 1534 isInSnippetScope = isSnippetScope(scopeNode); 1535 1536 const symbolMeanings = (isTypeOnly ? SymbolFlags.None : SymbolFlags.Value) | SymbolFlags.Type | SymbolFlags.Namespace | SymbolFlags.Alias; 1537 1538 symbols = typeChecker.getSymbolsInScope(scopeNode, symbolMeanings); 1539 Debug.assertEachIsDefined(symbols, "getSymbolsInScope() should all be defined"); 1540 for (const symbol of symbols) { 1541 if (!typeChecker.isArgumentsSymbol(symbol) && 1542 !some(symbol.declarations, d => d.getSourceFile() === sourceFile)) { 1543 symbolToSortTextMap[getSymbolId(symbol)] = SortText.GlobalsOrKeywords; 1544 } 1545 } 1546 1547 // Need to insert 'this.' before properties of `this` type, so only do that if `includeInsertTextCompletions` 1548 if (preferences.includeCompletionsWithInsertText && scopeNode.kind !== SyntaxKind.SourceFile) { 1549 const thisType = typeChecker.tryGetThisTypeAt(scopeNode, /*includeGlobalThis*/ false); 1550 if (thisType && !isProbablyGlobalType(thisType, sourceFile, typeChecker)) { 1551 for (const symbol of getPropertiesForCompletion(thisType, typeChecker)) { 1552 symbolToOriginInfoMap[getSymbolId(symbol)] = { kind: SymbolOriginInfoKind.ThisType }; 1553 symbols.push(symbol); 1554 symbolToSortTextMap[getSymbolId(symbol)] = SortText.SuggestedClassMembers; 1555 } 1556 } 1557 } 1558 1559 if (shouldOfferImportCompletions()) { 1560 const lowerCaseTokenText = previousToken && isIdentifier(previousToken) ? previousToken.text.toLowerCase() : ""; 1561 const autoImportSuggestions = getSymbolsFromOtherSourceFileExports(program.getCompilerOptions().target!, host); 1562 if (!detailsEntryId && importSuggestionsCache) { 1563 importSuggestionsCache.set(sourceFile.fileName, autoImportSuggestions, host.getProjectVersion && host.getProjectVersion()); 1564 } 1565 autoImportSuggestions.forEach(({ symbol, symbolName, skipFilter, origin }) => { 1566 if (detailsEntryId) { 1567 if (detailsEntryId.source && stripQuotes(origin.moduleSymbol.name) !== detailsEntryId.source) { 1568 return; 1569 } 1570 } 1571 else if (!skipFilter && !stringContainsCharactersInOrder(symbolName.toLowerCase(), lowerCaseTokenText)) { 1572 return; 1573 } 1574 1575 const symbolId = getSymbolId(symbol); 1576 symbols.push(symbol); 1577 symbolToOriginInfoMap[symbolId] = origin; 1578 symbolToSortTextMap[symbolId] = SortText.AutoImportSuggestions; 1579 }); 1580 } 1581 filterGlobalCompletion(symbols); 1582 } 1583 1584 function shouldOfferImportCompletions(): boolean { 1585 // If current completion is for non-contextual Object literal shortahands, ignore auto-import symbols 1586 if (isNonContextualObjectLiteral) return false; 1587 // If not already a module, must have modules enabled. 1588 if (!preferences.includeCompletionsForModuleExports) return false; 1589 // If already using ES6 modules, OK to continue using them. 1590 if (sourceFile.externalModuleIndicator || sourceFile.commonJsModuleIndicator) return true; 1591 // If module transpilation is enabled or we're targeting es6 or above, or not emitting, OK. 1592 if (compilerOptionsIndicateEs6Modules(program.getCompilerOptions())) return true; 1593 // If some file is using ES6 modules, assume that it's OK to add more. 1594 return programContainsModules(program); 1595 } 1596 1597 function isSnippetScope(scopeNode: Node): boolean { 1598 switch (scopeNode.kind) { 1599 case SyntaxKind.SourceFile: 1600 case SyntaxKind.TemplateExpression: 1601 case SyntaxKind.JsxExpression: 1602 case SyntaxKind.Block: 1603 return true; 1604 default: 1605 return isStatement(scopeNode); 1606 } 1607 } 1608 1609 function filterGlobalCompletion(symbols: Symbol[]): void { 1610 const isTypeOnly = isTypeOnlyCompletion(); 1611 if (isTypeOnly) { 1612 keywordFilters = contextToken && isAssertionExpression(contextToken.parent) 1613 ? KeywordCompletionFilters.TypeAssertionKeywords 1614 : KeywordCompletionFilters.TypeKeywords; 1615 } 1616 1617 const variableDeclaration = getVariableDeclaration(location); 1618 1619 filterMutate(symbols, symbol => { 1620 if (!isSourceFile(location)) { 1621 // export = /**/ here we want to get all meanings, so any symbol is ok 1622 if (isExportAssignment(location.parent)) { 1623 return true; 1624 } 1625 1626 // Filter out variables from their own initializers 1627 // `const a = /* no 'a' here */` 1628 if (variableDeclaration && symbol.valueDeclaration === variableDeclaration) { 1629 return false; 1630 } 1631 1632 // External modules can have global export declarations that will be 1633 // available as global keywords in all scopes. But if the external module 1634 // already has an explicit export and user only wants to user explicit 1635 // module imports then the global keywords will be filtered out so auto 1636 // import suggestions will win in the completion 1637 const symbolOrigin = skipAlias(symbol, typeChecker); 1638 // We only want to filter out the global keywords 1639 // Auto Imports are not available for scripts so this conditional is always false 1640 if (!!sourceFile.externalModuleIndicator 1641 && !compilerOptions.allowUmdGlobalAccess 1642 && symbolToSortTextMap[getSymbolId(symbol)] === SortText.GlobalsOrKeywords 1643 && symbolToSortTextMap[getSymbolId(symbolOrigin)] === SortText.AutoImportSuggestions) { 1644 return false; 1645 } 1646 // Continue with origin symbol 1647 symbol = symbolOrigin; 1648 1649 // import m = /**/ <-- It can only access namespace (if typing import = x. this would get member symbols and not namespace) 1650 if (isInRightSideOfInternalImportEqualsDeclaration(location)) { 1651 return !!(symbol.flags & SymbolFlags.Namespace); 1652 } 1653 1654 if (isTypeOnly) { 1655 // It's a type, but you can reach it by namespace.type as well 1656 return symbolCanBeReferencedAtTypeLocation(symbol); 1657 } 1658 } 1659 1660 // expressions are value space (which includes the value namespaces) 1661 return !!(getCombinedLocalAndExportSymbolFlags(symbol) & SymbolFlags.Value); 1662 }); 1663 } 1664 1665 function getVariableDeclaration(property: Node): VariableDeclaration | undefined { 1666 const variableDeclaration = findAncestor(property, node => 1667 isFunctionBlock(node) || isArrowFunctionBody(node) || isBindingPattern(node) 1668 ? "quit" 1669 : isVariableDeclaration(node)); 1670 1671 return variableDeclaration as VariableDeclaration | undefined; 1672 } 1673 1674 function isArrowFunctionBody(node: Node) { 1675 return node.parent && isArrowFunction(node.parent) && node.parent.body === node; 1676 }; 1677 1678 function isTypeOnlyCompletion(): boolean { 1679 return insideJsDocTagTypeExpression 1680 || !isContextTokenValueLocation(contextToken) && 1681 (isPossiblyTypeArgumentPosition(contextToken, sourceFile, typeChecker) 1682 || isPartOfTypeNode(location) 1683 || isContextTokenTypeLocation(contextToken)); 1684 } 1685 1686 function isContextTokenValueLocation(contextToken: Node) { 1687 return contextToken && 1688 contextToken.kind === SyntaxKind.TypeOfKeyword && 1689 (contextToken.parent.kind === SyntaxKind.TypeQuery || isTypeOfExpression(contextToken.parent)); 1690 } 1691 1692 function isContextTokenTypeLocation(contextToken: Node): boolean { 1693 if (contextToken) { 1694 const parentKind = contextToken.parent.kind; 1695 switch (contextToken.kind) { 1696 case SyntaxKind.ColonToken: 1697 return parentKind === SyntaxKind.PropertyDeclaration || 1698 parentKind === SyntaxKind.PropertySignature || 1699 parentKind === SyntaxKind.Parameter || 1700 parentKind === SyntaxKind.VariableDeclaration || 1701 isFunctionLikeKind(parentKind); 1702 1703 case SyntaxKind.EqualsToken: 1704 return parentKind === SyntaxKind.TypeAliasDeclaration; 1705 1706 case SyntaxKind.AsKeyword: 1707 return parentKind === SyntaxKind.AsExpression; 1708 1709 case SyntaxKind.LessThanToken: 1710 return parentKind === SyntaxKind.TypeReference || 1711 parentKind === SyntaxKind.TypeAssertionExpression; 1712 1713 case SyntaxKind.ExtendsKeyword: 1714 return parentKind === SyntaxKind.TypeParameter; 1715 } 1716 } 1717 return false; 1718 } 1719 1720 /** True if symbol is a type or a module containing at least one type. */ 1721 function symbolCanBeReferencedAtTypeLocation(symbol: Symbol, seenModules = new Map<string, true>()): boolean { 1722 const sym = skipAlias(symbol.exportSymbol || symbol, typeChecker); 1723 return !!(sym.flags & SymbolFlags.Type) || 1724 !!(sym.flags & SymbolFlags.Module) && 1725 addToSeen(seenModules, getSymbolId(sym)) && 1726 typeChecker.getExportsOfModule(sym).some(e => symbolCanBeReferencedAtTypeLocation(e, seenModules)); 1727 } 1728 1729 /** 1730 * Gathers symbols that can be imported from other files, de-duplicating along the way. Symbols can be "duplicates" 1731 * if re-exported from another module, e.g. `export { foo } from "./a"`. That syntax creates a fresh symbol, but 1732 * it’s just an alias to the first, and both have the same name, so we generally want to filter those aliases out, 1733 * if and only if the the first can be imported (it may be excluded due to package.json filtering in 1734 * `codefix.forEachExternalModuleToImportFrom`). 1735 * 1736 * Example. Imagine a chain of node_modules re-exporting one original symbol: 1737 * 1738 * ```js 1739 * node_modules/x/index.js node_modules/y/index.js node_modules/z/index.js 1740 * +-----------------------+ +--------------------------+ +--------------------------+ 1741 * | | | | | | 1742 * | export const foo = 0; | <--- | export { foo } from 'x'; | <--- | export { foo } from 'y'; | 1743 * | | | | | | 1744 * +-----------------------+ +--------------------------+ +--------------------------+ 1745 * ``` 1746 * 1747 * Also imagine three buckets, which we’ll reference soon: 1748 * 1749 * ```md 1750 * | | | | | | 1751 * | **Bucket A** | | **Bucket B** | | **Bucket C** | 1752 * | Symbols to | | Aliases to symbols | | Symbols to return | 1753 * | definitely | | in Buckets A or C | | if nothing better | 1754 * | return | | (don’t return these) | | comes along | 1755 * |__________________| |______________________| |___________________| 1756 * ``` 1757 * 1758 * We _probably_ want to show `foo` from 'x', but not from 'y' or 'z'. However, if 'x' is not in a package.json, it 1759 * will not appear in a `forEachExternalModuleToImportFrom` iteration. Furthermore, the order of iterations is not 1760 * guaranteed, as it is host-dependent. Therefore, when presented with the symbol `foo` from module 'y' alone, we 1761 * may not be sure whether or not it should go in the list. So, we’ll take the following steps: 1762 * 1763 * 1. Resolve alias `foo` from 'y' to the export declaration in 'x', get the symbol there, and see if that symbol is 1764 * already in Bucket A (symbols we already know will be returned). If it is, put `foo` from 'y' in Bucket B 1765 * (symbols that are aliases to symbols in Bucket A). If it’s not, put it in Bucket C. 1766 * 2. Next, imagine we see `foo` from module 'z'. Again, we resolve the alias to the nearest export, which is in 'y'. 1767 * At this point, if that nearest export from 'y' is in _any_ of the three buckets, we know the symbol in 'z' 1768 * should never be returned in the final list, so put it in Bucket B. 1769 * 3. Next, imagine we see `foo` from module 'x', the original. Syntactically, it doesn’t look like a re-export, so 1770 * we can just check Bucket C to see if we put any aliases to the original in there. If they exist, throw them out. 1771 * Put this symbol in Bucket A. 1772 * 4. After we’ve iterated through every symbol of every module, any symbol left in Bucket C means that step 3 didn’t 1773 * occur for that symbol---that is, the original symbol is not in Bucket A, so we should include the alias. Move 1774 * everything from Bucket C to Bucket A. 1775 */ 1776 function getSymbolsFromOtherSourceFileExports(target: ScriptTarget, host: LanguageServiceHost): readonly AutoImportSuggestion[] { 1777 const cached = importSuggestionsCache && importSuggestionsCache.get( 1778 sourceFile.fileName, 1779 typeChecker, 1780 detailsEntryId && host.getProjectVersion ? host.getProjectVersion() : undefined); 1781 1782 if (cached) { 1783 log("getSymbolsFromOtherSourceFileExports: Using cached list"); 1784 return cached; 1785 } 1786 1787 const startTime = timestamp(); 1788 log(`getSymbolsFromOtherSourceFileExports: Recomputing list${detailsEntryId ? " for details entry" : ""}`); 1789 const seenResolvedModules = new Map<string, true>(); 1790 const seenExports = new Map<string, true>(); 1791 /** Bucket B */ 1792 const aliasesToAlreadyIncludedSymbols = new Map<string, true>(); 1793 /** Bucket C */ 1794 const aliasesToReturnIfOriginalsAreMissing = new Map<string, { alias: Symbol, moduleSymbol: Symbol, isFromPackageJson: boolean }>(); 1795 /** Bucket A */ 1796 const results: AutoImportSuggestion[] = []; 1797 /** Ids present in `results` for faster lookup */ 1798 const resultSymbolIds = new Map<string, true>(); 1799 1800 codefix.forEachExternalModuleToImportFrom(program, host, sourceFile, !detailsEntryId, /*useAutoImportProvider*/ true, (moduleSymbol, _, program, isFromPackageJson) => { 1801 // Perf -- ignore other modules if this is a request for details 1802 if (detailsEntryId && detailsEntryId.source && stripQuotes(moduleSymbol.name) !== detailsEntryId.source) { 1803 return; 1804 } 1805 1806 const typeChecker = program.getTypeChecker(); 1807 const resolvedModuleSymbol = typeChecker.resolveExternalModuleSymbol(moduleSymbol); 1808 // resolvedModuleSymbol may be a namespace. A namespace may be `export =` by multiple module declarations, but only keep the first one. 1809 if (!addToSeen(seenResolvedModules, getSymbolId(resolvedModuleSymbol))) { 1810 return; 1811 } 1812 1813 // Don't add another completion for `export =` of a symbol that's already global. 1814 // So in `declare namespace foo {} declare module "foo" { export = foo; }`, there will just be the global completion for `foo`. 1815 if (resolvedModuleSymbol !== moduleSymbol && every(resolvedModuleSymbol.declarations, isNonGlobalDeclaration)) { 1816 pushSymbol(resolvedModuleSymbol, moduleSymbol, isFromPackageJson, /*skipFilter*/ true); 1817 } 1818 1819 for (const symbol of typeChecker.getExportsAndPropertiesOfModule(moduleSymbol)) { 1820 const symbolId = getSymbolId(symbol).toString(); 1821 // `getExportsAndPropertiesOfModule` can include duplicates 1822 if (!addToSeen(seenExports, symbolId)) { 1823 continue; 1824 } 1825 // If this is `export { _break as break };` (a keyword) -- skip this and prefer the keyword completion. 1826 if (some(symbol.declarations, d => isExportSpecifier(d) && !!d.propertyName && isIdentifierANonContextualKeyword(d.name))) { 1827 continue; 1828 } 1829 1830 // If `symbol.parent !== moduleSymbol`, this is an `export * from "foo"` re-export. Those don't create new symbols. 1831 const isExportStarFromReExport = typeChecker.getMergedSymbol(symbol.parent!) !== resolvedModuleSymbol; 1832 // If `!!d.parent.parent.moduleSpecifier`, this is `export { foo } from "foo"` re-export, which creates a new symbol (thus isn't caught by the first check). 1833 if (isExportStarFromReExport || some(symbol.declarations, d => isExportSpecifier(d) && !d.propertyName && !!d.parent.parent.moduleSpecifier)) { 1834 // Walk the export chain back one module (step 1 or 2 in diagrammed example). 1835 // Or, in the case of `export * from "foo"`, `symbol` already points to the original export, so just use that. 1836 const nearestExportSymbol = isExportStarFromReExport ? symbol : getNearestExportSymbol(symbol); 1837 if (!nearestExportSymbol) continue; 1838 const nearestExportSymbolId = getSymbolId(nearestExportSymbol).toString(); 1839 const symbolHasBeenSeen = resultSymbolIds.has(nearestExportSymbolId) || aliasesToAlreadyIncludedSymbols.has(nearestExportSymbolId); 1840 if (!symbolHasBeenSeen) { 1841 aliasesToReturnIfOriginalsAreMissing.set(nearestExportSymbolId, { alias: symbol, moduleSymbol, isFromPackageJson }); 1842 aliasesToAlreadyIncludedSymbols.set(symbolId, true); 1843 } 1844 else { 1845 // Perf - we know this symbol is an alias to one that’s already covered in `symbols`, so store it here 1846 // in case another symbol re-exports this one; that way we can short-circuit as soon as we see this symbol id. 1847 addToSeen(aliasesToAlreadyIncludedSymbols, symbolId); 1848 } 1849 } 1850 else { 1851 // This is not a re-export, so see if we have any aliases pending and remove them (step 3 in diagrammed example) 1852 aliasesToReturnIfOriginalsAreMissing.delete(symbolId); 1853 pushSymbol(symbol, moduleSymbol, isFromPackageJson, /*skipFilter*/ false); 1854 } 1855 } 1856 }); 1857 1858 // By this point, any potential duplicates that were actually duplicates have been 1859 // removed, so the rest need to be added. (Step 4 in diagrammed example) 1860 aliasesToReturnIfOriginalsAreMissing.forEach(({ alias, moduleSymbol, isFromPackageJson }) => pushSymbol(alias, moduleSymbol, isFromPackageJson, /*skipFilter*/ false)); 1861 log(`getSymbolsFromOtherSourceFileExports: ${timestamp() - startTime}`); 1862 return results; 1863 1864 function pushSymbol(symbol: Symbol, moduleSymbol: Symbol, isFromPackageJson: boolean, skipFilter: boolean) { 1865 const isDefaultExport = symbol.escapedName === InternalSymbolName.Default; 1866 if (isDefaultExport) { 1867 symbol = getLocalSymbolForExportDefault(symbol) || symbol; 1868 } 1869 if (typeChecker.isUndefinedSymbol(symbol)) { 1870 return; 1871 } 1872 addToSeen(resultSymbolIds, getSymbolId(symbol)); 1873 const origin: SymbolOriginInfoExport = { kind: SymbolOriginInfoKind.Export, moduleSymbol, isDefaultExport, isFromPackageJson }; 1874 results.push({ 1875 symbol, 1876 symbolName: getNameForExportedSymbol(symbol, target), 1877 origin, 1878 skipFilter, 1879 }); 1880 } 1881 } 1882 1883 function getNearestExportSymbol(fromSymbol: Symbol) { 1884 return findAlias(typeChecker, fromSymbol, alias => { 1885 return some(alias.declarations, d => isExportSpecifier(d) || !!d.localSymbol); 1886 }); 1887 } 1888 1889 /** 1890 * True if you could remove some characters in `a` to get `b`. 1891 * E.g., true for "abcdef" and "bdf". 1892 * But not true for "abcdef" and "dbf". 1893 */ 1894 function stringContainsCharactersInOrder(str: string, characters: string): boolean { 1895 if (characters.length === 0) { 1896 return true; 1897 } 1898 1899 let characterIndex = 0; 1900 for (let strIndex = 0; strIndex < str.length; strIndex++) { 1901 if (str.charCodeAt(strIndex) === characters.charCodeAt(characterIndex)) { 1902 characterIndex++; 1903 if (characterIndex === characters.length) { 1904 return true; 1905 } 1906 } 1907 } 1908 1909 // Did not find all characters 1910 return false; 1911 } 1912 1913 /** 1914 * Finds the first node that "embraces" the position, so that one may 1915 * accurately aggregate locals from the closest containing scope. 1916 */ 1917 function getScopeNode(initialToken: Node | undefined, position: number, sourceFile: SourceFile) { 1918 let scope: Node | undefined = initialToken; 1919 while (scope && !positionBelongsToNode(scope, position, sourceFile)) { 1920 scope = scope.parent; 1921 } 1922 return scope; 1923 } 1924 1925 function isCompletionListBlocker(contextToken: Node): boolean { 1926 const start = timestamp(); 1927 const result = isInStringOrRegularExpressionOrTemplateLiteral(contextToken) || 1928 isSolelyIdentifierDefinitionLocation(contextToken) || 1929 isDotOfNumericLiteral(contextToken) || 1930 isInJsxText(contextToken); 1931 log("getCompletionsAtPosition: isCompletionListBlocker: " + (timestamp() - start)); 1932 return result; 1933 } 1934 1935 function isInJsxText(contextToken: Node): boolean { 1936 if (contextToken.kind === SyntaxKind.JsxText) { 1937 return true; 1938 } 1939 1940 if (contextToken.kind === SyntaxKind.GreaterThanToken && contextToken.parent) { 1941 if (contextToken.parent.kind === SyntaxKind.JsxOpeningElement) { 1942 // Two possibilities: 1943 // 1. <div>/**/ 1944 // - contextToken: GreaterThanToken (before cursor) 1945 // - location: JSXElement 1946 // - different parents (JSXOpeningElement, JSXElement) 1947 // 2. <Component<string> /**/> 1948 // - contextToken: GreaterThanToken (before cursor) 1949 // - location: GreaterThanToken (after cursor) 1950 // - same parent (JSXOpeningElement) 1951 return location.parent.kind !== SyntaxKind.JsxOpeningElement; 1952 } 1953 1954 if (contextToken.parent.kind === SyntaxKind.JsxClosingElement || contextToken.parent.kind === SyntaxKind.JsxSelfClosingElement) { 1955 return !!contextToken.parent.parent && contextToken.parent.parent.kind === SyntaxKind.JsxElement; 1956 } 1957 } 1958 return false; 1959 } 1960 1961 function isNewIdentifierDefinitionLocation(): boolean { 1962 if (contextToken) { 1963 const containingNodeKind = contextToken.parent.kind; 1964 // Previous token may have been a keyword that was converted to an identifier. 1965 switch (keywordForNode(contextToken)) { 1966 case SyntaxKind.CommaToken: 1967 return containingNodeKind === SyntaxKind.CallExpression // func( a, | 1968 || containingNodeKind === SyntaxKind.Constructor // constructor( a, | /* public, protected, private keywords are allowed here, so show completion */ 1969 || containingNodeKind === SyntaxKind.NewExpression // new C(a, | 1970 || containingNodeKind === SyntaxKind.ArrayLiteralExpression // [a, | 1971 || containingNodeKind === SyntaxKind.BinaryExpression // const x = (a, | 1972 || containingNodeKind === SyntaxKind.FunctionType // var x: (s: string, list| 1973 || containingNodeKind === SyntaxKind.ObjectLiteralExpression; // const obj = { x, | 1974 1975 case SyntaxKind.OpenParenToken: 1976 return containingNodeKind === SyntaxKind.CallExpression // func( | 1977 || containingNodeKind === SyntaxKind.Constructor // constructor( | 1978 || containingNodeKind === SyntaxKind.NewExpression // new C(a| 1979 || containingNodeKind === SyntaxKind.ParenthesizedExpression // const x = (a| 1980 || containingNodeKind === SyntaxKind.ParenthesizedType; // function F(pred: (a| /* this can become an arrow function, where 'a' is the argument */ 1981 1982 case SyntaxKind.OpenBracketToken: 1983 return containingNodeKind === SyntaxKind.ArrayLiteralExpression // [ | 1984 || containingNodeKind === SyntaxKind.IndexSignature // [ | : string ] 1985 || containingNodeKind === SyntaxKind.ComputedPropertyName; // [ | /* this can become an index signature */ 1986 1987 case SyntaxKind.ModuleKeyword: // module | 1988 case SyntaxKind.NamespaceKeyword: // namespace | 1989 return true; 1990 1991 case SyntaxKind.DotToken: 1992 return containingNodeKind === SyntaxKind.ModuleDeclaration; // module A.| 1993 1994 case SyntaxKind.OpenBraceToken: 1995 return containingNodeKind === SyntaxKind.ClassDeclaration // class A { | 1996 || containingNodeKind === SyntaxKind.StructDeclaration // struct A { | 1997 || containingNodeKind === SyntaxKind.ObjectLiteralExpression; // const obj = { | 1998 1999 case SyntaxKind.EqualsToken: 2000 return containingNodeKind === SyntaxKind.VariableDeclaration // const x = a| 2001 || containingNodeKind === SyntaxKind.BinaryExpression; // x = a| 2002 2003 case SyntaxKind.TemplateHead: 2004 return containingNodeKind === SyntaxKind.TemplateExpression; // `aa ${| 2005 2006 case SyntaxKind.TemplateMiddle: 2007 return containingNodeKind === SyntaxKind.TemplateSpan; // `aa ${10} dd ${| 2008 2009 case SyntaxKind.PublicKeyword: 2010 case SyntaxKind.PrivateKeyword: 2011 case SyntaxKind.ProtectedKeyword: 2012 return containingNodeKind === SyntaxKind.PropertyDeclaration; // class A{ public | 2013 } 2014 } 2015 2016 return false; 2017 } 2018 2019 function isInStringOrRegularExpressionOrTemplateLiteral(contextToken: Node): boolean { 2020 // To be "in" one of these literals, the position has to be: 2021 // 1. entirely within the token text. 2022 // 2. at the end position of an unterminated token. 2023 // 3. at the end of a regular expression (due to trailing flags like '/foo/g'). 2024 return (isRegularExpressionLiteral(contextToken) || isStringTextContainingNode(contextToken)) && ( 2025 rangeContainsPositionExclusive(createTextRangeFromSpan(createTextSpanFromNode(contextToken)), position) || 2026 position === contextToken.end && (!!contextToken.isUnterminated || isRegularExpressionLiteral(contextToken))); 2027 } 2028 2029 /** 2030 * Aggregates relevant symbols for completion in object literals and object binding patterns. 2031 * Relevant symbols are stored in the captured 'symbols' variable. 2032 * 2033 * @returns true if 'symbols' was successfully populated; false otherwise. 2034 */ 2035 function tryGetObjectLikeCompletionSymbols(): GlobalsSearch | undefined { 2036 const objectLikeContainer = tryGetObjectLikeCompletionContainer(contextToken); 2037 if (!objectLikeContainer) return GlobalsSearch.Continue; 2038 2039 // We're looking up possible property names from contextual/inferred/declared type. 2040 completionKind = CompletionKind.ObjectPropertyDeclaration; 2041 2042 let typeMembers: Symbol[] | undefined; 2043 let existingMembers: readonly Declaration[] | undefined; 2044 2045 if (objectLikeContainer.kind === SyntaxKind.ObjectLiteralExpression) { 2046 const instantiatedType = tryGetObjectLiteralContextualType(objectLikeContainer, typeChecker); 2047 2048 // Check completions for Object property value shorthand 2049 if (instantiatedType === undefined) { 2050 if (objectLikeContainer.flags & NodeFlags.InWithStatement) { 2051 return GlobalsSearch.Fail; 2052 } 2053 isNonContextualObjectLiteral = true; 2054 return GlobalsSearch.Continue; 2055 } 2056 const completionsType = typeChecker.getContextualType(objectLikeContainer, ContextFlags.Completions); 2057 const hasStringIndexType = (completionsType || instantiatedType).getStringIndexType(); 2058 const hasNumberIndextype = (completionsType || instantiatedType).getNumberIndexType(); 2059 isNewIdentifierLocation = !!hasStringIndexType || !!hasNumberIndextype; 2060 typeMembers = getPropertiesForObjectExpression(instantiatedType, completionsType, objectLikeContainer, typeChecker); 2061 existingMembers = objectLikeContainer.properties; 2062 2063 if (typeMembers.length === 0) { 2064 // Edge case: If NumberIndexType exists 2065 if (!hasNumberIndextype) { 2066 isNonContextualObjectLiteral = true; 2067 return GlobalsSearch.Continue; 2068 } 2069 } 2070 } 2071 else { 2072 Debug.assert(objectLikeContainer.kind === SyntaxKind.ObjectBindingPattern); 2073 // We are *only* completing on properties from the type being destructured. 2074 isNewIdentifierLocation = false; 2075 2076 const rootDeclaration = getRootDeclaration(objectLikeContainer.parent); 2077 if (!isVariableLike(rootDeclaration)) return Debug.fail("Root declaration is not variable-like."); 2078 2079 // We don't want to complete using the type acquired by the shape 2080 // of the binding pattern; we are only interested in types acquired 2081 // through type declaration or inference. 2082 // Also proceed if rootDeclaration is a parameter and if its containing function expression/arrow function is contextually typed - 2083 // type of parameter will flow in from the contextual type of the function 2084 let canGetType = hasInitializer(rootDeclaration) || hasType(rootDeclaration) || rootDeclaration.parent.parent.kind === SyntaxKind.ForOfStatement; 2085 if (!canGetType && rootDeclaration.kind === SyntaxKind.Parameter) { 2086 if (isExpression(rootDeclaration.parent)) { 2087 canGetType = !!typeChecker.getContextualType(<Expression>rootDeclaration.parent); 2088 } 2089 else if (rootDeclaration.parent.kind === SyntaxKind.MethodDeclaration || rootDeclaration.parent.kind === SyntaxKind.SetAccessor) { 2090 canGetType = isExpression(rootDeclaration.parent.parent) && !!typeChecker.getContextualType(<Expression>rootDeclaration.parent.parent); 2091 } 2092 } 2093 if (canGetType) { 2094 const typeForObject = typeChecker.getTypeAtLocation(objectLikeContainer); 2095 if (!typeForObject) return GlobalsSearch.Fail; 2096 // In a binding pattern, get only known properties (unless in the same scope). 2097 // Everywhere else we will get all possible properties. 2098 const containerClass = getContainingClass(objectLikeContainer); 2099 typeMembers = typeChecker.getPropertiesOfType(typeForObject).filter(symbol => 2100 // either public 2101 !(getDeclarationModifierFlagsFromSymbol(symbol) & ModifierFlags.NonPublicAccessibilityModifier) 2102 // or we're in it 2103 || containerClass && contains(typeForObject.symbol.declarations, containerClass)); 2104 existingMembers = objectLikeContainer.elements; 2105 } 2106 } 2107 2108 if (typeMembers && typeMembers.length > 0) { 2109 // Add filtered items to the completion list 2110 symbols = filterObjectMembersList(typeMembers, Debug.checkDefined(existingMembers)); 2111 } 2112 setSortTextToOptionalMember(); 2113 2114 return GlobalsSearch.Success; 2115 } 2116 2117 /** 2118 * Aggregates relevant symbols for completion in import clauses and export clauses 2119 * whose declarations have a module specifier; for instance, symbols will be aggregated for 2120 * 2121 * import { | } from "moduleName"; 2122 * export { a as foo, | } from "moduleName"; 2123 * 2124 * but not for 2125 * 2126 * export { | }; 2127 * 2128 * Relevant symbols are stored in the captured 'symbols' variable. 2129 */ 2130 function tryGetImportOrExportClauseCompletionSymbols(): GlobalsSearch { 2131 // `import { |` or `import { a as 0, | }` 2132 const namedImportsOrExports = contextToken && (contextToken.kind === SyntaxKind.OpenBraceToken || contextToken.kind === SyntaxKind.CommaToken) 2133 ? tryCast(contextToken.parent, isNamedImportsOrExports) : undefined; 2134 if (!namedImportsOrExports) return GlobalsSearch.Continue; 2135 2136 // try to show exported member for imported/re-exported module 2137 const { moduleSpecifier } = namedImportsOrExports.kind === SyntaxKind.NamedImports ? namedImportsOrExports.parent.parent : namedImportsOrExports.parent; 2138 if (!moduleSpecifier) return namedImportsOrExports.kind === SyntaxKind.NamedImports ? GlobalsSearch.Fail : GlobalsSearch.Continue; 2139 const moduleSpecifierSymbol = typeChecker.getSymbolAtLocation(moduleSpecifier); // TODO: GH#18217 2140 if (!moduleSpecifierSymbol) return GlobalsSearch.Fail; 2141 2142 completionKind = CompletionKind.MemberLike; 2143 isNewIdentifierLocation = false; 2144 const exports = typeChecker.getExportsAndPropertiesOfModule(moduleSpecifierSymbol); 2145 const existing = new Set((namedImportsOrExports.elements as NodeArray<ImportOrExportSpecifier>).filter(n => !isCurrentlyEditingNode(n)).map(n => (n.propertyName || n.name).escapedText)); 2146 symbols = exports.filter(e => e.escapedName !== InternalSymbolName.Default && !existing.has(e.escapedName)); 2147 return GlobalsSearch.Success; 2148 } 2149 2150 /** 2151 * Adds local declarations for completions in named exports: 2152 * 2153 * export { | }; 2154 * 2155 * Does not check for the absence of a module specifier (`export {} from "./other"`) 2156 * because `tryGetImportOrExportClauseCompletionSymbols` runs first and handles that, 2157 * preventing this function from running. 2158 */ 2159 function tryGetLocalNamedExportCompletionSymbols(): GlobalsSearch { 2160 const namedExports = contextToken && (contextToken.kind === SyntaxKind.OpenBraceToken || contextToken.kind === SyntaxKind.CommaToken) 2161 ? tryCast(contextToken.parent, isNamedExports) 2162 : undefined; 2163 2164 if (!namedExports) { 2165 return GlobalsSearch.Continue; 2166 } 2167 2168 const localsContainer = findAncestor(namedExports, or(isSourceFile, isModuleDeclaration))!; 2169 completionKind = CompletionKind.None; 2170 isNewIdentifierLocation = false; 2171 localsContainer.locals?.forEach((symbol, name) => { 2172 symbols.push(symbol); 2173 if (localsContainer.symbol?.exports?.has(name)) { 2174 symbolToSortTextMap[getSymbolId(symbol)] = SortText.OptionalMember; 2175 } 2176 }); 2177 return GlobalsSearch.Success; 2178 } 2179 2180 /** 2181 * Aggregates relevant symbols for completion in class declaration 2182 * Relevant symbols are stored in the captured 'symbols' variable. 2183 */ 2184 function tryGetClassLikeCompletionSymbols(): GlobalsSearch { 2185 const decl = tryGetObjectTypeDeclarationCompletionContainer(sourceFile, contextToken, location, position); 2186 if (!decl) return GlobalsSearch.Continue; 2187 2188 // We're looking up possible property names from parent type. 2189 completionKind = CompletionKind.MemberLike; 2190 // Declaring new property/method/accessor 2191 isNewIdentifierLocation = true; 2192 keywordFilters = contextToken.kind === SyntaxKind.AsteriskToken ? KeywordCompletionFilters.None : 2193 isClassLike(decl) ? KeywordCompletionFilters.ClassElementKeywords : KeywordCompletionFilters.InterfaceElementKeywords; 2194 2195 // If you're in an interface you don't want to repeat things from super-interface. So just stop here. 2196 if (!isClassLike(decl)) return GlobalsSearch.Success; 2197 2198 const classElement = contextToken.kind === SyntaxKind.SemicolonToken ? contextToken.parent.parent : contextToken.parent; 2199 let classElementModifierFlags = isClassElement(classElement) ? getEffectiveModifierFlags(classElement) : ModifierFlags.None; 2200 // If this is context token is not something we are editing now, consider if this would lead to be modifier 2201 if (contextToken.kind === SyntaxKind.Identifier && !isCurrentlyEditingNode(contextToken)) { 2202 switch (contextToken.getText()) { 2203 case "private": 2204 classElementModifierFlags = classElementModifierFlags | ModifierFlags.Private; 2205 break; 2206 case "static": 2207 classElementModifierFlags = classElementModifierFlags | ModifierFlags.Static; 2208 break; 2209 } 2210 } 2211 2212 // No member list for private methods 2213 if (!(classElementModifierFlags & ModifierFlags.Private)) { 2214 // List of property symbols of base type that are not private and already implemented 2215 const baseSymbols = flatMap(getAllSuperTypeNodes(decl), baseTypeNode => { 2216 const type = typeChecker.getTypeAtLocation(baseTypeNode); 2217 return classElementModifierFlags & ModifierFlags.Static ? 2218 type?.symbol && typeChecker.getPropertiesOfType(typeChecker.getTypeOfSymbolAtLocation(type.symbol, decl)) : 2219 type && typeChecker.getPropertiesOfType(type); 2220 }); 2221 symbols = filterClassMembersList(baseSymbols, decl.members, classElementModifierFlags); 2222 } 2223 2224 return GlobalsSearch.Success; 2225 } 2226 2227 /** 2228 * Returns the immediate owning object literal or binding pattern of a context token, 2229 * on the condition that one exists and that the context implies completion should be given. 2230 */ 2231 function tryGetObjectLikeCompletionContainer(contextToken: Node): ObjectLiteralExpression | ObjectBindingPattern | undefined { 2232 if (contextToken) { 2233 const { parent } = contextToken; 2234 switch (contextToken.kind) { 2235 case SyntaxKind.OpenBraceToken: // const x = { | 2236 case SyntaxKind.CommaToken: // const x = { a: 0, | 2237 if (isObjectLiteralExpression(parent) || isObjectBindingPattern(parent)) { 2238 return parent; 2239 } 2240 break; 2241 case SyntaxKind.AsteriskToken: 2242 return isMethodDeclaration(parent) ? tryCast(parent.parent, isObjectLiteralExpression) : undefined; 2243 case SyntaxKind.Identifier: 2244 return (contextToken as Identifier).text === "async" && isShorthandPropertyAssignment(contextToken.parent) 2245 ? contextToken.parent.parent : undefined; 2246 } 2247 } 2248 2249 return undefined; 2250 } 2251 2252 function isConstructorParameterCompletion(node: Node): boolean { 2253 return !!node.parent && isParameter(node.parent) && isConstructorDeclaration(node.parent.parent) 2254 && (isParameterPropertyModifier(node.kind) || isDeclarationName(node)); 2255 } 2256 2257 /** 2258 * Returns the immediate owning class declaration of a context token, 2259 * on the condition that one exists and that the context implies completion should be given. 2260 */ 2261 function tryGetConstructorLikeCompletionContainer(contextToken: Node): ConstructorDeclaration | undefined { 2262 if (contextToken) { 2263 const parent = contextToken.parent; 2264 switch (contextToken.kind) { 2265 case SyntaxKind.OpenParenToken: 2266 case SyntaxKind.CommaToken: 2267 return isConstructorDeclaration(contextToken.parent) ? contextToken.parent : undefined; 2268 2269 default: 2270 if (isConstructorParameterCompletion(contextToken)) { 2271 return parent.parent as ConstructorDeclaration; 2272 } 2273 } 2274 } 2275 return undefined; 2276 } 2277 2278 function tryGetFunctionLikeBodyCompletionContainer(contextToken: Node): FunctionLikeDeclaration | undefined { 2279 if (contextToken) { 2280 let prev: Node; 2281 const container = findAncestor(contextToken.parent, (node: Node) => { 2282 if (isClassLike(node)) { 2283 return "quit"; 2284 } 2285 if (isFunctionLikeDeclaration(node) && prev === node.body) { 2286 return true; 2287 } 2288 prev = node; 2289 return false; 2290 }); 2291 return container && container as FunctionLikeDeclaration; 2292 } 2293 } 2294 2295 function tryGetContainingJsxElement(contextToken: Node): JsxOpeningLikeElement | undefined { 2296 if (contextToken) { 2297 const parent = contextToken.parent; 2298 switch (contextToken.kind) { 2299 case SyntaxKind.GreaterThanToken: // End of a type argument list 2300 case SyntaxKind.LessThanSlashToken: 2301 case SyntaxKind.SlashToken: 2302 case SyntaxKind.Identifier: 2303 case SyntaxKind.PropertyAccessExpression: 2304 case SyntaxKind.JsxAttributes: 2305 case SyntaxKind.JsxAttribute: 2306 case SyntaxKind.JsxSpreadAttribute: 2307 if (parent && (parent.kind === SyntaxKind.JsxSelfClosingElement || parent.kind === SyntaxKind.JsxOpeningElement)) { 2308 if (contextToken.kind === SyntaxKind.GreaterThanToken) { 2309 const precedingToken = findPrecedingToken(contextToken.pos, sourceFile, /*startNode*/ undefined); 2310 if (!(parent as JsxOpeningLikeElement).typeArguments || (precedingToken && precedingToken.kind === SyntaxKind.SlashToken)) break; 2311 } 2312 return <JsxOpeningLikeElement>parent; 2313 } 2314 else if (parent.kind === SyntaxKind.JsxAttribute) { 2315 // Currently we parse JsxOpeningLikeElement as: 2316 // JsxOpeningLikeElement 2317 // attributes: JsxAttributes 2318 // properties: NodeArray<JsxAttributeLike> 2319 return parent.parent.parent as JsxOpeningLikeElement; 2320 } 2321 break; 2322 2323 // The context token is the closing } or " of an attribute, which means 2324 // its parent is a JsxExpression, whose parent is a JsxAttribute, 2325 // whose parent is a JsxOpeningLikeElement 2326 case SyntaxKind.StringLiteral: 2327 if (parent && ((parent.kind === SyntaxKind.JsxAttribute) || (parent.kind === SyntaxKind.JsxSpreadAttribute))) { 2328 // Currently we parse JsxOpeningLikeElement as: 2329 // JsxOpeningLikeElement 2330 // attributes: JsxAttributes 2331 // properties: NodeArray<JsxAttributeLike> 2332 return parent.parent.parent as JsxOpeningLikeElement; 2333 } 2334 2335 break; 2336 2337 case SyntaxKind.CloseBraceToken: 2338 if (parent && 2339 parent.kind === SyntaxKind.JsxExpression && 2340 parent.parent && parent.parent.kind === SyntaxKind.JsxAttribute) { 2341 // Currently we parse JsxOpeningLikeElement as: 2342 // JsxOpeningLikeElement 2343 // attributes: JsxAttributes 2344 // properties: NodeArray<JsxAttributeLike> 2345 // each JsxAttribute can have initializer as JsxExpression 2346 return parent.parent.parent.parent as JsxOpeningLikeElement; 2347 } 2348 2349 if (parent && parent.kind === SyntaxKind.JsxSpreadAttribute) { 2350 // Currently we parse JsxOpeningLikeElement as: 2351 // JsxOpeningLikeElement 2352 // attributes: JsxAttributes 2353 // properties: NodeArray<JsxAttributeLike> 2354 return parent.parent.parent as JsxOpeningLikeElement; 2355 } 2356 2357 break; 2358 } 2359 } 2360 return undefined; 2361 } 2362 2363 /** 2364 * @returns true if we are certain that the currently edited location must define a new location; false otherwise. 2365 */ 2366 function isSolelyIdentifierDefinitionLocation(contextToken: Node): boolean { 2367 const parent = contextToken.parent; 2368 const containingNodeKind = parent.kind; 2369 switch (contextToken.kind) { 2370 case SyntaxKind.CommaToken: 2371 return containingNodeKind === SyntaxKind.VariableDeclaration || 2372 isVariableDeclarationListButNotTypeArgument(contextToken) || 2373 containingNodeKind === SyntaxKind.VariableStatement || 2374 containingNodeKind === SyntaxKind.EnumDeclaration || // enum a { foo, | 2375 isFunctionLikeButNotConstructor(containingNodeKind) || 2376 containingNodeKind === SyntaxKind.InterfaceDeclaration || // interface A<T, | 2377 containingNodeKind === SyntaxKind.ArrayBindingPattern || // var [x, y| 2378 containingNodeKind === SyntaxKind.TypeAliasDeclaration || // type Map, K, | 2379 // class A<T, | 2380 // var C = class D<T, | 2381 (isClassLike(parent) && 2382 !!parent.typeParameters && 2383 parent.typeParameters.end >= contextToken.pos); 2384 2385 case SyntaxKind.DotToken: 2386 return containingNodeKind === SyntaxKind.ArrayBindingPattern; // var [.| 2387 2388 case SyntaxKind.ColonToken: 2389 return containingNodeKind === SyntaxKind.BindingElement; // var {x :html| 2390 2391 case SyntaxKind.OpenBracketToken: 2392 return containingNodeKind === SyntaxKind.ArrayBindingPattern; // var [x| 2393 2394 case SyntaxKind.OpenParenToken: 2395 return containingNodeKind === SyntaxKind.CatchClause || 2396 isFunctionLikeButNotConstructor(containingNodeKind); 2397 2398 case SyntaxKind.OpenBraceToken: 2399 return containingNodeKind === SyntaxKind.EnumDeclaration; // enum a { | 2400 2401 case SyntaxKind.LessThanToken: 2402 return containingNodeKind === SyntaxKind.ClassDeclaration || // class A< | 2403 containingNodeKind === SyntaxKind.ClassExpression || // var C = class D< | 2404 containingNodeKind === SyntaxKind.InterfaceDeclaration || // interface A< | 2405 containingNodeKind === SyntaxKind.TypeAliasDeclaration || // type List< | 2406 isFunctionLikeKind(containingNodeKind); 2407 2408 case SyntaxKind.StaticKeyword: 2409 return containingNodeKind === SyntaxKind.PropertyDeclaration && !isClassLike(parent.parent); 2410 2411 case SyntaxKind.DotDotDotToken: 2412 return containingNodeKind === SyntaxKind.Parameter || 2413 (!!parent.parent && parent.parent.kind === SyntaxKind.ArrayBindingPattern); // var [...z| 2414 2415 case SyntaxKind.PublicKeyword: 2416 case SyntaxKind.PrivateKeyword: 2417 case SyntaxKind.ProtectedKeyword: 2418 return containingNodeKind === SyntaxKind.Parameter && !isConstructorDeclaration(parent.parent); 2419 2420 case SyntaxKind.AsKeyword: 2421 return containingNodeKind === SyntaxKind.ImportSpecifier || 2422 containingNodeKind === SyntaxKind.ExportSpecifier || 2423 containingNodeKind === SyntaxKind.NamespaceImport; 2424 2425 case SyntaxKind.GetKeyword: 2426 case SyntaxKind.SetKeyword: 2427 return !isFromObjectTypeDeclaration(contextToken); 2428 2429 case SyntaxKind.ClassKeyword: 2430 case SyntaxKind.StructKeyword: 2431 case SyntaxKind.EnumKeyword: 2432 case SyntaxKind.InterfaceKeyword: 2433 case SyntaxKind.FunctionKeyword: 2434 case SyntaxKind.VarKeyword: 2435 case SyntaxKind.ImportKeyword: 2436 case SyntaxKind.LetKeyword: 2437 case SyntaxKind.ConstKeyword: 2438 case SyntaxKind.InferKeyword: 2439 case SyntaxKind.TypeKeyword: // type htm| 2440 return true; 2441 2442 case SyntaxKind.AsteriskToken: 2443 return isFunctionLike(contextToken.parent) && !isMethodDeclaration(contextToken.parent); 2444 } 2445 2446 // If the previous token is keyword correspoding to class member completion keyword 2447 // there will be completion available here 2448 if (isClassMemberCompletionKeyword(keywordForNode(contextToken)) && isFromObjectTypeDeclaration(contextToken)) { 2449 return false; 2450 } 2451 2452 if (isConstructorParameterCompletion(contextToken)) { 2453 // constructor parameter completion is available only if 2454 // - its modifier of the constructor parameter or 2455 // - its name of the parameter and not being edited 2456 // eg. constructor(a |<- this shouldnt show completion 2457 if (!isIdentifier(contextToken) || 2458 isParameterPropertyModifier(keywordForNode(contextToken)) || 2459 isCurrentlyEditingNode(contextToken)) { 2460 return false; 2461 } 2462 } 2463 2464 // Previous token may have been a keyword that was converted to an identifier. 2465 switch (keywordForNode(contextToken)) { 2466 case SyntaxKind.AbstractKeyword: 2467 case SyntaxKind.ClassKeyword: 2468 case SyntaxKind.StructKeyword: 2469 case SyntaxKind.ConstKeyword: 2470 case SyntaxKind.DeclareKeyword: 2471 case SyntaxKind.EnumKeyword: 2472 case SyntaxKind.FunctionKeyword: 2473 case SyntaxKind.InterfaceKeyword: 2474 case SyntaxKind.LetKeyword: 2475 case SyntaxKind.PrivateKeyword: 2476 case SyntaxKind.ProtectedKeyword: 2477 case SyntaxKind.PublicKeyword: 2478 case SyntaxKind.StaticKeyword: 2479 case SyntaxKind.VarKeyword: 2480 return true; 2481 case SyntaxKind.AsyncKeyword: 2482 return isPropertyDeclaration(contextToken.parent); 2483 } 2484 2485 return isDeclarationName(contextToken) 2486 && !isShorthandPropertyAssignment(contextToken.parent) 2487 && !isJsxAttribute(contextToken.parent) 2488 // 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`. 2489 // If `contextToken !== previousToken`, this is `class C ex/**/`. 2490 && !(isClassLike(contextToken.parent) && (contextToken !== previousToken || position > previousToken.end)); 2491 } 2492 2493 function isFunctionLikeButNotConstructor(kind: SyntaxKind) { 2494 return isFunctionLikeKind(kind) && kind !== SyntaxKind.Constructor; 2495 } 2496 2497 function isDotOfNumericLiteral(contextToken: Node): boolean { 2498 if (contextToken.kind === SyntaxKind.NumericLiteral) { 2499 const text = contextToken.getFullText(); 2500 return text.charAt(text.length - 1) === "."; 2501 } 2502 2503 return false; 2504 } 2505 2506 function isVariableDeclarationListButNotTypeArgument(node: Node): boolean { 2507 return node.parent.kind === SyntaxKind.VariableDeclarationList 2508 && !isPossiblyTypeArgumentPosition(node, sourceFile, typeChecker); 2509 } 2510 2511 /** 2512 * Filters out completion suggestions for named imports or exports. 2513 * 2514 * @returns Symbols to be suggested in an object binding pattern or object literal expression, barring those whose declarations 2515 * do not occur at the current position and have not otherwise been typed. 2516 */ 2517 function filterObjectMembersList(contextualMemberSymbols: Symbol[], existingMembers: readonly Declaration[]): Symbol[] { 2518 if (existingMembers.length === 0) { 2519 return contextualMemberSymbols; 2520 } 2521 2522 const membersDeclaredBySpreadAssignment = new Set<string>(); 2523 const existingMemberNames = new Set<__String>(); 2524 for (const m of existingMembers) { 2525 // Ignore omitted expressions for missing members 2526 if (m.kind !== SyntaxKind.PropertyAssignment && 2527 m.kind !== SyntaxKind.ShorthandPropertyAssignment && 2528 m.kind !== SyntaxKind.BindingElement && 2529 m.kind !== SyntaxKind.MethodDeclaration && 2530 m.kind !== SyntaxKind.GetAccessor && 2531 m.kind !== SyntaxKind.SetAccessor && 2532 m.kind !== SyntaxKind.SpreadAssignment) { 2533 continue; 2534 } 2535 2536 // If this is the current item we are editing right now, do not filter it out 2537 if (isCurrentlyEditingNode(m)) { 2538 continue; 2539 } 2540 2541 let existingName: __String | undefined; 2542 2543 if (isSpreadAssignment(m)) { 2544 setMembersDeclaredBySpreadAssignment(m, membersDeclaredBySpreadAssignment); 2545 } 2546 else if (isBindingElement(m) && m.propertyName) { 2547 // include only identifiers in completion list 2548 if (m.propertyName.kind === SyntaxKind.Identifier) { 2549 existingName = m.propertyName.escapedText; 2550 } 2551 } 2552 else { 2553 // TODO: Account for computed property name 2554 // NOTE: if one only performs this step when m.name is an identifier, 2555 // things like '__proto__' are not filtered out. 2556 const name = getNameOfDeclaration(m); 2557 existingName = name && isPropertyNameLiteral(name) ? getEscapedTextOfIdentifierOrLiteral(name) : undefined; 2558 } 2559 2560 if (existingName !== undefined) { 2561 existingMemberNames.add(existingName); 2562 } 2563 } 2564 2565 const filteredSymbols = contextualMemberSymbols.filter(m => !existingMemberNames.has(m.escapedName)); 2566 setSortTextToMemberDeclaredBySpreadAssignment(membersDeclaredBySpreadAssignment, filteredSymbols); 2567 2568 return filteredSymbols; 2569 } 2570 2571 function setMembersDeclaredBySpreadAssignment(declaration: SpreadAssignment | JsxSpreadAttribute, membersDeclaredBySpreadAssignment: Set<string>) { 2572 const expression = declaration.expression; 2573 const symbol = typeChecker.getSymbolAtLocation(expression); 2574 const type = symbol && typeChecker.getTypeOfSymbolAtLocation(symbol, expression); 2575 const properties = type && (<ObjectType>type).properties; 2576 if (properties) { 2577 properties.forEach(property => { 2578 membersDeclaredBySpreadAssignment.add(property.name); 2579 }); 2580 } 2581 } 2582 2583 // Set SortText to OptionalMember if it is an optional member 2584 function setSortTextToOptionalMember() { 2585 symbols.forEach(m => { 2586 if (m.flags & SymbolFlags.Optional) { 2587 symbolToSortTextMap[getSymbolId(m)] = symbolToSortTextMap[getSymbolId(m)] || SortText.OptionalMember; 2588 } 2589 }); 2590 } 2591 2592 // Set SortText to MemberDeclaredBySpreadAssignment if it is fulfilled by spread assignment 2593 function setSortTextToMemberDeclaredBySpreadAssignment(membersDeclaredBySpreadAssignment: Set<string>, contextualMemberSymbols: Symbol[]): void { 2594 if (membersDeclaredBySpreadAssignment.size === 0) { 2595 return; 2596 } 2597 for (const contextualMemberSymbol of contextualMemberSymbols) { 2598 if (membersDeclaredBySpreadAssignment.has(contextualMemberSymbol.name)) { 2599 symbolToSortTextMap[getSymbolId(contextualMemberSymbol)] = SortText.MemberDeclaredBySpreadAssignment; 2600 } 2601 } 2602 } 2603 2604 /** 2605 * Filters out completion suggestions for class elements. 2606 * 2607 * @returns Symbols to be suggested in an class element depending on existing memebers and symbol flags 2608 */ 2609 function filterClassMembersList(baseSymbols: readonly Symbol[], existingMembers: readonly ClassElement[], currentClassElementModifierFlags: ModifierFlags): Symbol[] { 2610 const existingMemberNames = new Set<__String>(); 2611 for (const m of existingMembers) { 2612 // Ignore omitted expressions for missing members 2613 if (m.kind !== SyntaxKind.PropertyDeclaration && 2614 m.kind !== SyntaxKind.MethodDeclaration && 2615 m.kind !== SyntaxKind.GetAccessor && 2616 m.kind !== SyntaxKind.SetAccessor) { 2617 continue; 2618 } 2619 2620 // If this is the current item we are editing right now, do not filter it out 2621 if (isCurrentlyEditingNode(m)) { 2622 continue; 2623 } 2624 2625 // Dont filter member even if the name matches if it is declared private in the list 2626 if (hasEffectiveModifier(m, ModifierFlags.Private)) { 2627 continue; 2628 } 2629 2630 // do not filter it out if the static presence doesnt match 2631 if (hasEffectiveModifier(m, ModifierFlags.Static) !== !!(currentClassElementModifierFlags & ModifierFlags.Static)) { 2632 continue; 2633 } 2634 2635 const existingName = getPropertyNameForPropertyNameNode(m.name!); 2636 if (existingName) { 2637 existingMemberNames.add(existingName); 2638 } 2639 } 2640 2641 return baseSymbols.filter(propertySymbol => 2642 !existingMemberNames.has(propertySymbol.escapedName) && 2643 !!propertySymbol.declarations && 2644 !(getDeclarationModifierFlagsFromSymbol(propertySymbol) & ModifierFlags.Private) && 2645 !(propertySymbol.valueDeclaration && isPrivateIdentifierPropertyDeclaration(propertySymbol.valueDeclaration))); 2646 } 2647 2648 /** 2649 * Filters out completion suggestions from 'symbols' according to existing JSX attributes. 2650 * 2651 * @returns Symbols to be suggested in a JSX element, barring those whose attributes 2652 * do not occur at the current position and have not otherwise been typed. 2653 */ 2654 function filterJsxAttributes(symbols: Symbol[], attributes: NodeArray<JsxAttribute | JsxSpreadAttribute>): Symbol[] { 2655 const seenNames = new Set<__String>(); 2656 const membersDeclaredBySpreadAssignment = new Set<string>(); 2657 for (const attr of attributes) { 2658 // If this is the current item we are editing right now, do not filter it out 2659 if (isCurrentlyEditingNode(attr)) { 2660 continue; 2661 } 2662 2663 if (attr.kind === SyntaxKind.JsxAttribute) { 2664 seenNames.add(attr.name.escapedText); 2665 } 2666 else if (isJsxSpreadAttribute(attr)) { 2667 setMembersDeclaredBySpreadAssignment(attr, membersDeclaredBySpreadAssignment); 2668 } 2669 } 2670 const filteredSymbols = symbols.filter(a => !seenNames.has(a.escapedName)); 2671 2672 setSortTextToMemberDeclaredBySpreadAssignment(membersDeclaredBySpreadAssignment, filteredSymbols); 2673 2674 return filteredSymbols; 2675 } 2676 2677 function isCurrentlyEditingNode(node: Node): boolean { 2678 return node.getStart(sourceFile) <= position && position <= node.getEnd(); 2679 } 2680 } 2681 2682 interface CompletionEntryDisplayNameForSymbol { 2683 readonly name: string; 2684 readonly needsConvertPropertyAccess: boolean; 2685 } 2686 function getCompletionEntryDisplayNameForSymbol( 2687 symbol: Symbol, 2688 target: ScriptTarget, 2689 origin: SymbolOriginInfo | undefined, 2690 kind: CompletionKind, 2691 jsxIdentifierExpected: boolean, 2692 ): CompletionEntryDisplayNameForSymbol | undefined { 2693 const name = originIsExport(origin) ? getNameForExportedSymbol(symbol, target) : symbol.name; 2694 if (name === undefined 2695 // If the symbol is external module, don't show it in the completion list 2696 // (i.e declare module "http" { const x; } | // <= request completion here, "http" should not be there) 2697 || symbol.flags & SymbolFlags.Module && isSingleOrDoubleQuote(name.charCodeAt(0)) 2698 // If the symbol is the internal name of an ES symbol, it is not a valid entry. Internal names for ES symbols start with "__@" 2699 || isKnownSymbol(symbol)) { 2700 return undefined; 2701 } 2702 2703 const validNameResult: CompletionEntryDisplayNameForSymbol = { name, needsConvertPropertyAccess: false }; 2704 if (isIdentifierText(name, target, jsxIdentifierExpected ? LanguageVariant.JSX : LanguageVariant.Standard) || symbol.valueDeclaration && isPrivateIdentifierPropertyDeclaration(symbol.valueDeclaration)) { 2705 return validNameResult; 2706 } 2707 switch (kind) { 2708 case CompletionKind.MemberLike: 2709 return undefined; 2710 case CompletionKind.ObjectPropertyDeclaration: 2711 // TODO: GH#18169 2712 return { name: JSON.stringify(name), needsConvertPropertyAccess: false }; 2713 case CompletionKind.PropertyAccess: 2714 case CompletionKind.Global: // For a 'this.' completion it will be in a global context, but may have a non-identifier name. 2715 // Don't add a completion for a name starting with a space. See https://github.com/Microsoft/TypeScript/pull/20547 2716 return name.charCodeAt(0) === CharacterCodes.space ? undefined : { name, needsConvertPropertyAccess: true }; 2717 case CompletionKind.None: 2718 case CompletionKind.String: 2719 return validNameResult; 2720 default: 2721 Debug.assertNever(kind); 2722 } 2723 } 2724 2725 // A cache of completion entries for keywords, these do not change between sessions 2726 const _keywordCompletions: CompletionEntry[][] = []; 2727 const allKeywordsCompletions: () => readonly CompletionEntry[] = memoize(() => { 2728 const res: CompletionEntry[] = []; 2729 for (let i = SyntaxKind.FirstKeyword; i <= SyntaxKind.LastKeyword; i++) { 2730 res.push({ 2731 name: tokenToString(i)!, 2732 kind: ScriptElementKind.keyword, 2733 kindModifiers: ScriptElementKindModifier.none, 2734 sortText: SortText.GlobalsOrKeywords 2735 }); 2736 } 2737 return res; 2738 }); 2739 2740 function getKeywordCompletions(keywordFilter: KeywordCompletionFilters, filterOutTsOnlyKeywords: boolean): readonly CompletionEntry[] { 2741 if (!filterOutTsOnlyKeywords) return getTypescriptKeywordCompletions(keywordFilter); 2742 2743 const index = keywordFilter + KeywordCompletionFilters.Last + 1; 2744 return _keywordCompletions[index] || 2745 (_keywordCompletions[index] = getTypescriptKeywordCompletions(keywordFilter) 2746 .filter(entry => !isTypeScriptOnlyKeyword(stringToToken(entry.name)!)) 2747 ); 2748 } 2749 2750 function getTypescriptKeywordCompletions(keywordFilter: KeywordCompletionFilters): readonly CompletionEntry[] { 2751 return _keywordCompletions[keywordFilter] || (_keywordCompletions[keywordFilter] = allKeywordsCompletions().filter(entry => { 2752 const kind = stringToToken(entry.name)!; 2753 switch (keywordFilter) { 2754 case KeywordCompletionFilters.None: 2755 return false; 2756 case KeywordCompletionFilters.All: 2757 return isFunctionLikeBodyKeyword(kind) 2758 || kind === SyntaxKind.DeclareKeyword 2759 || kind === SyntaxKind.ModuleKeyword 2760 || kind === SyntaxKind.TypeKeyword 2761 || kind === SyntaxKind.NamespaceKeyword 2762 || isTypeKeyword(kind) && kind !== SyntaxKind.UndefinedKeyword; 2763 case KeywordCompletionFilters.FunctionLikeBodyKeywords: 2764 return isFunctionLikeBodyKeyword(kind); 2765 case KeywordCompletionFilters.ClassElementKeywords: 2766 return isClassMemberCompletionKeyword(kind); 2767 case KeywordCompletionFilters.InterfaceElementKeywords: 2768 return isInterfaceOrTypeLiteralCompletionKeyword(kind); 2769 case KeywordCompletionFilters.ConstructorParameterKeywords: 2770 return isParameterPropertyModifier(kind); 2771 case KeywordCompletionFilters.TypeAssertionKeywords: 2772 return isTypeKeyword(kind) || kind === SyntaxKind.ConstKeyword; 2773 case KeywordCompletionFilters.TypeKeywords: 2774 return isTypeKeyword(kind); 2775 default: 2776 return Debug.assertNever(keywordFilter); 2777 } 2778 })); 2779 } 2780 2781 function isTypeScriptOnlyKeyword(kind: SyntaxKind) { 2782 switch (kind) { 2783 case SyntaxKind.AbstractKeyword: 2784 case SyntaxKind.AnyKeyword: 2785 case SyntaxKind.BigIntKeyword: 2786 case SyntaxKind.BooleanKeyword: 2787 case SyntaxKind.DeclareKeyword: 2788 case SyntaxKind.EnumKeyword: 2789 case SyntaxKind.GlobalKeyword: 2790 case SyntaxKind.ImplementsKeyword: 2791 case SyntaxKind.InferKeyword: 2792 case SyntaxKind.InterfaceKeyword: 2793 case SyntaxKind.IsKeyword: 2794 case SyntaxKind.KeyOfKeyword: 2795 case SyntaxKind.ModuleKeyword: 2796 case SyntaxKind.NamespaceKeyword: 2797 case SyntaxKind.NeverKeyword: 2798 case SyntaxKind.NumberKeyword: 2799 case SyntaxKind.ObjectKeyword: 2800 case SyntaxKind.PrivateKeyword: 2801 case SyntaxKind.ProtectedKeyword: 2802 case SyntaxKind.PublicKeyword: 2803 case SyntaxKind.ReadonlyKeyword: 2804 case SyntaxKind.StringKeyword: 2805 case SyntaxKind.SymbolKeyword: 2806 case SyntaxKind.TypeKeyword: 2807 case SyntaxKind.UniqueKeyword: 2808 case SyntaxKind.UnknownKeyword: 2809 return true; 2810 default: 2811 return false; 2812 } 2813 } 2814 2815 function isInterfaceOrTypeLiteralCompletionKeyword(kind: SyntaxKind): boolean { 2816 return kind === SyntaxKind.ReadonlyKeyword; 2817 } 2818 2819 function isClassMemberCompletionKeyword(kind: SyntaxKind) { 2820 switch (kind) { 2821 case SyntaxKind.AbstractKeyword: 2822 case SyntaxKind.ConstructorKeyword: 2823 case SyntaxKind.GetKeyword: 2824 case SyntaxKind.SetKeyword: 2825 case SyntaxKind.AsyncKeyword: 2826 case SyntaxKind.DeclareKeyword: 2827 return true; 2828 default: 2829 return isClassMemberModifier(kind); 2830 } 2831 } 2832 2833 function isFunctionLikeBodyKeyword(kind: SyntaxKind) { 2834 return kind === SyntaxKind.AsyncKeyword 2835 || kind === SyntaxKind.AwaitKeyword 2836 || kind === SyntaxKind.AsKeyword 2837 || !isContextualKeyword(kind) && !isClassMemberCompletionKeyword(kind); 2838 } 2839 2840 function keywordForNode(node: Node): SyntaxKind { 2841 return isIdentifier(node) ? node.originalKeywordKind || SyntaxKind.Unknown : node.kind; 2842 } 2843 2844 /** Get the corresponding JSDocTag node if the position is in a jsDoc comment */ 2845 function getJsDocTagAtPosition(node: Node, position: number): JSDocTag | undefined { 2846 const jsdoc = findAncestor(node, isJSDoc); 2847 return jsdoc && jsdoc.tags && (rangeContainsPosition(jsdoc, position) ? findLast(jsdoc.tags, tag => tag.pos < position) : undefined); 2848 } 2849 2850 export function getPropertiesForObjectExpression(contextualType: Type, completionsType: Type | undefined, obj: ObjectLiteralExpression | JsxAttributes, checker: TypeChecker): Symbol[] { 2851 const hasCompletionsType = completionsType && completionsType !== contextualType; 2852 const type = hasCompletionsType && !(completionsType!.flags & TypeFlags.AnyOrUnknown) 2853 ? checker.getUnionType([contextualType, completionsType!]) 2854 : contextualType; 2855 2856 const properties = type.isUnion() 2857 ? checker.getAllPossiblePropertiesOfTypes(type.types.filter(memberType => 2858 // If we're providing completions for an object literal, skip primitive, array-like, or callable types since those shouldn't be implemented by object literals. 2859 !(memberType.flags & TypeFlags.Primitive || 2860 checker.isArrayLikeType(memberType) || 2861 typeHasCallOrConstructSignatures(memberType, checker) || 2862 checker.isTypeInvalidDueToUnionDiscriminant(memberType, obj)))) 2863 : type.getApparentProperties(); 2864 2865 return hasCompletionsType ? properties.filter(hasDeclarationOtherThanSelf) : properties; 2866 2867 // Filter out members whose only declaration is the object literal itself to avoid 2868 // self-fulfilling completions like: 2869 // 2870 // function f<T>(x: T) {} 2871 // f({ abc/**/: "" }) // `abc` is a member of `T` but only because it declares itself 2872 function hasDeclarationOtherThanSelf(member: Symbol) { 2873 return some(member.declarations, decl => decl.parent !== obj); 2874 } 2875 } 2876 2877 /** 2878 * Gets all properties on a type, but if that type is a union of several types, 2879 * excludes array-like types or callable/constructable types. 2880 */ 2881 function getPropertiesForCompletion(type: Type, checker: TypeChecker): Symbol[] { 2882 return type.isUnion() 2883 ? Debug.checkEachDefined(checker.getAllPossiblePropertiesOfTypes(type.types), "getAllPossiblePropertiesOfTypes() should all be defined") 2884 : Debug.checkEachDefined(type.getApparentProperties(), "getApparentProperties() should all be defined"); 2885 } 2886 2887 /** 2888 * Returns the immediate owning class declaration of a context token, 2889 * on the condition that one exists and that the context implies completion should be given. 2890 */ 2891 function tryGetObjectTypeDeclarationCompletionContainer(sourceFile: SourceFile, contextToken: Node | undefined, location: Node, position: number): ObjectTypeDeclaration | undefined { 2892 // class c { method() { } | method2() { } } 2893 switch (location.kind) { 2894 case SyntaxKind.SyntaxList: 2895 return tryCast(location.parent, isObjectTypeDeclaration); 2896 case SyntaxKind.EndOfFileToken: 2897 const cls = tryCast(lastOrUndefined(cast(location.parent, isSourceFile).statements), isObjectTypeDeclaration); 2898 if (cls && !findChildOfKind(cls, SyntaxKind.CloseBraceToken, sourceFile)) { 2899 return cls; 2900 } 2901 break; 2902 case SyntaxKind.Identifier: { 2903 // class c { public prop = c| } 2904 if (isPropertyDeclaration(location.parent) && location.parent.initializer === location) { 2905 return undefined; 2906 } 2907 // class c extends React.Component { a: () => 1\n compon| } 2908 if (isFromObjectTypeDeclaration(location)) { 2909 return findAncestor(location, isObjectTypeDeclaration); 2910 } 2911 } 2912 } 2913 2914 if (!contextToken) return undefined; 2915 2916 switch (contextToken.kind) { 2917 case SyntaxKind.EqualsToken: // class c { public prop = | /* global completions */ } 2918 return undefined; 2919 2920 case SyntaxKind.SemicolonToken: // class c {getValue(): number; | } 2921 case SyntaxKind.CloseBraceToken: // class c { method() { } | } 2922 // class c { method() { } b| } 2923 return isFromObjectTypeDeclaration(location) && (location.parent as ClassElement | TypeElement).name === location 2924 ? location.parent.parent as ObjectTypeDeclaration 2925 : tryCast(location, isObjectTypeDeclaration); 2926 case SyntaxKind.OpenBraceToken: // class c { | 2927 case SyntaxKind.CommaToken: // class c {getValue(): number, | } 2928 return tryCast(contextToken.parent, isObjectTypeDeclaration); 2929 default: 2930 if (!isFromObjectTypeDeclaration(contextToken)) { 2931 // class c extends React.Component { a: () => 1\n| } 2932 if (getLineAndCharacterOfPosition(sourceFile, contextToken.getEnd()).line !== getLineAndCharacterOfPosition(sourceFile, position).line && isObjectTypeDeclaration(location)) { 2933 return location; 2934 } 2935 return undefined; 2936 } 2937 const isValidKeyword = isClassLike(contextToken.parent.parent) ? isClassMemberCompletionKeyword : isInterfaceOrTypeLiteralCompletionKeyword; 2938 return (isValidKeyword(contextToken.kind) || contextToken.kind === SyntaxKind.AsteriskToken || isIdentifier(contextToken) && isValidKeyword(stringToToken(contextToken.text)!)) // TODO: GH#18217 2939 ? contextToken.parent.parent as ObjectTypeDeclaration : undefined; 2940 } 2941 } 2942 2943 // TODO: GH#19856 Would like to return `node is Node & { parent: (ClassElement | TypeElement) & { parent: ObjectTypeDeclaration } }` but then compilation takes > 10 minutes 2944 function isFromObjectTypeDeclaration(node: Node): boolean { 2945 return node.parent && isClassOrTypeElement(node.parent) && isObjectTypeDeclaration(node.parent.parent); 2946 } 2947 2948 function isValidTrigger(sourceFile: SourceFile, triggerCharacter: CompletionsTriggerCharacter, contextToken: Node | undefined, position: number): boolean { 2949 switch (triggerCharacter) { 2950 case ".": 2951 case "@": 2952 return true; 2953 case '"': 2954 case "'": 2955 case "`": 2956 // Only automatically bring up completions if this is an opening quote. 2957 return !!contextToken && isStringLiteralOrTemplate(contextToken) && position === contextToken.getStart(sourceFile) + 1; 2958 case "#": 2959 return !!contextToken && isPrivateIdentifier(contextToken) && !!getContainingClass(contextToken); 2960 case "<": 2961 // Opening JSX tag 2962 return !!contextToken && contextToken.kind === SyntaxKind.LessThanToken && (!isBinaryExpression(contextToken.parent) || binaryExpressionMayBeOpenTag(contextToken.parent)); 2963 case "/": 2964 return !!contextToken && (isStringLiteralLike(contextToken) 2965 ? !!tryGetImportFromModuleSpecifier(contextToken) 2966 : contextToken.kind === SyntaxKind.SlashToken && isJsxClosingElement(contextToken.parent)); 2967 default: 2968 return Debug.assertNever(triggerCharacter); 2969 } 2970 } 2971 2972 function binaryExpressionMayBeOpenTag({ left }: BinaryExpression): boolean { 2973 return nodeIsMissing(left); 2974 } 2975 2976 function findAlias(typeChecker: TypeChecker, symbol: Symbol, predicate: (symbol: Symbol) => boolean): Symbol | undefined { 2977 let currentAlias: Symbol | undefined = symbol; 2978 while (currentAlias.flags & SymbolFlags.Alias && (currentAlias = typeChecker.getImmediateAliasedSymbol(currentAlias))) { 2979 if (predicate(currentAlias)) { 2980 return currentAlias; 2981 } 2982 } 2983 } 2984 2985 /** Determines if a type is exactly the same type resolved by the global 'self', 'global', or 'globalThis'. */ 2986 function isProbablyGlobalType(type: Type, sourceFile: SourceFile, checker: TypeChecker) { 2987 // The type of `self` and `window` is the same in lib.dom.d.ts, but `window` does not exist in 2988 // lib.webworker.d.ts, so checking against `self` is also a check against `window` when it exists. 2989 const selfSymbol = checker.resolveName("self", /*location*/ undefined, SymbolFlags.Value, /*excludeGlobals*/ false); 2990 if (selfSymbol && checker.getTypeOfSymbolAtLocation(selfSymbol, sourceFile) === type) { 2991 return true; 2992 } 2993 const globalSymbol = checker.resolveName("global", /*location*/ undefined, SymbolFlags.Value, /*excludeGlobals*/ false); 2994 if (globalSymbol && checker.getTypeOfSymbolAtLocation(globalSymbol, sourceFile) === type) { 2995 return true; 2996 } 2997 const globalThisSymbol = checker.resolveName("globalThis", /*location*/ undefined, SymbolFlags.Value, /*excludeGlobals*/ false); 2998 if (globalThisSymbol && checker.getTypeOfSymbolAtLocation(globalThisSymbol, sourceFile) === type) { 2999 return true; 3000 } 3001 return false; 3002 } 3003 3004 function isStaticProperty(symbol: Symbol) { 3005 return !!(symbol.valueDeclaration && getEffectiveModifierFlags(symbol.valueDeclaration) & ModifierFlags.Static && isClassLike(symbol.valueDeclaration.parent)); 3006 } 3007 3008 function tryGetObjectLiteralContextualType(node: ObjectLiteralExpression, typeChecker: TypeChecker) { 3009 const type = typeChecker.getContextualType(node); 3010 if (type) { 3011 return type; 3012 } 3013 if (isBinaryExpression(node.parent) && node.parent.operatorToken.kind === SyntaxKind.EqualsToken) { 3014 return typeChecker.getTypeAtLocation(node.parent); 3015 } 3016 return undefined; 3017 } 3018} 3019