diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index 307dcc7aee1..91ff98be946 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -42817,17 +42817,18 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { * Checks a member declaration node to see if has a missing or invalid `override` modifier. * @param node Class-like node where the member is declared. * @param member Member declaration node. + * @param memberSymbol Member symbol. * Note: `member` can be a synthetic node without a parent. */ - function getMemberOverrideModifierStatus(node: ClassLikeDeclaration, member: ClassElement): MemberOverrideStatus { + function getMemberOverrideModifierStatus(node: ClassLikeDeclaration, member: ClassElement, memberSymbol: Symbol): MemberOverrideStatus { if (!member.name) { return MemberOverrideStatus.Ok; } - const symbol = getSymbolOfDeclaration(node); - const type = getDeclaredTypeOfSymbol(symbol) as InterfaceType; + const classSymbol = getSymbolOfDeclaration(node); + const type = getDeclaredTypeOfSymbol(classSymbol) as InterfaceType; const typeWithThis = getTypeWithThisArgument(type); - const staticType = getTypeOfSymbol(symbol) as ObjectType; + const staticType = getTypeOfSymbol(classSymbol) as ObjectType; const baseTypeNode = getEffectiveBaseTypeNode(node); const baseTypes = baseTypeNode && getBaseTypes(type); @@ -42838,8 +42839,6 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { ? hasOverrideModifier(member) : hasSyntacticModifier(member, ModifierFlags.Override); - const memberName = unescapeLeadingUnderscores(getTextOfPropertyName(member.name)); - return checkMemberForOverrideModifier( node, staticType, @@ -42851,7 +42850,7 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { hasAbstractModifier(member), isStatic(member), /* memberIsParameterProperty */ false, - memberName, + symbolName(memberSymbol), ); } diff --git a/src/compiler/types.ts b/src/compiler/types.ts index aa013763ddb..38b3c767934 100644 --- a/src/compiler/types.ts +++ b/src/compiler/types.ts @@ -5238,7 +5238,7 @@ export interface TypeChecker { /** @internal */ isDeclarationVisible(node: Declaration | AnyImportSyntax): boolean; /** @internal */ isPropertyAccessible(node: Node, isSuper: boolean, isWrite: boolean, containingType: Type, property: Symbol): boolean; /** @internal */ getTypeOnlyAliasDeclaration(symbol: Symbol): TypeOnlyAliasDeclaration | undefined; - /** @internal */ getMemberOverrideModifierStatus(node: ClassLikeDeclaration, member: ClassElement): MemberOverrideStatus; + /** @internal */ getMemberOverrideModifierStatus(node: ClassLikeDeclaration, member: ClassElement, memberSymbol: Symbol): MemberOverrideStatus; /** @internal */ isTypeParameterPossiblyReferenced(tp: TypeParameter, node: Node): boolean; } diff --git a/src/services/completions.ts b/src/services/completions.ts index a69f56c09de..88c639f58d8 100644 --- a/src/services/completions.ts +++ b/src/services/completions.ts @@ -62,6 +62,7 @@ import { first, firstDefined, flatMap, + forEach, formatting, FunctionLikeDeclaration, getAllSuperTypeNodes, @@ -427,15 +428,16 @@ export enum CompletionSource { /** @internal */ export const enum SymbolOriginInfoKind { - ThisType = 1 << 0, - SymbolMember = 1 << 1, - Export = 1 << 2, - Promise = 1 << 3, - Nullable = 1 << 4, - ResolvedExport = 1 << 5, - TypeOnlyAlias = 1 << 6, - ObjectLiteralMethod = 1 << 7, - Ignore = 1 << 8, + ThisType = 1 << 0, + SymbolMember = 1 << 1, + Export = 1 << 2, + Promise = 1 << 3, + Nullable = 1 << 4, + ResolvedExport = 1 << 5, + TypeOnlyAlias = 1 << 6, + ObjectLiteralMethod = 1 << 7, + Ignore = 1 << 8, + ComputedPropertyName = 1 << 9, SymbolMemberNoExport = SymbolMember, SymbolMemberExport = SymbolMember | Export, @@ -475,6 +477,10 @@ interface SymbolOriginInfoObjectLiteralMethod extends SymbolOriginInfo { isSnippet?: true, } +interface SymbolOriginInfoComputedPropertyName extends SymbolOriginInfo { + symbolName: string; +} + function originIsThisType(origin: SymbolOriginInfo): boolean { return !!(origin.kind & SymbolOriginInfoKind.ThisType); } @@ -491,8 +497,8 @@ function originIsResolvedExport(origin: SymbolOriginInfo | undefined): origin is return !!(origin && origin.kind === SymbolOriginInfoKind.ResolvedExport); } -function originIncludesSymbolName(origin: SymbolOriginInfo | undefined): origin is SymbolOriginInfoExport | SymbolOriginInfoResolvedExport { - return originIsExport(origin) || originIsResolvedExport(origin); +function originIncludesSymbolName(origin: SymbolOriginInfo | undefined): origin is SymbolOriginInfoExport | SymbolOriginInfoResolvedExport | SymbolOriginInfoComputedPropertyName { + return originIsExport(origin) || originIsResolvedExport(origin) || originIsComputedPropertyName(origin); } function originIsPackageJsonImport(origin: SymbolOriginInfo | undefined): origin is SymbolOriginInfoExport { @@ -519,6 +525,10 @@ function originIsIgnore(origin: SymbolOriginInfo | undefined): boolean { return !!(origin && origin.kind & SymbolOriginInfoKind.Ignore); } +function originIsComputedPropertyName(origin: SymbolOriginInfo | undefined): origin is SymbolOriginInfoComputedPropertyName { + return !!(origin && origin.kind & SymbolOriginInfoKind.ComputedPropertyName); +} + /** @internal */ export interface UniqueNameSet { add(name: string): void; @@ -1552,10 +1562,9 @@ function getEntryForMemberCompletion( requiredModifiers |= ModifierFlags.Abstract; } if (isClassElement(node) - && checker.getMemberOverrideModifierStatus(classLikeDeclaration, node) === MemberOverrideStatus.NeedsOverride) { + && checker.getMemberOverrideModifierStatus(classLikeDeclaration, node, symbol) === MemberOverrideStatus.NeedsOverride) { requiredModifiers |= ModifierFlags.Override; } - if (!completionNodes.length) { // Keep track of added missing required modifiers and modifiers already present. // This is needed when we have overloaded signatures, @@ -2322,7 +2331,8 @@ export function getCompletionEntryDetails( case "symbol": { const { symbol, location, contextToken, origin, previousToken } = symbolCompletion; const { codeActions, sourceDisplay } = getCompletionEntryCodeActionsAndSourceDisplay(name, location, contextToken, origin, symbol, program, host, compilerOptions, sourceFile, position, previousToken, formatContext, preferences, data, source, cancellationToken); - return createCompletionDetailsForSymbol(symbol, typeChecker, sourceFile, location, cancellationToken, codeActions, sourceDisplay); // TODO: GH#18217 + const symbolName = originIsComputedPropertyName(origin) ? origin.symbolName : symbol.name; + return createCompletionDetailsForSymbol(symbol, symbolName, typeChecker, sourceFile, location, cancellationToken, codeActions, sourceDisplay); // TODO: GH#18217 } case "literal": { const { literal } = symbolCompletion; @@ -2374,12 +2384,12 @@ function createSimpleDetails(name: string, kind: ScriptElementKind, kind2: Symbo } /** @internal */ -export function createCompletionDetailsForSymbol(symbol: Symbol, checker: TypeChecker, sourceFile: SourceFile, location: Node, cancellationToken: CancellationToken, codeActions?: CodeAction[], sourceDisplay?: SymbolDisplayPart[]): CompletionEntryDetails { +export function createCompletionDetailsForSymbol(symbol: Symbol, name: string, checker: TypeChecker, sourceFile: SourceFile, location: Node, cancellationToken: CancellationToken, codeActions?: CodeAction[], sourceDisplay?: SymbolDisplayPart[]): CompletionEntryDetails { const { displayParts, documentation, symbolKind, tags } = checker.runWithCancellationToken(cancellationToken, checker => SymbolDisplay.getSymbolDisplayPartsDocumentationAndSymbolKind(checker, symbol, sourceFile, location, location, SemanticMeaning.All) ); - return createCompletionDetails(symbol.name, SymbolDisplay.getSymbolModifiers(checker, symbol), symbolKind, displayParts, documentation, tags, codeActions, sourceDisplay); + return createCompletionDetails(name, SymbolDisplay.getSymbolModifiers(checker, symbol), symbolKind, displayParts, documentation, tags, codeActions, sourceDisplay); } /** @internal */ @@ -3963,8 +3973,17 @@ function getCompletionData( type && typeChecker.getPropertiesOfType(type); }); symbols = concatenate(symbols, filterClassMembersList(baseSymbols, decl.members, classElementModifierFlags)); + forEach(symbols, (symbol, index) => { + const declaration = symbol?.valueDeclaration; + if (declaration && isClassElement(declaration) && declaration.name && isComputedPropertyName(declaration.name)) { + const origin: SymbolOriginInfoComputedPropertyName = { + kind: SymbolOriginInfoKind.ComputedPropertyName, + symbolName: typeChecker.symbolToString(symbol), + }; + symbolToOriginInfoMap[index] = origin; + } + }); } - return GlobalsSearch.Success; } @@ -4545,7 +4564,7 @@ function getCompletionEntryDisplayNameForSymbol( } switch (kind) { case CompletionKind.MemberLike: - return undefined; + return originIsComputedPropertyName(origin) ? { name: origin.symbolName, needsConvertPropertyAccess: false } : undefined; case CompletionKind.ObjectPropertyDeclaration: // TODO: GH#18169 return { name: JSON.stringify(name), needsConvertPropertyAccess: false }; diff --git a/src/services/stringCompletions.ts b/src/services/stringCompletions.ts index 1ac93da9208..66044c55c00 100644 --- a/src/services/stringCompletions.ts +++ b/src/services/stringCompletions.ts @@ -287,7 +287,7 @@ function stringLiteralCompletionDetails(name: string, location: Node, completion } case StringLiteralCompletionKind.Properties: { const match = find(completion.symbols, s => s.name === name); - return match && createCompletionDetailsForSymbol(match, checker, sourceFile, location, cancellationToken); + return match && createCompletionDetailsForSymbol(match, match.name, checker, sourceFile, location, cancellationToken); } case StringLiteralCompletionKind.Types: return find(completion.types, t => t.value === name) ? createCompletionDetails(name, ScriptElementKindModifier.none, ScriptElementKind.string, [textPart(name)]) : undefined; diff --git a/tests/baselines/reference/completionsClassMembers5.baseline b/tests/baselines/reference/completionsClassMembers5.baseline new file mode 100644 index 00000000000..6585e05077f --- /dev/null +++ b/tests/baselines/reference/completionsClassMembers5.baseline @@ -0,0 +1,254 @@ +=== /tests/cases/fourslash/completionsClassMembers5.ts === +// export const SOME_CONSTANT = 'SOME_TEXT'; +// export class Base { +// [SOME_CONSTANT]: boolean; +// } +// export class Derived extends Base { +// +// ^ +// | ---------------------------------------------------------------------- +// | abstract +// | accessor +// | async +// | constructor +// | declare +// | get +// | override +// | private +// | protected +// | public +// | readonly +// | set +// | static +// | (property) Base[SOME_CONSTANT]: boolean +// | ---------------------------------------------------------------------- +// } + +[ + { + "marker": { + "fileName": "/tests/cases/fourslash/completionsClassMembers5.ts", + "position": 134, + "name": "" + }, + "item": { + "flags": 0, + "isGlobalCompletion": false, + "isMemberCompletion": true, + "isNewIdentifierLocation": true, + "entries": [ + { + "name": "abstract", + "kind": "keyword", + "kindModifiers": "", + "sortText": "15", + "displayParts": [ + { + "text": "abstract", + "kind": "keyword" + } + ] + }, + { + "name": "accessor", + "kind": "keyword", + "kindModifiers": "", + "sortText": "15", + "displayParts": [ + { + "text": "accessor", + "kind": "keyword" + } + ] + }, + { + "name": "async", + "kind": "keyword", + "kindModifiers": "", + "sortText": "15", + "displayParts": [ + { + "text": "async", + "kind": "keyword" + } + ] + }, + { + "name": "constructor", + "kind": "keyword", + "kindModifiers": "", + "sortText": "15", + "displayParts": [ + { + "text": "constructor", + "kind": "keyword" + } + ] + }, + { + "name": "declare", + "kind": "keyword", + "kindModifiers": "", + "sortText": "15", + "displayParts": [ + { + "text": "declare", + "kind": "keyword" + } + ] + }, + { + "name": "get", + "kind": "keyword", + "kindModifiers": "", + "sortText": "15", + "displayParts": [ + { + "text": "get", + "kind": "keyword" + } + ] + }, + { + "name": "override", + "kind": "keyword", + "kindModifiers": "", + "sortText": "15", + "displayParts": [ + { + "text": "override", + "kind": "keyword" + } + ] + }, + { + "name": "private", + "kind": "keyword", + "kindModifiers": "", + "sortText": "15", + "displayParts": [ + { + "text": "private", + "kind": "keyword" + } + ] + }, + { + "name": "protected", + "kind": "keyword", + "kindModifiers": "", + "sortText": "15", + "displayParts": [ + { + "text": "protected", + "kind": "keyword" + } + ] + }, + { + "name": "public", + "kind": "keyword", + "kindModifiers": "", + "sortText": "15", + "displayParts": [ + { + "text": "public", + "kind": "keyword" + } + ] + }, + { + "name": "readonly", + "kind": "keyword", + "kindModifiers": "", + "sortText": "15", + "displayParts": [ + { + "text": "readonly", + "kind": "keyword" + } + ] + }, + { + "name": "set", + "kind": "keyword", + "kindModifiers": "", + "sortText": "15", + "displayParts": [ + { + "text": "set", + "kind": "keyword" + } + ] + }, + { + "name": "static", + "kind": "keyword", + "kindModifiers": "", + "sortText": "15", + "displayParts": [ + { + "text": "static", + "kind": "keyword" + } + ] + }, + { + "name": "[SOME_CONSTANT]", + "kind": "property", + "kindModifiers": "", + "sortText": "17", + "insertText": "[SOME_CONSTANT]: boolean;", + "isSnippet": true, + "displayParts": [ + { + "text": "(", + "kind": "punctuation" + }, + { + "text": "property", + "kind": "text" + }, + { + "text": ")", + "kind": "punctuation" + }, + { + "text": " ", + "kind": "space" + }, + { + "text": "Base", + "kind": "className" + }, + { + "text": "[", + "kind": "punctuation" + }, + { + "text": "SOME_CONSTANT", + "kind": "propertyName" + }, + { + "text": "]", + "kind": "punctuation" + }, + { + "text": ":", + "kind": "punctuation" + }, + { + "text": " ", + "kind": "space" + }, + { + "text": "boolean", + "kind": "keyword" + } + ], + "documentation": [] + } + ] + } + } +] \ No newline at end of file diff --git a/tests/cases/fourslash/completionsClassMembers5.ts b/tests/cases/fourslash/completionsClassMembers5.ts new file mode 100644 index 00000000000..92bb02c81ea --- /dev/null +++ b/tests/cases/fourslash/completionsClassMembers5.ts @@ -0,0 +1,15 @@ +/// + +////export const SOME_CONSTANT = 'SOME_TEXT'; +////export class Base { +//// [SOME_CONSTANT]: boolean; +////} +////export class Derived extends Base { +//// /**/ +////} + +verify.baselineCompletions({ + includeCompletionsWithInsertText: true, + includeCompletionsWithSnippetText: true, + includeCompletionsWithClassMemberSnippets: true, +});