Skip to content

Commit dcd211e

Browse files
authored
Fix rename refactor issue when not ending with EOL (DonJayamanne#1748)
1 parent 8f68bb3 commit dcd211e

File tree

5 files changed

+116
-4
lines changed

5 files changed

+116
-4
lines changed

news/2 Fixes/695.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Resoves rename refactor issue that remvoes the last line of the source file when the line is being refactored and source does not end with an EOL.

pythonFiles/refactor.py

Lines changed: 27 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,11 @@
22
# 1. Working directory.
33
# 2. Rope folder
44

5+
import difflib
56
import io
6-
import sys
77
import json
8+
import os
9+
import sys
810
import traceback
911

1012
try:
@@ -55,6 +57,27 @@ def __init__(self, filePath, fileMode=ChangeType.EDIT, diff=""):
5557
self.diff = diff
5658
self.fileMode = fileMode
5759

60+
def get_diff(changeset):
61+
"""This is a copy of the code form the ChangeSet.get_description method found in Rope."""
62+
new = changeset.new_contents
63+
old = changeset.old_contents
64+
if old is None:
65+
if changeset.resource.exists():
66+
old = changeset.resource.read()
67+
else:
68+
old = ''
69+
70+
# Ensure code has a trailing empty lines, before generating a diff.
71+
# https://github.com/Microsoft/vscode-python/issues/695.
72+
old_lines = old.splitlines(True)
73+
if not old_lines[-1].endswith('\n'):
74+
old_lines[-1] = old_lines[-1] + os.linesep
75+
new = new + os.linesep
76+
77+
result = difflib.unified_diff(
78+
old_lines, new.splitlines(True),
79+
'a/' + changeset.resource.path, 'b/' + changeset.resource.path)
80+
return ''.join(list(result))
5881

5982
class BaseRefactoring(object):
6083
"""
@@ -117,7 +140,7 @@ def onRefactor(self):
117140
for item in changes.changes:
118141
if isinstance(item, rope.base.change.ChangeContents):
119142
self.changes.append(
120-
Change(item.resource.real_path, ChangeType.EDIT, item.get_description()))
143+
Change(item.resource.real_path, ChangeType.EDIT, get_diff(item)))
121144
else:
122145
raise Exception('Unknown Change')
123146

@@ -141,7 +164,7 @@ def onRefactor(self):
141164
for item in changes.changes:
142165
if isinstance(item, rope.base.change.ChangeContents):
143166
self.changes.append(
144-
Change(item.resource.real_path, ChangeType.EDIT, item.get_description()))
167+
Change(item.resource.real_path, ChangeType.EDIT, get_diff(item)))
145168
else:
146169
raise Exception('Unknown Change')
147170

@@ -160,7 +183,7 @@ def onRefactor(self):
160183
for item in changes.changes:
161184
if isinstance(item, rope.base.change.ChangeContents):
162185
self.changes.append(
163-
Change(item.resource.real_path, ChangeType.EDIT, item.get_description()))
186+
Change(item.resource.real_path, ChangeType.EDIT, get_diff(item)))
164187
else:
165188
raise Exception('Unknown Change')
166189

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
import os
2+
3+
def one():
4+
return True
5+
6+
def two():
7+
if one():
8+
print("A" + one())
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
import os
2+
3+
def one():
4+
return True
5+
6+
def two():
7+
if one():
8+
print("A" + one())

src/test/refactor/rename.test.ts

Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
1+
// Copyright (c) Microsoft Corporation. All rights reserved.
2+
// Licensed under the MIT License.
3+
4+
'use strict';
5+
6+
import { expect } from 'chai';
7+
import { EOL } from 'os';
8+
import * as path from 'path';
9+
import * as typeMoq from 'typemoq';
10+
import { Range, TextEditorCursorStyle, TextEditorLineNumbersStyle, TextEditorOptions, window, workspace } from 'vscode';
11+
import { EXTENSION_ROOT_DIR } from '../../client/common/constants';
12+
import { BufferDecoder } from '../../client/common/process/decoder';
13+
import { ProcessService } from '../../client/common/process/proc';
14+
import { PythonExecutionFactory } from '../../client/common/process/pythonExecutionFactory';
15+
import { IProcessServiceFactory, IPythonExecutionFactory } from '../../client/common/process/types';
16+
import { IConfigurationService, IPythonSettings } from '../../client/common/types';
17+
import { IServiceContainer } from '../../client/ioc/types';
18+
import { RefactorProxy } from '../../client/refactor/proxy';
19+
import { PYTHON_PATH } from '../common';
20+
import { closeActiveWindows, initialize, initializeTest } from './../initialize';
21+
22+
type RenameResponse = {
23+
results: [{ diff: string }];
24+
};
25+
26+
suite('Refactor Rename', () => {
27+
const options: TextEditorOptions = { cursorStyle: TextEditorCursorStyle.Line, insertSpaces: true, lineNumbers: TextEditorLineNumbersStyle.Off, tabSize: 4 };
28+
let pythonSettings: typeMoq.IMock<IPythonSettings>;
29+
let serviceContainer: typeMoq.IMock<IServiceContainer>;
30+
suiteSetup(initialize);
31+
setup(async () => {
32+
pythonSettings = typeMoq.Mock.ofType<IPythonSettings>();
33+
pythonSettings.setup(p => p.pythonPath).returns(() => PYTHON_PATH);
34+
const configService = typeMoq.Mock.ofType<IConfigurationService>();
35+
configService.setup(c => c.getSettings(typeMoq.It.isAny())).returns(() => pythonSettings.object);
36+
const processServiceFactory = typeMoq.Mock.ofType<IProcessServiceFactory>();
37+
processServiceFactory.setup(p => p.create(typeMoq.It.isAny())).returns(() => Promise.resolve(new ProcessService(new BufferDecoder())));
38+
39+
serviceContainer = typeMoq.Mock.ofType<IServiceContainer>();
40+
serviceContainer.setup(s => s.get(typeMoq.It.isValue(IConfigurationService), typeMoq.It.isAny())).returns(() => configService.object);
41+
serviceContainer.setup(s => s.get(typeMoq.It.isValue(IProcessServiceFactory), typeMoq.It.isAny())).returns(() => processServiceFactory.object);
42+
serviceContainer.setup(s => s.get(typeMoq.It.isValue(IPythonExecutionFactory), typeMoq.It.isAny())).returns(() => new PythonExecutionFactory(serviceContainer.object));
43+
await initializeTest();
44+
});
45+
teardown(closeActiveWindows);
46+
suiteTeardown(closeActiveWindows);
47+
48+
test('Rename function in source without a trailing empty line', async () => {
49+
const sourceFile = path.join(EXTENSION_ROOT_DIR, 'src', 'test', 'pythonFiles', 'refactoring', 'source folder', 'without empty line.py');
50+
const expectedDiff = `--- a/${path.basename(sourceFile)}${EOL}+++ b/${path.basename(sourceFile)}${EOL}@@ -1,8 +1,8 @@${EOL} import os${EOL} ${EOL}-def one():${EOL}+def three():${EOL} return True${EOL} ${EOL} def two():${EOL}- if one():${EOL}- print(\"A\" + one())${EOL}+ if three():${EOL}+ print(\"A\" + three())${EOL}`;
51+
52+
const proxy = new RefactorProxy(EXTENSION_ROOT_DIR, pythonSettings.object, path.dirname(sourceFile), serviceContainer.object);
53+
const textDocument = await workspace.openTextDocument(sourceFile);
54+
await window.showTextDocument(textDocument);
55+
56+
const response = await proxy.rename<RenameResponse>(textDocument, 'three', sourceFile, new Range(7, 20, 7, 23), options);
57+
expect(response.results).to.be.lengthOf(1);
58+
expect(response.results[0].diff).to.be.equal(expectedDiff);
59+
});
60+
test('Rename function in source with a trailing empty line', async () => {
61+
const sourceFile = path.join(EXTENSION_ROOT_DIR, 'src', 'test', 'pythonFiles', 'refactoring', 'source folder', 'with empty line.py');
62+
const expectedDiff = `--- a/${path.basename(sourceFile)}${EOL}+++ b/${path.basename(sourceFile)}${EOL}@@ -1,8 +1,8 @@${EOL} import os${EOL} ${EOL}-def one():${EOL}+def three():${EOL} return True${EOL} ${EOL} def two():${EOL}- if one():${EOL}- print(\"A\" + one())${EOL}+ if three():${EOL}+ print(\"A\" + three())${EOL}`;
63+
64+
const proxy = new RefactorProxy(EXTENSION_ROOT_DIR, pythonSettings.object, path.dirname(sourceFile), serviceContainer.object);
65+
const textDocument = await workspace.openTextDocument(sourceFile);
66+
await window.showTextDocument(textDocument);
67+
68+
const response = await proxy.rename<RenameResponse>(textDocument, 'three', sourceFile, new Range(7, 20, 7, 23), options);
69+
expect(response.results).to.be.lengthOf(1);
70+
expect(response.results[0].diff).to.be.equal(expectedDiff);
71+
});
72+
});

0 commit comments

Comments
 (0)