Add infrastructure for refactors#14624
Conversation
| // the semanticDiag message | ||
| 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.
"messages" should be "message"
src/server/client.ts
Outdated
| } | ||
|
|
||
| getRefactorDiagnostics(fileName: string, range?: TextRange): RefactorDiagnostic[] { | ||
| const startLineOffset = this.positionToOneBasedLineOffset(fileName, range.pos); |
There was a problem hiding this comment.
Does this crash if range === undefined ?
There was a problem hiding this comment.
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.
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.
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.
Do you want to save the result in args.startPosition (resp. args.endPosition)?
| /* @internal */ | ||
| namespace ts { | ||
| interface BaseRefactor { | ||
| /** Description of the refactor to display in the UI of the editor */ |
There was a problem hiding this comment.
should this have a property canBeSuggested: boolean; ?
| export type Refactor = SuggestableRefactor | NonSuggestableRefactor; | ||
|
|
||
| export interface LightRefactorContext { | ||
| nonBoundSourceFile: SourceFile; |
There was a problem hiding this comment.
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.
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.
Do we have a convention for when we split function parameters across multiple lines?
There was a problem hiding this comment.
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
| refactorCode: number; | ||
| } | ||
|
|
||
| export interface GetCodeActionsForRefactorResponse extends Response { |
There was a problem hiding this comment.
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.
we do not use namespaces anywhere else.
src/server/protocol.ts
Outdated
| } | ||
|
|
||
| export interface ApplicableRefactorInfo { | ||
| refactorKind: number; |
There was a problem hiding this comment.
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.
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.
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.
do not think we need this, it should be covered by getCodeActions
… refactor_only
… refactor_only
| const { startPosition, endPosition } = this.getStartAndEndPosition(args, scriptInfo); | ||
| textRange = { pos: startPosition, end: endPosition }; | ||
| } | ||
| return { position, textRange }; |
There was a problem hiding this comment.
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.
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.
This member is declared as optional, but isn't checked for undefined in this method
There was a problem hiding this comment.
in line 41 it already checked the existence of createCodeFixDiagnosticIfApplicable
| isApplicableForPositionOrRange(context: LightRefactorContext, positionOrRange: number | TextRange): boolean; | ||
| } | ||
|
|
||
| export interface LightRefactorContext { |
There was a problem hiding this comment.
Comment defining what "Light" means
| return results; | ||
| } | ||
|
|
||
| export function getRefactorCodeActions( |
There was a problem hiding this comment.
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.