();
codefix.forEachExternalModuleToImportFrom(program, host, sourceFile, !detailsEntryId, /*useAutoImportProvider*/ true, (moduleSymbol, _, program, isFromPackageJson) => {
// Perf -- ignore other modules if this is a request for details
if (detailsEntryId && detailsEntryId.source && stripQuotes(moduleSymbol.name) !== detailsEntryId.source) {
return;
}
const typeChecker = program.getTypeChecker();
const resolvedModuleSymbol = typeChecker.resolveExternalModuleSymbol(moduleSymbol);
// resolvedModuleSymbol may be a namespace. A namespace may be `export =` by multiple module declarations, but only keep the first one.
if (!addToSeen(seenResolvedModules, getSymbolId(resolvedModuleSymbol))) {
return;
}
// Don't add another completion for `export =` of a symbol that's already global.
// So in `declare namespace foo {} declare module "foo" { export = foo; }`, there will just be the global completion for `foo`.
if (resolvedModuleSymbol !== moduleSymbol && every(resolvedModuleSymbol.declarations, isNonGlobalDeclaration)) {
pushSymbol(resolvedModuleSymbol, moduleSymbol, isFromPackageJson, /*skipFilter*/ true);
}
for (const symbol of typeChecker.getExportsAndPropertiesOfModule(moduleSymbol)) {
const symbolId = getSymbolId(symbol).toString();
// `getExportsAndPropertiesOfModule` can include duplicates
if (!addToSeen(seenExports, symbolId)) {
continue;
}
// If this is `export { _break as break };` (a keyword) -- skip this and prefer the keyword completion.
if (some(symbol.declarations, d => isExportSpecifier(d) && !!d.propertyName && isIdentifierANonContextualKeyword(d.name))) {
continue;
}
// If `symbol.parent !== moduleSymbol`, this is an `export * from "foo"` re-export. Those don't create new symbols.
const isExportStarFromReExport = typeChecker.getMergedSymbol(symbol.parent!) !== resolvedModuleSymbol;
// 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).
if (isExportStarFromReExport || some(symbol.declarations, d => isExportSpecifier(d) && !d.propertyName && !!d.parent.parent.moduleSpecifier)) {
// Walk the export chain back one module (step 1 or 2 in diagrammed example).
// Or, in the case of `export * from "foo"`, `symbol` already points to the original export, so just use that.
const nearestExportSymbol = isExportStarFromReExport ? symbol : getNearestExportSymbol(symbol);
if (!nearestExportSymbol) continue;
const nearestExportSymbolId = getSymbolId(nearestExportSymbol).toString();
const symbolHasBeenSeen = resultSymbolIds.has(nearestExportSymbolId) || aliasesToAlreadyIncludedSymbols.has(nearestExportSymbolId);
if (!symbolHasBeenSeen) {
aliasesToReturnIfOriginalsAreMissing.set(nearestExportSymbolId, { alias: symbol, moduleSymbol, isFromPackageJson });
aliasesToAlreadyIncludedSymbols.set(symbolId, true);
}
else {
// Perf - we know this symbol is an alias to one that’s already covered in `symbols`, so store it here
// in case another symbol re-exports this one; that way we can short-circuit as soon as we see this symbol id.
addToSeen(aliasesToAlreadyIncludedSymbols, symbolId);
}
}
else {
// This is not a re-export, so see if we have any aliases pending and remove them (step 3 in diagrammed example)
aliasesToReturnIfOriginalsAreMissing.delete(symbolId);
pushSymbol(symbol, moduleSymbol, isFromPackageJson, /*skipFilter*/ false);
}
}
});
// By this point, any potential duplicates that were actually duplicates have been
// removed, so the rest need to be added. (Step 4 in diagrammed example)
aliasesToReturnIfOriginalsAreMissing.forEach(({ alias, moduleSymbol, isFromPackageJson }) => pushSymbol(alias, moduleSymbol, isFromPackageJson, /*skipFilter*/ false));
log(`getSymbolsFromOtherSourceFileExports: ${timestamp() - startTime}`);
return results;
function pushSymbol(symbol: Symbol, moduleSymbol: Symbol, isFromPackageJson: boolean, skipFilter: boolean) {
const isDefaultExport = symbol.escapedName === InternalSymbolName.Default;
if (isDefaultExport) {
symbol = getLocalSymbolForExportDefault(symbol) || symbol;
}
if (typeChecker.isUndefinedSymbol(symbol)) {
return;
}
addToSeen(resultSymbolIds, getSymbolId(symbol));
const origin: SymbolOriginInfoExport = { kind: SymbolOriginInfoKind.Export, moduleSymbol, isDefaultExport, isFromPackageJson };
results.push({
symbol,
symbolName: getNameForExportedSymbol(symbol, target),
origin,
skipFilter,
});
}
}
function getNearestExportSymbol(fromSymbol: Symbol) {
return findAlias(typeChecker, fromSymbol, alias => {
return some(alias.declarations, d => isExportSpecifier(d) || !!d.localSymbol);
});
}
/**
* True if you could remove some characters in `a` to get `b`.
* E.g., true for "abcdef" and "bdf".
* But not true for "abcdef" and "dbf".
*/
function stringContainsCharactersInOrder(str: string, characters: string): boolean {
if (characters.length === 0) {
return true;
}
let characterIndex = 0;
for (let strIndex = 0; strIndex < str.length; strIndex++) {
if (str.charCodeAt(strIndex) === characters.charCodeAt(characterIndex)) {
characterIndex++;
if (characterIndex === characters.length) {
return true;
}
}
}
// Did not find all characters
return false;
}
/**
* Finds the first node that "embraces" the position, so that one may
* accurately aggregate locals from the closest containing scope.
*/
function getScopeNode(initialToken: Node | undefined, position: number, sourceFile: SourceFile) {
let scope: Node | undefined = initialToken;
while (scope && !positionBelongsToNode(scope, position, sourceFile)) {
scope = scope.parent;
}
return scope;
}
function isCompletionListBlocker(contextToken: Node): boolean {
const start = timestamp();
const result = isInStringOrRegularExpressionOrTemplateLiteral(contextToken) ||
isSolelyIdentifierDefinitionLocation(contextToken) ||
isDotOfNumericLiteral(contextToken) ||
isInJsxText(contextToken);
log("getCompletionsAtPosition: isCompletionListBlocker: " + (timestamp() - start));
return result;
}
function isInJsxText(contextToken: Node): boolean {
if (contextToken.kind === SyntaxKind.JsxText) {
return true;
}
if (contextToken.kind === SyntaxKind.GreaterThanToken && contextToken.parent) {
if (contextToken.parent.kind === SyntaxKind.JsxOpeningElement) {
// Two possibilities:
// 1. /**/
// - contextToken: GreaterThanToken (before cursor)
// - location: JSXElement
// - different parents (JSXOpeningElement, JSXElement)
// 2. /**/>
// - contextToken: GreaterThanToken (before cursor)
// - location: GreaterThanToken (after cursor)
// - same parent (JSXOpeningElement)
return location.parent.kind !== SyntaxKind.JsxOpeningElement;
}
if (contextToken.parent.kind === SyntaxKind.JsxClosingElement || contextToken.parent.kind === SyntaxKind.JsxSelfClosingElement) {
return !!contextToken.parent.parent && contextToken.parent.parent.kind === SyntaxKind.JsxElement;
}
}
return false;
}
function isNewIdentifierDefinitionLocation(): boolean {
if (contextToken) {
const containingNodeKind = contextToken.parent.kind;
// Previous token may have been a keyword that was converted to an identifier.
switch (keywordForNode(contextToken)) {
case SyntaxKind.CommaToken:
return containingNodeKind === SyntaxKind.CallExpression // func( a, |
|| containingNodeKind === SyntaxKind.Constructor // constructor( a, | /* public, protected, private keywords are allowed here, so show completion */
|| containingNodeKind === SyntaxKind.NewExpression // new C(a, |
|| containingNodeKind === SyntaxKind.ArrayLiteralExpression // [a, |
|| containingNodeKind === SyntaxKind.BinaryExpression // const x = (a, |
|| containingNodeKind === SyntaxKind.FunctionType // var x: (s: string, list|
|| containingNodeKind === SyntaxKind.ObjectLiteralExpression; // const obj = { x, |
case SyntaxKind.OpenParenToken:
return containingNodeKind === SyntaxKind.CallExpression // func( |
|| containingNodeKind === SyntaxKind.Constructor // constructor( |
|| containingNodeKind === SyntaxKind.NewExpression // new C(a|
|| containingNodeKind === SyntaxKind.ParenthesizedExpression // const x = (a|
|| containingNodeKind === SyntaxKind.ParenthesizedType; // function F(pred: (a| /* this can become an arrow function, where 'a' is the argument */
case SyntaxKind.OpenBracketToken:
return containingNodeKind === SyntaxKind.ArrayLiteralExpression // [ |
|| containingNodeKind === SyntaxKind.IndexSignature // [ | : string ]
|| containingNodeKind === SyntaxKind.ComputedPropertyName; // [ | /* this can become an index signature */
case SyntaxKind.ModuleKeyword: // module |
case SyntaxKind.NamespaceKeyword: // namespace |
return true;
case SyntaxKind.DotToken:
return containingNodeKind === SyntaxKind.ModuleDeclaration; // module A.|
case SyntaxKind.OpenBraceToken:
return containingNodeKind === SyntaxKind.ClassDeclaration // class A { |
|| containingNodeKind === SyntaxKind.StructDeclaration // struct A { |
|| containingNodeKind === SyntaxKind.ObjectLiteralExpression; // const obj = { |
case SyntaxKind.EqualsToken:
return containingNodeKind === SyntaxKind.VariableDeclaration // const x = a|
|| containingNodeKind === SyntaxKind.BinaryExpression; // x = a|
case SyntaxKind.TemplateHead:
return containingNodeKind === SyntaxKind.TemplateExpression; // `aa ${|
case SyntaxKind.TemplateMiddle:
return containingNodeKind === SyntaxKind.TemplateSpan; // `aa ${10} dd ${|
case SyntaxKind.PublicKeyword:
case SyntaxKind.PrivateKeyword:
case SyntaxKind.ProtectedKeyword:
return containingNodeKind === SyntaxKind.PropertyDeclaration; // class A{ public |
}
}
return false;
}
function isInStringOrRegularExpressionOrTemplateLiteral(contextToken: Node): boolean {
// To be "in" one of these literals, the position has to be:
// 1. entirely within the token text.
// 2. at the end position of an unterminated token.
// 3. at the end of a regular expression (due to trailing flags like '/foo/g').
return (isRegularExpressionLiteral(contextToken) || isStringTextContainingNode(contextToken)) && (
rangeContainsPositionExclusive(createTextRangeFromSpan(createTextSpanFromNode(contextToken)), position) ||
position === contextToken.end && (!!contextToken.isUnterminated || isRegularExpressionLiteral(contextToken)));
}
/**
* Aggregates relevant symbols for completion in object literals and object binding patterns.
* Relevant symbols are stored in the captured 'symbols' variable.
*
* @returns true if 'symbols' was successfully populated; false otherwise.
*/
function tryGetObjectLikeCompletionSymbols(): GlobalsSearch | undefined {
const objectLikeContainer = tryGetObjectLikeCompletionContainer(contextToken);
if (!objectLikeContainer) return GlobalsSearch.Continue;
// We're looking up possible property names from contextual/inferred/declared type.
completionKind = CompletionKind.ObjectPropertyDeclaration;
let typeMembers: Symbol[] | undefined;
let existingMembers: readonly Declaration[] | undefined;
if (objectLikeContainer.kind === SyntaxKind.ObjectLiteralExpression) {
const instantiatedType = tryGetObjectLiteralContextualType(objectLikeContainer, typeChecker);
// Check completions for Object property value shorthand
if (instantiatedType === undefined) {
if (objectLikeContainer.flags & NodeFlags.InWithStatement) {
return GlobalsSearch.Fail;
}
isNonContextualObjectLiteral = true;
return GlobalsSearch.Continue;
}
const completionsType = typeChecker.getContextualType(objectLikeContainer, ContextFlags.Completions);
const hasStringIndexType = (completionsType || instantiatedType).getStringIndexType();
const hasNumberIndextype = (completionsType || instantiatedType).getNumberIndexType();
isNewIdentifierLocation = !!hasStringIndexType || !!hasNumberIndextype;
typeMembers = getPropertiesForObjectExpression(instantiatedType, completionsType, objectLikeContainer, typeChecker);
existingMembers = objectLikeContainer.properties;
if (typeMembers.length === 0) {
// Edge case: If NumberIndexType exists
if (!hasNumberIndextype) {
isNonContextualObjectLiteral = true;
return GlobalsSearch.Continue;
}
}
}
else {
Debug.assert(objectLikeContainer.kind === SyntaxKind.ObjectBindingPattern);
// We are *only* completing on properties from the type being destructured.
isNewIdentifierLocation = false;
const rootDeclaration = getRootDeclaration(objectLikeContainer.parent);
if (!isVariableLike(rootDeclaration)) return Debug.fail("Root declaration is not variable-like.");
// We don't want to complete using the type acquired by the shape
// of the binding pattern; we are only interested in types acquired
// through type declaration or inference.
// Also proceed if rootDeclaration is a parameter and if its containing function expression/arrow function is contextually typed -
// type of parameter will flow in from the contextual type of the function
let canGetType = hasInitializer(rootDeclaration) || hasType(rootDeclaration) || rootDeclaration.parent.parent.kind === SyntaxKind.ForOfStatement;
if (!canGetType && rootDeclaration.kind === SyntaxKind.Parameter) {
if (isExpression(rootDeclaration.parent)) {
canGetType = !!typeChecker.getContextualType(rootDeclaration.parent);
}
else if (rootDeclaration.parent.kind === SyntaxKind.MethodDeclaration || rootDeclaration.parent.kind === SyntaxKind.SetAccessor) {
canGetType = isExpression(rootDeclaration.parent.parent) && !!typeChecker.getContextualType(rootDeclaration.parent.parent);
}
}
if (canGetType) {
const typeForObject = typeChecker.getTypeAtLocation(objectLikeContainer);
if (!typeForObject) return GlobalsSearch.Fail;
// In a binding pattern, get only known properties (unless in the same scope).
// Everywhere else we will get all possible properties.
const containerClass = getContainingClass(objectLikeContainer);
typeMembers = typeChecker.getPropertiesOfType(typeForObject).filter(symbol =>
// either public
!(getDeclarationModifierFlagsFromSymbol(symbol) & ModifierFlags.NonPublicAccessibilityModifier)
// or we're in it
|| containerClass && contains(typeForObject.symbol.declarations, containerClass));
existingMembers = objectLikeContainer.elements;
}
}
if (typeMembers && typeMembers.length > 0) {
// Add filtered items to the completion list
symbols = filterObjectMembersList(typeMembers, Debug.checkDefined(existingMembers));
}
setSortTextToOptionalMember();
return GlobalsSearch.Success;
}
/**
* Aggregates relevant symbols for completion in import clauses and export clauses
* whose declarations have a module specifier; for instance, symbols will be aggregated for
*
* import { | } from "moduleName";
* export { a as foo, | } from "moduleName";
*
* but not for
*
* export { | };
*
* Relevant symbols are stored in the captured 'symbols' variable.
*/
function tryGetImportOrExportClauseCompletionSymbols(): GlobalsSearch {
// `import { |` or `import { a as 0, | }`
const namedImportsOrExports = contextToken && (contextToken.kind === SyntaxKind.OpenBraceToken || contextToken.kind === SyntaxKind.CommaToken)
? tryCast(contextToken.parent, isNamedImportsOrExports) : undefined;
if (!namedImportsOrExports) return GlobalsSearch.Continue;
// try to show exported member for imported/re-exported module
const { moduleSpecifier } = namedImportsOrExports.kind === SyntaxKind.NamedImports ? namedImportsOrExports.parent.parent : namedImportsOrExports.parent;
if (!moduleSpecifier) return namedImportsOrExports.kind === SyntaxKind.NamedImports ? GlobalsSearch.Fail : GlobalsSearch.Continue;
const moduleSpecifierSymbol = typeChecker.getSymbolAtLocation(moduleSpecifier); // TODO: GH#18217
if (!moduleSpecifierSymbol) return GlobalsSearch.Fail;
completionKind = CompletionKind.MemberLike;
isNewIdentifierLocation = false;
const exports = typeChecker.getExportsAndPropertiesOfModule(moduleSpecifierSymbol);
const existing = new Set((namedImportsOrExports.elements as NodeArray).filter(n => !isCurrentlyEditingNode(n)).map(n => (n.propertyName || n.name).escapedText));
symbols = exports.filter(e => e.escapedName !== InternalSymbolName.Default && !existing.has(e.escapedName));
return GlobalsSearch.Success;
}
/**
* Adds local declarations for completions in named exports:
*
* export { | };
*
* Does not check for the absence of a module specifier (`export {} from "./other"`)
* because `tryGetImportOrExportClauseCompletionSymbols` runs first and handles that,
* preventing this function from running.
*/
function tryGetLocalNamedExportCompletionSymbols(): GlobalsSearch {
const namedExports = contextToken && (contextToken.kind === SyntaxKind.OpenBraceToken || contextToken.kind === SyntaxKind.CommaToken)
? tryCast(contextToken.parent, isNamedExports)
: undefined;
if (!namedExports) {
return GlobalsSearch.Continue;
}
const localsContainer = findAncestor(namedExports, or(isSourceFile, isModuleDeclaration))!;
completionKind = CompletionKind.None;
isNewIdentifierLocation = false;
localsContainer.locals?.forEach((symbol, name) => {
symbols.push(symbol);
if (localsContainer.symbol?.exports?.has(name)) {
symbolToSortTextMap[getSymbolId(symbol)] = SortText.OptionalMember;
}
});
return GlobalsSearch.Success;
}
/**
* Aggregates relevant symbols for completion in class declaration
* Relevant symbols are stored in the captured 'symbols' variable.
*/
function tryGetClassLikeCompletionSymbols(): GlobalsSearch {
const decl = tryGetObjectTypeDeclarationCompletionContainer(sourceFile, contextToken, location, position);
if (!decl) return GlobalsSearch.Continue;
// We're looking up possible property names from parent type.
completionKind = CompletionKind.MemberLike;
// Declaring new property/method/accessor
isNewIdentifierLocation = true;
keywordFilters = contextToken.kind === SyntaxKind.AsteriskToken ? KeywordCompletionFilters.None :
isClassLike(decl) ? KeywordCompletionFilters.ClassElementKeywords : KeywordCompletionFilters.InterfaceElementKeywords;
// If you're in an interface you don't want to repeat things from super-interface. So just stop here.
if (!isClassLike(decl)) return GlobalsSearch.Success;
const classElement = contextToken.kind === SyntaxKind.SemicolonToken ? contextToken.parent.parent : contextToken.parent;
let classElementModifierFlags = isClassElement(classElement) ? getEffectiveModifierFlags(classElement) : ModifierFlags.None;
// If this is context token is not something we are editing now, consider if this would lead to be modifier
if (contextToken.kind === SyntaxKind.Identifier && !isCurrentlyEditingNode(contextToken)) {
switch (contextToken.getText()) {
case "private":
classElementModifierFlags = classElementModifierFlags | ModifierFlags.Private;
break;
case "static":
classElementModifierFlags = classElementModifierFlags | ModifierFlags.Static;
break;
}
}
// No member list for private methods
if (!(classElementModifierFlags & ModifierFlags.Private)) {
// List of property symbols of base type that are not private and already implemented
const baseSymbols = flatMap(getAllSuperTypeNodes(decl), baseTypeNode => {
const type = typeChecker.getTypeAtLocation(baseTypeNode);
return classElementModifierFlags & ModifierFlags.Static ?
type?.symbol && typeChecker.getPropertiesOfType(typeChecker.getTypeOfSymbolAtLocation(type.symbol, decl)) :
type && typeChecker.getPropertiesOfType(type);
});
symbols = filterClassMembersList(baseSymbols, decl.members, classElementModifierFlags);
}
return GlobalsSearch.Success;
}
/**
* Returns the immediate owning object literal or binding pattern of a context token,
* on the condition that one exists and that the context implies completion should be given.
*/
function tryGetObjectLikeCompletionContainer(contextToken: Node): ObjectLiteralExpression | ObjectBindingPattern | undefined {
if (contextToken) {
const { parent } = contextToken;
switch (contextToken.kind) {
case SyntaxKind.OpenBraceToken: // const x = { |
case SyntaxKind.CommaToken: // const x = { a: 0, |
if (isObjectLiteralExpression(parent) || isObjectBindingPattern(parent)) {
return parent;
}
break;
case SyntaxKind.AsteriskToken:
return isMethodDeclaration(parent) ? tryCast(parent.parent, isObjectLiteralExpression) : undefined;
case SyntaxKind.Identifier:
return (contextToken as Identifier).text === "async" && isShorthandPropertyAssignment(contextToken.parent)
? contextToken.parent.parent : undefined;
}
}
return undefined;
}
function isConstructorParameterCompletion(node: Node): boolean {
return !!node.parent && isParameter(node.parent) && isConstructorDeclaration(node.parent.parent)
&& (isParameterPropertyModifier(node.kind) || isDeclarationName(node));
}
/**
* Returns the immediate owning class declaration of a context token,
* on the condition that one exists and that the context implies completion should be given.
*/
function tryGetConstructorLikeCompletionContainer(contextToken: Node): ConstructorDeclaration | undefined {
if (contextToken) {
const parent = contextToken.parent;
switch (contextToken.kind) {
case SyntaxKind.OpenParenToken:
case SyntaxKind.CommaToken:
return isConstructorDeclaration(contextToken.parent) ? contextToken.parent : undefined;
default:
if (isConstructorParameterCompletion(contextToken)) {
return parent.parent as ConstructorDeclaration;
}
}
}
return undefined;
}
function tryGetFunctionLikeBodyCompletionContainer(contextToken: Node): FunctionLikeDeclaration | undefined {
if (contextToken) {
let prev: Node;
const container = findAncestor(contextToken.parent, (node: Node) => {
if (isClassLike(node)) {
return "quit";
}
if (isFunctionLikeDeclaration(node) && prev === node.body) {
return true;
}
prev = node;
return false;
});
return container && container as FunctionLikeDeclaration;
}
}
function tryGetContainingJsxElement(contextToken: Node): JsxOpeningLikeElement | undefined {
if (contextToken) {
const parent = contextToken.parent;
switch (contextToken.kind) {
case SyntaxKind.GreaterThanToken: // End of a type argument list
case SyntaxKind.LessThanSlashToken:
case SyntaxKind.SlashToken:
case SyntaxKind.Identifier:
case SyntaxKind.PropertyAccessExpression:
case SyntaxKind.JsxAttributes:
case SyntaxKind.JsxAttribute:
case SyntaxKind.JsxSpreadAttribute:
if (parent && (parent.kind === SyntaxKind.JsxSelfClosingElement || parent.kind === SyntaxKind.JsxOpeningElement)) {
if (contextToken.kind === SyntaxKind.GreaterThanToken) {
const precedingToken = findPrecedingToken(contextToken.pos, sourceFile, /*startNode*/ undefined);
if (!(parent as JsxOpeningLikeElement).typeArguments || (precedingToken && precedingToken.kind === SyntaxKind.SlashToken)) break;
}
return parent;
}
else if (parent.kind === SyntaxKind.JsxAttribute) {
// Currently we parse JsxOpeningLikeElement as:
// JsxOpeningLikeElement
// attributes: JsxAttributes
// properties: NodeArray
return parent.parent.parent as JsxOpeningLikeElement;
}
break;
// The context token is the closing } or " of an attribute, which means
// its parent is a JsxExpression, whose parent is a JsxAttribute,
// whose parent is a JsxOpeningLikeElement
case SyntaxKind.StringLiteral:
if (parent && ((parent.kind === SyntaxKind.JsxAttribute) || (parent.kind === SyntaxKind.JsxSpreadAttribute))) {
// Currently we parse JsxOpeningLikeElement as:
// JsxOpeningLikeElement
// attributes: JsxAttributes
// properties: NodeArray
return parent.parent.parent as JsxOpeningLikeElement;
}
break;
case SyntaxKind.CloseBraceToken:
if (parent &&
parent.kind === SyntaxKind.JsxExpression &&
parent.parent && parent.parent.kind === SyntaxKind.JsxAttribute) {
// Currently we parse JsxOpeningLikeElement as:
// JsxOpeningLikeElement
// attributes: JsxAttributes
// properties: NodeArray
// each JsxAttribute can have initializer as JsxExpression
return parent.parent.parent.parent as JsxOpeningLikeElement;
}
if (parent && parent.kind === SyntaxKind.JsxSpreadAttribute) {
// Currently we parse JsxOpeningLikeElement as:
// JsxOpeningLikeElement
// attributes: JsxAttributes
// properties: NodeArray
return parent.parent.parent as JsxOpeningLikeElement;
}
break;
}
}
return undefined;
}
/**
* @returns true if we are certain that the currently edited location must define a new location; false otherwise.
*/
function isSolelyIdentifierDefinitionLocation(contextToken: Node): boolean {
const parent = contextToken.parent;
const containingNodeKind = parent.kind;
switch (contextToken.kind) {
case SyntaxKind.CommaToken:
return containingNodeKind === SyntaxKind.VariableDeclaration ||
isVariableDeclarationListButNotTypeArgument(contextToken) ||
containingNodeKind === SyntaxKind.VariableStatement ||
containingNodeKind === SyntaxKind.EnumDeclaration || // enum a { foo, |
isFunctionLikeButNotConstructor(containingNodeKind) ||
containingNodeKind === SyntaxKind.InterfaceDeclaration || // interface A= contextToken.pos);
case SyntaxKind.DotToken:
return containingNodeKind === SyntaxKind.ArrayBindingPattern; // var [.|
case SyntaxKind.ColonToken:
return containingNodeKind === SyntaxKind.BindingElement; // var {x :html|
case SyntaxKind.OpenBracketToken:
return containingNodeKind === SyntaxKind.ArrayBindingPattern; // var [x|
case SyntaxKind.OpenParenToken:
return containingNodeKind === SyntaxKind.CatchClause ||
isFunctionLikeButNotConstructor(containingNodeKind);
case SyntaxKind.OpenBraceToken:
return containingNodeKind === SyntaxKind.EnumDeclaration; // enum a { |
case SyntaxKind.LessThanToken:
return containingNodeKind === SyntaxKind.ClassDeclaration || // class A< |
containingNodeKind === SyntaxKind.ClassExpression || // var C = class D< |
containingNodeKind === SyntaxKind.InterfaceDeclaration || // interface A< |
containingNodeKind === SyntaxKind.TypeAliasDeclaration || // type List< |
isFunctionLikeKind(containingNodeKind);
case SyntaxKind.StaticKeyword:
return containingNodeKind === SyntaxKind.PropertyDeclaration && !isClassLike(parent.parent);
case SyntaxKind.DotDotDotToken:
return containingNodeKind === SyntaxKind.Parameter ||
(!!parent.parent && parent.parent.kind === SyntaxKind.ArrayBindingPattern); // var [...z|
case SyntaxKind.PublicKeyword:
case SyntaxKind.PrivateKeyword:
case SyntaxKind.ProtectedKeyword:
return containingNodeKind === SyntaxKind.Parameter && !isConstructorDeclaration(parent.parent);
case SyntaxKind.AsKeyword:
return containingNodeKind === SyntaxKind.ImportSpecifier ||
containingNodeKind === SyntaxKind.ExportSpecifier ||
containingNodeKind === SyntaxKind.NamespaceImport;
case SyntaxKind.GetKeyword:
case SyntaxKind.SetKeyword:
return !isFromObjectTypeDeclaration(contextToken);
case SyntaxKind.ClassKeyword:
case SyntaxKind.StructKeyword:
case SyntaxKind.EnumKeyword:
case SyntaxKind.InterfaceKeyword:
case SyntaxKind.FunctionKeyword:
case SyntaxKind.VarKeyword:
case SyntaxKind.ImportKeyword:
case SyntaxKind.LetKeyword:
case SyntaxKind.ConstKeyword:
case SyntaxKind.InferKeyword:
case SyntaxKind.TypeKeyword: // type htm|
return true;
case SyntaxKind.AsteriskToken:
return isFunctionLike(contextToken.parent) && !isMethodDeclaration(contextToken.parent);
}
// If the previous token is keyword correspoding to class member completion keyword
// there will be completion available here
if (isClassMemberCompletionKeyword(keywordForNode(contextToken)) && isFromObjectTypeDeclaration(contextToken)) {
return false;
}
if (isConstructorParameterCompletion(contextToken)) {
// constructor parameter completion is available only if
// - its modifier of the constructor parameter or
// - its name of the parameter and not being edited
// eg. constructor(a |<- this shouldnt show completion
if (!isIdentifier(contextToken) ||
isParameterPropertyModifier(keywordForNode(contextToken)) ||
isCurrentlyEditingNode(contextToken)) {
return false;
}
}
// Previous token may have been a keyword that was converted to an identifier.
switch (keywordForNode(contextToken)) {
case SyntaxKind.AbstractKeyword:
case SyntaxKind.ClassKeyword:
case SyntaxKind.StructKeyword:
case SyntaxKind.ConstKeyword:
case SyntaxKind.DeclareKeyword:
case SyntaxKind.EnumKeyword:
case SyntaxKind.FunctionKeyword:
case SyntaxKind.InterfaceKeyword:
case SyntaxKind.LetKeyword:
case SyntaxKind.PrivateKeyword:
case SyntaxKind.ProtectedKeyword:
case SyntaxKind.PublicKeyword:
case SyntaxKind.StaticKeyword:
case SyntaxKind.VarKeyword:
return true;
case SyntaxKind.AsyncKeyword:
return isPropertyDeclaration(contextToken.parent);
}
return isDeclarationName(contextToken)
&& !isShorthandPropertyAssignment(contextToken.parent)
&& !isJsxAttribute(contextToken.parent)
// 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`.
// If `contextToken !== previousToken`, this is `class C ex/**/`.
&& !(isClassLike(contextToken.parent) && (contextToken !== previousToken || position > previousToken.end));
}
function isFunctionLikeButNotConstructor(kind: SyntaxKind) {
return isFunctionLikeKind(kind) && kind !== SyntaxKind.Constructor;
}
function isDotOfNumericLiteral(contextToken: Node): boolean {
if (contextToken.kind === SyntaxKind.NumericLiteral) {
const text = contextToken.getFullText();
return text.charAt(text.length - 1) === ".";
}
return false;
}
function isVariableDeclarationListButNotTypeArgument(node: Node): boolean {
return node.parent.kind === SyntaxKind.VariableDeclarationList
&& !isPossiblyTypeArgumentPosition(node, sourceFile, typeChecker);
}
/**
* Filters out completion suggestions for named imports or exports.
*
* @returns Symbols to be suggested in an object binding pattern or object literal expression, barring those whose declarations
* do not occur at the current position and have not otherwise been typed.
*/
function filterObjectMembersList(contextualMemberSymbols: Symbol[], existingMembers: readonly Declaration[]): Symbol[] {
if (existingMembers.length === 0) {
return contextualMemberSymbols;
}
const membersDeclaredBySpreadAssignment = new Set();
const existingMemberNames = new Set<__String>();
for (const m of existingMembers) {
// Ignore omitted expressions for missing members
if (m.kind !== SyntaxKind.PropertyAssignment &&
m.kind !== SyntaxKind.ShorthandPropertyAssignment &&
m.kind !== SyntaxKind.BindingElement &&
m.kind !== SyntaxKind.MethodDeclaration &&
m.kind !== SyntaxKind.GetAccessor &&
m.kind !== SyntaxKind.SetAccessor &&
m.kind !== SyntaxKind.SpreadAssignment) {
continue;
}
// If this is the current item we are editing right now, do not filter it out
if (isCurrentlyEditingNode(m)) {
continue;
}
let existingName: __String | undefined;
if (isSpreadAssignment(m)) {
setMembersDeclaredBySpreadAssignment(m, membersDeclaredBySpreadAssignment);
}
else if (isBindingElement(m) && m.propertyName) {
// include only identifiers in completion list
if (m.propertyName.kind === SyntaxKind.Identifier) {
existingName = m.propertyName.escapedText;
}
}
else {
// TODO: Account for computed property name
// NOTE: if one only performs this step when m.name is an identifier,
// things like '__proto__' are not filtered out.
const name = getNameOfDeclaration(m);
existingName = name && isPropertyNameLiteral(name) ? getEscapedTextOfIdentifierOrLiteral(name) : undefined;
}
if (existingName !== undefined) {
existingMemberNames.add(existingName);
}
}
const filteredSymbols = contextualMemberSymbols.filter(m => !existingMemberNames.has(m.escapedName));
setSortTextToMemberDeclaredBySpreadAssignment(membersDeclaredBySpreadAssignment, filteredSymbols);
return filteredSymbols;
}
function setMembersDeclaredBySpreadAssignment(declaration: SpreadAssignment | JsxSpreadAttribute, membersDeclaredBySpreadAssignment: Set) {
const expression = declaration.expression;
const symbol = typeChecker.getSymbolAtLocation(expression);
const type = symbol && typeChecker.getTypeOfSymbolAtLocation(symbol, expression);
const properties = type && (type).properties;
if (properties) {
properties.forEach(property => {
membersDeclaredBySpreadAssignment.add(property.name);
});
}
}
// Set SortText to OptionalMember if it is an optional member
function setSortTextToOptionalMember() {
symbols.forEach(m => {
if (m.flags & SymbolFlags.Optional) {
symbolToSortTextMap[getSymbolId(m)] = symbolToSortTextMap[getSymbolId(m)] || SortText.OptionalMember;
}
});
}
// Set SortText to MemberDeclaredBySpreadAssignment if it is fulfilled by spread assignment
function setSortTextToMemberDeclaredBySpreadAssignment(membersDeclaredBySpreadAssignment: Set, contextualMemberSymbols: Symbol[]): void {
if (membersDeclaredBySpreadAssignment.size === 0) {
return;
}
for (const contextualMemberSymbol of contextualMemberSymbols) {
if (membersDeclaredBySpreadAssignment.has(contextualMemberSymbol.name)) {
symbolToSortTextMap[getSymbolId(contextualMemberSymbol)] = SortText.MemberDeclaredBySpreadAssignment;
}
}
}
/**
* Filters out completion suggestions for class elements.
*
* @returns Symbols to be suggested in an class element depending on existing memebers and symbol flags
*/
function filterClassMembersList(baseSymbols: readonly Symbol[], existingMembers: readonly ClassElement[], currentClassElementModifierFlags: ModifierFlags): Symbol[] {
const existingMemberNames = new Set<__String>();
for (const m of existingMembers) {
// Ignore omitted expressions for missing members
if (m.kind !== SyntaxKind.PropertyDeclaration &&
m.kind !== SyntaxKind.MethodDeclaration &&
m.kind !== SyntaxKind.GetAccessor &&
m.kind !== SyntaxKind.SetAccessor) {
continue;
}
// If this is the current item we are editing right now, do not filter it out
if (isCurrentlyEditingNode(m)) {
continue;
}
// Dont filter member even if the name matches if it is declared private in the list
if (hasEffectiveModifier(m, ModifierFlags.Private)) {
continue;
}
// do not filter it out if the static presence doesnt match
if (hasEffectiveModifier(m, ModifierFlags.Static) !== !!(currentClassElementModifierFlags & ModifierFlags.Static)) {
continue;
}
const existingName = getPropertyNameForPropertyNameNode(m.name!);
if (existingName) {
existingMemberNames.add(existingName);
}
}
return baseSymbols.filter(propertySymbol =>
!existingMemberNames.has(propertySymbol.escapedName) &&
!!propertySymbol.declarations &&
!(getDeclarationModifierFlagsFromSymbol(propertySymbol) & ModifierFlags.Private) &&
!(propertySymbol.valueDeclaration && isPrivateIdentifierPropertyDeclaration(propertySymbol.valueDeclaration)));
}
/**
* Filters out completion suggestions from 'symbols' according to existing JSX attributes.
*
* @returns Symbols to be suggested in a JSX element, barring those whose attributes
* do not occur at the current position and have not otherwise been typed.
*/
function filterJsxAttributes(symbols: Symbol[], attributes: NodeArray): Symbol[] {
const seenNames = new Set<__String>();
const membersDeclaredBySpreadAssignment = new Set();
for (const attr of attributes) {
// If this is the current item we are editing right now, do not filter it out
if (isCurrentlyEditingNode(attr)) {
continue;
}
if (attr.kind === SyntaxKind.JsxAttribute) {
seenNames.add(attr.name.escapedText);
}
else if (isJsxSpreadAttribute(attr)) {
setMembersDeclaredBySpreadAssignment(attr, membersDeclaredBySpreadAssignment);
}
}
const filteredSymbols = symbols.filter(a => !seenNames.has(a.escapedName));
setSortTextToMemberDeclaredBySpreadAssignment(membersDeclaredBySpreadAssignment, filteredSymbols);
return filteredSymbols;
}
function isCurrentlyEditingNode(node: Node): boolean {
return node.getStart(sourceFile) <= position && position <= node.getEnd();
}
}
interface CompletionEntryDisplayNameForSymbol {
readonly name: string;
readonly needsConvertPropertyAccess: boolean;
}
function getCompletionEntryDisplayNameForSymbol(
symbol: Symbol,
target: ScriptTarget,
origin: SymbolOriginInfo | undefined,
kind: CompletionKind,
jsxIdentifierExpected: boolean,
): CompletionEntryDisplayNameForSymbol | undefined {
const name = originIsExport(origin) ? getNameForExportedSymbol(symbol, target) : symbol.name;
if (name === undefined
// If the symbol is external module, don't show it in the completion list
// (i.e declare module "http" { const x; } | // <= request completion here, "http" should not be there)
|| symbol.flags & SymbolFlags.Module && isSingleOrDoubleQuote(name.charCodeAt(0))
// If the symbol is the internal name of an ES symbol, it is not a valid entry. Internal names for ES symbols start with "__@"
|| isKnownSymbol(symbol)) {
return undefined;
}
const validNameResult: CompletionEntryDisplayNameForSymbol = { name, needsConvertPropertyAccess: false };
if (isIdentifierText(name, target, jsxIdentifierExpected ? LanguageVariant.JSX : LanguageVariant.Standard) || symbol.valueDeclaration && isPrivateIdentifierPropertyDeclaration(symbol.valueDeclaration)) {
return validNameResult;
}
switch (kind) {
case CompletionKind.MemberLike:
return undefined;
case CompletionKind.ObjectPropertyDeclaration:
// TODO: GH#18169
return { name: JSON.stringify(name), needsConvertPropertyAccess: false };
case CompletionKind.PropertyAccess:
case CompletionKind.Global: // For a 'this.' completion it will be in a global context, but may have a non-identifier name.
// Don't add a completion for a name starting with a space. See https://github.com/Microsoft/TypeScript/pull/20547
return name.charCodeAt(0) === CharacterCodes.space ? undefined : { name, needsConvertPropertyAccess: true };
case CompletionKind.None:
case CompletionKind.String:
return validNameResult;
default:
Debug.assertNever(kind);
}
}
// A cache of completion entries for keywords, these do not change between sessions
const _keywordCompletions: CompletionEntry[][] = [];
const allKeywordsCompletions: () => readonly CompletionEntry[] = memoize(() => {
const res: CompletionEntry[] = [];
for (let i = SyntaxKind.FirstKeyword; i <= SyntaxKind.LastKeyword; i++) {
res.push({
name: tokenToString(i)!,
kind: ScriptElementKind.keyword,
kindModifiers: ScriptElementKindModifier.none,
sortText: SortText.GlobalsOrKeywords
});
}
return res;
});
function getKeywordCompletions(keywordFilter: KeywordCompletionFilters, filterOutTsOnlyKeywords: boolean): readonly CompletionEntry[] {
if (!filterOutTsOnlyKeywords) return getTypescriptKeywordCompletions(keywordFilter);
const index = keywordFilter + KeywordCompletionFilters.Last + 1;
return _keywordCompletions[index] ||
(_keywordCompletions[index] = getTypescriptKeywordCompletions(keywordFilter)
.filter(entry => !isTypeScriptOnlyKeyword(stringToToken(entry.name)!))
);
}
function getTypescriptKeywordCompletions(keywordFilter: KeywordCompletionFilters): readonly CompletionEntry[] {
return _keywordCompletions[keywordFilter] || (_keywordCompletions[keywordFilter] = allKeywordsCompletions().filter(entry => {
const kind = stringToToken(entry.name)!;
switch (keywordFilter) {
case KeywordCompletionFilters.None:
return false;
case KeywordCompletionFilters.All:
return isFunctionLikeBodyKeyword(kind)
|| kind === SyntaxKind.DeclareKeyword
|| kind === SyntaxKind.ModuleKeyword
|| kind === SyntaxKind.TypeKeyword
|| kind === SyntaxKind.NamespaceKeyword
|| isTypeKeyword(kind) && kind !== SyntaxKind.UndefinedKeyword;
case KeywordCompletionFilters.FunctionLikeBodyKeywords:
return isFunctionLikeBodyKeyword(kind);
case KeywordCompletionFilters.ClassElementKeywords:
return isClassMemberCompletionKeyword(kind);
case KeywordCompletionFilters.InterfaceElementKeywords:
return isInterfaceOrTypeLiteralCompletionKeyword(kind);
case KeywordCompletionFilters.ConstructorParameterKeywords:
return isParameterPropertyModifier(kind);
case KeywordCompletionFilters.TypeAssertionKeywords:
return isTypeKeyword(kind) || kind === SyntaxKind.ConstKeyword;
case KeywordCompletionFilters.TypeKeywords:
return isTypeKeyword(kind);
default:
return Debug.assertNever(keywordFilter);
}
}));
}
function isTypeScriptOnlyKeyword(kind: SyntaxKind) {
switch (kind) {
case SyntaxKind.AbstractKeyword:
case SyntaxKind.AnyKeyword:
case SyntaxKind.BigIntKeyword:
case SyntaxKind.BooleanKeyword:
case SyntaxKind.DeclareKeyword:
case SyntaxKind.EnumKeyword:
case SyntaxKind.GlobalKeyword:
case SyntaxKind.ImplementsKeyword:
case SyntaxKind.InferKeyword:
case SyntaxKind.InterfaceKeyword:
case SyntaxKind.IsKeyword:
case SyntaxKind.KeyOfKeyword:
case SyntaxKind.ModuleKeyword:
case SyntaxKind.NamespaceKeyword:
case SyntaxKind.NeverKeyword:
case SyntaxKind.NumberKeyword:
case SyntaxKind.ObjectKeyword:
case SyntaxKind.PrivateKeyword:
case SyntaxKind.ProtectedKeyword:
case SyntaxKind.PublicKeyword:
case SyntaxKind.ReadonlyKeyword:
case SyntaxKind.StringKeyword:
case SyntaxKind.SymbolKeyword:
case SyntaxKind.TypeKeyword:
case SyntaxKind.UniqueKeyword:
case SyntaxKind.UnknownKeyword:
return true;
default:
return false;
}
}
function isInterfaceOrTypeLiteralCompletionKeyword(kind: SyntaxKind): boolean {
return kind === SyntaxKind.ReadonlyKeyword;
}
function isClassMemberCompletionKeyword(kind: SyntaxKind) {
switch (kind) {
case SyntaxKind.AbstractKeyword:
case SyntaxKind.ConstructorKeyword:
case SyntaxKind.GetKeyword:
case SyntaxKind.SetKeyword:
case SyntaxKind.AsyncKeyword:
case SyntaxKind.DeclareKeyword:
return true;
default:
return isClassMemberModifier(kind);
}
}
function isFunctionLikeBodyKeyword(kind: SyntaxKind) {
return kind === SyntaxKind.AsyncKeyword
|| kind === SyntaxKind.AwaitKeyword
|| kind === SyntaxKind.AsKeyword
|| !isContextualKeyword(kind) && !isClassMemberCompletionKeyword(kind);
}
function keywordForNode(node: Node): SyntaxKind {
return isIdentifier(node) ? node.originalKeywordKind || SyntaxKind.Unknown : node.kind;
}
/** Get the corresponding JSDocTag node if the position is in a jsDoc comment */
function getJsDocTagAtPosition(node: Node, position: number): JSDocTag | undefined {
const jsdoc = findAncestor(node, isJSDoc);
return jsdoc && jsdoc.tags && (rangeContainsPosition(jsdoc, position) ? findLast(jsdoc.tags, tag => tag.pos < position) : undefined);
}
export function getPropertiesForObjectExpression(contextualType: Type, completionsType: Type | undefined, obj: ObjectLiteralExpression | JsxAttributes, checker: TypeChecker): Symbol[] {
const hasCompletionsType = completionsType && completionsType !== contextualType;
const type = hasCompletionsType && !(completionsType!.flags & TypeFlags.AnyOrUnknown)
? checker.getUnionType([contextualType, completionsType!])
: contextualType;
const properties = type.isUnion()
? checker.getAllPossiblePropertiesOfTypes(type.types.filter(memberType =>
// 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.
!(memberType.flags & TypeFlags.Primitive ||
checker.isArrayLikeType(memberType) ||
typeHasCallOrConstructSignatures(memberType, checker) ||
checker.isTypeInvalidDueToUnionDiscriminant(memberType, obj))))
: type.getApparentProperties();
return hasCompletionsType ? properties.filter(hasDeclarationOtherThanSelf) : properties;
// Filter out members whose only declaration is the object literal itself to avoid
// self-fulfilling completions like:
//
// function f(x: T) {}
// f({ abc/**/: "" }) // `abc` is a member of `T` but only because it declares itself
function hasDeclarationOtherThanSelf(member: Symbol) {
return some(member.declarations, decl => decl.parent !== obj);
}
}
/**
* Gets all properties on a type, but if that type is a union of several types,
* excludes array-like types or callable/constructable types.
*/
function getPropertiesForCompletion(type: Type, checker: TypeChecker): Symbol[] {
return type.isUnion()
? Debug.checkEachDefined(checker.getAllPossiblePropertiesOfTypes(type.types), "getAllPossiblePropertiesOfTypes() should all be defined")
: Debug.checkEachDefined(type.getApparentProperties(), "getApparentProperties() should all be defined");
}
/**
* Returns the immediate owning class declaration of a context token,
* on the condition that one exists and that the context implies completion should be given.
*/
function tryGetObjectTypeDeclarationCompletionContainer(sourceFile: SourceFile, contextToken: Node | undefined, location: Node, position: number): ObjectTypeDeclaration | undefined {
// class c { method() { } | method2() { } }
switch (location.kind) {
case SyntaxKind.SyntaxList:
return tryCast(location.parent, isObjectTypeDeclaration);
case SyntaxKind.EndOfFileToken:
const cls = tryCast(lastOrUndefined(cast(location.parent, isSourceFile).statements), isObjectTypeDeclaration);
if (cls && !findChildOfKind(cls, SyntaxKind.CloseBraceToken, sourceFile)) {
return cls;
}
break;
case SyntaxKind.Identifier: {
// class c { public prop = c| }
if (isPropertyDeclaration(location.parent) && location.parent.initializer === location) {
return undefined;
}
// class c extends React.Component { a: () => 1\n compon| }
if (isFromObjectTypeDeclaration(location)) {
return findAncestor(location, isObjectTypeDeclaration);
}
}
}
if (!contextToken) return undefined;
switch (contextToken.kind) {
case SyntaxKind.EqualsToken: // class c { public prop = | /* global completions */ }
return undefined;
case SyntaxKind.SemicolonToken: // class c {getValue(): number; | }
case SyntaxKind.CloseBraceToken: // class c { method() { } | }
// class c { method() { } b| }
return isFromObjectTypeDeclaration(location) && (location.parent as ClassElement | TypeElement).name === location
? location.parent.parent as ObjectTypeDeclaration
: tryCast(location, isObjectTypeDeclaration);
case SyntaxKind.OpenBraceToken: // class c { |
case SyntaxKind.CommaToken: // class c {getValue(): number, | }
return tryCast(contextToken.parent, isObjectTypeDeclaration);
default:
if (!isFromObjectTypeDeclaration(contextToken)) {
// class c extends React.Component { a: () => 1\n| }
if (getLineAndCharacterOfPosition(sourceFile, contextToken.getEnd()).line !== getLineAndCharacterOfPosition(sourceFile, position).line && isObjectTypeDeclaration(location)) {
return location;
}
return undefined;
}
const isValidKeyword = isClassLike(contextToken.parent.parent) ? isClassMemberCompletionKeyword : isInterfaceOrTypeLiteralCompletionKeyword;
return (isValidKeyword(contextToken.kind) || contextToken.kind === SyntaxKind.AsteriskToken || isIdentifier(contextToken) && isValidKeyword(stringToToken(contextToken.text)!)) // TODO: GH#18217
? contextToken.parent.parent as ObjectTypeDeclaration : undefined;
}
}
// TODO: GH#19856 Would like to return `node is Node & { parent: (ClassElement | TypeElement) & { parent: ObjectTypeDeclaration } }` but then compilation takes > 10 minutes
function isFromObjectTypeDeclaration(node: Node): boolean {
return node.parent && isClassOrTypeElement(node.parent) && isObjectTypeDeclaration(node.parent.parent);
}
function isValidTrigger(sourceFile: SourceFile, triggerCharacter: CompletionsTriggerCharacter, contextToken: Node | undefined, position: number): boolean {
switch (triggerCharacter) {
case ".":
case "@":
return true;
case '"':
case "'":
case "`":
// Only automatically bring up completions if this is an opening quote.
return !!contextToken && isStringLiteralOrTemplate(contextToken) && position === contextToken.getStart(sourceFile) + 1;
case "#":
return !!contextToken && isPrivateIdentifier(contextToken) && !!getContainingClass(contextToken);
case "<":
// Opening JSX tag
return !!contextToken && contextToken.kind === SyntaxKind.LessThanToken && (!isBinaryExpression(contextToken.parent) || binaryExpressionMayBeOpenTag(contextToken.parent));
case "/":
return !!contextToken && (isStringLiteralLike(contextToken)
? !!tryGetImportFromModuleSpecifier(contextToken)
: contextToken.kind === SyntaxKind.SlashToken && isJsxClosingElement(contextToken.parent));
default:
return Debug.assertNever(triggerCharacter);
}
}
function binaryExpressionMayBeOpenTag({ left }: BinaryExpression): boolean {
return nodeIsMissing(left);
}
function findAlias(typeChecker: TypeChecker, symbol: Symbol, predicate: (symbol: Symbol) => boolean): Symbol | undefined {
let currentAlias: Symbol | undefined = symbol;
while (currentAlias.flags & SymbolFlags.Alias && (currentAlias = typeChecker.getImmediateAliasedSymbol(currentAlias))) {
if (predicate(currentAlias)) {
return currentAlias;
}
}
}
/** Determines if a type is exactly the same type resolved by the global 'self', 'global', or 'globalThis'. */
function isProbablyGlobalType(type: Type, sourceFile: SourceFile, checker: TypeChecker) {
// The type of `self` and `window` is the same in lib.dom.d.ts, but `window` does not exist in
// lib.webworker.d.ts, so checking against `self` is also a check against `window` when it exists.
const selfSymbol = checker.resolveName("self", /*location*/ undefined, SymbolFlags.Value, /*excludeGlobals*/ false);
if (selfSymbol && checker.getTypeOfSymbolAtLocation(selfSymbol, sourceFile) === type) {
return true;
}
const globalSymbol = checker.resolveName("global", /*location*/ undefined, SymbolFlags.Value, /*excludeGlobals*/ false);
if (globalSymbol && checker.getTypeOfSymbolAtLocation(globalSymbol, sourceFile) === type) {
return true;
}
const globalThisSymbol = checker.resolveName("globalThis", /*location*/ undefined, SymbolFlags.Value, /*excludeGlobals*/ false);
if (globalThisSymbol && checker.getTypeOfSymbolAtLocation(globalThisSymbol, sourceFile) === type) {
return true;
}
return false;
}
function isStaticProperty(symbol: Symbol) {
return !!(symbol.valueDeclaration && getEffectiveModifierFlags(symbol.valueDeclaration) & ModifierFlags.Static && isClassLike(symbol.valueDeclaration.parent));
}
function tryGetObjectLiteralContextualType(node: ObjectLiteralExpression, typeChecker: TypeChecker) {
const type = typeChecker.getContextualType(node);
if (type) {
return type;
}
if (isBinaryExpression(node.parent) && node.parent.operatorToken.kind === SyntaxKind.EqualsToken) {
return typeChecker.getTypeAtLocation(node.parent);
}
return undefined;
}
}