diff --git a/src/compiler/diagnosticMessages.json b/src/compiler/diagnosticMessages.json
index fab7583d90cb3..d7c12fd6395d3 100644
--- a/src/compiler/diagnosticMessages.json
+++ b/src/compiler/diagnosticMessages.json
@@ -3890,5 +3890,9 @@
"Install '{0}'": {
"category": "Message",
"code": 95014
+ },
+ "Use bracket notation instead of dot notation": {
+ "category": "Message",
+ "code": 95015
}
}
diff --git a/src/services/completions.ts b/src/services/completions.ts
index d893f4852e4f8..05f21736a7158 100644
--- a/src/services/completions.ts
+++ b/src/services/completions.ts
@@ -50,18 +50,17 @@ namespace ts.Completions {
return undefined;
}
- const { symbols, isGlobalCompletion, isMemberCompletion, allowStringLiteral, isNewIdentifierLocation, location, request, keywordFilters, symbolToOriginInfoMap, recommendedCompletion } = completionData;
+ const { symbols, kind, isNewIdentifierLocation, location, request, keywordFilters, symbolToOriginInfoMap, recommendedCompletion } = completionData;
- if (sourceFile.languageVariant === LanguageVariant.JSX &&
- location && location.parent && location.parent.kind === SyntaxKind.JsxClosingElement) {
+ if (sourceFile.languageVariant === LanguageVariant.JSX && location && location.parent && isJsxClosingElement(location.parent)) {
// In the TypeScript JSX element, if such element is not defined. When users query for completion at closing tag,
// instead of simply giving unknown value, the completion will return the tag-name of an associated opening-element.
// For example:
// var x =
/*1*/> completion list at "1" will contain "div" with type any
- const tagName = (
location.parent.parent).openingElement.tagName;
+ const tagName = location.parent.parent.openingElement.tagName;
return { isGlobalCompletion: false, isMemberCompletion: true, isNewIdentifierLocation: false,
entries: [{
- name: (tagName).getFullText(),
+ name: tagName.getFullText(),
kind: ScriptElementKind.classElement,
kindModifiers: undefined,
sortText: "0",
@@ -82,7 +81,7 @@ namespace ts.Completions {
const entries: CompletionEntry[] = [];
if (isSourceFileJavaScript(sourceFile)) {
- const uniqueNames = getCompletionEntriesFromSymbols(symbols, entries, location, /*performCharacterChecks*/ true, typeChecker, compilerOptions.target, log, allowStringLiteral, recommendedCompletion, symbolToOriginInfoMap);
+ const uniqueNames = getCompletionEntriesFromSymbols(symbols, entries, location, typeChecker, compilerOptions.target, log, kind, recommendedCompletion, symbolToOriginInfoMap);
getJavaScriptCompletionEntries(sourceFile, location.pos, uniqueNames, compilerOptions.target, entries);
}
else {
@@ -90,7 +89,7 @@ namespace ts.Completions {
return undefined;
}
- getCompletionEntriesFromSymbols(symbols, entries, location, /*performCharacterChecks*/ true, typeChecker, compilerOptions.target, log, allowStringLiteral, recommendedCompletion, symbolToOriginInfoMap);
+ getCompletionEntriesFromSymbols(symbols, entries, location, typeChecker, compilerOptions.target, log, kind, recommendedCompletion, symbolToOriginInfoMap);
}
// TODO add filter for keyword based on type/value/namespace and also location
@@ -98,17 +97,29 @@ namespace ts.Completions {
// Add all keywords if
// - this is not a member completion list (all the keywords)
// - other filters are enabled in required scenario so add those keywords
+ const isMemberCompletion = isMemberCompletionKind(kind);
if (keywordFilters !== KeywordCompletionFilters.None || !isMemberCompletion) {
addRange(entries, getKeywordCompletions(keywordFilters));
}
- return { isGlobalCompletion, isMemberCompletion, isNewIdentifierLocation, entries };
+ return { isGlobalCompletion: kind === CompletionKind.Global, isMemberCompletion, isNewIdentifierLocation, entries };
+ }
+
+ function isMemberCompletionKind(kind: CompletionKind): boolean {
+ switch (kind) {
+ case CompletionKind.ObjectPropertyDeclaration:
+ case CompletionKind.MemberLike:
+ case CompletionKind.PropertyAccess:
+ return true;
+ default:
+ return false;
+ }
}
function getJavaScriptCompletionEntries(
sourceFile: SourceFile,
position: number,
- uniqueNames: Map<{}>,
+ uniqueNames: Map,
target: ScriptTarget,
entries: Push): void {
getNameTable(sourceFile).forEach((pos, name) => {
@@ -117,16 +128,9 @@ namespace ts.Completions {
return;
}
const realName = unescapeLeadingUnderscores(name);
-
- if (uniqueNames.has(realName) || isStringANonContextualKeyword(realName)) {
- return;
- }
-
- uniqueNames.set(realName, true);
- const displayName = getCompletionEntryDisplayName(realName, target, /*performCharacterChecks*/ true, /*allowStringLiteral*/ false);
- if (displayName) {
+ if (addToSeen(uniqueNames, realName) && isIdentifierText(realName, target) && !isStringANonContextualKeyword(realName)) {
entries.push({
- name: displayName,
+ name: realName,
kind: ScriptElementKind.warning,
kindModifiers: "",
sortText: "1"
@@ -138,20 +142,17 @@ namespace ts.Completions {
function createCompletionEntry(
symbol: Symbol,
location: Node,
- performCharacterChecks: boolean,
typeChecker: TypeChecker,
target: ScriptTarget,
- allowStringLiteral: boolean,
+ kind: CompletionKind,
origin: SymbolOriginInfo | undefined,
recommendedCompletion: Symbol | undefined,
): CompletionEntry | undefined {
- // Try to get a valid display name for this symbol, if we could not find one, then ignore it.
- // We would like to only show things that can be added after a dot, so for instance numeric properties can
- // not be accessed with a dot (a.1 <- invalid)
- const displayName = getCompletionEntryDisplayNameForSymbol(symbol, target, performCharacterChecks, allowStringLiteral, origin);
- if (!displayName) {
+ const info = getCompletionEntryDisplayNameForSymbol(symbol, target, origin, kind);
+ if (!info) {
return undefined;
}
+ const { name, needsErasePeriod } = info;
// TODO(drosen): Right now we just permit *all* semantic meanings when calling
// 'getSymbolKind' which is permissible given that it is backwards compatible; but
@@ -162,12 +163,12 @@ namespace ts.Completions {
// Use a 'sortText' of 0' so that all symbol completion entries come before any other
// entries (like JavaScript identifier entries).
return {
- name: displayName,
+ name,
kind: SymbolDisplay.getSymbolKind(typeChecker, symbol, location),
kindModifiers: SymbolDisplay.getSymbolModifiers(symbol),
sortText: "0",
source: getSourceFromOrigin(origin),
- hasAction: trueOrUndefined(origin !== undefined),
+ hasAction: trueOrUndefined(needsErasePeriod || origin !== undefined),
isRecommended: trueOrUndefined(isRecommendedCompletionMatch(symbol, recommendedCompletion, typeChecker)),
};
}
@@ -189,11 +190,10 @@ namespace ts.Completions {
symbols: ReadonlyArray,
entries: Push,
location: Node,
- performCharacterChecks: boolean,
typeChecker: TypeChecker,
target: ScriptTarget,
log: Log,
- allowStringLiteral: boolean,
+ kind: CompletionKind,
recommendedCompletion?: Symbol,
symbolToOriginInfoMap?: SymbolOriginInfoMap,
): Map {
@@ -206,7 +206,7 @@ namespace ts.Completions {
if (symbols) {
for (const symbol of symbols) {
const origin = symbolToOriginInfoMap ? symbolToOriginInfoMap[getSymbolId(symbol)] : undefined;
- const entry = createCompletionEntry(symbol, location, performCharacterChecks, typeChecker, target, allowStringLiteral, origin, recommendedCompletion);
+ const entry = createCompletionEntry(symbol, location, typeChecker, target, kind, origin, recommendedCompletion);
if (!entry) {
continue;
}
@@ -323,7 +323,7 @@ namespace ts.Completions {
const type = typeChecker.getContextualType((element.parent));
const entries: CompletionEntry[] = [];
if (type) {
- getCompletionEntriesFromSymbols(type.getApparentProperties(), entries, element, /*performCharacterChecks*/ false, typeChecker, target, log, /*allowStringLiteral*/ true);
+ getCompletionEntriesFromSymbols(type.getApparentProperties(), entries, element, typeChecker, target, log, CompletionKind.String);
if (entries.length) {
return { isGlobalCompletion: false, isMemberCompletion: true, isNewIdentifierLocation: true, entries };
}
@@ -352,7 +352,7 @@ namespace ts.Completions {
const type = typeChecker.getTypeAtLocation(node.expression);
const entries: CompletionEntry[] = [];
if (type) {
- getCompletionEntriesFromSymbols(type.getApparentProperties(), entries, node, /*performCharacterChecks*/ false, typeChecker, target, log, /*allowStringLiteral*/ true);
+ getCompletionEntriesFromSymbols(type.getApparentProperties(), entries, node, typeChecker, target, log, CompletionKind.String);
if (entries.length) {
return { isGlobalCompletion: false, isMemberCompletion: true, isNewIdentifierLocation: true, entries };
}
@@ -423,6 +423,13 @@ namespace ts.Completions {
}
}
+ interface SymbolCompletion {
+ type: "symbol";
+ symbol: Symbol;
+ dotToErase: Token | undefined;
+ location: Node;
+ symbolToOriginInfoMap: SymbolOriginInfoMap;
+ }
function getSymbolCompletionFromEntryId(
typeChecker: TypeChecker,
log: (message: string) => void,
@@ -431,13 +438,13 @@ namespace ts.Completions {
position: number,
{ name, source }: CompletionEntryIdentifier,
allSourceFiles: ReadonlyArray,
- ): { type: "symbol", symbol: Symbol, location: Node, symbolToOriginInfoMap: SymbolOriginInfoMap } | { type: "request", request: Request } | { type: "none" } {
+ ): SymbolCompletion | { type: "request", request: Request } | { type: "none" } {
const completionData = getCompletionData(typeChecker, log, sourceFile, position, allSourceFiles, { includeExternalModuleExports: true }, compilerOptions.target);
if (!completionData) {
return { type: "none" };
}
- const { symbols, location, allowStringLiteral, symbolToOriginInfoMap, request } = completionData;
+ const { symbols, location, kind, dot, symbolToOriginInfoMap, request } = completionData;
if (request) {
return { type: "request", request };
}
@@ -446,12 +453,11 @@ namespace ts.Completions {
// We don't need to perform character checks here because we're only comparing the
// name against 'entryName' (which is known to be good), not building a new
// completion entry.
- const symbol = find(symbols, s => {
- const origin = symbolToOriginInfoMap[getSymbolId(s)];
- return getCompletionEntryDisplayNameForSymbol(s, compilerOptions.target, /*performCharacterChecks*/ false, allowStringLiteral, origin) === name
- && getSourceFromOrigin(origin) === source;
- });
- return symbol ? { type: "symbol", symbol, location, symbolToOriginInfoMap } : { type: "none" };
+ return firstDefined(symbols, (symbol): SymbolCompletion => { // TODO: Shouldn't need return type annotation (GH#12632)
+ const origin = symbolToOriginInfoMap[getSymbolId(symbol)];
+ const x = getCompletionEntryDisplayNameForSymbol(symbol, compilerOptions.target, origin, kind);
+ return x && x.name === name && getSourceFromOrigin(origin) === source ? { type: "symbol" as "symbol", symbol, dotToErase: x.needsErasePeriod ? dot : undefined, location, symbolToOriginInfoMap } : undefined;
+ }) || { type: "none" };
}
function getSymbolName(symbol: Symbol, origin: SymbolOriginInfo | undefined, target: ScriptTarget): string {
@@ -496,8 +502,8 @@ namespace ts.Completions {
}
}
case "symbol": {
- const { symbol, location, symbolToOriginInfoMap } = symbolCompletion;
- const { codeActions, sourceDisplay } = getCompletionEntryCodeActionsAndSourceDisplay(symbolToOriginInfoMap, symbol, program, typeChecker, host, compilerOptions, sourceFile, formatContext, getCanonicalFileName, allSourceFiles);
+ const { symbol, location, dotToErase, symbolToOriginInfoMap } = symbolCompletion;
+ const { codeActions, sourceDisplay } = getCompletionEntryCodeActionsAndSourceDisplay(symbolToOriginInfoMap, symbol, program, typeChecker, host, compilerOptions, sourceFile, formatContext, getCanonicalFileName, allSourceFiles, dotToErase);
const kindModifiers = SymbolDisplay.getSymbolModifiers(symbol);
const { displayParts, documentation, symbolKind, tags } = SymbolDisplay.getSymbolDisplayPartsDocumentationAndSymbolKind(typeChecker, symbol, sourceFile, location, location, SemanticMeaning.All);
return { name, kindModifiers, kind: symbolKind, displayParts, documentation, tags, codeActions, source: sourceDisplay };
@@ -521,6 +527,10 @@ namespace ts.Completions {
}
}
+ interface CodeActionsAndSourceDisplay {
+ readonly codeActions: CodeAction[] | undefined;
+ readonly sourceDisplay: SymbolDisplayPart[] | undefined;
+ }
function getCompletionEntryCodeActionsAndSourceDisplay(
symbolToOriginInfoMap: SymbolOriginInfoMap,
symbol: Symbol,
@@ -532,12 +542,43 @@ namespace ts.Completions {
formatContext: formatting.FormatContext,
getCanonicalFileName: GetCanonicalFileName,
allSourceFiles: ReadonlyArray,
- ): { codeActions: CodeAction[] | undefined, sourceDisplay: SymbolDisplayPart[] | undefined } {
+ dotToErase: Token | undefined,
+ ): CodeActionsAndSourceDisplay {
const symbolOriginInfo = symbolToOriginInfoMap[getSymbolId(symbol)];
- if (!symbolOriginInfo) {
- return { codeActions: undefined, sourceDisplay: undefined };
- }
+ Debug.assert(!(symbolOriginInfo && dotToErase));
+ return dotToErase
+ ? { codeActions: erasePeriod(dotToErase), sourceDisplay: undefined }
+ : symbolOriginInfo
+ ? getCodeActionsAndSourceDisplayForImport(symbolOriginInfo, symbol, program, checker, host, compilerOptions, sourceFile, formatContext, getCanonicalFileName, allSourceFiles)
+ : { codeActions: undefined, sourceDisplay: undefined };
+ }
+
+ function erasePeriod(dot: Token): CodeAction[] {
+ const changes: ts.FileTextChanges[] = [
+ {
+ fileName: dot.getSourceFile().fileName,
+ textChanges: [
+ { span: { start: dot.getStart(), length: 1 }, newText: '["', },
+ { span: { start: dot.getEnd(), length: 0 }, newText: '"]', },
+ ],
+ },
+ ];
+ const description = formatMessage(/*dummy*/ undefined, Diagnostics.Use_bracket_notation_instead_of_dot_notation);
+ return [{ changes, description }];
+ }
+ function getCodeActionsAndSourceDisplayForImport(
+ symbolOriginInfo: SymbolOriginInfo,
+ symbol: Symbol,
+ program: Program,
+ checker: TypeChecker,
+ host: LanguageServiceHost,
+ compilerOptions: CompilerOptions,
+ sourceFile: SourceFile,
+ formatContext: formatting.FormatContext,
+ getCanonicalFileName: GetCanonicalFileName,
+ allSourceFiles: ReadonlyArray
+ ): CodeActionsAndSourceDisplay {
const { moduleSymbol, isDefaultExport } = symbolOriginInfo;
const exportedSymbol = skipAlias(symbol.exportSymbol || symbol, checker);
const moduleSymbols = getAllReExportingModules(exportedSymbol, checker, allSourceFiles);
@@ -587,12 +628,10 @@ namespace ts.Completions {
interface CompletionData {
symbols: ReadonlyArray;
- isGlobalCompletion: boolean;
- isMemberCompletion: boolean;
- allowStringLiteral: boolean;
+ kind: CompletionKind;
+ dot: Token | undefined;
isNewIdentifierLocation: boolean;
location: Node | undefined;
- isRightOfDot: boolean;
request?: Request;
keywordFilters: KeywordCompletionFilters;
symbolToOriginInfoMap: SymbolOriginInfoMap;
@@ -600,6 +639,15 @@ namespace ts.Completions {
}
type Request = { kind: "JsDocTagName" } | { kind: "JsDocTag" } | { kind: "JsDocParameterName", tag: JSDocParameterTag };
+ const enum CompletionKind {
+ ObjectPropertyDeclaration,
+ Global,
+ PropertyAccess,
+ MemberLike,
+ String,
+ None,
+ }
+
function getRecommendedCompletion(currentToken: Node, checker: TypeChecker/*, symbolToOriginInfoMap: SymbolOriginInfoMap*/): Symbol | undefined {
const ty = checker.getContextualType(currentToken as Expression);
const symbol = ty && ty.symbol;
@@ -699,12 +747,10 @@ namespace ts.Completions {
if (request) {
return {
symbols: emptyArray,
- isGlobalCompletion: false,
- isMemberCompletion: false,
- allowStringLiteral: false,
+ kind: CompletionKind.None,
+ dot: undefined,
isNewIdentifierLocation: false,
location: undefined,
- isRightOfDot: false,
request,
keywordFilters: KeywordCompletionFilters.None,
symbolToOriginInfoMap: undefined,
@@ -740,7 +786,7 @@ namespace ts.Completions {
// Also determine whether we are trying to complete with members of that node
// or attributes of a JSX tag.
let node = currentToken;
- let isRightOfDot = false;
+ let dot: Token | undefined;
let isRightOfOpenTag = false;
let isStartingCloseTag = false;
@@ -754,13 +800,12 @@ namespace ts.Completions {
let parent = contextToken.parent;
if (contextToken.kind === SyntaxKind.DotToken) {
+ dot = contextToken as Token;
if (parent.kind === SyntaxKind.PropertyAccessExpression) {
node = (contextToken.parent).expression;
- isRightOfDot = true;
}
else if (parent.kind === SyntaxKind.QualifiedName) {
node = (contextToken.parent).left;
- isRightOfDot = true;
}
else {
// There is nothing that precedes the dot, so this likely just a stray character
@@ -805,15 +850,13 @@ namespace ts.Completions {
}
const semanticStart = timestamp();
- let isGlobalCompletion = false;
- let isMemberCompletion: boolean;
- let allowStringLiteral = false;
- let isNewIdentifierLocation: boolean;
+ let kind = CompletionKind.None;
+ let isNewIdentifierLocation = false;
let keywordFilters = KeywordCompletionFilters.None;
let symbols: Symbol[] = [];
const symbolToOriginInfoMap: SymbolOriginInfoMap = [];
- if (isRightOfDot) {
+ if (dot) {
getTypeScriptMemberSymbols();
}
else if (isRightOfOpenTag) {
@@ -824,8 +867,7 @@ namespace ts.Completions {
else {
symbols = tagSymbols;
}
- isMemberCompletion = true;
- isNewIdentifierLocation = false;
+ kind = CompletionKind.MemberLike;
}
else if (isStartingCloseTag) {
const tagName = (contextToken.parent.parent).openingElement.tagName;
@@ -834,8 +876,7 @@ namespace ts.Completions {
if (!typeChecker.isUnknownSymbol(tagSymbol)) {
symbols = [tagSymbol];
}
- isMemberCompletion = true;
- isNewIdentifierLocation = false;
+ kind = CompletionKind.MemberLike;
}
else {
// For JavaScript or TypeScript, if we're not after a dot, then just try to get the
@@ -849,7 +890,7 @@ namespace ts.Completions {
log("getCompletionData: Semantic work: " + (timestamp() - semanticStart));
const recommendedCompletion = getRecommendedCompletion(previousToken, typeChecker);
- return { symbols, isGlobalCompletion, isMemberCompletion, allowStringLiteral, isNewIdentifierLocation, location, isRightOfDot: (isRightOfDot || isRightOfOpenTag), request, keywordFilters, symbolToOriginInfoMap, recommendedCompletion };
+ return { symbols, kind, dot, isNewIdentifierLocation, location, request, keywordFilters, symbolToOriginInfoMap, recommendedCompletion };
type JSDocTagWithTypeExpression = JSDocParameterTag | JSDocPropertyTag | JSDocReturnTag | JSDocTypeTag | JSDocTypedefTag;
@@ -866,9 +907,7 @@ namespace ts.Completions {
function getTypeScriptMemberSymbols(): void {
// Right of dot member completion list
- isGlobalCompletion = false;
- isMemberCompletion = true;
- isNewIdentifierLocation = false;
+ kind = CompletionKind.PropertyAccess;
// Since this is qualified name check its a type node location
const isTypeLocation = insideJsDocTagTypeExpression || isPartOfTypeNode(node.parent);
@@ -945,7 +984,7 @@ namespace ts.Completions {
if (tryGetConstructorLikeCompletionContainer(contextToken)) {
// no members, only keywords
- isMemberCompletion = false;
+ kind = CompletionKind.None;
// Declaring new property/method/accessor
isNewIdentifierLocation = true;
// Has keywords for constructor parameter
@@ -967,7 +1006,7 @@ namespace ts.Completions {
if (attrsType) {
symbols = filterJsxAttributes(typeChecker.getPropertiesOfType(attrsType), (jsxContainer).attributes.properties);
- isMemberCompletion = true;
+ kind = CompletionKind.MemberLike;
isNewIdentifierLocation = false;
return true;
}
@@ -975,7 +1014,7 @@ namespace ts.Completions {
}
// Get all entities in the current scope.
- isMemberCompletion = false;
+ kind = CompletionKind.None;
isNewIdentifierLocation = isNewIdentifierDefinitionLocation(contextToken);
if (previousToken !== contextToken) {
@@ -1011,13 +1050,8 @@ namespace ts.Completions {
position;
const scopeNode = getScopeNode(contextToken, adjustedPosition, sourceFile) || sourceFile;
- if (scopeNode) {
- isGlobalCompletion =
- scopeNode.kind === SyntaxKind.SourceFile ||
- scopeNode.kind === SyntaxKind.TemplateExpression ||
- scopeNode.kind === SyntaxKind.JsxExpression ||
- scopeNode.kind === SyntaxKind.Block || // Some blocks aren't statements, but all get global completions
- isStatement(scopeNode);
+ if (isGlobalCompletionScope(scopeNode)) {
+ kind = CompletionKind.Global;
}
const symbolMeanings = SymbolFlags.Type | SymbolFlags.Value | SymbolFlags.Namespace | SymbolFlags.Alias;
@@ -1031,6 +1065,18 @@ namespace ts.Completions {
return true;
}
+ function isGlobalCompletionScope(scopeNode: Node): boolean {
+ switch (scopeNode.kind) {
+ case SyntaxKind.SourceFile:
+ case SyntaxKind.TemplateExpression:
+ case SyntaxKind.JsxExpression:
+ case SyntaxKind.Block:
+ return true;
+ default:
+ return isStatement(scopeNode);
+ }
+ }
+
function filterGlobalCompletion(symbols: Symbol[]): void {
filterMutate(symbols, symbol => {
if (!isSourceFile(location)) {
@@ -1297,8 +1343,7 @@ namespace ts.Completions {
*/
function tryGetObjectLikeCompletionSymbols(objectLikeContainer: ObjectLiteralExpression | ObjectBindingPattern): boolean {
// We're looking up possible property names from contextual/inferred/declared type.
- isMemberCompletion = true;
- allowStringLiteral = true;
+ kind = CompletionKind.ObjectPropertyDeclaration;
let typeMembers: Symbol[];
let existingMembers: ReadonlyArray;
@@ -1376,7 +1421,7 @@ namespace ts.Completions {
return false;
}
- isMemberCompletion = true;
+ kind = CompletionKind.MemberLike;
isNewIdentifierLocation = false;
const moduleSpecifierSymbol = typeChecker.getSymbolAtLocation(moduleSpecifier);
@@ -1396,7 +1441,7 @@ namespace ts.Completions {
*/
function getGetClassLikeCompletionSymbols(classLikeDeclaration: ClassLikeDeclaration) {
// We're looking up possible property names from parent type.
- isMemberCompletion = true;
+ kind = CompletionKind.MemberLike;
// Declaring new property/method/accessor
isNewIdentifierLocation = true;
// Has keywords for class elements
@@ -1952,7 +1997,12 @@ namespace ts.Completions {
*
* @return undefined if the name is of external module
*/
- function getCompletionEntryDisplayNameForSymbol(symbol: Symbol, target: ScriptTarget, performCharacterChecks: boolean, allowStringLiteral: boolean, origin: SymbolOriginInfo | undefined): string | undefined {
+ function getCompletionEntryDisplayNameForSymbol(
+ symbol: Symbol,
+ target: ScriptTarget,
+ origin: SymbolOriginInfo | undefined,
+ kind: CompletionKind,
+ ): { name: string, needsErasePeriod: boolean } | undefined {
const name = getSymbolName(symbol, origin, target);
if (!name) return undefined;
@@ -1978,24 +2028,23 @@ namespace ts.Completions {
}
}
- return getCompletionEntryDisplayName(name, target, performCharacterChecks, allowStringLiteral);
- }
-
- /**
- * Get a displayName from a given for completion list, performing any necessary quotes stripping
- * and checking whether the name is valid identifier name.
- */
- function getCompletionEntryDisplayName(name: string, target: ScriptTarget, performCharacterChecks: boolean, allowStringLiteral: boolean): string {
- // If the user entered name for the symbol was quoted, removing the quotes is not enough, as the name could be an
- // invalid identifier name. We need to check if whatever was inside the quotes is actually a valid identifier name.
- // e.g "b a" is valid quoted name but when we strip off the quotes, it is invalid.
- // We, thus, need to check if whatever was inside the quotes is actually a valid identifier name.
- if (performCharacterChecks && !isIdentifierText(name, target)) {
- // TODO: GH#18169
- return allowStringLiteral ? JSON.stringify(name) : undefined;
+ const validIdentiferResult = { name, needsErasePeriod: false };
+ if (isIdentifierText(name, target)) return validIdentiferResult;
+ switch (kind) {
+ case CompletionKind.None:
+ case CompletionKind.Global:
+ case CompletionKind.MemberLike:
+ return undefined;
+ case CompletionKind.ObjectPropertyDeclaration:
+ // TODO: GH#18169
+ return { name: JSON.stringify(name), needsErasePeriod: false };
+ case CompletionKind.PropertyAccess:
+ return { name, needsErasePeriod: true };
+ case CompletionKind.String:
+ return validIdentiferResult;
+ default:
+ Debug.assertNever(kind);
}
-
- return name;
}
// A cache of completion entries for keywords, these do not change between sessions
@@ -2008,7 +2057,7 @@ namespace ts.Completions {
return _keywordCompletions[keywordFilter] = generateKeywordCompletions(keywordFilter);
type FilterKeywordCompletions = (entryName: string) => boolean;
- function generateKeywordCompletions(keywordFilter: KeywordCompletionFilters) {
+ function generateKeywordCompletions(keywordFilter: KeywordCompletionFilters): CompletionEntry[] {
switch (keywordFilter) {
case KeywordCompletionFilters.None:
return getAllKeywordCompletions();
@@ -2016,6 +2065,8 @@ namespace ts.Completions {
return getFilteredKeywordCompletions(isClassMemberCompletionKeywordText);
case KeywordCompletionFilters.ConstructorParameterKeywords:
return getFilteredKeywordCompletions(isConstructorParameterCompletionKeywordText);
+ default:
+ Debug.assertNever(keywordFilter);
}
}
diff --git a/tests/cases/fourslash/completionListInvalidMemberNames.ts b/tests/cases/fourslash/completionListInvalidMemberNames.ts
index 8e62ba2fb67b9..82fadcaa36577 100644
--- a/tests/cases/fourslash/completionListInvalidMemberNames.ts
+++ b/tests/cases/fourslash/completionListInvalidMemberNames.ts
@@ -11,8 +11,14 @@
//// "\u0031\u0062": "invalid unicode identifer name (1b)"
////};
////
-////x./*a*/;
+////[|x./*a*/;|]
////x["/*b*/"];
-verify.completionsAt("a", ["bar", "break", "any", "$", "b"]);
-verify.completionsAt("b", ["foo ", "bar", "break", "any", "#", "$", "b", "\u0031\u0062"]);
+verify.completionsAt("b", ["foo ", "bar", "break", "any", "#", "$", "b", "1b"]);
+
+verify.completionsAt("a", ["foo ", "bar", "break", "any", "#", "$", "b", "1b"]);
+verify.applyCodeActionFromCompletion("a", {
+ name: "foo ",
+ description: "Use bracket notation instead of dot notation",
+ newRangeContent: 'x[""];',
+});
diff --git a/tests/cases/fourslash/completionListInvalidMemberNames2.ts b/tests/cases/fourslash/completionListInvalidMemberNames2.ts
index 753f9bbcb3064..153ed3bdb5e32 100644
--- a/tests/cases/fourslash/completionListInvalidMemberNames2.ts
+++ b/tests/cases/fourslash/completionListInvalidMemberNames2.ts
@@ -1,10 +1,19 @@
///
-////enum Foo {
-//// X, Y, '☆'
+// TODO: we should probably support this like we do in completionListInvalidMemberNames.ts
+
+////declare var Symbol: SymbolConstructor;
+////interface SymbolConstructor {
+//// readonly hasInstance: symbol;
+////}
+////interface Function {
+//// [Symbol.hasInstance](value: any): boolean;
+////}
+////interface SomeInterface {
+//// (value: number): any;
////}
-////Foo./*a*/;
-////Foo["/*b*/"];
+////var _ : SomeInterface;
+////_./**/
-verify.completionsAt("a", ["X", "Y"]);
-verify.completionsAt("b", ["X", "Y", "☆"]);
+goTo.marker();
+verify.not.completionListContains("[Symbol.hasInstance]");
diff --git a/tests/cases/fourslash/completionListInvalidMemberNames3.ts b/tests/cases/fourslash/completionListInvalidMemberNames3.ts
deleted file mode 100644
index cf1141b409409..0000000000000
--- a/tests/cases/fourslash/completionListInvalidMemberNames3.ts
+++ /dev/null
@@ -1,71 +0,0 @@
-///
-
-// @allowjs: true
-
-// @Filename: test.js
-////interface Symbol {
-//// /** Returns a string representation of an object. */
-//// toString(): string;
-
-//// /** Returns the primitive value of the specified object. */
-//// valueOf(): Object;
-////}
-
-////interface SymbolConstructor {
-//// /**
-//// * A reference to the prototype.
-//// */
-//// readonly prototype: Symbol;
-
-//// /**
-//// * Returns a new unique Symbol value.
-//// * @param description Description of the new Symbol object.
-//// */
-//// (description?: string | number): symbol;
-
-//// /**
-//// * Returns a Symbol object from the global symbol registry matching the given key if found.
-//// * Otherwise, returns a new symbol with this key.
-//// * @param key key to search for.
-//// */
-//// for(key: string): symbol;
-
-//// /**
-//// * Returns a key from the global symbol registry matching the given Symbol if found.
-//// * Otherwise, returns a undefined.
-//// * @param sym Symbol to find the key for.
-//// */
-//// keyFor(sym: symbol): string | undefined;
-////}
-
-////declare var Symbol: SymbolConstructor;///
-
-////interface SymbolConstructor {
-//// /**
-//// * A method that determines if a constructor object recognizes an object as one of the
-//// * constructor’s instances. Called by the semantics of the instanceof operator.
-//// */
-//// readonly hasInstance: symbol;
-////}
-
-////interface Function {
-//// /**
-//// * Determines whether the given value inherits from this function if this function was used
-//// * as a constructor function.
-//// *
-//// * A constructor function can control which objects are recognized as its instances by
-//// * 'instanceof' by overriding this method.
-//// */
-//// [Symbol.hasInstance](value: any): boolean;
-////}
-
-////interface SomeInterface {
-//// (value: number): any;
-////}
-
-////var _ : SomeInterface;
-////_./**/
-
-goTo.marker();
-
-verify.not.completionListContains("[Symbol.hasInstance]");
\ No newline at end of file
diff --git a/tests/cases/fourslash/completion_enum-members-with-invalid-identifiers-should-not-show-in-completion.ts b/tests/cases/fourslash/completion_enum-members-with-invalid-identifiers-should-not-show-in-completion.ts
deleted file mode 100644
index 6d5b316719838..0000000000000
--- a/tests/cases/fourslash/completion_enum-members-with-invalid-identifiers-should-not-show-in-completion.ts
+++ /dev/null
@@ -1,13 +0,0 @@
-///
-
-//// enum e {
-//// "1",
-//// 2 = 3,
-//// 3,
-//// a,
-//// b
-//// }
-////
-//// e./**/
-
-verify.completionsAt("", ["a", "b"]);