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 =
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"]);