diff --git a/src/compiler/diagnosticMessages.json b/src/compiler/diagnosticMessages.json index b0a56257a957b..0bedb964ffdc4 100644 --- a/src/compiler/diagnosticMessages.json +++ b/src/compiler/diagnosticMessages.json @@ -7019,6 +7019,10 @@ "category": "Message", "code": 90060 }, + "Update modifiers of '{0}'": { + "category": "Message", + "code": 90061 + }, "Convert function to an ES2015 class": { "category": "Message", diff --git a/src/services/completions.ts b/src/services/completions.ts index 0e4075150830e..6ebdefce12490 100644 --- a/src/services/completions.ts +++ b/src/services/completions.ts @@ -1757,10 +1757,21 @@ function createCompletionEntry( isClassLikeMemberCompletion(symbol, location, sourceFile) ) { let importAdder; - const memberCompletionEntry = getEntryForMemberCompletion(host, program, options, preferences, name, symbol, location, position, contextToken, formatContext); + const memberCompletionEntry = getEntryForMemberCompletion( + host, + program, + options, + preferences, + name, + symbol, + location, + position, + contextToken, + formatContext, + ); if (memberCompletionEntry) { ({ insertText, filterText, isSnippet, importAdder } = memberCompletionEntry); - if (importAdder?.hasFixes()) { + if (importAdder?.hasFixes() || memberCompletionEntry.eraseRange) { hasAction = true; source = CompletionSource.ClassMemberSnippet; } @@ -2068,6 +2079,7 @@ function getPresentModifiers( let decorators: Decorator[] | undefined; let contextMod; const range: TextRange = { pos: position, end: position }; + /* Cases supported: In @@ -2083,23 +2095,41 @@ function getPresentModifiers( }` `contextToken` is ``override`` (as a keyword), `contextToken.parent` is property declaration, - `location` is identifier ``m``, - `location.parent` is property declaration ``protected override m``, - `location.parent.parent` is class declaration ``class C { ... }``. + `location` is identifier ``m``. */ - if (isPropertyDeclaration(contextToken.parent) && contextToken.parent.modifiers) { - modifiers |= modifiersToFlags(contextToken.parent.modifiers) & ModifierFlags.Modifier; - decorators = contextToken.parent.modifiers.filter(isDecorator) || []; - range.pos = Math.min(range.pos, contextToken.parent.modifiers.pos); - } - if (contextMod = isModifierLike(contextToken)) { + if (isPropertyDeclaration(contextToken.parent) && (contextMod = isModifierLike(contextToken))) { + if (contextToken.parent.modifiers) { + modifiers |= modifiersToFlags(contextToken.parent.modifiers) & ModifierFlags.Modifier; + decorators = contextToken.parent.modifiers.filter(isDecorator) || []; + range.pos = Math.min(...contextToken.parent.modifiers.map(n => n.getStart(sourceFile))); + } const contextModifierFlag = modifierToFlag(contextMod); if (!(modifiers & contextModifierFlag)) { modifiers |= contextModifierFlag; - range.pos = Math.min(range.pos, contextToken.pos); + range.pos = Math.min(range.pos, contextToken.getStart(sourceFile)); + } + /* + We have two cases: + 1. + `class C { + modifier | + }` + `contextToken` is `modifier`, + and the range should be `modifier |`, ending at `position`, + and + 2. + `class C { + modifier otherToken| + }` + `contextToken` is `modifier`, + `contextToken.parent.name` is `otherToken`, + and the range should be `modifier `, ending at the start of `otherToken`. + */ + if (contextToken.parent.name !== contextToken) { + range.end = contextToken.parent.name.getStart(sourceFile); } } - return { modifiers, decorators, range: range.pos !== position ? range : undefined }; + return { modifiers, decorators, range: range.pos < range.end ? range : undefined }; } function isModifierLike(node: Node): ModifierSyntaxKind | undefined { @@ -2941,7 +2971,7 @@ function getCompletionEntryCodeActionsAndSourceDisplay( contextToken, formatContext, )!; - if (importAdder || eraseRange) { + if (importAdder?.hasFixes() || eraseRange) { const changes = textChanges.ChangeTracker.with( { host, formatContext, preferences }, tracker => { @@ -2957,7 +2987,9 @@ function getCompletionEntryCodeActionsAndSourceDisplay( sourceDisplay: undefined, codeActions: [{ changes, - description: diagnosticToString([Diagnostics.Includes_imports_of_types_referenced_by_0, name]), + description: importAdder?.hasFixes() ? + diagnosticToString([Diagnostics.Includes_imports_of_types_referenced_by_0, name]) : + diagnosticToString([Diagnostics.Update_modifiers_of_0, name]), }], }; } diff --git a/tests/cases/fourslash/completionsOverridingMethod0.ts b/tests/cases/fourslash/completionsOverridingMethod0.ts index b4d462c00195f..aa93da5435f4e 100644 --- a/tests/cases/fourslash/completionsOverridingMethod0.ts +++ b/tests/cases/fourslash/completionsOverridingMethod0.ts @@ -267,6 +267,8 @@ verify.completions({ sortText: completion.SortText.LocationPriority, insertText: "static met(n: number): number {\n}", filterText: "met", + hasAction: true, + source: completion.CompletionSource.ClassMemberSnippet, } ], }); diff --git a/tests/cases/fourslash/completionsOverridingMethod12.ts b/tests/cases/fourslash/completionsOverridingMethod12.ts index cc1c027809282..6d579c306f20d 100644 --- a/tests/cases/fourslash/completionsOverridingMethod12.ts +++ b/tests/cases/fourslash/completionsOverridingMethod12.ts @@ -31,6 +31,8 @@ verify.completions({ sortText: completion.SortText.LocationPriority, insertText: "abstract get P(): string;", filterText: "P", + hasAction: true, + source: completion.CompletionSource.ClassMemberSnippet, }, ], }); @@ -49,6 +51,33 @@ verify.completions({ sortText: completion.SortText.LocationPriority, insertText: "abstract override get P(): string;", filterText: "P", + hasAction: true, + source: completion.CompletionSource.ClassMemberSnippet, }, ], +}); + +verify.applyCodeActionFromCompletion("b", { + preferences: { + includeCompletionsWithInsertText: true, + includeCompletionsWithSnippetText: false, + includeCompletionsWithClassMemberSnippets: true, + }, + name: "P", + source: completion.CompletionSource.ClassMemberSnippet, + description: "Update modifiers of 'P'", + newFileContent: +`abstract class A { + public get P(): string { + return ""; + } +} + +abstract class B extends A { + abstract +} + +abstract class B1 extends A { + +}` }); \ No newline at end of file diff --git a/tests/cases/fourslash/completionsOverridingMethod18.ts b/tests/cases/fourslash/completionsOverridingMethod18.ts new file mode 100644 index 0000000000000..f0586b5fbdd86 --- /dev/null +++ b/tests/cases/fourslash/completionsOverridingMethod18.ts @@ -0,0 +1,67 @@ +/// + +// @Filename: a.ts +// @newline: LF + +//// declare function decorator(...args: any[]): any; + +//// class DecoratorBase { +//// protected foo(a: string): string; +//// protected foo(a: number): number; +//// protected foo(a: any): any { +//// return a; +//// } +//// } + +//// class DecoratorSub extends DecoratorBase { +//// @decorator protected /**/ +//// } + +verify.completions({ + marker: "", + isNewIdentifierLocation: true, + preferences: { + includeCompletionsWithInsertText: true, + includeCompletionsWithSnippetText: false, + includeCompletionsWithClassMemberSnippets: true, + }, + includes: [ + { + name: "foo", + sortText: completion.SortText.LocationPriority, + insertText: +`protected foo(a: string): string; +protected foo(a: number): number; +@decorator +protected foo(a: any) { +}`, + filterText: "foo", + replacementSpan: undefined, + hasAction: true, + source: completion.CompletionSource.ClassMemberSnippet, + }, + ] +}); + +verify.applyCodeActionFromCompletion("", { + preferences: { + includeCompletionsWithInsertText: true, + includeCompletionsWithSnippetText: false, + includeCompletionsWithClassMemberSnippets: true, + }, + name: "foo", + source: completion.CompletionSource.ClassMemberSnippet, + description: "Update modifiers of 'foo'", + newFileContent: +`declare function decorator(...args: any[]): any; +class DecoratorBase { + protected foo(a: string): string; + protected foo(a: number): number; + protected foo(a: any): any { + return a; + } +} +class DecoratorSub extends DecoratorBase { + +}` +}); diff --git a/tests/cases/fourslash/completionsOverridingMethod19.ts b/tests/cases/fourslash/completionsOverridingMethod19.ts new file mode 100644 index 0000000000000..81bd874eaffcb --- /dev/null +++ b/tests/cases/fourslash/completionsOverridingMethod19.ts @@ -0,0 +1,60 @@ +/// + +// @Filename: a.ts +// @newline: LF +//// class Base { +//// method() {} +//// protected prop = 1; +//// } +//// class E extends Base { +//// protected notamodifier override /**/ +//// } + +verify.completions({ + marker: "", + isNewIdentifierLocation: true, + preferences: { + includeCompletionsWithInsertText: true, + includeCompletionsWithSnippetText: false, + includeCompletionsWithClassMemberSnippets: true, + }, + includes: [ + { + name: "method", + sortText: completion.SortText.LocationPriority, + insertText: "override method(): void {\n}", + filterText: "method", + replacementSpan: undefined, + hasAction: true, + source: completion.CompletionSource.ClassMemberSnippet, + }, + { + name: "prop", + sortText: completion.SortText.LocationPriority, + insertText: "protected override prop: number;", + filterText: "prop", + replacementSpan: undefined, + hasAction: true, + source: completion.CompletionSource.ClassMemberSnippet, + }, + ] +},); + +verify.applyCodeActionFromCompletion("", { + preferences: { + includeCompletionsWithInsertText: true, + includeCompletionsWithSnippetText: false, + includeCompletionsWithClassMemberSnippets: true, + }, + name: "method", + source: completion.CompletionSource.ClassMemberSnippet, + description: "Update modifiers of 'method'", + newFileContent: +`class Base { + method() {} + protected prop = 1; +} +class E extends Base { + protected notamodifier +}` +}); diff --git a/tests/cases/fourslash/completionsOverridingMethod20.ts b/tests/cases/fourslash/completionsOverridingMethod20.ts new file mode 100644 index 0000000000000..c5e86189b7d70 --- /dev/null +++ b/tests/cases/fourslash/completionsOverridingMethod20.ts @@ -0,0 +1,49 @@ +/// + +// @Filename: a.ts +// @newline: LF +//// abstract class AFoo { +//// abstract bar(): Promise; +//// } +//// class Foo extends AFoo { +//// async b/*a*/ +//// } + +verify.completions({ + marker: "a", + isNewIdentifierLocation: true, + preferences: { + includeCompletionsWithInsertText: true, + includeCompletionsWithSnippetText: false, + includeCompletionsWithClassMemberSnippets: true, + }, + includes: [ + { + name: "bar", + sortText: completion.SortText.LocationPriority, + insertText: "async bar(): Promise {\n}", + filterText: "bar", + replacementSpan: undefined, + hasAction: true, + source: completion.CompletionSource.ClassMemberSnippet, + }, + ] +}); + +verify.applyCodeActionFromCompletion("a", { + preferences: { + includeCompletionsWithInsertText: true, + includeCompletionsWithSnippetText: false, + includeCompletionsWithClassMemberSnippets: true, + }, + name: "bar", + source: completion.CompletionSource.ClassMemberSnippet, + description: "Update modifiers of 'bar'", + newFileContent: +`abstract class AFoo { + abstract bar(): Promise; +} +class Foo extends AFoo { + b +}` +}); \ No newline at end of file diff --git a/tests/cases/fourslash/completionsOverridingMethod21.ts b/tests/cases/fourslash/completionsOverridingMethod21.ts new file mode 100644 index 0000000000000..15ec940a86d34 --- /dev/null +++ b/tests/cases/fourslash/completionsOverridingMethod21.ts @@ -0,0 +1,48 @@ +/// + +// @Filename: a.ts +// @newline: LF +//// abstract class AFoo { +//// abstract bar(): Promise; +//// } +//// class BFoo extends AFoo { +//// async /*b*/ +//// } + +verify.completions({ + marker: "b", + isNewIdentifierLocation: true, + preferences: { + includeCompletionsWithInsertText: true, + includeCompletionsWithSnippetText: false, + includeCompletionsWithClassMemberSnippets: true, + }, + includes: [ + { + name: "bar", + sortText: completion.SortText.LocationPriority, + insertText: "async bar(): Promise {\n}", + filterText: "bar", + replacementSpan: undefined, + hasAction: true, + source: completion.CompletionSource.ClassMemberSnippet, + }, + ] +}); +verify.applyCodeActionFromCompletion("b", { + preferences: { + includeCompletionsWithInsertText: true, + includeCompletionsWithSnippetText: false, + includeCompletionsWithClassMemberSnippets: true, + }, + name: "bar", + source: completion.CompletionSource.ClassMemberSnippet, + description: "Update modifiers of 'bar'", + newFileContent: +`abstract class AFoo { + abstract bar(): Promise; +} +class BFoo extends AFoo { + +}` +}); \ No newline at end of file diff --git a/tests/cases/fourslash/completionsOverridingMethod22.ts b/tests/cases/fourslash/completionsOverridingMethod22.ts new file mode 100644 index 0000000000000..44253d383d60d --- /dev/null +++ b/tests/cases/fourslash/completionsOverridingMethod22.ts @@ -0,0 +1,53 @@ +/// + +// Bug #57333 +// @newline: LF +// @Filename: b.ts +//// export type C = { x: number } +//// export interface ExtShape { +//// $returnPromise(id: number): Promise; +//// $return(id: number): C; +//// } + +// @Filename: test.ts +//// import { ExtShape } from './b'; +//// abstract class ExtBase implements ExtShape { +//// public $/**/ +//// } + +verify.completions({ + marker: "", + isNewIdentifierLocation: true, + preferences: { + includeCompletionsWithInsertText: true, + includeCompletionsWithSnippetText: false, + includeCompletionsWithClassMemberSnippets: true, + }, + includes: [ + { + name: "$returnPromise", + sortText: completion.SortText.LocationPriority, + insertText: "public \\$returnPromise(id: number): Promise {\n}", + filterText: "$returnPromise", + replacementSpan: undefined, + hasAction: true, + source: completion.CompletionSource.ClassMemberSnippet, + } + ] +}); + +verify.applyCodeActionFromCompletion("", { + preferences: { + includeCompletionsWithInsertText: true, + includeCompletionsWithSnippetText: false, + includeCompletionsWithClassMemberSnippets: true, + }, + name: "$returnPromise", + source: completion.CompletionSource.ClassMemberSnippet, + description: "Includes imports of types referenced by '$returnPromise'", + newFileContent: +`import { C, ExtShape } from './b'; +abstract class ExtBase implements ExtShape { + $ +}` +}); \ No newline at end of file diff --git a/tests/cases/fourslash/completionsOverridingMethod5.ts b/tests/cases/fourslash/completionsOverridingMethod5.ts index 8b8d9587fb888..f6f70ba17533e 100644 --- a/tests/cases/fourslash/completionsOverridingMethod5.ts +++ b/tests/cases/fourslash/completionsOverridingMethod5.ts @@ -54,13 +54,17 @@ verify.completions({ name: "met", sortText: completion.SortText.LocationPriority, insertText: "abstract met(n: string): void;", - filterText: "met" + filterText: "met", + hasAction: true, + source: completion.CompletionSource.ClassMemberSnippet, }, { name: "met2", sortText: completion.SortText.LocationPriority, insertText: "abstract met2(n: number): void;", - filterText: "met2" + filterText: "met2", + hasAction: true, + source: completion.CompletionSource.ClassMemberSnippet, } ], }); @@ -78,13 +82,17 @@ verify.completions({ name: "met", sortText: completion.SortText.LocationPriority, insertText: "abstract met(n: string): void;", - filterText: "met" + filterText: "met", + hasAction: true, + source: completion.CompletionSource.ClassMemberSnippet, }, { name: "met2", sortText: completion.SortText.LocationPriority, insertText: "abstract met2(n: number): void;", - filterText: "met2" + filterText: "met2", + hasAction: true, + source: completion.CompletionSource.ClassMemberSnippet, } ], }); diff --git a/tests/cases/fourslash/completionsOverridingMethod6.ts b/tests/cases/fourslash/completionsOverridingMethod6.ts index 86cda5c2e09a8..6bc97568b00e0 100644 --- a/tests/cases/fourslash/completionsOverridingMethod6.ts +++ b/tests/cases/fourslash/completionsOverridingMethod6.ts @@ -24,28 +24,10 @@ //// override /*d*/ //// } -//// class E extends Base { -//// protected notamodifier override /*e*/ -//// } - //// class f extends Base { //// protected /*f*/ //// } -//// declare function decorator(...args: any[]): any; - -//// class DecoratorBase { -//// protected foo(a: string): string; -//// protected foo(a: number): number; -//// protected foo(a: any): any { -//// return a; -//// } -//// } - -//// class DecoratorSub extends DecoratorBase { -//// @decorator protected /*g*/ -//// } - verify.completions( { marker: "a", @@ -72,6 +54,8 @@ verify.completions( insertText: "public abstract method(): void;", filterText: "method", replacementSpan: undefined, + hasAction: true, + source: completion.CompletionSource.ClassMemberSnippet, }, { name: "prop", @@ -79,6 +63,8 @@ verify.completions( insertText: "public abstract prop: number;", filterText: "prop", replacementSpan: undefined, + hasAction: true, + source: completion.CompletionSource.ClassMemberSnippet, }, ], }, @@ -97,6 +83,8 @@ verify.completions( insertText: "public override method(): void {\n}", filterText: "method", replacementSpan: undefined, + hasAction: true, + source: completion.CompletionSource.ClassMemberSnippet, }, { name: "prop", @@ -104,6 +92,8 @@ verify.completions( insertText: "public override prop: number;", filterText: "prop", replacementSpan: undefined, + hasAction: true, + source: completion.CompletionSource.ClassMemberSnippet, }, ] }, @@ -122,6 +112,8 @@ verify.completions( insertText: "override method(): void {\n}", filterText: "method", replacementSpan: undefined, + hasAction: true, + source: completion.CompletionSource.ClassMemberSnippet, }, { name: "prop", @@ -129,31 +121,8 @@ verify.completions( insertText: "protected override prop: number;", filterText: "prop", replacementSpan: undefined, - }, - ] - }, - { - marker: "e", - isNewIdentifierLocation: true, - preferences: { - includeCompletionsWithInsertText: true, - includeCompletionsWithSnippetText: false, - includeCompletionsWithClassMemberSnippets: true, - }, - includes: [ - { - name: "method", - sortText: completion.SortText.LocationPriority, - insertText: "override method(): void {\n}", - filterText: "method", - replacementSpan: undefined, - }, - { - name: "prop", - sortText: completion.SortText.LocationPriority, - insertText: "protected override prop: number;", - filterText: "prop", - replacementSpan: undefined, + hasAction: true, + source: completion.CompletionSource.ClassMemberSnippet, }, ] }, @@ -173,31 +142,9 @@ verify.completions( insertText: "protected prop: number;", filterText: "prop", replacementSpan: undefined, + hasAction: true, + source: completion.CompletionSource.ClassMemberSnippet, }, ] }, - { - marker: "g", - isNewIdentifierLocation: true, - preferences: { - includeCompletionsWithInsertText: true, - includeCompletionsWithSnippetText: false, - includeCompletionsWithClassMemberSnippets: true, - }, - includes: [ - { - name: "foo", - sortText: completion.SortText.LocationPriority, - insertText: -`protected foo(a: string): string; -protected foo(a: number): number; -@decorator -protected foo(a: any) { -}`, - filterText: "foo", - replacementSpan: undefined, - }, - ] - }, -); - +); \ No newline at end of file diff --git a/tests/cases/fourslash/completionsOverridingMethod7.ts b/tests/cases/fourslash/completionsOverridingMethod7.ts index 3c42864d59a75..d0a2c7159cd8b 100644 --- a/tests/cases/fourslash/completionsOverridingMethod7.ts +++ b/tests/cases/fourslash/completionsOverridingMethod7.ts @@ -28,6 +28,28 @@ verify.completions({ `abstract M(t: T): void; abstract M(t: T, x: number): void;`, filterText: "M", + hasAction: true, + source: completion.CompletionSource.ClassMemberSnippet, }, ], -}); \ No newline at end of file +}); + +verify.applyCodeActionFromCompletion("a", { + preferences: { + includeCompletionsWithInsertText: true, + includeCompletionsWithSnippetText: false, + includeCompletionsWithClassMemberSnippets: true, + }, + name: "M", + source: completion.CompletionSource.ClassMemberSnippet, + description: "Update modifiers of 'M'", + newFileContent: +`abstract class Base { + abstract M(t: T): void; + abstract M(t: T, x: number): void; +} + +abstract class Derived extends Base { + +}` +});