diff --git a/src/server/session.ts b/src/server/session.ts index 296a677769836..a0a5c7e5ec434 100644 --- a/src/server/session.ts +++ b/src/server/session.ts @@ -2750,7 +2750,8 @@ export class Session implements EventSender { private getApplicableRefactors(args: protocol.GetApplicableRefactorsRequestArgs): protocol.ApplicableRefactorInfo[] { const { file, project } = this.getFileAndProject(args); const scriptInfo = project.getScriptInfoForNormalizedPath(file)!; - return project.getLanguageService().getApplicableRefactors(file, this.extractPositionOrRange(args, scriptInfo), this.getPreferences(file), args.triggerReason, args.kind, args.includeInteractiveActions); + const result = project.getLanguageService().getApplicableRefactors(file, this.extractPositionOrRange(args, scriptInfo), this.getPreferences(file), args.triggerReason, args.kind, args.includeInteractiveActions); + return result.map(result => ({ ...result, actions: result.actions.map(action => ({ ...action, range: action.range ? { start: convertToLocation({ line: action.range.start.line, character: action.range.start.offset }), end: convertToLocation({ line: action.range.end.line, character: action.range.end.offset }) } : undefined })) })); } private getEditsForRefactor(args: protocol.GetEditsForRefactorRequestArgs, simplifiedResult: boolean): RefactorEditInfo | protocol.RefactorEditInfo { diff --git a/src/services/refactors/extractSymbol.ts b/src/services/refactors/extractSymbol.ts index b9406e8f1b594..484112be14421 100644 --- a/src/services/refactors/extractSymbol.ts +++ b/src/services/refactors/extractSymbol.ts @@ -47,6 +47,7 @@ import { getEffectiveTypeParameterDeclarations, getEmitScriptTarget, getEnclosingBlockScopeContainer, + getLineAndCharacterOfPosition, getLocaleSpecificMessage, getModifiers, getNodeId, @@ -219,7 +220,7 @@ export function getRefactorActionsToExtractSymbol(context: RefactorContext): rea return errors; } - const extractions = getPossibleExtractions(targetRange, context); + const { affectedTextRange, extractions } = getPossibleExtractions(targetRange, context); if (extractions === undefined) { // No extractions possible return emptyArray; @@ -247,6 +248,10 @@ export function getRefactorActionsToExtractSymbol(context: RefactorContext): rea description, name: `function_scope_${i}`, kind: extractFunctionAction.kind, + range: { + start: { line: getLineAndCharacterOfPosition(context.file, affectedTextRange.pos).line, offset: getLineAndCharacterOfPosition(context.file, affectedTextRange.pos).character }, + end: { line: getLineAndCharacterOfPosition(context.file, affectedTextRange.end).line, offset: getLineAndCharacterOfPosition(context.file, affectedTextRange.end).character }, + }, }); } } @@ -272,6 +277,10 @@ export function getRefactorActionsToExtractSymbol(context: RefactorContext): rea description, name: `constant_scope_${i}`, kind: extractConstantAction.kind, + range: { + start: { line: getLineAndCharacterOfPosition(context.file, affectedTextRange.pos).line, offset: getLineAndCharacterOfPosition(context.file, affectedTextRange.pos).character }, + end: { line: getLineAndCharacterOfPosition(context.file, affectedTextRange.end).line, offset: getLineAndCharacterOfPosition(context.file, affectedTextRange.end).character }, + }, }); } } @@ -909,8 +918,8 @@ interface ScopeExtractions { * Each returned ExtractResultForScope corresponds to a possible target scope and is either a set of changes * or an error explaining why we can't extract into that scope. */ -function getPossibleExtractions(targetRange: TargetRange, context: RefactorContext): readonly ScopeExtractions[] | undefined { - const { scopes, readsAndWrites: { functionErrorsPerScope, constantErrorsPerScope } } = getPossibleExtractionsWorker(targetRange, context); +function getPossibleExtractions(targetRange: TargetRange, context: RefactorContext): { readonly affectedTextRange: TextRange; readonly extractions: ScopeExtractions[] | undefined; } { + const { scopes, affectedTextRange, readsAndWrites: { functionErrorsPerScope, constantErrorsPerScope } } = getPossibleExtractionsWorker(targetRange, context); // Need the inner type annotation to avoid https://github.com/Microsoft/TypeScript/issues/7547 const extractions = scopes.map((scope, i): ScopeExtractions => { const functionDescriptionPart = getDescriptionForFunctionInScope(scope); @@ -953,10 +962,10 @@ function getPossibleExtractions(targetRange: TargetRange, context: RefactorConte }, }; }); - return extractions; + return { affectedTextRange, extractions }; } -function getPossibleExtractionsWorker(targetRange: TargetRange, context: RefactorContext): { readonly scopes: Scope[]; readonly readsAndWrites: ReadsAndWrites; } { +function getPossibleExtractionsWorker(targetRange: TargetRange, context: RefactorContext): { readonly scopes: Scope[]; readonly affectedTextRange: TextRange; readonly readsAndWrites: ReadsAndWrites; } { const { file: sourceFile } = context; const scopes = collectEnclosingScopes(targetRange); @@ -969,7 +978,7 @@ function getPossibleExtractionsWorker(targetRange: TargetRange, context: Refacto context.program.getTypeChecker(), context.cancellationToken!, ); - return { scopes, readsAndWrites }; + return { scopes, affectedTextRange: enclosingTextRange, readsAndWrites }; } function getDescriptionForFunctionInScope(scope: Scope): string { diff --git a/src/services/refactors/extractType.ts b/src/services/refactors/extractType.ts index e9d43a1a3c219..fe1e8e3ddb2fa 100644 --- a/src/services/refactors/extractType.ts +++ b/src/services/refactors/extractType.ts @@ -99,16 +99,27 @@ registerRefactor(refactorName, { extractToTypeDefAction.kind, ], getAvailableActions: function getRefactorActionsToExtractType(context): readonly ApplicableRefactorInfo[] { - const info = getRangeToExtract(context, context.triggerReason === "invoked"); + const { info, affectedTextRange } = getRangeToExtract(context, context.triggerReason === "invoked"); if (!info) return emptyArray; if (!isRefactorErrorInfo(info)) { - return [{ + const refactorInfo: ApplicableRefactorInfo[] = [{ name: refactorName, description: getLocaleSpecificMessage(Diagnostics.Extract_type), actions: info.isJS ? [extractToTypeDefAction] : append([extractToTypeAliasAction], info.typeElements && extractToInterfaceAction), }]; + return refactorInfo.map(info => ({ + ...info, + actions: info.actions.map(action => ({ + ...action, + range: affectedTextRange ? { + start: { line: getLineAndCharacterOfPosition(context.file, affectedTextRange.pos).line, offset: getLineAndCharacterOfPosition(context.file, affectedTextRange.pos).character }, + end: { line: getLineAndCharacterOfPosition(context.file, affectedTextRange.end).line, offset: getLineAndCharacterOfPosition(context.file, affectedTextRange.end).character }, + } + : undefined, + })), + })); } if (context.preferences.provideRefactorNotApplicableReason) { @@ -127,7 +138,7 @@ registerRefactor(refactorName, { }, getEditsForAction: function getRefactorEditsToExtractType(context, actionName): RefactorEditInfo { const { file } = context; - const info = getRangeToExtract(context); + const { info } = getRangeToExtract(context); Debug.assert(info && !isRefactorErrorInfo(info), "Expected to find a range to extract"); const name = getUniqueName("NewType", file); @@ -171,20 +182,20 @@ interface InterfaceInfo { type ExtractInfo = TypeAliasInfo | InterfaceInfo; -function getRangeToExtract(context: RefactorContext, considerEmptySpans = true): ExtractInfo | RefactorErrorInfo | undefined { +function getRangeToExtract(context: RefactorContext, considerEmptySpans = true): { info: ExtractInfo | RefactorErrorInfo | undefined; affectedTextRange?: TextRange; } { const { file, startPosition } = context; const isJS = isSourceFileJS(file); const range = createTextRangeFromSpan(getRefactorContextSpan(context)); const isCursorRequest = range.pos === range.end && considerEmptySpans; const firstType = getFirstTypeAt(file, startPosition, range, isCursorRequest); - if (!firstType || !isTypeNode(firstType)) return { error: getLocaleSpecificMessage(Diagnostics.Selection_is_not_a_valid_type_node) }; + if (!firstType || !isTypeNode(firstType)) return { info: { error: getLocaleSpecificMessage(Diagnostics.Selection_is_not_a_valid_type_node) }, affectedTextRange: undefined }; const checker = context.program.getTypeChecker(); const enclosingNode = getEnclosingNode(firstType, isJS); - if (enclosingNode === undefined) return { error: getLocaleSpecificMessage(Diagnostics.No_type_could_be_extracted_from_this_type_node) }; + if (enclosingNode === undefined) return { info: { error: getLocaleSpecificMessage(Diagnostics.No_type_could_be_extracted_from_this_type_node) }, affectedTextRange: undefined }; const expandedFirstType = getExpandedSelectionNode(firstType, enclosingNode); - if (!isTypeNode(expandedFirstType)) return { error: getLocaleSpecificMessage(Diagnostics.Selection_is_not_a_valid_type_node) }; + if (!isTypeNode(expandedFirstType)) return { info: { error: getLocaleSpecificMessage(Diagnostics.Selection_is_not_a_valid_type_node) }, affectedTextRange: undefined }; const typeList: TypeNode[] = []; if ((isUnionTypeNode(expandedFirstType.parent) || isIntersectionTypeNode(expandedFirstType.parent)) && range.end > firstType.end) { @@ -198,11 +209,11 @@ function getRangeToExtract(context: RefactorContext, considerEmptySpans = true): } const selection = typeList.length > 1 ? typeList : expandedFirstType; - const typeParameters = collectTypeParameters(checker, selection, enclosingNode, file); - if (!typeParameters) return { error: getLocaleSpecificMessage(Diagnostics.No_type_could_be_extracted_from_this_type_node) }; + const { typeParameters, affectedTextRange } = collectTypeParameters(checker, selection, enclosingNode, file); + if (!typeParameters) return { info: { error: getLocaleSpecificMessage(Diagnostics.No_type_could_be_extracted_from_this_type_node) }, affectedTextRange: undefined }; const typeElements = flattenTypeLiteralNodeReference(checker, selection); - return { isJS, selection, enclosingNode, typeParameters, typeElements }; + return { info: { isJS, selection, enclosingNode, typeParameters, typeElements }, affectedTextRange }; } function getFirstTypeAt(file: SourceFile, startPosition: number, range: TextRange, isCursorRequest: boolean): Node | undefined { @@ -260,14 +271,14 @@ function rangeContainsSkipTrivia(r1: TextRange, node: TextRange, file: SourceFil return rangeContainsStartEnd(r1, skipTrivia(file.text, node.pos), node.end); } -function collectTypeParameters(checker: TypeChecker, selection: TypeNode | TypeNode[], enclosingNode: Node, file: SourceFile): TypeParameterDeclaration[] | undefined { +function collectTypeParameters(checker: TypeChecker, selection: TypeNode | TypeNode[], enclosingNode: Node, file: SourceFile): { typeParameters: TypeParameterDeclaration[] | undefined; affectedTextRange: TextRange | undefined; } { const result: TypeParameterDeclaration[] = []; const selectionArray = toArray(selection); - const selectionRange = { pos: selectionArray[0].pos, end: selectionArray[selectionArray.length - 1].end }; + const selectionRange = { pos: selectionArray[0].getStart(file), end: selectionArray[selectionArray.length - 1].end }; for (const t of selectionArray) { - if (visitor(t)) return undefined; + if (visitor(t)) return { typeParameters: undefined, affectedTextRange: undefined }; } - return result; + return { typeParameters: result, affectedTextRange: selectionRange }; function visitor(node: Node): true | undefined { if (isTypeReferenceNode(node)) { diff --git a/src/services/refactors/moveToFile.ts b/src/services/refactors/moveToFile.ts index 1d3d072d89c07..f7d75f590fdc5 100644 --- a/src/services/refactors/moveToFile.ts +++ b/src/services/refactors/moveToFile.ts @@ -50,6 +50,7 @@ import { GetCanonicalFileName, getDecorators, getDirectoryPath, + getLineAndCharacterOfPosition, getLocaleSpecificMessage, getModifiers, getPropertySymbolFromBindingElement, @@ -164,6 +165,7 @@ const moveToFileAction = { registerRefactor(refactorNameForMoveToFile, { kinds: [moveToFileAction.kind], getAvailableActions: function getRefactorActionsToMoveToFile(context, interactiveRefactorArguments): readonly ApplicableRefactorInfo[] { + const file = context.file; const statements = getStatementsToMove(context); if (!interactiveRefactorArguments) { return emptyArray; @@ -171,7 +173,6 @@ registerRefactor(refactorNameForMoveToFile, { /** If the start/end nodes of the selection are inside a block like node do not show the `Move to file` code action * This condition is used in order to show less often the `Move to file` code action */ if (context.endPosition !== undefined) { - const file = context.file; const startNodeAncestor = findAncestor(getTokenAtPosition(file, context.startPosition), isBlockLike); const endNodeAncestor = findAncestor(getTokenAtPosition(file, context.endPosition), isBlockLike); if (startNodeAncestor && !isSourceFile(startNodeAncestor) && endNodeAncestor && !isSourceFile(endNodeAncestor)) { @@ -179,7 +180,11 @@ registerRefactor(refactorNameForMoveToFile, { } } if (context.preferences.allowTextChangesInNewFiles && statements) { - return [{ name: refactorNameForMoveToFile, description, actions: [moveToFileAction] }]; + const affectedTextRange = { + start: { line: getLineAndCharacterOfPosition(file, statements.all[0].getStart(file)).line, offset: getLineAndCharacterOfPosition(file, statements.all[0].getStart(file)).character }, + end: { line: getLineAndCharacterOfPosition(file, last(statements.all).end).line, offset: getLineAndCharacterOfPosition(file, last(statements.all).end).character }, + }; + return [{ name: refactorNameForMoveToFile, description, actions: [{ ...moveToFileAction, range: affectedTextRange }] }]; } if (context.preferences.provideRefactorNotApplicableReason) { return [{ name: refactorNameForMoveToFile, description, actions: [{ ...moveToFileAction, notApplicableReason: getLocaleSpecificMessage(Diagnostics.Selection_is_not_a_valid_statement_or_statements) }] }]; diff --git a/src/services/refactors/moveToNewFile.ts b/src/services/refactors/moveToNewFile.ts index d2cb36084499c..99b6612fecf85 100644 --- a/src/services/refactors/moveToNewFile.ts +++ b/src/services/refactors/moveToNewFile.ts @@ -6,6 +6,7 @@ import { emptyArray, fileShouldUseJavaScriptRequire, getBaseFileName, + getLineAndCharacterOfPosition, getLocaleSpecificMessage, getQuotePreference, hasSyntacticModifier, @@ -14,6 +15,7 @@ import { insertImports, isPrologueDirective, LanguageServiceHost, + last, ModifierFlags, nodeSeenTracker, Program, @@ -64,7 +66,12 @@ registerRefactor(refactorName, { getAvailableActions: function getRefactorActionsToMoveToNewFile(context): readonly ApplicableRefactorInfo[] { const statements = getStatementsToMove(context); if (context.preferences.allowTextChangesInNewFiles && statements) { - return [{ name: refactorName, description, actions: [moveToNewFileAction] }]; + const file = context.file; + const affectedTextRange = { + start: { line: getLineAndCharacterOfPosition(file, statements.all[0].getStart(file)).line, offset: getLineAndCharacterOfPosition(file, statements.all[0].getStart(file)).character }, + end: { line: getLineAndCharacterOfPosition(file, last(statements.all).end).line, offset: getLineAndCharacterOfPosition(file, last(statements.all).end).character }, + }; + return [{ name: refactorName, description, actions: [{ ...moveToNewFileAction, range: affectedTextRange }] }]; } if (context.preferences.provideRefactorNotApplicableReason) { return [{ name: refactorName, description, actions: [{ ...moveToNewFileAction, notApplicableReason: getLocaleSpecificMessage(Diagnostics.Selection_is_not_a_valid_statement_or_statements) }] }]; diff --git a/src/services/types.ts b/src/services/types.ts index 342636d0d96aa..f98d317d61dd5 100644 --- a/src/services/types.ts +++ b/src/services/types.ts @@ -1008,6 +1008,11 @@ export interface RefactorActionInfo { * when calling `getEditsForRefactor`. */ isInteractive?: boolean; + + /** + * Range of code the refactoring will be applied to. + */ + range?: { start: { line: number; offset: number; }; end: { line: number; offset: number; }; }; } /** diff --git a/src/testRunner/unittests/tsserver/getApplicableRefactors.ts b/src/testRunner/unittests/tsserver/getApplicableRefactors.ts index 6ee8a48be6ce7..92cbc2b0abd49 100644 --- a/src/testRunner/unittests/tsserver/getApplicableRefactors.ts +++ b/src/testRunner/unittests/tsserver/getApplicableRefactors.ts @@ -21,4 +21,69 @@ describe("unittests:: tsserver:: getApplicableRefactors", () => { }); baselineTsserverLogs("getApplicableRefactors", "works when taking position", session); }); + + it("returns the affected range of text for extract symbol refactor", () => { + const file1: File = { + path: "/a.ts", + content: `class Foo { + someMethod(m: number) { + var x = m; + x = x * 3; + var y = 30; + var j = 10; + var z = y + j; + console.log(z); + var q = 10; + return q; + } +}`, + }; + const host = createServerHost([file1]); + const session = new TestSession(host); + openFilesForSession([file1], session); + session.executeCommandSeq({ + command: ts.server.protocol.CommandTypes.GetApplicableRefactors, + arguments: { file: file1.path, startLine: 3, startOffset: 9, endLine: 5, endOffset: 20 }, + }); + baselineTsserverLogs("getApplicableRefactors", "returns the affected range of text for extract symbol refactor", session); + }); + + it("returns the affected range of text for extract type refactor", () => { + const file1: File = { + path: "/a.ts", + content: `type A = Partial & D | C;`, + }; + const host = createServerHost([file1]); + const session = new TestSession(host); + openFilesForSession([file1], session); + session.executeCommandSeq({ + command: ts.server.protocol.CommandTypes.GetApplicableRefactors, + arguments: { file: file1.path, startLine: 1, startOffset: 26, endLine: 1, endOffset: 38 }, + }); + baselineTsserverLogs("getApplicableRefactors", "returns the affected range of text for extract type refactor", session); + }); + + it("returns the affected range of text for 'move to file' and 'move to new file' refactors", () => { + const file1: File = { + path: "/a.ts", + content: `const a = 1; +const b = 1; +function foo() { }`, + }; + const host = createServerHost([file1]); + const session = new TestSession(host); + openFilesForSession([file1], session); + + session.executeCommandSeq({ + command: ts.server.protocol.CommandTypes.Configure, + arguments: { + preferences: { allowTextChangesInNewFiles: true }, + }, + }); + session.executeCommandSeq({ + command: ts.server.protocol.CommandTypes.GetApplicableRefactors, + arguments: { file: file1.path, startLine: 1, startOffset: 3, endLine: 2, endOffset: 3, includeInteractiveActions: true }, + }); + baselineTsserverLogs("getApplicableRefactors", "returns the affected range of text for 'move to file' and 'move to new file' refactors", session); + }); }); diff --git a/tests/baselines/reference/api/typescript.d.ts b/tests/baselines/reference/api/typescript.d.ts index 61c467dc32ec9..f39a304375e7a 100644 --- a/tests/baselines/reference/api/typescript.d.ts +++ b/tests/baselines/reference/api/typescript.d.ts @@ -10351,6 +10351,19 @@ declare namespace ts { * when calling `getEditsForRefactor`. */ isInteractive?: boolean; + /** + * Range of code the refactoring will be applied to. + */ + range?: { + start: { + line: number; + offset: number; + }; + end: { + line: number; + offset: number; + }; + }; } /** * A set of edits to make in response to a refactor action, plus an optional diff --git a/tests/baselines/reference/tsserver/fourslashServer/fixExtractToInnerFunctionDuplicaton.js b/tests/baselines/reference/tsserver/fourslashServer/fixExtractToInnerFunctionDuplicaton.js index aa50b4634e62c..96656bf3bacbb 100644 --- a/tests/baselines/reference/tsserver/fourslashServer/fixExtractToInnerFunctionDuplicaton.js +++ b/tests/baselines/reference/tsserver/fourslashServer/fixExtractToInnerFunctionDuplicaton.js @@ -156,12 +156,32 @@ Info seq [hh:mm:ss:mss] response: { "description": "Extract to inner function in function 'foo'", "name": "function_scope_0", - "kind": "refactor.extract.function" + "kind": "refactor.extract.function", + "range": { + "start": { + "line": 1, + "offset": 24 + }, + "end": { + "line": 1, + "offset": 41 + } + } }, { "description": "Extract to function in global scope", "name": "function_scope_1", - "kind": "refactor.extract.function" + "kind": "refactor.extract.function", + "range": { + "start": { + "line": 1, + "offset": 24 + }, + "end": { + "line": 1, + "offset": 41 + } + } } ] }, @@ -172,12 +192,32 @@ Info seq [hh:mm:ss:mss] response: { "description": "Extract to constant in enclosing scope", "name": "constant_scope_0", - "kind": "refactor.extract.constant" + "kind": "refactor.extract.constant", + "range": { + "start": { + "line": 1, + "offset": 24 + }, + "end": { + "line": 1, + "offset": 41 + } + } }, { "description": "Extract to constant in global scope", "name": "constant_scope_1", - "kind": "refactor.extract.constant" + "kind": "refactor.extract.constant", + "range": { + "start": { + "line": 1, + "offset": 24 + }, + "end": { + "line": 1, + "offset": 41 + } + } } ] } @@ -246,12 +286,32 @@ Info seq [hh:mm:ss:mss] response: { "description": "Extract to inner function in function 'foo'", "name": "function_scope_0", - "kind": "refactor.extract.function" + "kind": "refactor.extract.function", + "range": { + "start": { + "line": 1, + "offset": 24 + }, + "end": { + "line": 1, + "offset": 41 + } + } }, { "description": "Extract to function in global scope", "name": "function_scope_1", - "kind": "refactor.extract.function" + "kind": "refactor.extract.function", + "range": { + "start": { + "line": 1, + "offset": 24 + }, + "end": { + "line": 1, + "offset": 41 + } + } } ] }, @@ -262,12 +322,32 @@ Info seq [hh:mm:ss:mss] response: { "description": "Extract to constant in enclosing scope", "name": "constant_scope_0", - "kind": "refactor.extract.constant" + "kind": "refactor.extract.constant", + "range": { + "start": { + "line": 1, + "offset": 24 + }, + "end": { + "line": 1, + "offset": 41 + } + } }, { "description": "Extract to constant in global scope", "name": "constant_scope_1", - "kind": "refactor.extract.constant" + "kind": "refactor.extract.constant", + "range": { + "start": { + "line": 1, + "offset": 24 + }, + "end": { + "line": 1, + "offset": 41 + } + } } ] } @@ -336,12 +416,32 @@ Info seq [hh:mm:ss:mss] response: { "description": "Extract to inner function in function 'foo'", "name": "function_scope_0", - "kind": "refactor.extract.function" + "kind": "refactor.extract.function", + "range": { + "start": { + "line": 1, + "offset": 24 + }, + "end": { + "line": 1, + "offset": 41 + } + } }, { "description": "Extract to function in global scope", "name": "function_scope_1", - "kind": "refactor.extract.function" + "kind": "refactor.extract.function", + "range": { + "start": { + "line": 1, + "offset": 24 + }, + "end": { + "line": 1, + "offset": 41 + } + } } ] }, @@ -352,12 +452,32 @@ Info seq [hh:mm:ss:mss] response: { "description": "Extract to constant in enclosing scope", "name": "constant_scope_0", - "kind": "refactor.extract.constant" + "kind": "refactor.extract.constant", + "range": { + "start": { + "line": 1, + "offset": 24 + }, + "end": { + "line": 1, + "offset": 41 + } + } }, { "description": "Extract to constant in global scope", "name": "constant_scope_1", - "kind": "refactor.extract.constant" + "kind": "refactor.extract.constant", + "range": { + "start": { + "line": 1, + "offset": 24 + }, + "end": { + "line": 1, + "offset": 41 + } + } } ] } @@ -426,12 +546,32 @@ Info seq [hh:mm:ss:mss] response: { "description": "Extract to inner function in function 'foo'", "name": "function_scope_0", - "kind": "refactor.extract.function" + "kind": "refactor.extract.function", + "range": { + "start": { + "line": 1, + "offset": 24 + }, + "end": { + "line": 1, + "offset": 41 + } + } }, { "description": "Extract to function in global scope", "name": "function_scope_1", - "kind": "refactor.extract.function" + "kind": "refactor.extract.function", + "range": { + "start": { + "line": 1, + "offset": 24 + }, + "end": { + "line": 1, + "offset": 41 + } + } } ] }, @@ -442,12 +582,32 @@ Info seq [hh:mm:ss:mss] response: { "description": "Extract to constant in enclosing scope", "name": "constant_scope_0", - "kind": "refactor.extract.constant" + "kind": "refactor.extract.constant", + "range": { + "start": { + "line": 1, + "offset": 24 + }, + "end": { + "line": 1, + "offset": 41 + } + } }, { "description": "Extract to constant in global scope", "name": "constant_scope_1", - "kind": "refactor.extract.constant" + "kind": "refactor.extract.constant", + "range": { + "start": { + "line": 1, + "offset": 24 + }, + "end": { + "line": 1, + "offset": 41 + } + } } ] } @@ -516,12 +676,32 @@ Info seq [hh:mm:ss:mss] response: { "description": "Extract to inner function in function 'foo'", "name": "function_scope_0", - "kind": "refactor.extract.function" + "kind": "refactor.extract.function", + "range": { + "start": { + "line": 1, + "offset": 24 + }, + "end": { + "line": 1, + "offset": 41 + } + } }, { "description": "Extract to function in global scope", "name": "function_scope_1", - "kind": "refactor.extract.function" + "kind": "refactor.extract.function", + "range": { + "start": { + "line": 1, + "offset": 24 + }, + "end": { + "line": 1, + "offset": 41 + } + } } ] }, @@ -532,12 +712,32 @@ Info seq [hh:mm:ss:mss] response: { "description": "Extract to constant in enclosing scope", "name": "constant_scope_0", - "kind": "refactor.extract.constant" + "kind": "refactor.extract.constant", + "range": { + "start": { + "line": 1, + "offset": 24 + }, + "end": { + "line": 1, + "offset": 41 + } + } }, { "description": "Extract to constant in global scope", "name": "constant_scope_1", - "kind": "refactor.extract.constant" + "kind": "refactor.extract.constant", + "range": { + "start": { + "line": 1, + "offset": 24 + }, + "end": { + "line": 1, + "offset": 41 + } + } } ] } @@ -606,12 +806,32 @@ Info seq [hh:mm:ss:mss] response: { "description": "Extract to inner function in function 'foo'", "name": "function_scope_0", - "kind": "refactor.extract.function" + "kind": "refactor.extract.function", + "range": { + "start": { + "line": 1, + "offset": 24 + }, + "end": { + "line": 1, + "offset": 41 + } + } }, { "description": "Extract to function in global scope", "name": "function_scope_1", - "kind": "refactor.extract.function" + "kind": "refactor.extract.function", + "range": { + "start": { + "line": 1, + "offset": 24 + }, + "end": { + "line": 1, + "offset": 41 + } + } } ] }, @@ -622,12 +842,32 @@ Info seq [hh:mm:ss:mss] response: { "description": "Extract to constant in enclosing scope", "name": "constant_scope_0", - "kind": "refactor.extract.constant" + "kind": "refactor.extract.constant", + "range": { + "start": { + "line": 1, + "offset": 24 + }, + "end": { + "line": 1, + "offset": 41 + } + } }, { "description": "Extract to constant in global scope", "name": "constant_scope_1", - "kind": "refactor.extract.constant" + "kind": "refactor.extract.constant", + "range": { + "start": { + "line": 1, + "offset": 24 + }, + "end": { + "line": 1, + "offset": 41 + } + } } ] } diff --git a/tests/baselines/reference/tsserver/fourslashServer/moveToFile_emptyTargetFile.js b/tests/baselines/reference/tsserver/fourslashServer/moveToFile_emptyTargetFile.js index 0a364963050f6..5414c78b40e24 100644 --- a/tests/baselines/reference/tsserver/fourslashServer/moveToFile_emptyTargetFile.js +++ b/tests/baselines/reference/tsserver/fourslashServer/moveToFile_emptyTargetFile.js @@ -248,7 +248,17 @@ Info seq [hh:mm:ss:mss] response: { "name": "Move to a new file", "description": "Move to a new file", - "kind": "refactor.move.newFile" + "kind": "refactor.move.newFile", + "range": { + "start": { + "line": 1, + "offset": 1 + }, + "end": { + "line": 1, + "offset": 20 + } + } } ] }, @@ -259,7 +269,17 @@ Info seq [hh:mm:ss:mss] response: { "name": "Move to file", "description": "Move to file", - "kind": "refactor.move.file" + "kind": "refactor.move.file", + "range": { + "start": { + "line": 1, + "offset": 1 + }, + "end": { + "line": 1, + "offset": 20 + } + } } ] }, @@ -270,7 +290,17 @@ Info seq [hh:mm:ss:mss] response: { "description": "Extract to function in module scope", "name": "function_scope_0", - "kind": "refactor.extract.function" + "kind": "refactor.extract.function", + "range": { + "start": { + "line": 1, + "offset": 17 + }, + "end": { + "line": 1, + "offset": 19 + } + } } ] }, @@ -281,7 +311,17 @@ Info seq [hh:mm:ss:mss] response: { "description": "Extract to constant in enclosing scope", "name": "constant_scope_0", - "kind": "refactor.extract.constant" + "kind": "refactor.extract.constant", + "range": { + "start": { + "line": 1, + "offset": 17 + }, + "end": { + "line": 1, + "offset": 19 + } + } } ] } diff --git a/tests/baselines/reference/tsserver/getApplicableRefactors/returns-the-affected-range-of-text-for-'move-to-file'-and-'move-to-new-file'-refactors.js b/tests/baselines/reference/tsserver/getApplicableRefactors/returns-the-affected-range-of-text-for-'move-to-file'-and-'move-to-new-file'-refactors.js new file mode 100644 index 0000000000000..963e2a025f6b4 --- /dev/null +++ b/tests/baselines/reference/tsserver/getApplicableRefactors/returns-the-affected-range-of-text-for-'move-to-file'-and-'move-to-new-file'-refactors.js @@ -0,0 +1,155 @@ +currentDirectory:: / useCaseSensitiveFileNames: false +Info seq [hh:mm:ss:mss] Provided types map file "/typesMap.json" doesn't exist +Before request +//// [/a.ts] +const a = 1; +const b = 1; +function foo() { } + + +Info seq [hh:mm:ss:mss] request: + { + "command": "open", + "arguments": { + "file": "/a.ts" + }, + "seq": 1, + "type": "request" + } +Info seq [hh:mm:ss:mss] Search path: / +Info seq [hh:mm:ss:mss] For info: /a.ts :: No config files found. +Info seq [hh:mm:ss:mss] Starting updateGraphWorker: Project: /dev/null/inferredProject1* +Info seq [hh:mm:ss:mss] FileWatcher:: Added:: WatchInfo: /a/lib/lib.d.ts 500 undefined Project: /dev/null/inferredProject1* WatchType: Missing file +Info seq [hh:mm:ss:mss] Finishing updateGraphWorker: Project: /dev/null/inferredProject1* projectStateVersion: 1 projectProgramVersion: 0 structureChanged: true structureIsReused:: Not Elapsed:: *ms +Info seq [hh:mm:ss:mss] Project '/dev/null/inferredProject1*' (Inferred) +Info seq [hh:mm:ss:mss] Files (1) + /a.ts SVC-1-0 "const a = 1;\nconst b = 1;\nfunction foo() { }" + + + a.ts + Root file specified for compilation + +Info seq [hh:mm:ss:mss] ----------------------------------------------- +Info seq [hh:mm:ss:mss] Project '/dev/null/inferredProject1*' (Inferred) +Info seq [hh:mm:ss:mss] Files (1) + +Info seq [hh:mm:ss:mss] ----------------------------------------------- +Info seq [hh:mm:ss:mss] Open files: +Info seq [hh:mm:ss:mss] FileName: /a.ts ProjectRootPath: undefined +Info seq [hh:mm:ss:mss] Projects: /dev/null/inferredProject1* +Info seq [hh:mm:ss:mss] response: + { + "responseRequired": false + } +After request + +PolledWatches:: +/a/lib/lib.d.ts: *new* + {"pollingInterval":500} + +Projects:: +/dev/null/inferredProject1* (Inferred) *new* + projectStateVersion: 1 + projectProgramVersion: 1 + +ScriptInfos:: +/a.ts (Open) *new* + version: SVC-1-0 + containingProjects: 1 + /dev/null/inferredProject1* *default* + +Before request + +Info seq [hh:mm:ss:mss] request: + { + "command": "configure", + "arguments": { + "preferences": { + "allowTextChangesInNewFiles": true + } + }, + "seq": 2, + "type": "request" + } +Info seq [hh:mm:ss:mss] response: + { + "seq": 0, + "type": "response", + "command": "configure", + "request_seq": 2, + "success": true, + "performanceData": { + "updateGraphDurationMs": * + } + } +Info seq [hh:mm:ss:mss] response: + { + "responseRequired": false + } +After request + +Before request + +Info seq [hh:mm:ss:mss] request: + { + "command": "getApplicableRefactors", + "arguments": { + "file": "/a.ts", + "startLine": 1, + "startOffset": 3, + "endLine": 2, + "endOffset": 3, + "includeInteractiveActions": true + }, + "seq": 3, + "type": "request" + } +Info seq [hh:mm:ss:mss] response: + { + "response": [ + { + "name": "Move to a new file", + "description": "Move to a new file", + "actions": [ + { + "name": "Move to a new file", + "description": "Move to a new file", + "kind": "refactor.move.newFile", + "range": { + "start": { + "line": 1, + "offset": 1 + }, + "end": { + "line": 2, + "offset": 13 + } + } + } + ] + }, + { + "name": "Move to file", + "description": "Move to file", + "actions": [ + { + "name": "Move to file", + "description": "Move to file", + "kind": "refactor.move.file", + "range": { + "start": { + "line": 1, + "offset": 1 + }, + "end": { + "line": 2, + "offset": 13 + } + } + } + ] + } + ], + "responseRequired": true + } +After request diff --git a/tests/baselines/reference/tsserver/getApplicableRefactors/returns-the-affected-range-of-text-for-extract-symbol-refactor.js b/tests/baselines/reference/tsserver/getApplicableRefactors/returns-the-affected-range-of-text-for-extract-symbol-refactor.js new file mode 100644 index 0000000000000..ac7ee1c4c179a --- /dev/null +++ b/tests/baselines/reference/tsserver/getApplicableRefactors/returns-the-affected-range-of-text-for-extract-symbol-refactor.js @@ -0,0 +1,142 @@ +currentDirectory:: / useCaseSensitiveFileNames: false +Info seq [hh:mm:ss:mss] Provided types map file "/typesMap.json" doesn't exist +Before request +//// [/a.ts] +class Foo { + someMethod(m: number) { + var x = m; + x = x * 3; + var y = 30; + var j = 10; + var z = y + j; + console.log(z); + var q = 10; + return q; + } +} + + +Info seq [hh:mm:ss:mss] request: + { + "command": "open", + "arguments": { + "file": "/a.ts" + }, + "seq": 1, + "type": "request" + } +Info seq [hh:mm:ss:mss] Search path: / +Info seq [hh:mm:ss:mss] For info: /a.ts :: No config files found. +Info seq [hh:mm:ss:mss] Starting updateGraphWorker: Project: /dev/null/inferredProject1* +Info seq [hh:mm:ss:mss] FileWatcher:: Added:: WatchInfo: /a/lib/lib.d.ts 500 undefined Project: /dev/null/inferredProject1* WatchType: Missing file +Info seq [hh:mm:ss:mss] Finishing updateGraphWorker: Project: /dev/null/inferredProject1* projectStateVersion: 1 projectProgramVersion: 0 structureChanged: true structureIsReused:: Not Elapsed:: *ms +Info seq [hh:mm:ss:mss] Project '/dev/null/inferredProject1*' (Inferred) +Info seq [hh:mm:ss:mss] Files (1) + /a.ts SVC-1-0 "class Foo {\n someMethod(m: number) {\n var x = m;\n x = x * 3;\n var y = 30;\n var j = 10;\n var z = y + j;\n console.log(z);\n var q = 10;\n return q;\n }\n}" + + + a.ts + Root file specified for compilation + +Info seq [hh:mm:ss:mss] ----------------------------------------------- +Info seq [hh:mm:ss:mss] Project '/dev/null/inferredProject1*' (Inferred) +Info seq [hh:mm:ss:mss] Files (1) + +Info seq [hh:mm:ss:mss] ----------------------------------------------- +Info seq [hh:mm:ss:mss] Open files: +Info seq [hh:mm:ss:mss] FileName: /a.ts ProjectRootPath: undefined +Info seq [hh:mm:ss:mss] Projects: /dev/null/inferredProject1* +Info seq [hh:mm:ss:mss] response: + { + "responseRequired": false + } +After request + +PolledWatches:: +/a/lib/lib.d.ts: *new* + {"pollingInterval":500} + +Projects:: +/dev/null/inferredProject1* (Inferred) *new* + projectStateVersion: 1 + projectProgramVersion: 1 + +ScriptInfos:: +/a.ts (Open) *new* + version: SVC-1-0 + containingProjects: 1 + /dev/null/inferredProject1* *default* + +Before request + +Info seq [hh:mm:ss:mss] request: + { + "command": "getApplicableRefactors", + "arguments": { + "file": "/a.ts", + "startLine": 3, + "startOffset": 9, + "endLine": 5, + "endOffset": 20 + }, + "seq": 2, + "type": "request" + } +Info seq [hh:mm:ss:mss] response: + { + "response": [ + { + "name": "Extract Symbol", + "description": "Extract function", + "actions": [ + { + "description": "Extract to inner function in method 'someMethod'", + "name": "function_scope_0", + "kind": "refactor.extract.function", + "range": { + "start": { + "line": 3, + "offset": 9 + }, + "end": { + "line": 5, + "offset": 20 + } + } + }, + { + "description": "Extract to method in class 'Foo'", + "name": "function_scope_1", + "kind": "refactor.extract.function", + "range": { + "start": { + "line": 3, + "offset": 9 + }, + "end": { + "line": 5, + "offset": 20 + } + } + }, + { + "description": "Extract to function in global scope", + "name": "function_scope_2", + "kind": "refactor.extract.function", + "range": { + "start": { + "line": 3, + "offset": 9 + }, + "end": { + "line": 5, + "offset": 20 + } + } + } + ] + } + ], + "responseRequired": true + } +After request diff --git a/tests/baselines/reference/tsserver/getApplicableRefactors/returns-the-affected-range-of-text-for-extract-type-refactor.js b/tests/baselines/reference/tsserver/getApplicableRefactors/returns-the-affected-range-of-text-for-extract-type-refactor.js new file mode 100644 index 0000000000000..0a26420cbf9e2 --- /dev/null +++ b/tests/baselines/reference/tsserver/getApplicableRefactors/returns-the-affected-range-of-text-for-extract-type-refactor.js @@ -0,0 +1,101 @@ +currentDirectory:: / useCaseSensitiveFileNames: false +Info seq [hh:mm:ss:mss] Provided types map file "/typesMap.json" doesn't exist +Before request +//// [/a.ts] +type A = Partial & D | C; + + +Info seq [hh:mm:ss:mss] request: + { + "command": "open", + "arguments": { + "file": "/a.ts" + }, + "seq": 1, + "type": "request" + } +Info seq [hh:mm:ss:mss] Search path: / +Info seq [hh:mm:ss:mss] For info: /a.ts :: No config files found. +Info seq [hh:mm:ss:mss] Starting updateGraphWorker: Project: /dev/null/inferredProject1* +Info seq [hh:mm:ss:mss] FileWatcher:: Added:: WatchInfo: /a/lib/lib.d.ts 500 undefined Project: /dev/null/inferredProject1* WatchType: Missing file +Info seq [hh:mm:ss:mss] Finishing updateGraphWorker: Project: /dev/null/inferredProject1* projectStateVersion: 1 projectProgramVersion: 0 structureChanged: true structureIsReused:: Not Elapsed:: *ms +Info seq [hh:mm:ss:mss] Project '/dev/null/inferredProject1*' (Inferred) +Info seq [hh:mm:ss:mss] Files (1) + /a.ts SVC-1-0 "type A = Partial & D | C;" + + + a.ts + Root file specified for compilation + +Info seq [hh:mm:ss:mss] ----------------------------------------------- +Info seq [hh:mm:ss:mss] Project '/dev/null/inferredProject1*' (Inferred) +Info seq [hh:mm:ss:mss] Files (1) + +Info seq [hh:mm:ss:mss] ----------------------------------------------- +Info seq [hh:mm:ss:mss] Open files: +Info seq [hh:mm:ss:mss] FileName: /a.ts ProjectRootPath: undefined +Info seq [hh:mm:ss:mss] Projects: /dev/null/inferredProject1* +Info seq [hh:mm:ss:mss] response: + { + "responseRequired": false + } +After request + +PolledWatches:: +/a/lib/lib.d.ts: *new* + {"pollingInterval":500} + +Projects:: +/dev/null/inferredProject1* (Inferred) *new* + projectStateVersion: 1 + projectProgramVersion: 1 + +ScriptInfos:: +/a.ts (Open) *new* + version: SVC-1-0 + containingProjects: 1 + /dev/null/inferredProject1* *default* + +Before request + +Info seq [hh:mm:ss:mss] request: + { + "command": "getApplicableRefactors", + "arguments": { + "file": "/a.ts", + "startLine": 1, + "startOffset": 26, + "endLine": 1, + "endOffset": 38 + }, + "seq": 2, + "type": "request" + } +Info seq [hh:mm:ss:mss] response: + { + "response": [ + { + "name": "Extract type", + "description": "Extract type", + "actions": [ + { + "name": "Extract to type alias", + "description": "Extract to type alias", + "kind": "refactor.extract.type", + "range": { + "start": { + "line": 1, + "offset": 23 + }, + "end": { + "line": 1, + "offset": 46 + } + } + } + ] + } + ], + "responseRequired": true + } +After request