Skip to content

Commit 23924e8

Browse files
committed
added canRename API call
1 parent eada3d5 commit 23924e8

File tree

10 files changed

+151
-59
lines changed

10 files changed

+151
-59
lines changed

CHANGELOG.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,10 @@
22

33
## dev branch / next version (2.x.x)
44

5+
## 2.2.0 (2022-05-03)
6+
7+
- added canRename API call
8+
59
## 2.1.4 (2022-05-02)
610

711
- fixed handling of star imports

haxelib.json

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,8 +8,8 @@
88
"refactor"
99
],
1010
"description": "A code renaming tool for Haxe",
11-
"version": "2.1.4",
12-
"releasenote": "fixed handling of star imports",
11+
"version": "2.2.0",
12+
"releasenote": "added canRename API call - see CHANGELOG",
1313
"contributors": [
1414
"AlexHaxe"
1515
],

package-lock.json

Lines changed: 2 additions & 2 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "@haxecheckstyle/haxe-rename",
3-
"version": "2.1.4",
3+
"version": "2.2.0",
44
"description": "Renaming tool for Haxe",
55
"repository": {
66
"type": "git",

src/refactor/CanRefactorContext.hx

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
package refactor;
2+
3+
import refactor.ITyper;
4+
import refactor.RefactorContext;
5+
import refactor.discover.FileList;
6+
import refactor.discover.NameMap;
7+
import refactor.discover.TypeList;
8+
9+
typedef CanRefactorContext = {
10+
var nameMap:NameMap;
11+
var fileList:FileList;
12+
var typeList:TypeList;
13+
var what:RefactorWhat;
14+
var verboseLog:VerboseLogger;
15+
var typer:Null<ITyper>;
16+
}

src/refactor/CanRefactorResult.hx

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
package refactor;
2+
3+
import refactor.discover.IdentifierPos;
4+
5+
typedef CanRefactorResult = {
6+
var name:String;
7+
var pos:IdentifierPos;
8+
}

src/refactor/Refactor.hx

Lines changed: 53 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ package refactor;
22

33
import refactor.discover.File;
44
import refactor.discover.Identifier;
5+
import refactor.discover.IdentifierPos;
56
import refactor.rename.RenameAnonStructField;
67
import refactor.rename.RenameEnumField;
78
import refactor.rename.RenameField;
@@ -12,6 +13,48 @@ import refactor.rename.RenameScopedLocal;
1213
import refactor.rename.RenameTypeName;
1314

1415
class Refactor {
16+
public static function canRename(context:CanRefactorContext):Promise<CanRefactorResult> {
17+
var file:Null<File> = context.fileList.getFile(context.what.fileName);
18+
if (file == null) {
19+
return Promise.reject(RefactorResult.NotFound.printRefactorResult());
20+
}
21+
var identifier:Identifier = file.getIdentifier(context.what.pos);
22+
if (identifier == null) {
23+
return Promise.reject(RefactorResult.NotFound.printRefactorResult());
24+
}
25+
return switch (identifier.type) {
26+
case PackageName | ImportAlias | Abstract | Class | Enum | Interface | Typedef | ModuleLevelStaticVar | ModuleLevelStaticMethod | Property |
27+
FieldVar(_) | Method(_) | TypedefField(_) | StructureField(_) | InterfaceProperty | InterfaceVar | InterfaceMethod | EnumField(_) |
28+
ScopedLocal(_, _):
29+
Promise.resolve({name: identifier.name, pos: identifier.pos});
30+
case ImportModul | UsingModul | Extends | Implements | AbstractOver | AbstractFrom | AbstractTo | TypeHint | StringConst | TypedParameter |
31+
TypedefBase | Call(true) | CaseLabel(_):
32+
Promise.reject(RefactorResult.Unsupported(identifier.toString()).printRefactorResult());
33+
case Call(false) | Access | ArrayAccess(_) | ForIterator:
34+
var candidate:Null<Identifier> = findActualWhat(context, file, identifier);
35+
if (candidate == null) {
36+
return Promise.reject(RefactorResult.Unsupported(identifier.toString()).printRefactorResult());
37+
}
38+
if (identifier.name.startsWith(candidate.name)) {
39+
var pos:IdentifierPos = {
40+
fileName: identifier.pos.fileName,
41+
start: identifier.pos.start,
42+
end: identifier.pos.start + context.what.toName.length
43+
}
44+
return Promise.resolve({name: candidate.name, pos: pos});
45+
}
46+
if (identifier.name.endsWith(candidate.name)) {
47+
var pos:IdentifierPos = {
48+
fileName: identifier.pos.fileName,
49+
start: identifier.pos.end - context.what.toName.length,
50+
end: identifier.pos.end
51+
}
52+
return Promise.resolve({name: candidate.name, pos: pos});
53+
}
54+
Promise.reject(RefactorResult.Unsupported(identifier.toString()).printRefactorResult());
55+
}
56+
}
57+
1558
public static function rename(context:RefactorContext):Promise<RefactorResult> {
1659
var file:Null<File> = context.fileList.getFile(context.what.fileName);
1760
if (file == null) {
@@ -68,7 +111,12 @@ class Refactor {
68111
Promise.reject(RefactorResult.Unsupported(identifier.toString()).printRefactorResult());
69112
case Call(false) | Access | ArrayAccess(_) | ForIterator:
70113
context.verboseLog('rename "${identifier.name}" at call/access location - trying to find definition');
71-
findActualWhat(context, file, identifier);
114+
var candidate:Null<Identifier> = findActualWhat(context, file, identifier);
115+
if (candidate == null) {
116+
return Promise.reject(RefactorResult.Unsupported(identifier.toString()).printRefactorResult());
117+
}
118+
context.what.pos = candidate.pos.start;
119+
rename(context);
72120
case CaseLabel(_):
73121
Promise.reject(RefactorResult.Unsupported(identifier.toString()).printRefactorResult());
74122
case ScopedLocal(scopeEnd, type):
@@ -77,10 +125,10 @@ class Refactor {
77125
}
78126
}
79127

80-
static function findActualWhat(context:RefactorContext, file:File, identifier:Identifier):Promise<RefactorResult> {
128+
static function findActualWhat(context:CanRefactorContext, file:File, identifier:Identifier):Null<Identifier> {
81129
var parts:Array<String> = identifier.name.split(".");
82130
if (parts.length <= 0) {
83-
return Promise.reject(RefactorResult.Unsupported(identifier.toString()).printRefactorResult());
131+
return null;
84132
}
85133
var firstPart:String = parts.shift();
86134
var onlyFields:Bool = false;
@@ -92,7 +140,7 @@ class Refactor {
92140
}
93141
if (context.what.pos > identifier.pos.start + firstPart.length + offset) {
94142
// rename position is not in first part of dotted identifiier
95-
return Promise.reject(RefactorResult.Unsupported(identifier.toString()).printRefactorResult());
143+
return null;
96144
}
97145
var allUses:Array<Identifier> = file.findAllIdentifiers((i) -> i.name == firstPart);
98146
var candidate:Null<Identifier> = null;
@@ -116,10 +164,6 @@ class Refactor {
116164
default:
117165
}
118166
}
119-
if (candidate != null) {
120-
context.what.pos = candidate.pos.start;
121-
return rename(context);
122-
}
123-
return Promise.reject(RefactorResult.Unsupported(identifier.toString()).printRefactorResult());
167+
return candidate;
124168
}
125169
}

src/refactor/RefactorContext.hx

Lines changed: 1 addition & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,21 +1,12 @@
11
package refactor;
22

33
import haxe.PosInfos;
4-
import refactor.ITyper;
5-
import refactor.discover.FileList;
6-
import refactor.discover.NameMap;
7-
import refactor.discover.TypeList;
84
import refactor.edits.IEditableDocument;
95

10-
typedef RefactorContext = {
11-
var nameMap:NameMap;
12-
var fileList:FileList;
13-
var typeList:TypeList;
6+
typedef RefactorContext = CanRefactorContext & {
147
var what:RefactorWhat;
158
var forRealExecute:Bool;
169
var docFactory:(fileName:String) -> IEditableDocument;
17-
var verboseLog:VerboseLogger;
18-
var typer:Null<ITyper>;
1910
}
2011

2112
typedef VerboseLogger = (text:String, ?pos:PosInfos) -> Void;

src/refactor/discover/UsageCollector.hx

Lines changed: 32 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -14,15 +14,8 @@ class UsageCollector {
1414
public function new() {}
1515

1616
public function parseFile(content:ByteData, context:UsageContext) {
17-
if (context.cache != null) {
18-
var file:Null<File> = context.cache.getFile(context.fileName, context.nameMap);
19-
if (file != null) {
20-
context.fileList.addFile(file);
21-
for (type in file.typeList) {
22-
context.typeList.addType(type);
23-
}
24-
return;
25-
}
17+
if (isCached(context)) {
18+
return;
2619
}
2720
var root:Null<TokenTree> = null;
2821
try {
@@ -35,6 +28,21 @@ class UsageCollector {
3528
t = lexer.token(haxeparser.HaxeLexer.tok);
3629
}
3730
root = TokenTreeBuilder.buildTokenTree(tokens, content, TypeLevel);
31+
parseFileWithTokens(root, context);
32+
} catch (e:ParserError) {
33+
throw 'failed to parse ${context.fileName} - ParserError: $e (${e.pos})';
34+
} catch (e:LexerError) {
35+
throw 'failed to parse ${context.fileName} - LexerError: ${e.msg} (${e.pos})';
36+
} catch (e:Exception) {
37+
throw 'failed to parse ${context.fileName} - ${e.details()}';
38+
}
39+
}
40+
41+
public function parseFileWithTokens(root:TokenTree, context:UsageContext) {
42+
if (isCached(context)) {
43+
return;
44+
}
45+
try {
3846
var file:File = new File(context.fileName);
3947
context.file = file;
4048
context.type = null;
@@ -45,15 +53,26 @@ class UsageCollector {
4553
if (context.cache != null) {
4654
context.cache.storeFile(file);
4755
}
48-
} catch (e:ParserError) {
49-
throw 'failed to parse ${context.fileName} - ParserError: $e (${e.pos})';
50-
} catch (e:LexerError) {
51-
throw 'failed to parse ${context.fileName} - LexerError: ${e.msg} (${e.pos})';
5256
} catch (e:Exception) {
5357
throw 'failed to parse ${context.fileName} - ${e.details()}';
5458
}
5559
}
5660

61+
function isCached(context:UsageContext):Bool {
62+
if (context.cache != null) {
63+
var file:Null<File> = context.cache.getFile(context.fileName, context.nameMap);
64+
if (file == null) {
65+
return false;
66+
}
67+
context.fileList.addFile(file);
68+
for (type in file.typeList) {
69+
context.typeList.addType(type);
70+
}
71+
return true;
72+
}
73+
return false;
74+
}
75+
5776
public function updateImportHx(context:UsageContext) {
5877
for (importHxFile in context.fileList.files) {
5978
var importHxPath:Path = new Path(importHxFile.name);

test/refactor/TestBase.hx

Lines changed: 32 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
package refactor;
22

3-
import haxe.CallStack;
43
import haxe.Exception;
54
import haxe.PosInfos;
65
import js.lib.Promise;
@@ -75,36 +74,47 @@ class TestBase implements ITest {
7574

7675
function doRefactor(what:RefactorWhat, edits:Array<TestEdit>, pos:PosInfos):Promise<RefactorResult> {
7776
var editList:TestEditList = new TestEditList();
78-
return Refactor.rename({
77+
return Refactor.canRename({
7978
nameMap: usageContext.nameMap,
8079
fileList: usageContext.fileList,
8180
typeList: usageContext.typeList,
8281
what: what,
83-
forRealExecute: true,
84-
docFactory: (fileName) -> editList.newDoc(fileName),
8582
verboseLog: function(text:String, ?pos:PosInfos) {
8683
Sys.println('${pos.fileName}:${pos.lineNumber}: $text');
8784
},
8885
typer: null
89-
}).then(function(success:RefactorResult) {
90-
editList.sortEdits();
91-
Assert.equals(Done, success, pos);
92-
Assert.equals(editList.docCounter, editList.docFinishedCounter, pos);
93-
Assert.equals(edits.length, editList.edits.length, pos);
94-
if (edits.length == editList.edits.length) {
95-
for (index in 0...edits.length) {
96-
var expected:TestEdit = edits[index];
97-
var actual:TestEdit = editList.edits[index];
98-
Assert.equals(expected.fileName, actual.fileName, expected.pos);
99-
Assert.equals(fileEditToString(expected.edit), fileEditToString(actual.edit), expected.pos);
86+
}).then(function(success:CanRefactorResult) {
87+
return Refactor.rename({
88+
nameMap: usageContext.nameMap,
89+
fileList: usageContext.fileList,
90+
typeList: usageContext.typeList,
91+
what: what,
92+
forRealExecute: true,
93+
docFactory: (fileName) -> editList.newDoc(fileName),
94+
verboseLog: function(text:String, ?pos:PosInfos) {
95+
Sys.println('${pos.fileName}:${pos.lineNumber}: $text');
96+
},
97+
typer: null
98+
}).then(function(success:RefactorResult) {
99+
editList.sortEdits();
100+
Assert.equals(Done, success, pos);
101+
Assert.equals(editList.docCounter, editList.docFinishedCounter, pos);
102+
Assert.equals(edits.length, editList.edits.length, pos);
103+
if (edits.length == editList.edits.length) {
104+
for (index in 0...edits.length) {
105+
var expected:TestEdit = edits[index];
106+
var actual:TestEdit = editList.edits[index];
107+
Assert.equals(expected.fileName, actual.fileName, expected.pos);
108+
Assert.equals(fileEditToString(expected.edit), fileEditToString(actual.edit), expected.pos);
109+
}
110+
} else {
111+
for (edit in editList.edits) {
112+
Sys.println(fileEditToString(edit.edit));
113+
}
114+
Assert.fail("length mismatch - edits were not checked", pos);
100115
}
101-
} else {
102-
for (edit in editList.edits) {
103-
Sys.println(fileEditToString(edit.edit));
104-
}
105-
Assert.fail("length mismatch - edits were not checked", pos);
106-
}
107-
return Promise.resolve(success);
116+
return Promise.resolve(success);
117+
});
108118
});
109119
}
110120

0 commit comments

Comments
 (0)