Skip to content

Properly set hasAction for erasing modifiers in class member completions #57643

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 6 commits into from
Mar 7, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions src/compiler/diagnosticMessages.json
Original file line number Diff line number Diff line change
Expand Up @@ -7019,6 +7019,10 @@
"category": "Message",
"code": 90060
},
"Update modifiers of '{0}'": {
"category": "Message",
"code": 90061
},

"Convert function to an ES2015 class": {
"category": "Message",
Expand Down
62 changes: 47 additions & 15 deletions src/services/completions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}
Expand Down Expand Up @@ -2068,6 +2079,7 @@ function getPresentModifiers(
let decorators: Decorator[] | undefined;
let contextMod;
const range: TextRange = { pos: position, end: position };

/*
Cases supported:
In
Expand All @@ -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 {
Expand Down Expand Up @@ -2941,7 +2971,7 @@ function getCompletionEntryCodeActionsAndSourceDisplay(
contextToken,
formatContext,
)!;
if (importAdder || eraseRange) {
if (importAdder?.hasFixes() || eraseRange) {
const changes = textChanges.ChangeTracker.with(
{ host, formatContext, preferences },
tracker => {
Expand All @@ -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]),
}],
};
}
Expand Down
2 changes: 2 additions & 0 deletions tests/cases/fourslash/completionsOverridingMethod0.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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,
}
],
});
Expand Down
29 changes: 29 additions & 0 deletions tests/cases/fourslash/completionsOverridingMethod12.ts
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,8 @@ verify.completions({
sortText: completion.SortText.LocationPriority,
insertText: "abstract get P(): string;",
filterText: "P",
hasAction: true,
source: completion.CompletionSource.ClassMemberSnippet,
},
],
});
Expand All @@ -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 {

}`
});
67 changes: 67 additions & 0 deletions tests/cases/fourslash/completionsOverridingMethod18.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
/// <reference path="fourslash.ts" />

// @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 {

}`
});
60 changes: 60 additions & 0 deletions tests/cases/fourslash/completionsOverridingMethod19.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
/// <reference path="fourslash.ts" />

// @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
}`
});
49 changes: 49 additions & 0 deletions tests/cases/fourslash/completionsOverridingMethod20.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
/// <reference path="fourslash.ts" />

// @Filename: a.ts
// @newline: LF
//// abstract class AFoo {
//// abstract bar(): Promise<void>;
//// }
//// 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<void> {\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<void>;
}
class Foo extends AFoo {
b
}`
});
48 changes: 48 additions & 0 deletions tests/cases/fourslash/completionsOverridingMethod21.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
/// <reference path="fourslash.ts" />

// @Filename: a.ts
// @newline: LF
//// abstract class AFoo {
//// abstract bar(): Promise<void>;
//// }
//// 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<void> {\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<void>;
}
class BFoo extends AFoo {

}`
});
Loading