Skip to content
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

Add RefactorTriggerReason #38378

Merged
merged 36 commits into from
Jun 3, 2020

Conversation

jessetrinity
Copy link
Contributor

@jessetrinity jessetrinity commented May 6, 2020

Adds RefactorTriggerReason proposed in #35096. Partially covers cases for refactor discoverability #37895.

This change allows the user to send the code action command (ctrl + . in VS and VSCode) to request available refactors at a cursor location (empty span) rather than having to select the entire span of the code to be refactored. The intent is to make refactors more discoverable by allowing a user to "ask" if a refactor is available, rather than having to know in advance that a refactor is applicable to a particular span.

In the future we may want to offer more refactors when the code action command is sent while non-empty span is selected. This change enables that behavior but intentionally does not implement it.

@typescript-bot
Copy link
Collaborator

Thanks for the PR! It looks like you've changed the TSServer protocol in some way. Please ensure that any changes here don't break consumers of the current TSServer API. For some extra review, we'll ping @sheetalkamat, @amcasey, @mjbvz, @minestarks for you. Feel free to loop in other consumers/maintainers if necessary

@jessetrinity jessetrinity requested a review from mjbvz May 7, 2020 20:18
Copy link
Member

@amcasey amcasey left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I like the idea but have some thoughts on various stylistic points.

src/server/protocol.ts Outdated Show resolved Hide resolved
@@ -546,7 +546,15 @@ namespace ts.server.protocol {
command: CommandTypes.GetApplicableRefactors;
arguments: GetApplicableRefactorsRequestArgs;
}
export type GetApplicableRefactorsRequestArgs = FileLocationOrRangeRequestArgs;
export type GetApplicableRefactorsRequestArgs = FileLocationOrRangeRequestArgs & {
triggerReason?: RefactorTriggerReason;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The simplest version of this is a boolean, isUserInvoked. I can see why we might want to be able to generalize that in the future though, so moving to an enum, { Invoked, Speculative }, seems reasonable - it gives us the option of addressing more nuances in the future. I'm not sure why we'd go more abstract than that though - it seems unlikely we'll need both an enum and an additional payload.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Switching to an enum

export enum RefactorTriggerReason {
    Implicit = "implicit",
    Invoked = "invoked",
}

seems fine to me. Curious what you think the value of preemptively adding a Speculative option would be.

@mjbvz is there potential that VSCode might want to send info in addition to the triggerReason, such as a request for a specific "refactorKind"?

Copy link
Member

@amcasey amcasey Jun 2, 2020

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

My Speculative is your Implicit. I'm fine with your name.

src/server/protocol.ts Outdated Show resolved Hide resolved
src/services/refactors/addOrRemoveBracesToArrowFunction.ts Outdated Show resolved Hide resolved
src/services/refactors/convertExport.ts Outdated Show resolved Hide resolved
src/services/refactors/extractSymbol.ts Outdated Show resolved Hide resolved
Copy link
Contributor

@mjbvz mjbvz left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Server protocol changes look good to me

@jessetrinity jessetrinity marked this pull request as ready for review May 29, 2020 18:58
Copy link
Member

@amcasey amcasey left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM modulo some minor naming comments.

I didn't review the expanded ranges carefully (let me know if that's something you're worried about) but the new tests don't obviously cover the newly supported regions.

@@ -104,12 +104,13 @@ namespace ts.codefix {
return modifierFlags;
}

export function getAccessorConvertiblePropertyAtPosition(file: SourceFile, start: number, end: number): Info | undefined {
export function getAccessorConvertiblePropertyAtPosition(file: SourceFile, start: number, end: number, userRequested = true): Info | undefined {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I find this a little confusing since the default elsewhere is implicit.

@@ -70,10 +70,12 @@ namespace ts.refactor.addOrRemoveBracesToArrowFunction {
return { renameFilename: undefined, renameLocation: undefined, edits };
}

function getConvertibleArrowFunctionAtPosition(file: SourceFile, startPosition: number): Info | undefined {
function getConvertibleArrowFunctionAtPosition(file: SourceFile, startPosition: number, userRequested = true): Info | undefined {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I can see the convenience of having this be called userRequested in all the different refactorings, but, personally, I think it would be clearer to give it a name that's specific to each function you've added it to. For example, in this case, the parameter could be called something like considerFunctionBodies - that would be more meaningful both within the function and at the call-sites that aren't part of a flow that can be either implicit or user-requested. As a bonus, it would also address my comment above, that this parameter defaults to explicit, whereas the ones in the protocol default to implicit.

const { file, startPosition } = context;
const info = getConvertibleArrowFunctionAtPosition(file, startPosition);
const { file, startPosition, triggerReason } = context;
const info = getConvertibleArrowFunctionAtPosition(file, startPosition, triggerReason === "invoked");
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Unfortunately, JS doesn't have named arguments. Instead, when we're passing a boolean, we tend to give the parameter name as a /*comment*/ before the argument (unless the boolean already has a descriptive name, of course).

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It wasn't my intention to pass a named argument, but to pass whatever triggerReason === "invoked" evaluates to (false if triggerReason is "implicit" or undefined. Did I do that incorrectly?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants