-
Notifications
You must be signed in to change notification settings - Fork 12.6k
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 infrastructure for refactors #14624
Conversation
host.runQueuedImmediateCallbacks(); | ||
assert.equal(host.getOutput().length, 2, "expect 2 messages"); | ||
assert.equal(host.getOutput().length, 1, "expect 1 messages"); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
"messages" should be "message"
src/server/client.ts
Outdated
@@ -692,6 +692,50 @@ namespace ts.server { | |||
return response.body.map(entry => this.convertCodeActions(entry, fileName)); | |||
} | |||
|
|||
getRefactorDiagnostics(fileName: string, range?: TextRange): RefactorDiagnostic[] { | |||
const startLineOffset = this.positionToOneBasedLineOffset(fileName, range.pos); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Does this crash if range === undefined
?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Here the range
is not supposed to be nullable, as we only use it to test the session API GetRefactorsForRange
. Will update
src/server/protocol.ts
Outdated
* Instances of this interface specify errorcodes on a specific location in a sourcefile. | ||
*/ | ||
export interface CodeFixRequestArgs extends FileRequestArgs { | ||
export interface TextRangeRequestArgs extends FileRequestArgs { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can you name the first two members line
and offset
to more closely coincide with FileLocationRequestArgs
andLocation
? Or use a Location
for the start and end?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Maybe FileRangeRequestArgs
would fit better. But using location
for start and end would be an unnecessary breaking change for editors already consuming this type.
src/server/session.ts
Outdated
return { startPosition, endPosition }; | ||
|
||
function getStartPosition() { | ||
return args.startPosition !== undefined ? args.startPosition : scriptInfo.lineOffsetToPosition(args.startLine, args.startOffset); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Do you want to save the result in args.startPosition
(resp. args.endPosition
)?
src/services/refactorProvider.ts
Outdated
/* @internal */ | ||
namespace ts { | ||
interface BaseRefactor { | ||
/** Description of the refactor to display in the UI of the editor */ |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
should this have a property canBeSuggested: boolean;
?
src/services/refactorProvider.ts
Outdated
export type Refactor = SuggestableRefactor | NonSuggestableRefactor; | ||
|
||
export interface LightRefactorContext { | ||
nonBoundSourceFile: SourceFile; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can you add a comment explaining what bound
and nonBound
means?
src/services/refactorProvider.ts
Outdated
return results; | ||
} | ||
|
||
export function getSuggestedRefactorDiagnosticsForNode(node: Node, context: RefactorContext) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Would it be right to name this getSuggestable...
? That would be more consistent with the naming of the interfaces.
src/services/services.ts
Outdated
} | ||
} | ||
|
||
function getCodeActionsForRefactorAtPosition( |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Do we have a convention for when we split function parameters across multiple lines?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't think so, though normally i do that when I have to use the horizontal scroll bar on my laptop if putting all in one line.
src/server/protocol.ts
Outdated
} | ||
|
||
export interface GetCodeActionsForRefactorResponse extends Response { | ||
body?: CodeAction[]; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
do not use an array. make it an map.
src/server/protocol.ts
Outdated
position?: number; | ||
} | ||
|
||
export namespace Refactor { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
we do not use namespaces anywhere else.
src/server/protocol.ts
Outdated
} | ||
|
||
export interface ApplicableRefactorInfo { | ||
refactorKind: number; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
i would call this name
and make it string.
src/server/protocol.ts
Outdated
} | ||
|
||
export interface GetApplicableRefactorsResponse extends Response { | ||
body?: ApplicableRefactorInfo[]; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
i would make this an object instead of an array.
src/server/protocol.ts
Outdated
|
||
export interface GetRefactorCodeActionsRequestArgs extends FileLocationOrSpanWithPositionRequestArgs { | ||
/* The kind of the applicable refactor */ | ||
refactorKinds?: number[]; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
probally just one refactoring to ask for code actions for
src/server/protocol.ts
Outdated
/* The kind of the applicable refactor */ | ||
refactorKinds?: number[]; | ||
/* The diagnostic code of a refactor diagnostic */ | ||
diagnosticCodes?: number[]; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
do not think we need this, it should be covered by getCodeActions
const { startPosition, endPosition } = this.getStartAndEndPosition(args, scriptInfo); | ||
textRange = { pos: startPosition, end: endPosition }; | ||
} | ||
return { position, textRange }; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
position may be undefined, is this intended? Initialize at line 1450 if so
src/server/session.ts
Outdated
return args.endPosition !== undefined ? args.endPosition : scriptInfo.lineOffsetToPosition(args.endLine, args.endOffset); | ||
} | ||
private getStartAndEndPosition(args: protocol.FileRangeRequestArgs, scriptInfo: ScriptInfo) { | ||
const startPosition = args.startPosition !== undefined |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is really confusing, can we just use a normal if
here
export function getCodeFixDiagnosticsForNode(context: CodeFixDiagnoseContext, node: Node): Diagnostic[] | undefined { | ||
let result: Diagnostic[]; | ||
for (const codeFix of diagnosticGeneratingCodeFixes) { | ||
const newDiag = codeFix.createCodeFixDiagnosticIfApplicable(node, context); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This member is declared as optional, but isn't checked for undefined
in this method
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
in line 41 it already checked the existence of createCodeFixDiagnosticIfApplicable
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
👍
src/services/refactorProvider.ts
Outdated
isApplicableForPositionOrRange(context: LightRefactorContext, positionOrRange: number | TextRange): boolean; | ||
} | ||
|
||
export interface LightRefactorContext { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Comment defining what "Light" means
return results; | ||
} | ||
|
||
export function getRefactorCodeActions( |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why does this return an empty array vs getApplicableRefactors
returns undefined
if the array would have been empty?
Should we add a trivial refactor with this just so we can have some testcases? |
Sure, I'm adding one now will update soon |
I added two things:
|
@mhegazy any other comments? |
closing in favor of #15569 |
This PR adds the infrastructure for refactors.
The PR adds one new kind of code fixes as well as refactors:
"diagnostic-generating code fixes": a special kind of code fixes that would generate non-error diagnostics. After normal syntactic check and semantic check, the language service will do an extra pass of the AST to get the non-error diagnostics generated by registered "diagnostic-generating code fixes". Then when the user interacts with the new diagnostics in code, a query for corresponding code actions will be triggered, just like with other code fixes.
"refactors": to get a refactor it is a two-step procedure. First, the editor will ask about all "applicable refactor info" at a given location / range, the returned "applicable refactor info" would only contain basic information like refactor names, it wouldn't compute the code actions yet. Then after the user chose a particular refactor, then the editor will send another request to compute the corresponding code actions.