From 106f38344ede2767beee2605d6d032614c4ca705 Mon Sep 17 00:00:00 2001 From: AlexHaxe Date: Mon, 4 Nov 2024 14:58:41 +0100 Subject: [PATCH 01/59] - added invalidateFile / removeFile to allow rescan in case of file changes or deletion - added support for create and delete file operations as edits - changed Refactor class to Rename - changed getFullModulName call to fullModuleName property - fixed type name renaming not changing in use locations --- CHANGELOG.md | 8 +++++++ src/refactor/Cli.hx | 2 +- src/refactor/{Refactor.hx => Rename.hx} | 2 +- src/refactor/cache/IFileCache.hx | 2 ++ src/refactor/cache/MemCache.hx | 11 +++++++++ src/refactor/discover/File.hx | 14 +++++++++-- src/refactor/discover/FileList.hx | 15 ++++++------ src/refactor/discover/Identifier.hx | 4 +++- src/refactor/discover/NameMap.hx | 18 ++++++++++++-- src/refactor/discover/TraverseSources.hx | 2 +- src/refactor/discover/Type.hx | 10 +++++++- src/refactor/discover/TypeList.hx | 30 +++++++++++++++++------- src/refactor/discover/UsageCollector.hx | 12 ++++------ src/refactor/edits/Changelist.hx | 10 +++++++- src/refactor/edits/FileEdit.hx | 2 ++ src/refactor/rename/RenameEnumField.hx | 2 +- src/refactor/rename/RenameField.hx | 2 +- src/refactor/rename/RenameHelper.hx | 4 ++-- src/refactor/rename/RenameTypeName.hx | 7 +++--- test/refactor/TestBase.hx | 10 +++++--- test/refactor/TestEditableDocument.hx | 8 +++++++ 21 files changed, 130 insertions(+), 45 deletions(-) rename src/refactor/{Refactor.hx => Rename.hx} (99%) diff --git a/CHANGELOG.md b/CHANGELOG.md index 02a273c..76d7042 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,14 @@ ## dev branch / next version (2.x.x) +## 2.4.0 (2024-11-) + +- added invalidateFile / removeFile to allow rescan in case of file changes or deletion +- added support for create and delete file operations as edits +- changed Refactor class to Rename +- changed getFullModulName call to fullModuleName property +- fixed type name renaming not changing in use locations + ## 2.3.1 (2024-11-01) - fixed classification of parameters in arrow functions diff --git a/src/refactor/Cli.hx b/src/refactor/Cli.hx index 82f32da..80232d8 100644 --- a/src/refactor/Cli.hx +++ b/src/refactor/Cli.hx @@ -110,7 +110,7 @@ class Cli { TraverseSources.traverseSources(paths, usageContext); usageContext.usageCollector.updateImportHx(usageContext); - var result:Promise = Refactor.rename({ + var result:Promise = Rename.rename({ nameMap: usageContext.nameMap, fileList: usageContext.fileList, typeList: usageContext.typeList, diff --git a/src/refactor/Refactor.hx b/src/refactor/Rename.hx similarity index 99% rename from src/refactor/Refactor.hx rename to src/refactor/Rename.hx index 60c7e70..e6795ac 100644 --- a/src/refactor/Refactor.hx +++ b/src/refactor/Rename.hx @@ -12,7 +12,7 @@ import refactor.rename.RenamePackage; import refactor.rename.RenameScopedLocal; import refactor.rename.RenameTypeName; -class Refactor { +class Rename { public static function canRename(context:CanRefactorContext):Promise { var file:Null = context.fileList.getFile(context.what.fileName); if (file == null) { diff --git a/src/refactor/cache/IFileCache.hx b/src/refactor/cache/IFileCache.hx index d42532f..d0f16a7 100644 --- a/src/refactor/cache/IFileCache.hx +++ b/src/refactor/cache/IFileCache.hx @@ -1,10 +1,12 @@ package refactor.cache; +import refactor.discover.TypeList; import refactor.discover.NameMap; interface IFileCache { function save():Void; function clear():Void; + function invalidateFile(name:String, nameMap:NameMap, typeList:TypeList):Void; function storeFile(file:refactor.discover.File):Void; function getFile(name:String, nameMap:NameMap):Null; } diff --git a/src/refactor/cache/MemCache.hx b/src/refactor/cache/MemCache.hx index 7d8b527..b0a3578 100644 --- a/src/refactor/cache/MemCache.hx +++ b/src/refactor/cache/MemCache.hx @@ -4,6 +4,7 @@ import sys.FileStat; import sys.FileSystem; import refactor.discover.File; import refactor.discover.NameMap; +import refactor.discover.TypeList; class MemCache implements IFileCache { var files:Map; @@ -22,6 +23,16 @@ class MemCache implements IFileCache { files.set(file.name, file); } + public function invalidateFile(name:String, nameMap:NameMap, typeList:TypeList):Void { + var file:File = files.get(name); + if (file != null) { + file.clear(); + files.remove(name); + } + nameMap.removeFile(name); + typeList.removeFile(name); + } + public function getFile(name:String, nameMap:NameMap):Null { var file:Null = files.get(name); if (file == null) { diff --git a/src/refactor/discover/File.hx b/src/refactor/discover/File.hx index 232b476..c549016 100644 --- a/src/refactor/discover/File.hx +++ b/src/refactor/discover/File.hx @@ -24,13 +24,16 @@ class File { typeList = []; } - public function init(packageIdent:Null, imports:Array, types:Array, posForImport:Int) { + public function initHeader(packageIdent:Null, imports:Array, posForImport:Int) { packageIdentifier = packageIdent; importList = imports; - typeList = types; importInsertPos = posForImport; } + public function setTypes(types:Array) { + typeList = types; + } + public function getPackage():String { if (packageIdentifier != null) { return packageIdentifier.name; @@ -142,6 +145,13 @@ class File { results.sort(Identifier.sortIdentifier); return results; } + + public function clear() { + packageIdentifier = null; + importHxFile = null; + importList = []; + typeList = []; + } } typedef Import = { diff --git a/src/refactor/discover/FileList.hx b/src/refactor/discover/FileList.hx index a5f8b3e..28c43b5 100644 --- a/src/refactor/discover/FileList.hx +++ b/src/refactor/discover/FileList.hx @@ -1,20 +1,19 @@ package refactor.discover; class FileList { - public final files:Array = []; + public final files:Map = []; public function new() {} public function addFile(file:File) { - files.push(file); + files.set(file.name, file); } public function getFile(fileName:String):Null { - for (file in files) { - if (file.name == fileName) { - return file; - } - } - return null; + return files.get(fileName); + } + + public function removeFile(fileName:String) { + files.remove(fileName); } } diff --git a/src/refactor/discover/Identifier.hx b/src/refactor/discover/Identifier.hx index d1edf3a..1b5329e 100644 --- a/src/refactor/discover/Identifier.hx +++ b/src/refactor/discover/Identifier.hx @@ -36,7 +36,9 @@ class Identifier { if (uses == null) { uses = []; } - uses.push(identifier); + if (!uses.contains(identifier)) { + uses.push(identifier); + } identifier.parent = this; } diff --git a/src/refactor/discover/NameMap.hx b/src/refactor/discover/NameMap.hx index 69092a9..b41655c 100644 --- a/src/refactor/discover/NameMap.hx +++ b/src/refactor/discover/NameMap.hx @@ -1,8 +1,8 @@ package refactor.discover; class NameMap { - final names:IdentifierMap; - final parts:IdentifierMap; + var names:IdentifierMap; + var parts:IdentifierMap; public function new() { names = new IdentifierMap(); @@ -73,6 +73,20 @@ class NameMap { } return results; } + + public function removeFile(fileName:String) { + var newNames:IdentifierMap = new IdentifierMap(); + for (key => idents in names) { + newNames.set(key, idents.filter(id -> id.pos.fileName != fileName)); + } + names = newNames; + + var newParts:IdentifierMap = new IdentifierMap(); + for (key => idents in parts) { + newParts.set(key, idents.filter(id -> id.pos.fileName != fileName)); + } + parts = newParts; + } } typedef IdentifierMap = Map>; diff --git a/src/refactor/discover/TraverseSources.hx b/src/refactor/discover/TraverseSources.hx index 2cf109b..8b6d16c 100644 --- a/src/refactor/discover/TraverseSources.hx +++ b/src/refactor/discover/TraverseSources.hx @@ -24,7 +24,7 @@ class TraverseSources { } } - static function collectIdentifierData(usageContext:UsageContext) { + public static function collectIdentifierData(usageContext:UsageContext) { var content:FileContentType = usageContext.fileReader(usageContext.fileName); switch (content) { case Text(text): diff --git a/src/refactor/discover/Type.hx b/src/refactor/discover/Type.hx index d0692f0..10e0429 100644 --- a/src/refactor/discover/Type.hx +++ b/src/refactor/discover/Type.hx @@ -6,6 +6,7 @@ class Type { public var uses:Array; public var file:File; public var name:Null; + public var fullModuleName(get, null):String; public function new(file:File) { this.file = file; @@ -13,7 +14,14 @@ class Type { uses = []; } - public function getFullModulName():String { + public function get_fullModuleName():String { + if (fullModuleName == null) { + fullModuleName = makeFullModuleName(); + } + return fullModuleName; + } + + function makeFullModuleName():String { var modulName:String = '${file.getMainModulName()}.'; if (file.getMainModulName() == name.name) { modulName = ""; diff --git a/src/refactor/discover/TypeList.hx b/src/refactor/discover/TypeList.hx index 9ed806f..0f6b795 100644 --- a/src/refactor/discover/TypeList.hx +++ b/src/refactor/discover/TypeList.hx @@ -3,24 +3,36 @@ package refactor.discover; import refactor.rename.RenameHelper.TypeHintType; class TypeList implements ITypeList { - public final types:Array = []; + public final types:Map; - public function new() {} + public function new() { + types = new Map(); + } public function addType(type:Type) { - types.push(type); + types.set(type.fullModuleName, type); } public function findTypeName(name:String):Array { - return types.filter((t) -> t.name.name == name); + return Lambda.filter({iterator: types.iterator}, (t) -> t.name.name == name); } public function makeTypeHintType(name:String):Null { - for (type in types) { - if (type.getFullModulName() == name) { - return KnownType(type, []); - } - }; + if (types.exists(name)) { + return KnownType(types.get(name), []); + } return null; } + + public function removeFile(fileName:String) { + var fullNames:Array = []; + for (key => type in types) { + if (type.name.pos.fileName == fileName) { + fullNames.push(type.fullModuleName); + } + } + for (name in fullNames) { + types.remove(name); + } + } } diff --git a/src/refactor/discover/UsageCollector.hx b/src/refactor/discover/UsageCollector.hx index 0b6fc8a..6fe1ece 100644 --- a/src/refactor/discover/UsageCollector.hx +++ b/src/refactor/discover/UsageCollector.hx @@ -48,7 +48,8 @@ class UsageCollector { context.type = null; var packageName:Null = readPackageName(root, context); var imports:Array = readImports(root, context); - file.init(packageName, imports, readTypes(root, context), findImportInsertPos(root)); + file.initHeader(packageName, imports, findImportInsertPos(root)); + file.setTypes(readTypes(root, context)); context.fileList.addFile(file); if (context.cache != null) { context.cache.storeFile(file); @@ -61,14 +62,9 @@ class UsageCollector { function isCached(context:UsageContext):Bool { if (context.cache != null) { var file:Null = context.cache.getFile(context.fileName, context.nameMap); - if (file == null) { - return false; + if (file != null) { + return true; } - context.fileList.addFile(file); - for (type in file.typeList) { - context.typeList.addType(type); - } - return true; } return false; } diff --git a/src/refactor/edits/Changelist.hx b/src/refactor/edits/Changelist.hx index 4bfe5fe..f651ec4 100644 --- a/src/refactor/edits/Changelist.hx +++ b/src/refactor/edits/Changelist.hx @@ -57,8 +57,12 @@ class Changelist { Sys.println('$file'); for (edit in edits) { switch (edit) { + case CreateFile(newFileName): + Sys.println('* create file "$newFileName"'); + case DeleteFile(fileName): + Sys.println('* delete file "$fileName"'); case Move(newFileName): - Sys.println('* rename to "$newFileName"'); + Sys.println('* rename to file "$newFileName"'); case InsertText(text, pos): Sys.println('* insert text "$text" @${pos.start}-${pos.end}'); Sys.println('+++ $text'); @@ -76,12 +80,16 @@ class Changelist { function sortFileEdits(a:FileEdit, b:FileEdit):Int { var offsetA:Int = switch (a) { + case CreateFile(_): 0; + case DeleteFile(_): 9999; case Move(_): 0; case InsertText(_, pos): pos.start; case ReplaceText(_, pos): pos.start; case RemoveText(pos): pos.start; }; var offsetB:Int = switch (b) { + case CreateFile(_): 0; + case DeleteFile(_): 9999; case Move(_): 0; case InsertText(_, pos): pos.start; case ReplaceText(_, pos): pos.start; diff --git a/src/refactor/edits/FileEdit.hx b/src/refactor/edits/FileEdit.hx index bece38c..cfc362b 100644 --- a/src/refactor/edits/FileEdit.hx +++ b/src/refactor/edits/FileEdit.hx @@ -3,6 +3,8 @@ package refactor.edits; import refactor.discover.IdentifierPos; enum FileEdit { + CreateFile(newFileName:String); + DeleteFile(fileName:String); Move(newFileName:String); ReplaceText(text:String, pos:IdentifierPos); InsertText(text:String, pos:IdentifierPos); diff --git a/src/refactor/rename/RenameEnumField.hx b/src/refactor/rename/RenameEnumField.hx index 98c4b02..03ff9c9 100644 --- a/src/refactor/rename/RenameEnumField.hx +++ b/src/refactor/rename/RenameEnumField.hx @@ -14,7 +14,7 @@ class RenameEnumField { var packName:String = file.getPackage(); var mainModuleName:String = file.getMainModulName(); var typeName:String = identifier.defineType.name.name; - var fullModuleTypeName:String = identifier.defineType.getFullModulName(); + var fullModuleTypeName:String = identifier.defineType.fullModuleName; var allUses:Array = context.nameMap.getIdentifiers('$typeName.${identifier.name}'); for (use in allUses) { switch (use.file.importsModule(packName, mainModuleName, typeName)) { diff --git a/src/refactor/rename/RenameField.hx b/src/refactor/rename/RenameField.hx index 8ef6c1f..653891a 100644 --- a/src/refactor/rename/RenameField.hx +++ b/src/refactor/rename/RenameField.hx @@ -130,7 +130,7 @@ class RenameField { RenameHelper.replaceTextWithPrefix(use, '${type.name.name}.', context.what.toName, changelist); } - var fullModuleName:String = type.getFullModulName(); + var fullModuleName:String = type.fullModuleName; var allUses:Array = context.nameMap.getIdentifiers('$fullModuleName.$fromName'); for (use in allUses) { RenameHelper.replaceTextWithPrefix(use, '$fullModuleName.', context.what.toName, changelist); diff --git a/src/refactor/rename/RenameHelper.hx b/src/refactor/rename/RenameHelper.hx index abe4eb8..c576734 100644 --- a/src/refactor/rename/RenameHelper.hx +++ b/src/refactor/rename/RenameHelper.hx @@ -105,7 +105,7 @@ class RenameHelper { } return true; case [KnownType(type1, params1), KnownType(type2, params2)]: - if (type1.getFullModulName() != type2.getFullModulName()) { + if (type1.fullModuleName != type2.fullModuleName) { return false; } if (params1.length != params2.length) { @@ -352,7 +352,7 @@ class RenameHelper { var allTypes:Array = context.typeList.findTypeName(typeName); if (parts.length > 0) { for (type in allTypes) { - if (type.getFullModulName() == hint.name) { + if (type.fullModuleName == hint.name) { return Promise.resolve(KnownType(type, typeParams)); } } diff --git a/src/refactor/rename/RenameTypeName.hx b/src/refactor/rename/RenameTypeName.hx index 3811628..3ce781a 100644 --- a/src/refactor/rename/RenameTypeName.hx +++ b/src/refactor/rename/RenameTypeName.hx @@ -26,7 +26,7 @@ class RenameTypeName { var allUses:Array; // find all fully qualified modul names of type if (file.packageIdentifier != null) { - var fullName:String = identifier.defineType.getFullModulName(); + var fullName:String = identifier.defineType.fullModuleName; var parts:Array = fullName.split("."); parts.pop(); var prefix:String = parts.join(".") + "."; @@ -53,15 +53,16 @@ class RenameTypeName { } case Global | SamePackage | Imported | StarImported: } + final searchName:String = if (use.name.startsWith(identifier.name)) identifier.name; else identifier.defineType.fullModuleName; changes.push(RenameHelper.findTypeOfIdentifier(context, { - name: use.name, + name: searchName, pos: use.pos.start, defineType: use.defineType }).then(function(typeHint:TypeHintType) { switch (typeHint) { case null: case KnownType(type, _): - if (type != identifier.defineType) { + if (type.fullModuleName != identifier.defineType.fullModuleName) { return; } case UnknownType(_): diff --git a/test/refactor/TestBase.hx b/test/refactor/TestBase.hx index aaf48eb..58a876c 100644 --- a/test/refactor/TestBase.hx +++ b/test/refactor/TestBase.hx @@ -4,9 +4,9 @@ import haxe.Exception; import haxe.PosInfos; import js.lib.Promise; import utest.Async; -import refactor.Refactor; import refactor.RefactorResult; import refactor.RefactorWhat; +import refactor.Rename; import refactor.TestEditableDocument; import refactor.discover.FileList; import refactor.discover.NameMap; @@ -89,7 +89,7 @@ class TestBase implements ITest { function doCanRefactor(what:RefactorWhat, edits:Array, pos:PosInfos):Promise { var editList:TestEditList = new TestEditList(); - return Refactor.canRename({ + return Rename.canRename({ nameMap: usageContext.nameMap, fileList: usageContext.fileList, typeList: usageContext.typeList, @@ -105,7 +105,7 @@ class TestBase implements ITest { function doRefactor(what:RefactorWhat, edits:Array, pos:PosInfos):Promise { var editList:TestEditList = new TestEditList(); - return Refactor.rename({ + return Rename.rename({ nameMap: usageContext.nameMap, fileList: usageContext.fileList, typeList: usageContext.typeList, @@ -140,6 +140,10 @@ class TestBase implements ITest { function fileEditToString(edit:FileEdit):String { return switch (edit) { + case CreateFile(newFileName): + 'Create $newFileName'; + case DeleteFile(fileName): + 'Delete $fileName'; case Move(newFileName): 'Move $newFileName'; case ReplaceText(text, pos): diff --git a/test/refactor/TestEditableDocument.hx b/test/refactor/TestEditableDocument.hx index f002e96..0c6ab88 100644 --- a/test/refactor/TestEditableDocument.hx +++ b/test/refactor/TestEditableDocument.hx @@ -16,6 +16,10 @@ class TestEditableDocument implements IEditableDocument { public function addChange(edit:FileEdit) { switch (edit) { + case CreateFile(newFileName): + Assert.notEquals(fileName, newFileName); + case DeleteFile(oldFileName): + Assert.notEquals(fileName, oldFileName); case Move(newFileName): Assert.notEquals(fileName, newFileName); case ReplaceText(_, pos): @@ -71,12 +75,16 @@ class TestEditList { return -1; } var offsetA:Int = switch (a.edit) { + case CreateFile(_): 0; + case DeleteFile(_): 9999; case Move(_): 0; case InsertText(_, pos): pos.start; case ReplaceText(_, pos): pos.start; case RemoveText(pos): pos.start; }; var offsetB:Int = switch (b.edit) { + case CreateFile(_): 0; + case DeleteFile(_): 9999; case Move(_): 0; case InsertText(_, pos): pos.start; case ReplaceText(_, pos): pos.start; From 811d14596e7d287d371b056ab0267b9a5d5117f4 Mon Sep 17 00:00:00 2001 From: AlexHaxe Date: Thu, 7 Nov 2024 22:26:35 +0100 Subject: [PATCH 02/59] added ExtractType and ExtractInterface refactor modules fixed discovery of arrow functions as type hints reorganised refactor and rename into different packages added unittests for refactor modules added clear method to FileList, MemCache, NameMap and TypeList --- CHANGELOG.md | 3 + src/refactor/Cli.hx | 6 +- src/refactor/RefactorContext.hx | 12 - src/refactor/Refactoring.hx | 30 ++ src/refactor/Rename.hx | 9 +- src/refactor/VerboseLogger.hx | 5 + src/refactor/cache/MemCache.hx | 4 +- src/refactor/discover/FileContentType.hx | 2 +- src/refactor/discover/FileList.hx | 4 + src/refactor/discover/NameMap.hx | 5 + src/refactor/discover/TraverseSources.hx | 2 +- src/refactor/discover/TypeList.hx | 4 + src/refactor/discover/UsageCollector.hx | 3 + src/refactor/edits/Changelist.hx | 6 +- src/refactor/edits/EditContext.hx | 6 + src/refactor/edits/EditableDocument.hx | 4 + src/refactor/refactor/CanRefactorContext.hx | 20 ++ src/refactor/refactor/CanRefactorResult.hx | 6 + src/refactor/refactor/ExtractInterface.hx | 315 ++++++++++++++++++ src/refactor/refactor/ExtractType.hx | 216 ++++++++++++ src/refactor/refactor/RefactorContext.hx | 5 + src/refactor/refactor/RefactorHelper.hx | 124 +++++++ src/refactor/refactor/RefactorType.hx | 6 + src/refactor/refactor/RefactorWhat.hx | 7 + .../CanRenameContext.hx} | 8 +- .../CanRenameResult.hx} | 4 +- src/refactor/rename/RenameAnonStructField.hx | 17 +- src/refactor/rename/RenameContext.hx | 5 + src/refactor/rename/RenameEnumField.hx | 4 +- src/refactor/rename/RenameField.hx | 8 +- src/refactor/rename/RenameHelper.hx | 24 +- src/refactor/rename/RenameImportAlias.hx | 4 +- .../rename/RenameModuleLevelStatic.hx | 6 +- src/refactor/rename/RenamePackage.hx | 6 +- src/refactor/rename/RenameScopedLocal.hx | 5 +- src/refactor/rename/RenameTypeName.hx | 4 +- .../{RefactorWhat.hx => rename/RenameWhat.hx} | 4 +- test/TestMain.hx | 36 +- test/refactor/TestBase.hx | 132 ++------ test/refactor/TestEditableDocument.hx | 4 +- test/refactor/refactor/RefactorClassTest.hx | 54 +++ test/refactor/refactor/RefactorTestBase.hx | 124 +++++++ test/refactor/refactor/RefactorTypedefTest.hx | 36 ++ .../RenameClassTest.hx} | 102 +++--- .../{EnumTest.hx => rename/RenameEnumTest.hx} | 12 +- .../RenameImportAliasTest.hx} | 8 +- .../RenameInterfaceTest.hx} | 18 +- .../RenameModuleLevelStaticTest.hx} | 8 +- .../RenamePackageTest.hx} | 14 +- .../RenameScopedLocalTest.hx} | 40 +-- test/refactor/rename/RenameTestBase.hx | 85 +++++ .../RenameTypedefTest.hx} | 22 +- testcases/classes/Printer.hx | 7 +- testcases/classes/import.hx | 2 +- testcases/classes/pack/UsePrinter.hx | 4 +- 55 files changed, 1306 insertions(+), 305 deletions(-) delete mode 100644 src/refactor/RefactorContext.hx create mode 100644 src/refactor/Refactoring.hx create mode 100644 src/refactor/VerboseLogger.hx create mode 100644 src/refactor/edits/EditContext.hx create mode 100644 src/refactor/refactor/CanRefactorContext.hx create mode 100644 src/refactor/refactor/CanRefactorResult.hx create mode 100644 src/refactor/refactor/ExtractInterface.hx create mode 100644 src/refactor/refactor/ExtractType.hx create mode 100644 src/refactor/refactor/RefactorContext.hx create mode 100644 src/refactor/refactor/RefactorHelper.hx create mode 100644 src/refactor/refactor/RefactorType.hx create mode 100644 src/refactor/refactor/RefactorWhat.hx rename src/refactor/{CanRefactorContext.hx => rename/CanRenameContext.hx} (70%) rename src/refactor/{CanRefactorResult.hx => rename/CanRenameResult.hx} (61%) create mode 100644 src/refactor/rename/RenameContext.hx rename src/refactor/{RefactorWhat.hx => rename/RenameWhat.hx} (55%) create mode 100644 test/refactor/refactor/RefactorClassTest.hx create mode 100644 test/refactor/refactor/RefactorTestBase.hx create mode 100644 test/refactor/refactor/RefactorTypedefTest.hx rename test/refactor/{ClassTest.hx => rename/RenameClassTest.hx} (74%) rename test/refactor/{EnumTest.hx => rename/RenameEnumTest.hx} (80%) rename test/refactor/{ImportAliasTest.hx => rename/RenameImportAliasTest.hx} (69%) rename test/refactor/{InterfaceTest.hx => rename/RenameInterfaceTest.hx} (82%) rename test/refactor/{ModuleLevelStaticTest.hx => rename/RenameModuleLevelStaticTest.hx} (78%) rename test/refactor/{PackageTest.hx => rename/RenamePackageTest.hx} (79%) rename test/refactor/{ScopedLocalTest.hx => rename/RenameScopedLocalTest.hx} (77%) create mode 100644 test/refactor/rename/RenameTestBase.hx rename test/refactor/{TypedefTest.hx => rename/RenameTypedefTest.hx} (78%) diff --git a/CHANGELOG.md b/CHANGELOG.md index 76d7042..330bb38 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,9 +6,12 @@ - added invalidateFile / removeFile to allow rescan in case of file changes or deletion - added support for create and delete file operations as edits +- added ExtractType refactor module +- added ExtractInterface refactor module - changed Refactor class to Rename - changed getFullModulName call to fullModuleName property - fixed type name renaming not changing in use locations +- fixed discovery of arrow functions as type hints ## 2.3.1 (2024-11-01) diff --git a/src/refactor/Cli.hx b/src/refactor/Cli.hx index 80232d8..ff77d07 100644 --- a/src/refactor/Cli.hx +++ b/src/refactor/Cli.hx @@ -2,7 +2,6 @@ package refactor; import haxe.PosInfos; import haxe.Timer; -import refactor.RefactorContext.VerboseLogger; import refactor.discover.FileList; import refactor.discover.NameMap; import refactor.discover.TraverseSources; @@ -10,6 +9,7 @@ import refactor.discover.TypeList; import refactor.discover.UsageCollector; import refactor.discover.UsageContext; import refactor.edits.EditableDocument; +import refactor.rename.RenameWhat; class Cli { var verbose:Bool = false; @@ -88,7 +88,7 @@ class Cli { printHelp(); Sys.exit(0); } - var what:Null = makeWhat(loc, toName); + var what:Null = makeWhat(loc, toName); if (what == null) { printHelp(); Sys.exit(1); @@ -145,7 +145,7 @@ class Cli { Sys.println(text); } - function makeWhat(location:String, toName:String):Null { + function makeWhat(location:String, toName:String):Null { var parts:Array = location.split("@"); if (parts.length != 2) { return null; diff --git a/src/refactor/RefactorContext.hx b/src/refactor/RefactorContext.hx deleted file mode 100644 index b312159..0000000 --- a/src/refactor/RefactorContext.hx +++ /dev/null @@ -1,12 +0,0 @@ -package refactor; - -import haxe.PosInfos; -import refactor.edits.IEditableDocument; - -typedef RefactorContext = CanRefactorContext & { - var what:RefactorWhat; - var forRealExecute:Bool; - var docFactory:(fileName:String) -> IEditableDocument; -} - -typedef VerboseLogger = (text:String, ?pos:PosInfos) -> Void; diff --git a/src/refactor/Refactoring.hx b/src/refactor/Refactoring.hx new file mode 100644 index 0000000..3dea300 --- /dev/null +++ b/src/refactor/Refactoring.hx @@ -0,0 +1,30 @@ +package refactor; + +import refactor.refactor.CanRefactorContext; +import refactor.refactor.CanRefactorResult; +import refactor.refactor.ExtractInterface; +import refactor.refactor.ExtractType; +import refactor.refactor.RefactorContext; +import refactor.refactor.RefactorType; + +class Refactoring { + public static function canRefactor(refactorType:RefactorType, context:CanRefactorContext):CanRefactorResult { + switch (refactorType) { + case RefactorExtractType: + return ExtractType.canRefactor(context); + case RefactorExtractInterface: + return ExtractInterface.canRefactor(context); + } + return null; + } + + public static function doRefactor(refactorType:RefactorType, context:RefactorContext):Promise { + switch (refactorType) { + case RefactorExtractType: + return ExtractType.doRefactor(context); + case RefactorExtractInterface: + return ExtractInterface.doRefactor(context); + } + return Promise.resolve(RefactorResult.Unsupported("no refactor type selected")); + } +} diff --git a/src/refactor/Rename.hx b/src/refactor/Rename.hx index e6795ac..8b69126 100644 --- a/src/refactor/Rename.hx +++ b/src/refactor/Rename.hx @@ -3,7 +3,10 @@ package refactor; import refactor.discover.File; import refactor.discover.Identifier; import refactor.discover.IdentifierPos; +import refactor.rename.CanRenameContext; +import refactor.rename.CanRenameResult; import refactor.rename.RenameAnonStructField; +import refactor.rename.RenameContext; import refactor.rename.RenameEnumField; import refactor.rename.RenameField; import refactor.rename.RenameImportAlias; @@ -13,7 +16,7 @@ import refactor.rename.RenameScopedLocal; import refactor.rename.RenameTypeName; class Rename { - public static function canRename(context:CanRefactorContext):Promise { + public static function canRename(context:CanRenameContext):Promise { var file:Null = context.fileList.getFile(context.what.fileName); if (file == null) { return Promise.reject(RefactorResult.NotFound.printRefactorResult()); @@ -55,7 +58,7 @@ class Rename { } } - public static function rename(context:RefactorContext):Promise { + public static function rename(context:RenameContext):Promise { var file:Null = context.fileList.getFile(context.what.fileName); if (file == null) { return Promise.reject(RefactorResult.NotFound.printRefactorResult()); @@ -125,7 +128,7 @@ class Rename { } } - static function findActualWhat(context:CanRefactorContext, file:File, identifier:Identifier):Null { + static function findActualWhat(context:CanRenameContext, file:File, identifier:Identifier):Null { var parts:Array = identifier.name.split("."); if (parts.length <= 0) { return null; diff --git a/src/refactor/VerboseLogger.hx b/src/refactor/VerboseLogger.hx new file mode 100644 index 0000000..644059a --- /dev/null +++ b/src/refactor/VerboseLogger.hx @@ -0,0 +1,5 @@ +package refactor; + +import haxe.PosInfos; + +typedef VerboseLogger = (text:String, ?pos:PosInfos) -> Void; diff --git a/src/refactor/cache/MemCache.hx b/src/refactor/cache/MemCache.hx index b0a3578..fc53799 100644 --- a/src/refactor/cache/MemCache.hx +++ b/src/refactor/cache/MemCache.hx @@ -10,13 +10,13 @@ class MemCache implements IFileCache { var files:Map; public function new() { - clear(); + files = new Map(); } public function save() {} public function clear() { - files = new Map(); + files.clear(); } public function storeFile(file:File) { diff --git a/src/refactor/discover/FileContentType.hx b/src/refactor/discover/FileContentType.hx index c4f8c72..a1cd6e1 100644 --- a/src/refactor/discover/FileContentType.hx +++ b/src/refactor/discover/FileContentType.hx @@ -2,5 +2,5 @@ package refactor.discover; enum FileContentType { Text(text:String); - Token(root:TokenTree); + Token(root:TokenTree, text:String); } diff --git a/src/refactor/discover/FileList.hx b/src/refactor/discover/FileList.hx index 28c43b5..2e0340c 100644 --- a/src/refactor/discover/FileList.hx +++ b/src/refactor/discover/FileList.hx @@ -16,4 +16,8 @@ class FileList { public function removeFile(fileName:String) { files.remove(fileName); } + + public function clear() { + files.clear(); + } } diff --git a/src/refactor/discover/NameMap.hx b/src/refactor/discover/NameMap.hx index b41655c..8f6d1c2 100644 --- a/src/refactor/discover/NameMap.hx +++ b/src/refactor/discover/NameMap.hx @@ -87,6 +87,11 @@ class NameMap { } parts = newParts; } + + public function clear() { + names.clear(); + parts.clear(); + } } typedef IdentifierMap = Map>; diff --git a/src/refactor/discover/TraverseSources.hx b/src/refactor/discover/TraverseSources.hx index 8b6d16c..c95443b 100644 --- a/src/refactor/discover/TraverseSources.hx +++ b/src/refactor/discover/TraverseSources.hx @@ -29,7 +29,7 @@ class TraverseSources { switch (content) { case Text(text): usageContext.usageCollector.parseFile(ByteData.ofString(text), usageContext); - case Token(root): + case Token(root, _): usageContext.usageCollector.parseFileWithTokens(root, usageContext); } } diff --git a/src/refactor/discover/TypeList.hx b/src/refactor/discover/TypeList.hx index 0f6b795..682a75a 100644 --- a/src/refactor/discover/TypeList.hx +++ b/src/refactor/discover/TypeList.hx @@ -35,4 +35,8 @@ class TypeList implements ITypeList { types.remove(name); } } + + public function clear() { + types.clear(); + } } diff --git a/src/refactor/discover/UsageCollector.hx b/src/refactor/discover/UsageCollector.hx index 6fe1ece..e546af7 100644 --- a/src/refactor/discover/UsageCollector.hx +++ b/src/refactor/discover/UsageCollector.hx @@ -1028,6 +1028,9 @@ class UsageCollector { case BrOpen: readAnonStructure(context, identifier, child); break; + case POpen: + readExpression(context, identifier, child); + break; default: } } diff --git a/src/refactor/edits/Changelist.hx b/src/refactor/edits/Changelist.hx index f651ec4..b6d29fd 100644 --- a/src/refactor/edits/Changelist.hx +++ b/src/refactor/edits/Changelist.hx @@ -2,16 +2,16 @@ package refactor.edits; import haxe.io.Bytes; import sys.io.File; -import refactor.RefactorContext; import refactor.RefactorResult; import refactor.discover.Identifier; import refactor.discover.IdentifierPos; +import refactor.rename.RenameContext; class Changelist { var changes:Map>; - var context:RefactorContext; + var context:EditContext; - public function new(context:RefactorContext) { + public function new(context:EditContext) { changes = new Map>(); this.context = context; } diff --git a/src/refactor/edits/EditContext.hx b/src/refactor/edits/EditContext.hx new file mode 100644 index 0000000..3906911 --- /dev/null +++ b/src/refactor/edits/EditContext.hx @@ -0,0 +1,6 @@ +package refactor.edits; + +typedef EditContext = { + var forRealExecute:Bool; + var docFactory:(fileName:String) -> IEditableDocument; +} diff --git a/src/refactor/edits/EditableDocument.hx b/src/refactor/edits/EditableDocument.hx index 157328e..bdbd9a3 100644 --- a/src/refactor/edits/EditableDocument.hx +++ b/src/refactor/edits/EditableDocument.hx @@ -22,6 +22,10 @@ class EditableDocument implements IEditableDocument { public function addChange(edit:FileEdit) { switch (edit) { + case CreateFile(newFileName): + fileName = newFileName; + case DeleteFile(oldFileName): + fileName = oldFileName; case Move(newFileName): fileName = newFileName; case ReplaceText(text, pos): diff --git a/src/refactor/refactor/CanRefactorContext.hx b/src/refactor/refactor/CanRefactorContext.hx new file mode 100644 index 0000000..0cf8a45 --- /dev/null +++ b/src/refactor/refactor/CanRefactorContext.hx @@ -0,0 +1,20 @@ +package refactor.refactor; + +import refactor.ITyper; +import refactor.discover.FileList; +import refactor.discover.FileReaderFunc; +import refactor.discover.NameMap; +import refactor.discover.TypeList; + +typedef CanRefactorContext = { + var nameMap:NameMap; + var fileList:FileList; + var typeList:TypeList; + var what:RefactorWhat; + var verboseLog:VerboseLogger; + var typer:Null; + var fileReader:FileReaderFunc; + var converter:ByteToCharConverterFunc; +} + +typedef ByteToCharConverterFunc = (string:String, byteOffset:Int) -> Int; diff --git a/src/refactor/refactor/CanRefactorResult.hx b/src/refactor/refactor/CanRefactorResult.hx new file mode 100644 index 0000000..405b0fd --- /dev/null +++ b/src/refactor/refactor/CanRefactorResult.hx @@ -0,0 +1,6 @@ +package refactor.refactor; + +enum CanRefactorResult { + Unsupported; + Supported(title:String); +} diff --git a/src/refactor/refactor/ExtractInterface.hx b/src/refactor/refactor/ExtractInterface.hx new file mode 100644 index 0000000..02156f8 --- /dev/null +++ b/src/refactor/refactor/ExtractInterface.hx @@ -0,0 +1,315 @@ +package refactor.refactor; + +import haxe.io.Path; +import sys.FileSystem; +import refactor.discover.File; +import refactor.discover.Identifier; +import refactor.discover.Type; +import refactor.edits.Changelist; + +class ExtractInterface { + public static function canRefactor(context:CanRefactorContext):CanRefactorResult { + final extractData = makeExtractInterfaceData(context); + if (extractData == null) { + return Unsupported; + } + return Supported('Extract Interface from "${extractData.srcType.name.name}" to ${Path.withoutDirectory(extractData.newFileName)}'); + } + + public static function doRefactor(context:RefactorContext):Promise { + final extractData = makeExtractInterfaceData(context); + if (extractData == null) { + return Promise.resolve(RefactorResult.NotFound); + } + + final changelist:Changelist = new Changelist(context); + + // create new file + changelist.addChange(extractData.newFileName, CreateFile(extractData.newFileName), null); + + // collect all public fields + var fields:Array = findPublicFields(extractData, context); + + // copy header + imports + final fileHeader:String = makeHeader(extractData, context, findImportCandidates(extractData, context, fields)); + // create interface + fields + final fieldDefinition:String = makeFields(extractData, context, fields); + final interfaceText:String = 'interface ${extractData.newTypeName} {\n' + fieldDefinition + "}"; + + changelist.addChange(extractData.newFileName, InsertText(fileHeader + interfaceText, {fileName: extractData.newFileName, start: 0, end: 0}), null); + + final implementsText:String = ' implements ${extractData.newTypeName}'; + final pos:Position = findImplementsPos(extractData); + changelist.addChange(extractData.srcFile.name, InsertText(implementsText, {fileName: extractData.srcFile.name, start: pos.max, end: pos.max}), null); + + return Promise.resolve(changelist.execute()); + } + + static function makeExtractInterfaceData(context:CanRefactorContext):Null { + final fileContent = context.fileReader(context.what.fileName); + var content:String; + var root:TokenTree; + switch (fileContent) { + case Text(_): + return null; + case Token(tokens, text): + content = text; + root = tokens; + } + if (root == null) { + return null; + } + if (content == null) { + return null; + } + + final token:Null = RefactorHelper.findTokenAtPos(root, context.what.posStart); + if (token == null) { + return null; + } + final firstImport:Null = RefactorHelper.findFirstImport(root); + + final classToken:Null = token.access().parent().matches(Kwd(KwdClass)).token; + if (classToken == null) { + return null; + } + + final path = new Path(context.what.fileName); + + final nameToken:Null = classToken.getFirstChild(); + if (nameToken == null) { + return null; + } + final srcName:String = nameToken.toString(); + final newName:String = "I" + srcName; + + final file:Null = context.fileList.getFile(context.what.fileName); + if (file == null) { + return null; + } + final type:Null = file.getType(srcName); + if (type == null) { + return null; + } + + final newFileName:String = Path.join([path.dir, newName + ".hx"]); + if (FileSystem.exists(newFileName)) { + return null; + } + + return { + content: content, + root: root, + firstImport: firstImport, + classToken: classToken, + newTypeName: newName, + newFileName: newFileName, + srcFile: file, + srcType: type, + }; + } + + static function findImplementsPos(extractData:ExtractInterfaceData):Position { + final className:Null = extractData.classToken.access().firstChild().token; + final pos:Position = {file: className.pos.file, min: className.pos.max, max: className.pos.max}; + if (!className.hasChildren()) { + return pos; + } + for (child in className.children) { + switch (child.tok) { + case BrOpen: + break; + default: + expandPos(pos, child.getPos()); + } + } + pos.min = pos.max; + return pos; + } + + static function findPublicFields(extractData:ExtractInterfaceData, context:RefactorContext):Array { + final brOpen:Null = extractData.classToken.access().firstChild().firstOf(BrOpen).token; + final fields:Array = []; + + findAllFields(brOpen, fields); + return fields; + } + + static function expandPos(pos:Position, newPos:Position) { + if (newPos.min < pos.min) { + pos.min = newPos.min; + } + if (newPos.max > pos.max) { + pos.max = newPos.max; + } + } + + static function addField(parent:TokenTree, fields:Array) { + if (!parent.hasChildren()) { + return; + } + var nameToken:TokenTree = parent.getFirstChild(); + if (nameToken == null || !nameToken.hasChildren()) { + return; + } + if (nameToken.matches(Kwd(KwdNew))) { + return; + } + + var pos:Position = {file: parent.pos.file, min: parent.pos.min, max: parent.pos.max}; + expandPos(pos, nameToken.pos); + var isPublic:Bool = false; + var hasHint:Bool = false; + + for (child in nameToken.children) { + switch (child.tok) { + case Kwd(KwdPrivate) | Kwd(KwdStatic): + return; + case Kwd(KwdPublic): + isPublic = true; + case Kwd(KwdDynamic) | Kwd(KwdOverride) | Kwd(KwdExtern) | Kwd(KwdInline): + expandPos(pos, child.pos); + case POpen: + expandPos(pos, child.getPos()); + case DblDot: + hasHint = true; + expandPos(pos, child.getPos()); + case BrOpen | Binop(OpAssign): + break; + default: + return; + } + } + if (!isPublic) { + return; + } + var data:FieldData = { + pos: pos, + hasHint: hasHint, + isSharp: false + } + fields.push(data); + } + + static function findAllFields(parent:TokenTree, fields:Array) { + if (parent == null) { + return; + } + if (!parent.hasChildren()) { + return; + } + for (child in parent.children) { + switch (child.tok) { + case Kwd(KwdVar) | Kwd(KwdFinal) | Kwd(KwdFunction): + addField(child, fields); + case Sharp("if") | Sharp("elseif"): + if (!child.hasChildren()) { + continue; + } + var pos:Position = {file: child.pos.file, min: child.pos.min, max: child.pos.max}; + expandPos(pos, child.getFirstChild().getPos()); + fields.push({ + pos: pos, + hasHint: true, + isSharp: true + }); + findAllFields(child, fields); + case Sharp("else"): + var pos:Position = {file: child.pos.file, min: child.pos.min, max: child.pos.max}; + fields.push({ + pos: pos, + hasHint: true, + isSharp: true + }); + findAllFields(child, fields); + case Sharp("end"): + var pos:Position = {file: child.pos.file, min: child.pos.min, max: child.pos.max}; + fields.push({ + pos: pos, + hasHint: true, + isSharp: true + }); + default: + } + } + } + + static function makeHeader(extractData:ExtractInterfaceData, context:RefactorContext, allUses:Array):String { + final imports = RefactorHelper.makeNewFileImports(context, extractData.srcFile, allUses); + + if (extractData.firstImport == null) { + final lastHeader = RefactorHelper.getLastHeaderToken(extractData.root); + if (extractData.firstImport == null) { + return ""; + } + final pos = lastHeader.getPos(); + return RefactorHelper.extractText(context.converter, extractData.content, 0, pos.min - 1) + "\n"; + } + + final pos = extractData.firstImport.getPos(); + return RefactorHelper.extractText(context.converter, extractData.content, 0, pos.min - 1) + "\n" + imports; + } + + static function findImportCandidates(extractData:ExtractInterfaceData, context:RefactorContext, fields:Array):Array { + var names:Array = []; + for (field in fields) { + function rangeMatcher(identifier:Identifier):Bool { + if (identifier.pos.start > field.pos.max) { + return false; + } + if (identifier.pos.end < field.pos.min) { + return false; + } + return true; + } + var allUses:Array = extractData.srcType.findAllIdentifiers(rangeMatcher); + names = names.concat(allUses.map(use -> { + final parts = use.name.split("."); + return parts.shift(); + })); + } + return names; + } + + static function makeFields(extractData:ExtractInterfaceData, context:RefactorContext, fields:Array):String { + final buf:StringBuf = new StringBuf(); + final defaultHint:String = ":Void"; + + for (field in fields) { + buf.add("\t"); + var text:String = RefactorHelper.extractText(context.converter, extractData.content, field.pos.min, field.pos.max); + text = text.replace(" public ", " "); + buf.add(text); + if (field.isSharp) { + buf.add("\n"); + continue; + } + if (!field.hasHint) { + buf.add(defaultHint); + } + if (!text.endsWith(";")) { + buf.add(";"); + } + buf.add("\n"); + } + + return buf.toString(); + } +} + +typedef ExtractInterfaceData = { + var content:String; + var root:TokenTree; + var firstImport:Null; + var classToken:TokenTree; + var newTypeName:String; + var newFileName:String; + var srcFile:File; + var srcType:Type; +} + +typedef FieldData = { + var pos:Position; + var hasHint:Bool; + var isSharp:Bool; +} diff --git a/src/refactor/refactor/ExtractType.hx b/src/refactor/refactor/ExtractType.hx new file mode 100644 index 0000000..f772c57 --- /dev/null +++ b/src/refactor/refactor/ExtractType.hx @@ -0,0 +1,216 @@ +package refactor.refactor; + +import haxe.io.Path; +import js.lib.Promise; +import sys.FileSystem; +import tokentree.TokenTree; +import tokentree.utils.TokenTreeCheckUtils; +import refactor.discover.File; +import refactor.discover.Identifier; +import refactor.discover.IdentifierPos; +import refactor.discover.Type; +import refactor.edits.Changelist; + +class ExtractType { + public static function canRefactor(context:CanRefactorContext):CanRefactorResult { + final extractData = makeExtractTypeData(context); + if (extractData == null) { + return Unsupported; + } + return Supported('Extract "${extractData.name}" to ${Path.withoutDirectory(extractData.newFileName)}'); + } + + public static function doRefactor(context:RefactorContext):Promise { + final extractData = makeExtractTypeData(context); + if (extractData == null) { + return Promise.resolve(RefactorResult.NotFound); + } + // copy header + imports + final fileHeader = makeHeader(extractData, context); + + // copy type body + final typePos = extractData.typeToken.getPos(); + final docComment:Null = TokenTreeCheckUtils.getDocComment(extractData.typeToken); + if (docComment != null) { + // expand pos.min to capture doc comment + if (docComment.pos.min < typePos.min) { + typePos.min = docComment.pos.min; + } + } + final changelist:Changelist = new Changelist(context); + final typeText = RefactorHelper.extractText(context.converter, extractData.content, typePos.min, typePos.max); + + // remove code from current file + changelist.addChange(context.what.fileName, RemoveText({fileName: typePos.file, start: typePos.min, end: typePos.max}), null); + + // create new file + changelist.addChange(extractData.newFileName, CreateFile(extractData.newFileName), null); + + // copy file header, type and doc comment into new file + changelist.addChange(extractData.newFileName, InsertText(fileHeader + typeText, {fileName: extractData.newFileName, start: 0, end: 0}), null); + + // find all places using type and update their imports + findImportLocations(context, extractData, changelist); + + return Promise.resolve(changelist.execute()); + } + + static function makeExtractTypeData(context:CanRefactorContext):Null { + final fileContent = context.fileReader(context.what.fileName); + var content:String; + var root:TokenTree; + switch (fileContent) { + case Text(_): + return null; + case Token(tokens, text): + content = text; + root = tokens; + } + if (root == null) { + return null; + } + if (content == null) { + return null; + } + + final token:Null = RefactorHelper.findTokenAtPos(root, context.what.posStart); + if (token == null) { + return null; + } + final firstImport:Null = RefactorHelper.findFirstImport(root); + + final typeToken:Null = findTypeToken(token); + if (typeToken == null) { + return null; + } + + if (insideConditional(typeToken)) { + return null; + } + + final path = new Path(context.what.fileName); + + final nameToken:Null = typeToken.getFirstChild(); + if (nameToken == null) { + return null; + } + final name:String = nameToken.toString(); + if (name == path.file || path.dir == null) { + return null; + } + + final file:Null = context.fileList.getFile(context.what.fileName); + if (file == null) { + return null; + } + final type:Null = file.getType(name); + if (type == null) { + return null; + } + + final newFileName:String = Path.join([path.dir, name + ".hx"]); + if (FileSystem.exists(newFileName)) { + return null; + } + + return { + content: content, + root: root, + firstImport: firstImport, + typeToken: typeToken, + name: name, + newFileName: newFileName, + oldFile: file, + oldType: type, + }; + } + + static function findTypeToken(token:Null):Null { + if (token == null) { + return null; + } + return switch (token.tok) { + case Kwd(KwdAbstract) | Kwd(KwdClass) | Kwd(KwdEnum) | Kwd(KwdInterface) | Kwd(KwdTypedef): + token; + case Root: + null; + default: + findTypeToken(token.parent); + } + } + + static function insideConditional(token:TokenTree):Bool { + final parent:Null = token.parent; + if (parent == null) { + return false; + } + return switch (parent.tok) { + case Root: + false; + case Sharp(s): + true; + default: + insideConditional(parent); + } + } + + static function makeHeader(extractData:ExtractTypeData, context:RefactorContext):String { + var allUses = extractData.oldType.uses.map(use -> { + final parts = use.name.split("."); + return parts.shift(); + }); + final imports = RefactorHelper.makeNewFileImports(context, extractData.oldFile, allUses); + + if (extractData.firstImport == null) { + final lastHeader = RefactorHelper.getLastHeaderToken(extractData.root); + if (extractData.firstImport == null) { + return ""; + } + final pos = lastHeader.getPos(); + return RefactorHelper.extractText(context.converter, extractData.content, 0, pos.min - 1) + "\n"; + } + + final pos = extractData.firstImport.getPos(); + return RefactorHelper.extractText(context.converter, extractData.content, 0, pos.min - 1) + "\n" + imports; + } + + static function findImportLocations(context:CanRefactorContext, extractData:ExtractTypeData, changelist:Changelist) { + final oldFullName = extractData.oldType.fullModuleName; + final oldPackageName = extractData.oldFile.packageIdentifier.name; + final oldModulName = oldPackageName + "." + extractData.oldFile.getMainModulName(); + final newFullName = oldPackageName + "." + extractData.name; + var allUses:Array = context.nameMap.getIdentifiers(oldFullName); + for (use in allUses) { + changelist.addChange(use.file.name, ReplaceText(newFullName, use.pos), use); + } + allUses = context.nameMap.getIdentifiers(extractData.name); + var needsImport:Array = []; + for (use in allUses) { + var file = use.file; + for (imp in file.importList) { + if (imp.moduleName.name == oldModulName) { + if (!needsImport.contains(file)) { + needsImport.push(file); + } + break; + } + } + } + final importNewModule = "import " + newFullName + ";\n"; + for (file in needsImport) { + var pos:IdentifierPos = {fileName: file.name, start: file.importInsertPos, end: file.importInsertPos}; + changelist.addChange(file.name, InsertText(importNewModule, pos), null); + } + } +} + +typedef ExtractTypeData = { + var content:String; + var root:TokenTree; + var firstImport:Null; + var typeToken:TokenTree; + var name:String; + var newFileName:String; + var oldFile:File; + var oldType:Type; +} diff --git a/src/refactor/refactor/RefactorContext.hx b/src/refactor/refactor/RefactorContext.hx new file mode 100644 index 0000000..496936c --- /dev/null +++ b/src/refactor/refactor/RefactorContext.hx @@ -0,0 +1,5 @@ +package refactor.refactor; + +import refactor.edits.EditContext; + +typedef RefactorContext = CanRefactorContext & EditContext; diff --git a/src/refactor/refactor/RefactorHelper.hx b/src/refactor/refactor/RefactorHelper.hx new file mode 100644 index 0000000..69dfa48 --- /dev/null +++ b/src/refactor/refactor/RefactorHelper.hx @@ -0,0 +1,124 @@ +package refactor.refactor; + +import refactor.discover.File; +import refactor.refactor.CanRefactorContext.ByteToCharConverterFunc; + +class RefactorHelper { + public static function findTokenAtPos(token:TokenTree, searchPos:Int):Null { + if (!token.hasChildren()) { + return null; + } + if (token.pos.min <= searchPos && token.pos.max > searchPos) { + return token; + } + for (child in token.children) { + var range = child.getPos(); + if (range.min <= searchPos && range.max > searchPos) { + return findTokenAtPos(child, searchPos); + } + } + return null; + } + + public static function extractText(converter:ByteToCharConverterFunc, text:String, start:Int, end:Int):String { + return text.substring(converter(text, start), converter(text, end)); + } + + public static function findFirstImport(tree:TokenTree):Null { + if (!tree.hasChildren()) { + return null; + } + for (child in tree.children) { + switch (child.tok) { + case Kwd(KwdImport) | Kwd(KwdUsing): + return child; + case Kwd(KwdAbstract) | Kwd(KwdClass) | Kwd(KwdEnum) | Kwd(KwdInterface) | Kwd(KwdTypedef) | Kwd(KwdVar) | Kwd(KwdFinal) | Kwd(KwdFunction): + return child; + case Sharp(_): + return null; + default: + } + } + return null; + } + + public static function makeNewFileImports(context:CanRefactorContext, file:File, potentialNames:Array) { + final needImports:Array = []; + final importedTypes:Array = []; + final imports:Array = []; + for (imp in file.importList) { + if (imp.moduleName.type == UsingModul) { + needImports.push(imp); + } + if (imp.starImport) { + needImports.push(imp); + continue; + } + if (imp.alias != null) { + importedTypes.push(imp.alias.name); + imports.push(imp); + continue; + } + final parts = imp.moduleName.name.split("."); + importedTypes.push(parts.pop()); + imports.push(imp); + } + + for (name in potentialNames) { + final index = importedTypes.indexOf(name); + if (index >= 0) { + final imp = imports[index]; + if (needImports.contains(imp)) { + continue; + } + needImports.push(imp); + } + } + final buf:StringBuf = new StringBuf(); + for (imp in needImports) { + switch (imp.moduleName.type) { + case ImportModul: + buf.add("import "); + buf.add(imp.moduleName.name); + if (imp.starImport) { + buf.add(".*"); + } + buf.add(";\n"); + case ImportAlias: + buf.add("import "); + buf.add(imp.moduleName.name); + buf.add(" as "); + buf.add(imp.alias.name); + buf.add(";\n"); + case UsingModul: + buf.add("using "); + buf.add(imp.moduleName.name); + buf.add(";\n"); + default: + } + } + + final imports = buf.toString(); + if (imports.length <= 0) { + return ""; + } + + return imports + "\n"; + } + + public static function getLastHeaderToken(tree:TokenTree):Null { + final headers:Array = tree.filterCallback(function(token:TokenTree, index:Int):FilterResult { + switch token.tok { + case Kwd(KwdAbstract) | Kwd(KwdClass) | Kwd(KwdEnum) | Kwd(KwdInterface) | Kwd(KwdTypedef) | Kwd(KwdVar) | Kwd(KwdFinal) | Kwd(KwdFunction): + return FoundSkipSubtree; + case Kwd(KwdPackage), Kwd(KwdImport), Kwd(KwdUsing): + return SkipSubtree; + case Comment(_) | CommentLine(_): + return SkipSubtree; + default: + } + return GoDeeper; + }); + return headers.shift(); + } +} diff --git a/src/refactor/refactor/RefactorType.hx b/src/refactor/refactor/RefactorType.hx new file mode 100644 index 0000000..4362e7e --- /dev/null +++ b/src/refactor/refactor/RefactorType.hx @@ -0,0 +1,6 @@ +package refactor.refactor; + +enum RefactorType { + RefactorExtractType; + RefactorExtractInterface; +} diff --git a/src/refactor/refactor/RefactorWhat.hx b/src/refactor/refactor/RefactorWhat.hx new file mode 100644 index 0000000..655855a --- /dev/null +++ b/src/refactor/refactor/RefactorWhat.hx @@ -0,0 +1,7 @@ +package refactor.refactor; + +typedef RefactorWhat = { + var fileName:String; + var posStart:Int; + var posEnd:Int; +} diff --git a/src/refactor/CanRefactorContext.hx b/src/refactor/rename/CanRenameContext.hx similarity index 70% rename from src/refactor/CanRefactorContext.hx rename to src/refactor/rename/CanRenameContext.hx index 1fc1d0d..04f8d15 100644 --- a/src/refactor/CanRefactorContext.hx +++ b/src/refactor/rename/CanRenameContext.hx @@ -1,16 +1,16 @@ -package refactor; +package refactor.rename; import refactor.ITyper; -import refactor.RefactorContext; +import refactor.VerboseLogger; import refactor.discover.FileList; import refactor.discover.NameMap; import refactor.discover.TypeList; -typedef CanRefactorContext = { +typedef CanRenameContext = { var nameMap:NameMap; var fileList:FileList; var typeList:TypeList; - var what:RefactorWhat; + var what:RenameWhat; var verboseLog:VerboseLogger; var typer:Null; } diff --git a/src/refactor/CanRefactorResult.hx b/src/refactor/rename/CanRenameResult.hx similarity index 61% rename from src/refactor/CanRefactorResult.hx rename to src/refactor/rename/CanRenameResult.hx index f31073a..0b268a5 100644 --- a/src/refactor/CanRefactorResult.hx +++ b/src/refactor/rename/CanRenameResult.hx @@ -1,8 +1,8 @@ -package refactor; +package refactor.rename; import refactor.discover.IdentifierPos; -typedef CanRefactorResult = { +typedef CanRenameResult = { var name:String; var pos:IdentifierPos; } diff --git a/src/refactor/rename/RenameAnonStructField.hx b/src/refactor/rename/RenameAnonStructField.hx index ceafb6b..f110063 100644 --- a/src/refactor/rename/RenameAnonStructField.hx +++ b/src/refactor/rename/RenameAnonStructField.hx @@ -1,15 +1,15 @@ package refactor.rename; -import refactor.RefactorContext; import refactor.RefactorResult; import refactor.discover.File; import refactor.discover.Identifier; import refactor.discover.IdentifierType.TypedefFieldType; import refactor.discover.Type; import refactor.edits.Changelist; +import refactor.rename.RenameContext; class RenameAnonStructField { - public static function refactorAnonStructField(context:RefactorContext, file:File, identifier:Identifier, + public static function refactorAnonStructField(context:RenameContext, file:File, identifier:Identifier, fields:Array):Promise { var changelist:Changelist = new Changelist(context); @@ -20,8 +20,7 @@ class RenameAnonStructField { }); } - public static function refactorStructureField(context:RefactorContext, file:File, identifier:Identifier, - fieldNames:Array):Promise { + public static function refactorStructureField(context:RenameContext, file:File, identifier:Identifier, fieldNames:Array):Promise { var allUses:Array = context.nameMap.getIdentifiers(identifier.name); for (use in allUses) { switch (use.type) { @@ -37,8 +36,7 @@ class RenameAnonStructField { return Promise.resolve(Unsupported(identifier.toString())); } - static function renameFieldsOfType(context:RefactorContext, changelist:Changelist, type:Type, fields:Array, - fromName:String):Promise { + static function renameFieldsOfType(context:RenameContext, changelist:Changelist, type:Type, fields:Array, fromName:String):Promise { var packName:String = type.file.getPackage(); var mainModuleName = type.file.getMainModulName(); fields = fields.concat(findBaseTypes(context, type)); @@ -69,8 +67,7 @@ class RenameAnonStructField { return Promise.all(promises).then(null); } - static function findAllExtending(context:RefactorContext, changelist:Changelist, type:Type, fields:Array, - fromName:String):Promise { + static function findAllExtending(context:RenameContext, changelist:Changelist, type:Type, fields:Array, fromName:String):Promise { var allUses:Array = context.nameMap.getIdentifiers(type.name.name); var promises:Array> = []; for (use in allUses) { @@ -104,7 +101,7 @@ class RenameAnonStructField { return true; } - static function findBaseTypes(context:RefactorContext, type:Type):Array { + static function findBaseTypes(context:RenameContext, type:Type):Array { var fieldTypes:Array = []; var baseTypes:Array = type.findAllIdentifiers((i) -> i.type.match(TypedefBase)); for (baseTypeName in baseTypes) { @@ -129,7 +126,7 @@ class RenameAnonStructField { return []; } - static function findBase(context:RefactorContext, baseTypeName:Identifier):Null { + static function findBase(context:RenameContext, baseTypeName:Identifier):Null { var allUses:Array = context.nameMap.getIdentifiers(baseTypeName.name); for (use in allUses) { switch (use.type) { diff --git a/src/refactor/rename/RenameContext.hx b/src/refactor/rename/RenameContext.hx new file mode 100644 index 0000000..8bb21b3 --- /dev/null +++ b/src/refactor/rename/RenameContext.hx @@ -0,0 +1,5 @@ +package refactor.rename; + +import refactor.edits.EditContext; + +typedef RenameContext = CanRenameContext & EditContext; diff --git a/src/refactor/rename/RenameEnumField.hx b/src/refactor/rename/RenameEnumField.hx index 03ff9c9..9406d63 100644 --- a/src/refactor/rename/RenameEnumField.hx +++ b/src/refactor/rename/RenameEnumField.hx @@ -1,13 +1,13 @@ package refactor.rename; -import refactor.RefactorContext; import refactor.RefactorResult; import refactor.discover.File; import refactor.discover.Identifier; import refactor.edits.Changelist; +import refactor.rename.RenameContext; class RenameEnumField { - public static function refactorEnumField(context:RefactorContext, file:File, identifier:Identifier):Promise { + public static function refactorEnumField(context:RenameContext, file:File, identifier:Identifier):Promise { var changelist:Changelist = new Changelist(context); changelist.addChange(identifier.pos.fileName, ReplaceText(context.what.toName, identifier.pos), identifier); diff --git a/src/refactor/rename/RenameField.hx b/src/refactor/rename/RenameField.hx index 653891a..b90dcbe 100644 --- a/src/refactor/rename/RenameField.hx +++ b/src/refactor/rename/RenameField.hx @@ -1,15 +1,15 @@ package refactor.rename; -import refactor.RefactorContext; import refactor.RefactorResult; import refactor.discover.File; import refactor.discover.Identifier; import refactor.discover.IdentifierPos; import refactor.discover.Type; import refactor.edits.Changelist; +import refactor.rename.RenameContext; class RenameField { - public static function refactorField(context:RefactorContext, file:File, identifier:Identifier, isStatic:Bool):Promise { + public static function refactorField(context:RenameContext, file:File, identifier:Identifier, isStatic:Bool):Promise { var changelist:Changelist = new Changelist(context); var packName:String = file.getPackage(); @@ -54,7 +54,7 @@ class RenameField { }); } - public static function replaceAccessOrCalls(context:RefactorContext, changelist:Changelist, identifier:Identifier, types:Array):Promise { + public static function replaceAccessOrCalls(context:RenameContext, changelist:Changelist, identifier:Identifier, types:Array):Promise { var allUses:Array = context.nameMap.matchIdentifierPart(identifier.name, true); var changes:Array> = []; for (use in allUses) { @@ -116,7 +116,7 @@ class RenameField { } } - static function replaceStaticUse(context:RefactorContext, changelist:Changelist, type:Type, fromName:String) { + static function replaceStaticUse(context:RenameContext, changelist:Changelist, type:Type, fromName:String) { var packName:String = type.file.getPackage(); var allUses:Array = context.nameMap.getIdentifiers('${type.name.name}.$fromName'); for (use in allUses) { diff --git a/src/refactor/rename/RenameHelper.hx b/src/refactor/rename/RenameHelper.hx index c576734..35235ac 100644 --- a/src/refactor/rename/RenameHelper.hx +++ b/src/refactor/rename/RenameHelper.hx @@ -20,7 +20,7 @@ class RenameHelper { } } - public static function findDescendantTypes(context:RefactorContext, packName:String, baseType:Type):Array { + public static function findDescendantTypes(context:RenameContext, packName:String, baseType:Type):Array { var types:Array = []; var fullModulName:String = '$packName.${baseType.name.name}'; @@ -85,7 +85,7 @@ class RenameHelper { return types; } - public static function matchesType(context:RefactorContext, searchTypeOf:SearchTypeOf, searchType:TypeHintType):Promise { + public static function matchesType(context:RenameContext, searchTypeOf:SearchTypeOf, searchType:TypeHintType):Promise { return findTypeOfIdentifier(context, searchTypeOf).then(function(identifierType:Null):Bool { if (identifierType == null) { return false; @@ -124,14 +124,14 @@ class RenameHelper { }); } - static function findTypeWithTyper(context:RefactorContext, fileName:String, pos:Int):Promise> { + static function findTypeWithTyper(context:RenameContext, fileName:String, pos:Int):Promise> { if (context.typer == null) { return Promise.reject("no typer"); } return context.typer.resolveType(fileName, pos); } - public static function findTypeOfIdentifier(context:RefactorContext, searchTypeOf:SearchTypeOf):Promise { + public static function findTypeOfIdentifier(context:RenameContext, searchTypeOf:SearchTypeOf):Promise { var parts:Array = searchTypeOf.name.split("."); var part:String = parts.shift(); @@ -144,8 +144,8 @@ class RenameHelper { var part:String = parts[index++]; switch (partType) { case null: - context.verboseLog('unable to determine type of "$part" in ${searchTypeOf.defineType.file.name}@${searchTypeOf.pos}'); - return Promise.reject('unable to determine type of "$part" in ${searchTypeOf.defineType.file.name}@${searchTypeOf.pos}'); + context.verboseLog('unable to determine type of "$part" in ${searchTypeOf.defineType?.file.name}@${searchTypeOf.pos}'); + return Promise.reject('unable to determine type of "$part" in ${searchTypeOf.defineType?.file.name}@${searchTypeOf.pos}'); case KnownType(t, params): return findField(context, t, part).then(findFieldForPart); case UnknownType(name, _): @@ -157,7 +157,7 @@ class RenameHelper { }); } - public static function findFieldOrScopedLocal(context:RefactorContext, containerType:Type, name:String, pos:Int):Promise { + public static function findFieldOrScopedLocal(context:RenameContext, containerType:Type, name:String, pos:Int):Promise { return findTypeWithTyper(context, containerType.file.name, pos).catchError(function(msg):Promise { var allUses:Array = containerType.getIdentifiers(name); var candidate:Null = null; @@ -276,7 +276,7 @@ class RenameHelper { }); } - public static function findField(context:RefactorContext, containerType:Type, name:String):Promise { + public static function findField(context:RenameContext, containerType:Type, name:String):Promise { var allUses:Array = containerType.getIdentifiers(name); var candidate:Null = null; for (use in allUses) { @@ -327,7 +327,7 @@ class RenameHelper { return null; } - public static function typeFromTypeHint(context:RefactorContext, hint:Identifier):Promise { + public static function typeFromTypeHint(context:RenameContext, hint:Identifier):Promise { if (hint.name == "Null") { if ((hint.uses == null) || (hint.uses.length <= 0)) { return Promise.reject(); @@ -370,7 +370,7 @@ class RenameHelper { return Promise.resolve(UnknownType(hint.name, typeParams)); } - public static function replaceStaticExtension(context:RefactorContext, changelist:Changelist, identifier:Identifier):Promise { + public static function replaceStaticExtension(context:RenameContext, changelist:Changelist, identifier:Identifier):Promise { var allUses:Array = context.nameMap.matchIdentifierPart(identifier.name, true); if (identifier.uses == null) { @@ -441,7 +441,7 @@ class RenameHelper { return Promise.all(changes).then(null); } - public static function replaceSingleAccessOrCall(context:RefactorContext, changelist:Changelist, use:Identifier, fromName:String, + public static function replaceSingleAccessOrCall(context:RenameContext, changelist:Changelist, use:Identifier, fromName:String, types:Array):Promise { var name:String = use.name; var index:Int = name.lastIndexOf('.$fromName'); @@ -481,7 +481,7 @@ class RenameHelper { }); } - public static function replaceArrayAccess(context:RefactorContext, changelist:Changelist, use:Identifier, fromName:String, types:Array, + public static function replaceArrayAccess(context:RenameContext, changelist:Changelist, use:Identifier, fromName:String, types:Array, posClosing:Int):Promise { var name:String = use.name; diff --git a/src/refactor/rename/RenameImportAlias.hx b/src/refactor/rename/RenameImportAlias.hx index d722114..930d2f2 100644 --- a/src/refactor/rename/RenameImportAlias.hx +++ b/src/refactor/rename/RenameImportAlias.hx @@ -1,13 +1,13 @@ package refactor.rename; -import refactor.RefactorContext; import refactor.RefactorResult; import refactor.discover.File; import refactor.discover.Identifier; import refactor.edits.Changelist; +import refactor.rename.RenameContext; class RenameImportAlias { - public static function refactorImportAlias(context:RefactorContext, file:File, identifier:Identifier):Promise { + public static function refactorImportAlias(context:RenameContext, file:File, identifier:Identifier):Promise { var allUses:Array = context.nameMap.getIdentifiers(identifier.name); var isImportHx:Bool = (file.getMainModulName() == "import"); diff --git a/src/refactor/rename/RenameModuleLevelStatic.hx b/src/refactor/rename/RenameModuleLevelStatic.hx index a6507dd..6704ace 100644 --- a/src/refactor/rename/RenameModuleLevelStatic.hx +++ b/src/refactor/rename/RenameModuleLevelStatic.hx @@ -1,14 +1,14 @@ package refactor.rename; -import refactor.RefactorContext; import refactor.RefactorResult; import refactor.discover.File; import refactor.discover.Identifier; import refactor.discover.IdentifierPos; import refactor.edits.Changelist; +import refactor.rename.RenameContext; class RenameModuleLevelStatic { - public static function refactorModuleLevelStatic(context:RefactorContext, file:File, identifier:Identifier):Promise { + public static function refactorModuleLevelStatic(context:RenameContext, file:File, identifier:Identifier):Promise { var changelist:Changelist = new Changelist(context); var packageName:String = file.getPackage(); @@ -58,7 +58,7 @@ class RenameModuleLevelStatic { }); } - static function refactorIdentifier(context:RefactorContext, changelist:Changelist, searchName:String, replaceName:String, + static function refactorIdentifier(context:RenameContext, changelist:Changelist, searchName:String, replaceName:String, filesWithStaticImport:Array) { var allUses:Array = context.nameMap.getIdentifiers(searchName); for (use in allUses) { diff --git a/src/refactor/rename/RenamePackage.hx b/src/refactor/rename/RenamePackage.hx index eb07428..d8abf9e 100644 --- a/src/refactor/rename/RenamePackage.hx +++ b/src/refactor/rename/RenamePackage.hx @@ -1,15 +1,15 @@ package refactor.rename; import haxe.io.Path; -import refactor.RefactorContext; import refactor.RefactorResult; import refactor.discover.File; import refactor.discover.Identifier; import refactor.discover.IdentifierPos; import refactor.edits.Changelist; +import refactor.rename.RenameContext; class RenamePackage { - public static function refactorPackageName(context:RefactorContext, file:File, identifier:Identifier):Promise { + public static function refactorPackageName(context:RenameContext, file:File, identifier:Identifier):Promise { var changelist:Changelist = new Changelist(context); var mainTypeName:String = file.getMainModulName(); @@ -78,7 +78,7 @@ class RenamePackage { return Promise.resolve(changelist.execute()); } - static function moveFileToPackage(context:RefactorContext, file:File, changelist:Changelist, packageName:String) { + static function moveFileToPackage(context:RenameContext, file:File, changelist:Changelist, packageName:String) { var path:Path = new Path(file.name); var mainTypeName:String = file.getMainModulName(); diff --git a/src/refactor/rename/RenameScopedLocal.hx b/src/refactor/rename/RenameScopedLocal.hx index f7a827c..06749f9 100644 --- a/src/refactor/rename/RenameScopedLocal.hx +++ b/src/refactor/rename/RenameScopedLocal.hx @@ -1,15 +1,14 @@ package refactor.rename; -import refactor.RefactorContext; import refactor.RefactorResult; import refactor.discover.File; import refactor.discover.Identifier; import refactor.discover.IdentifierPos; import refactor.edits.Changelist; +import refactor.rename.RenameContext; class RenameScopedLocal { - public static function refactorScopedLocal(context:RefactorContext, file:File, identifier:Identifier, scopeStart:Int, - scopeEnd:Int):Promise { + public static function refactorScopedLocal(context:RenameContext, file:File, identifier:Identifier, scopeStart:Int, scopeEnd:Int):Promise { var changelist:Changelist = new Changelist(context); var identifierDot:String = identifier.name + "."; var toNameDot:String = context.what.toName + "."; diff --git a/src/refactor/rename/RenameTypeName.hx b/src/refactor/rename/RenameTypeName.hx index 3ce781a..510c1ce 100644 --- a/src/refactor/rename/RenameTypeName.hx +++ b/src/refactor/rename/RenameTypeName.hx @@ -1,16 +1,16 @@ package refactor.rename; import haxe.io.Path; -import refactor.RefactorContext; import refactor.RefactorResult; import refactor.discover.File; import refactor.discover.Identifier; import refactor.discover.IdentifierPos; import refactor.edits.Changelist; +import refactor.rename.RenameContext; import refactor.rename.RenameHelper.TypeHintType; class RenameTypeName { - public static function refactorTypeName(context:RefactorContext, file:File, identifier:Identifier):Promise { + public static function refactorTypeName(context:RenameContext, file:File, identifier:Identifier):Promise { var changelist:Changelist = new Changelist(context); var packName:String = file.getPackage(); var mainModuleName:String = file.getMainModulName(); diff --git a/src/refactor/RefactorWhat.hx b/src/refactor/rename/RenameWhat.hx similarity index 55% rename from src/refactor/RefactorWhat.hx rename to src/refactor/rename/RenameWhat.hx index f607260..53f63e2 100644 --- a/src/refactor/RefactorWhat.hx +++ b/src/refactor/rename/RenameWhat.hx @@ -1,6 +1,6 @@ -package refactor; +package refactor.rename; -typedef RefactorWhat = { +typedef RenameWhat = { var fileName:String; var toName:String; var pos:Int; diff --git a/test/TestMain.hx b/test/TestMain.hx index 85b59be..b5e36ec 100644 --- a/test/TestMain.hx +++ b/test/TestMain.hx @@ -1,25 +1,29 @@ -import refactor.ClassTest; -import refactor.EnumTest; -import refactor.ImportAliasTest; -import refactor.InterfaceTest; -import refactor.ModuleLevelStaticTest; -import refactor.PackageTest; -import refactor.ScopedLocalTest; -import refactor.TypedefTest; +import refactor.refactor.RefactorClassTest; +import refactor.refactor.RefactorTypedefTest; +import refactor.rename.RenameClassTest; +import refactor.rename.RenameEnumTest; +import refactor.rename.RenameImportAliasTest; +import refactor.rename.RenameInterfaceTest; +import refactor.rename.RenameModuleLevelStaticTest; +import refactor.rename.RenamePackageTest; +import refactor.rename.RenameScopedLocalTest; +import refactor.rename.RenameTypedefTest; import utest.Runner; import utest.ui.text.DiagnosticsReport; class TestMain { static function main() { var tests:Array = [ - new ClassTest(), - new EnumTest(), - new ImportAliasTest(), - new InterfaceTest(), - new ModuleLevelStaticTest(), - new PackageTest(), - new ScopedLocalTest(), - new TypedefTest() + new RenameClassTest(), + new RenameEnumTest(), + new RenameImportAliasTest(), + new RenameInterfaceTest(), + new RenameModuleLevelStaticTest(), + new RenamePackageTest(), + new RenameScopedLocalTest(), + new RenameTypedefTest(), + new RefactorClassTest(), + new RefactorTypedefTest(), ]; var runner:Runner = new Runner(); diff --git a/test/refactor/TestBase.hx b/test/refactor/TestBase.hx index 58a876c..f0904ed 100644 --- a/test/refactor/TestBase.hx +++ b/test/refactor/TestBase.hx @@ -1,12 +1,8 @@ package refactor; -import haxe.Exception; import haxe.PosInfos; import js.lib.Promise; -import utest.Async; import refactor.RefactorResult; -import refactor.RefactorWhat; -import refactor.Rename; import refactor.TestEditableDocument; import refactor.discover.FileList; import refactor.discover.NameMap; @@ -47,97 +43,6 @@ class TestBase implements ITest { } } - function refactorAndCheck(what:RefactorWhat, edits:Array, async:Async, ?pos:PosInfos) { - try { - doCanRefactor(what, edits, pos).catchError(function(failure) { - Assert.fail('$failure', pos); - }).finally(function() { - async.done(); - }); - } catch (e:Exception) { - Assert.fail(e.toString(), pos); - } - } - - function failCanRefactor(what:RefactorWhat, expected:String, async:Async, ?pos:PosInfos) { - try { - doCanRefactor(what, [], pos).then(function(success:RefactorResult) { - Assert.equals(expected, PrintHelper.printRefactorResult(success), pos); - }).catchError(function(failure) { - Assert.equals(expected, '$failure', pos); - }).finally(function() { - async.done(); - }); - } catch (e:Exception) { - Assert.fail(e.toString(), pos); - } - } - - function failRefactor(what:RefactorWhat, expected:String, async:Async, ?pos:PosInfos) { - try { - doRefactor(what, [], pos).then(function(success:RefactorResult) { - Assert.equals(expected, PrintHelper.printRefactorResult(success), pos); - }).catchError(function(failure) { - Assert.equals(expected, '$failure', pos); - }).finally(function() { - async.done(); - }); - } catch (e:Exception) { - Assert.fail(e.toString(), pos); - } - } - - function doCanRefactor(what:RefactorWhat, edits:Array, pos:PosInfos):Promise { - var editList:TestEditList = new TestEditList(); - return Rename.canRename({ - nameMap: usageContext.nameMap, - fileList: usageContext.fileList, - typeList: usageContext.typeList, - what: what, - verboseLog: function(text:String, ?pos:PosInfos) { - Sys.println('${pos.fileName}:${pos.lineNumber}: $text'); - }, - typer: null - }).then(function(success:CanRefactorResult) { - return doRefactor(what, edits, pos); - }); - } - - function doRefactor(what:RefactorWhat, edits:Array, pos:PosInfos):Promise { - var editList:TestEditList = new TestEditList(); - return Rename.rename({ - nameMap: usageContext.nameMap, - fileList: usageContext.fileList, - typeList: usageContext.typeList, - what: what, - forRealExecute: true, - docFactory: (fileName) -> editList.newDoc(fileName), - verboseLog: function(text:String, ?pos:PosInfos) { - Sys.println('${pos.fileName}:${pos.lineNumber}: $text'); - }, - typer: null - }).then(function(success:RefactorResult) { - editList.sortEdits(); - Assert.equals(Done, success, pos); - Assert.equals(editList.docCounter, editList.docFinishedCounter, pos); - Assert.equals(edits.length, editList.edits.length, pos); - if (edits.length == editList.edits.length) { - for (index in 0...edits.length) { - var expected:TestEdit = edits[index]; - var actual:TestEdit = editList.edits[index]; - Assert.equals(expected.fileName, actual.fileName, expected.pos); - Assert.equals(fileEditToString(expected.edit), fileEditToString(actual.edit), expected.pos); - } - } else { - for (edit in editList.edits) { - Sys.println(fileEditToString(edit.edit)); - } - Assert.fail("length mismatch - edits were not checked", pos); - } - return Promise.resolve(success); - }); - } - function fileEditToString(edit:FileEdit):String { return switch (edit) { case CreateFile(newFileName): @@ -155,6 +60,14 @@ class TestBase implements ITest { } } + function makeCreateTestEdit(newFileName, ?pos:PosInfos):TestEdit { + return { + fileName: newFileName, + edit: CreateFile(newFileName), + pos: pos + } + } + function makeMoveTestEdit(oldFileName:String, newFileName, ?pos:PosInfos):TestEdit { return { fileName: oldFileName, @@ -163,6 +76,14 @@ class TestBase implements ITest { } } + function makeDeleteTestEdit(oldFileName, ?pos:PosInfos):TestEdit { + return { + fileName: oldFileName, + edit: DeleteFile(oldFileName), + pos: pos + } + } + function makeReplaceTestEdit(fileName:String, text:String, start:Int, end:Int, ?pos:PosInfos):TestEdit { return { fileName: fileName, @@ -186,4 +107,25 @@ class TestBase implements ITest { pos: pos } } + + function assertEdits(success:RefactorResult, editList:TestEditList, edits:Array, pos:PosInfos):Promise { + editList.sortEdits(); + Assert.equals(Done, success, pos); + Assert.equals(editList.docCounter, editList.docFinishedCounter, pos); + Assert.equals(edits.length, editList.edits.length, pos); + if (edits.length == editList.edits.length) { + for (index in 0...edits.length) { + var expected:TestEdit = edits[index]; + var actual:TestEdit = editList.edits[index]; + Assert.equals(expected.fileName, actual.fileName, expected.pos); + Assert.equals(fileEditToString(expected.edit), fileEditToString(actual.edit), expected.pos); + } + } else { + for (edit in editList.edits) { + Sys.println(fileEditToString(edit.edit)); + } + Assert.fail("length mismatch - edits were not checked", pos); + } + return Promise.resolve(success); + } } diff --git a/test/refactor/TestEditableDocument.hx b/test/refactor/TestEditableDocument.hx index 0c6ab88..7122429 100644 --- a/test/refactor/TestEditableDocument.hx +++ b/test/refactor/TestEditableDocument.hx @@ -17,9 +17,9 @@ class TestEditableDocument implements IEditableDocument { public function addChange(edit:FileEdit) { switch (edit) { case CreateFile(newFileName): - Assert.notEquals(fileName, newFileName); + Assert.equals(fileName, newFileName); case DeleteFile(oldFileName): - Assert.notEquals(fileName, oldFileName); + Assert.equals(fileName, oldFileName); case Move(newFileName): Assert.notEquals(fileName, newFileName); case ReplaceText(_, pos): diff --git a/test/refactor/refactor/RefactorClassTest.hx b/test/refactor/refactor/RefactorClassTest.hx new file mode 100644 index 0000000..280a66a --- /dev/null +++ b/test/refactor/refactor/RefactorClassTest.hx @@ -0,0 +1,54 @@ +package refactor.refactor; + +class RefactorClassTest extends RefactorTestBase { + function setupClass() { + setupTestSources(["testcases/classes"]); + } + + function testExtractTypeListOfChilds(async:Async) { + var edits:Array = [ + makeRemoveTestEdit("testcases/classes/ChildClass.hx", 860, 901), + makeCreateTestEdit("testcases/classes/ListOfChilds.hx"), + makeInsertTestEdit("testcases/classes/ListOfChilds.hx", "package classes;\n\ntypedef ListOfChilds = Array;", 0), + makeInsertTestEdit("testcases/classes/pack/UseChild.hx", "import classes.ListOfChilds;\n", 23), + ]; + checkRefactor(RefactorExtractType, {fileName: "testcases/classes/ChildClass.hx", posStart: 873, posEnd: 873}, edits, async); + } + + function testExtractTypeTextLoader(async:Async) { + var edits:Array = [ + makeRemoveTestEdit("testcases/classes/Printer.hx", 1262, 1397), + makeCreateTestEdit("testcases/classes/TextLoader.hx"), + makeInsertTestEdit("testcases/classes/TextLoader.hx", + "package classes;\n\n" + + "import js.lib.Promise;\n\n" + + "class TextLoader {\n" + + "\tpublic function new() {}\n\n" + + "\tpublic function load(text:String):Promise {\n" + + "\t\treturn Promise.resolve(text);\n" + + "\t}\n" + + "}", + 0), + makeInsertTestEdit("testcases/classes/pack/UsePrinter.hx", "import classes.TextLoader;\n", 23), + ]; + checkRefactor(RefactorExtractType, {fileName: "testcases/classes/Printer.hx", posStart: 1273, posEnd: 1283}, edits, async); + } + + function testExtractInterfaceBaseClass(async:Async) { + var edits:Array = [ + makeInsertTestEdit("testcases/classes/BaseClass.hx", " implements IBaseClass", 33), + makeCreateTestEdit("testcases/classes/IBaseClass.hx"), + makeInsertTestEdit("testcases/classes/IBaseClass.hx", + "package classes;\n\n" + + "interface IBaseClass {\n" + + "\tfunction doSomething(data:Array):Void;\n" + + "\tfunction doSomething3(d:Array):Void;\n" + + "\tfunction doSomething4(d:Array):Void;\n" + + "\tfunction doSomething5(d:Array):Void;\n" + + "\tfunction doSomething6(d:Array):Void;\n" + + "}", + 0), + ]; + checkRefactor(RefactorExtractInterface, {fileName: "testcases/classes/BaseClass.hx", posStart: 27, posEnd: 27}, edits, async); + } +} diff --git a/test/refactor/refactor/RefactorTestBase.hx b/test/refactor/refactor/RefactorTestBase.hx new file mode 100644 index 0000000..606e356 --- /dev/null +++ b/test/refactor/refactor/RefactorTestBase.hx @@ -0,0 +1,124 @@ +package refactor.refactor; + +import haxe.Exception; +import haxe.PosInfos; +import haxe.io.Path; +import js.html.AbortController; +import js.lib.Promise; +import byte.ByteData; +import haxeparser.HaxeLexer; +import hxparse.ParserError; +import tokentree.TokenTree; +import tokentree.TokenTreeBuilder; +import utest.Async; +import refactor.RefactorResult; +import refactor.TestEditableDocument; +import refactor.discover.FileContentType; + +class RefactorTestBase extends TestBase { + function checkRefactor(refactorType:RefactorType, what:RefactorWhat, edits:Array, async:Async, ?pos:PosInfos) { + try { + doCanRefactor(refactorType, what, edits, pos).catchError(function(failure) { + Assert.fail('$failure', pos); + }).finally(function() { + async.done(); + }); + } catch (e:Exception) { + Assert.fail(e.toString(), pos); + } + } + + function failCanRefactor(refactorType:RefactorType, what:RefactorWhat, expected:String, async:Async, ?pos:PosInfos) { + try { + doCanRefactor(refactorType, what, [], pos).then(function(success:RefactorResult) { + Assert.equals(expected, PrintHelper.printRefactorResult(success), pos); + }).catchError(function(failure) { + Assert.equals(expected, '$failure', pos); + }).finally(function() { + async.done(); + }); + } catch (e:Exception) { + Assert.fail(e.toString(), pos); + } + } + + function failRename(refactorType:RefactorType, what:RefactorWhat, expected:String, async:Async, ?pos:PosInfos) { + try { + doRefactor(refactorType, what, [], pos).then(function(success:RefactorResult) { + Assert.equals(expected, PrintHelper.printRefactorResult(success), pos); + }).catchError(function(failure) { + Assert.equals(expected, '$failure', pos); + }).finally(function() { + async.done(); + }); + } catch (e:Exception) { + Assert.fail(e.toString(), pos); + } + } + + function doCanRefactor(refactorType:RefactorType, what:RefactorWhat, edits:Array, pos:PosInfos):Promise { + var editList:TestEditList = new TestEditList(); + var result = Refactoring.canRefactor(refactorType, { + nameMap: usageContext.nameMap, + fileList: usageContext.fileList, + typeList: usageContext.typeList, + what: what, + verboseLog: function(text:String, ?pos:PosInfos) { + Sys.println('${pos.fileName}:${pos.lineNumber}: $text'); + }, + typer: null, + converter: (string, byteOffset) -> byteOffset, + fileReader: testFileReader, + }); + return switch (result) { + case Unsupported: + Promise.reject("unsupported"); + case Supported(title): + doRefactor(refactorType, what, edits, pos); + } + } + + function doRefactor(refactorType:RefactorType, what:RefactorWhat, edits:Array, pos:PosInfos):Promise { + var editList:TestEditList = new TestEditList(); + return Refactoring.doRefactor(refactorType, { + nameMap: usageContext.nameMap, + fileList: usageContext.fileList, + typeList: usageContext.typeList, + what: what, + forRealExecute: true, + docFactory: (fileName) -> editList.newDoc(fileName), + verboseLog: function(text:String, ?pos:PosInfos) { + Sys.println('${pos.fileName}:${pos.lineNumber}: $text'); + }, + typer: null, + converter: (string, byteOffset) -> byteOffset, + fileReader: testFileReader, + }).then(function(success:RefactorResult) { + return assertEdits(success, editList, edits, pos); + }); + } +} + +function testFileReader(path:String):FileContentType { + final text = sys.io.File.getContent(path); + var root:Null = null; + try { + final content = ByteData.ofString(text); + final lexer = new HaxeLexer(content, path); + var t:haxeparser.Data.Token = lexer.token(haxeparser.HaxeLexer.tok); + + final tokens:Array = []; + while (t.tok != Eof) { + tokens.push(t); + t = lexer.token(haxeparser.HaxeLexer.tok); + } + root = TokenTreeBuilder.buildTokenTree(tokens, content, TypeLevel); + return Token(root, text); + } catch (e:ParserError) { + throw 'failed to parse ${path} - ParserError: $e (${e.pos})'; + } catch (e:LexerError) { + throw 'failed to parse ${path} - LexerError: ${e.msg} (${e.pos})'; + } catch (e:Exception) { + throw 'failed to parse ${path} - ${e.details()}'; + } +} diff --git a/test/refactor/refactor/RefactorTypedefTest.hx b/test/refactor/refactor/RefactorTypedefTest.hx new file mode 100644 index 0000000..3d29959 --- /dev/null +++ b/test/refactor/refactor/RefactorTypedefTest.hx @@ -0,0 +1,36 @@ +package refactor.refactor; + +class RefactorTypedefTest extends RefactorTestBase { + function setupClass() { + setupTestSources(["testcases/typedefs"]); + } + + function testExtractTypeListOfChilds(async:Async) { + var edits:Array = [ + makeRemoveTestEdit("testcases/typedefs/Types.hx", 247, 811), + makeCreateTestEdit("testcases/typedefs/UserConfig.hx"), + makeInsertTestEdit("testcases/typedefs/UserConfig.hx", + "package typedefs;\n\n" + + "import haxe.extern.EitherType;\n\n" + + "typedef UserConfig = {\n" + + "\tvar enableCodeLens:Bool;\n" + + "\tvar enableDiagnostics:Bool;\n" + + "\tvar enableServerView:Bool;\n" + + "\tvar enableSignatureHelpDocumentation:Bool;\n" + + "\tvar diagnosticsPathFilter:String;\n" + + "\tvar displayPort:EitherType;\n" + + "\tvar buildCompletionCache:Bool;\n" + + "\tvar enableCompletionCacheWarning:Bool;\n" + + "\tvar useLegacyCompletion:Bool;\n" + + "\tvar codeGeneration:CodeGenerationConfig;\n" + + "\tvar exclude:Array;\n" + + "\tvar postfixCompletion:PostfixCompletionConfig;\n" + + "\tvar importsSortOrder:ImportsSortOrderConfig;\n" + + "\tvar maxCompletionItems:Int;\n" + + "\tvar renameSourceFolders:Array;\n" + + "}", + 0), + ]; + checkRefactor(RefactorExtractType, {fileName: "testcases/typedefs/Types.hx", posStart: 260, posEnd: 260}, edits, async); + } +} diff --git a/test/refactor/ClassTest.hx b/test/refactor/rename/RenameClassTest.hx similarity index 74% rename from test/refactor/ClassTest.hx rename to test/refactor/rename/RenameClassTest.hx index 89106f7..61545cb 100644 --- a/test/refactor/ClassTest.hx +++ b/test/refactor/rename/RenameClassTest.hx @@ -1,6 +1,6 @@ -package refactor; +package refactor.rename; -class ClassTest extends TestBase { +class RenameClassTest extends RenameTestBase { function setupClass() { setupTestSources(["testcases/classes"]); } @@ -13,7 +13,7 @@ class ClassTest extends TestBase { makeReplaceTestEdit("testcases/classes/Childs.hx", "ChildName", 167, 173), makeReplaceTestEdit("testcases/classes/Childs.hx", "ChildName", 212, 218), ]; - refactorAndCheck({fileName: "testcases/classes/Childs.hx", toName: "ChildName", pos: 25}, edits, async); + checkRename({fileName: "testcases/classes/Childs.hx", toName: "ChildName", pos: 25}, edits, async); } public function testRenameBaseClassMethod(async:Async) { @@ -26,7 +26,7 @@ class ClassTest extends TestBase { makeReplaceTestEdit("testcases/classes/pack/UseChild.hx", "addData", 453, 464), makeReplaceTestEdit("testcases/classes/pack/UseChild.hx", "addData", 641, 652), ]; - refactorAndCheck({fileName: "testcases/classes/BaseClass.hx", toName: "addData", pos: 128}, edits, async); + checkRename({fileName: "testcases/classes/BaseClass.hx", toName: "addData", pos: 128}, edits, async); } public function testRenameBaseClassVar(async:Async) { @@ -39,7 +39,7 @@ class ClassTest extends TestBase { makeReplaceTestEdit("testcases/classes/BaseClass.hx", "listOfData", 407, 411), makeReplaceTestEdit("testcases/classes/BaseClass.hx", "listOfData", 425, 429), ]; - refactorAndCheck({fileName: "testcases/classes/BaseClass.hx", toName: "listOfData", pos: 44}, edits, async); + checkRename({fileName: "testcases/classes/BaseClass.hx", toName: "listOfData", pos: 44}, edits, async); } public function testRenameBaseClassVarFromSomewhere(async:Async) { @@ -52,7 +52,7 @@ class ClassTest extends TestBase { makeReplaceTestEdit("testcases/classes/BaseClass.hx", "listOfData", 407, 411), makeReplaceTestEdit("testcases/classes/BaseClass.hx", "listOfData", 425, 429), ]; - refactorAndCheck({fileName: "testcases/classes/BaseClass.hx", toName: "listOfData", pos: 163}, edits, async); + checkRename({fileName: "testcases/classes/BaseClass.hx", toName: "listOfData", pos: 163}, edits, async); } public function testRenameChildClass(async:Async) { @@ -79,7 +79,7 @@ class ClassTest extends TestBase { makeReplaceTestEdit("testcases/classes/pack/UseChild.hx", "ItemClass", 499, 509), makeReplaceTestEdit("testcases/classes/pack/UseChild.hx", "ItemClass", 612, 622), ]; - refactorAndCheck({fileName: "testcases/classes/ChildClass.hx", toName: "ItemClass", pos: 28}, edits, async); + checkRename({fileName: "testcases/classes/ChildClass.hx", toName: "ItemClass", pos: 28}, edits, async); } public function testRenameChildPackage(async:Async) { @@ -92,7 +92,7 @@ class ClassTest extends TestBase { makeReplaceTestEdit("testcases/classes/pack/SecondChildHelper.hx", "classes.pack.ChildClass", 30, 48), makeReplaceTestEdit("testcases/classes/pack/UseChild.hx", "classes.pack.ChildClass", 48, 66), ]; - refactorAndCheck({fileName: "testcases/classes/ChildClass.hx", toName: "classes.pack", pos: 10}, edits, async); + checkRename({fileName: "testcases/classes/ChildClass.hx", toName: "classes.pack", pos: 10}, edits, async); } public function testRenameTypedef(async:Async) { @@ -100,7 +100,7 @@ class ClassTest extends TestBase { makeReplaceTestEdit("testcases/classes/ChildClass.hx", "ChildList", 868, 880), makeReplaceTestEdit("testcases/classes/pack/UseChild.hx", "ChildList", 96, 108), ]; - refactorAndCheck({fileName: "testcases/classes/ChildClass.hx", toName: "ChildList", pos: 872}, edits, async); + checkRename({fileName: "testcases/classes/ChildClass.hx", toName: "ChildList", pos: 872}, edits, async); } public function testRenameStaticExtentionSum(async:Async) { @@ -109,7 +109,7 @@ class ClassTest extends TestBase { makeReplaceTestEdit("testcases/classes/StaticUsing.hx", "sumChilds", 182, 185), makeReplaceTestEdit("testcases/classes/pack/UseChild.hx", "sumChilds", 706, 709), ]; - refactorAndCheck({fileName: "testcases/classes/ChildHelper.hx", toName: "sumChilds", pos: 64}, edits, async); + checkRename({fileName: "testcases/classes/ChildHelper.hx", toName: "sumChilds", pos: 64}, edits, async); } public function testRenameStaticExtensionPrint(async:Async) { @@ -119,7 +119,7 @@ class ClassTest extends TestBase { makeReplaceTestEdit("testcases/classes/StaticUsing.hx", "printChild", 386, 391), makeReplaceTestEdit("testcases/classes/pack/SecondChildHelper.hx", "printChild", 151, 156), ]; - refactorAndCheck({fileName: "testcases/classes/pack/SecondChildHelper.hx", toName: "printChild", pos: 153}, edits, async); + checkRename({fileName: "testcases/classes/pack/SecondChildHelper.hx", toName: "printChild", pos: 153}, edits, async); } public function testRenameStaticExtensionPrintText(async:Async) { @@ -127,7 +127,7 @@ class ClassTest extends TestBase { makeReplaceTestEdit("testcases/classes/StaticUsing.hx", "logText", 323, 332), makeReplaceTestEdit("testcases/classes/pack/SecondChildHelper.hx", "logText", 225, 234), ]; - refactorAndCheck({fileName: "testcases/classes/pack/SecondChildHelper.hx", toName: "logText", pos: 229}, edits, async); + checkRename({fileName: "testcases/classes/pack/SecondChildHelper.hx", toName: "logText", pos: 229}, edits, async); } public function testRenameChildClassParent(async:Async) { @@ -140,7 +140,7 @@ class ClassTest extends TestBase { makeReplaceTestEdit("testcases/classes/pack/UseChild.hx", "parentBase", 555, 561), makeReplaceTestEdit("testcases/classes/pack/UseChild.hx", "parentBase", 634, 640), ]; - refactorAndCheck({fileName: "testcases/classes/ChildClass.hx", toName: "parentBase", pos: 69}, edits, async); + checkRename({fileName: "testcases/classes/ChildClass.hx", toName: "parentBase", pos: 69}, edits, async); } public function testRenameIdentifierPos(async:Async) { @@ -148,7 +148,7 @@ class ClassTest extends TestBase { makeReplaceTestEdit("testcases/classes/ChildClass.hx", "position", 392, 395), makeReplaceTestEdit("testcases/classes/MyIdentifier.hx", "position", 253, 256), ]; - refactorAndCheck({fileName: "testcases/classes/MyIdentifier.hx", toName: "position", pos: 254}, edits, async); + checkRename({fileName: "testcases/classes/MyIdentifier.hx", toName: "position", pos: 254}, edits, async); } // requires external typer since built-in will not resolve array access @@ -172,7 +172,7 @@ class ClassTest extends TestBase { makeReplaceTestEdit("testcases/classes/JsonClass.hx", "jsonWidth", 463, 468), makeReplaceTestEdit("testcases/classes/JsonClass.hx", "jsonWidth", 620, 625), ]; - refactorAndCheck({fileName: "testcases/classes/JsonClass.hx", toName: "jsonWidth", pos: 74}, edits, async); + checkRename({fileName: "testcases/classes/JsonClass.hx", toName: "jsonWidth", pos: 74}, edits, async); } public function testRenameJsonClass(async:Async) { @@ -185,7 +185,7 @@ class ClassTest extends TestBase { makeReplaceTestEdit("testcases/classes/JsonClass.hx", "JsonImporter", 336, 345), makeReplaceTestEdit("testcases/classes/pack/UseChild.hx", "JsonImporter", 744, 753), ]; - refactorAndCheck({fileName: "testcases/classes/JsonClass.hx", toName: "JsonImporter", pos: 28}, edits, async); + checkRename({fileName: "testcases/classes/JsonClass.hx", toName: "JsonImporter", pos: 28}, edits, async); } public function testRenameBaseClassParamterWithShadow(async:Async) { @@ -194,46 +194,46 @@ class ClassTest extends TestBase { makeInsertTestEdit("testcases/classes/BaseClass.hx", "this.", 248), makeReplaceTestEdit("testcases/classes/BaseClass.hx", "data", 260, 261), ]; - refactorAndCheck({fileName: "testcases/classes/BaseClass.hx", toName: "data", pos: 227}, edits, async); + checkRename({fileName: "testcases/classes/BaseClass.hx", toName: "data", pos: 227}, edits, async); } public function testRenameBaseClassParamterWithShadowLocalVar(async:Async) { var edits:Array = []; - failCanRefactor({fileName: "testcases/classes/BaseClass.hx", toName: "data", pos: 298}, 'local var "data" exists', async); - failRefactor({fileName: "testcases/classes/BaseClass.hx", toName: "data", pos: 298}, 'local var "data" exists', async); + failCanRename({fileName: "testcases/classes/BaseClass.hx", toName: "data", pos: 298}, 'local var "data" exists', async); + failRename({fileName: "testcases/classes/BaseClass.hx", toName: "data", pos: 298}, 'local var "data" exists', async); } public function testRenameBaseClassCaseLabel(async:Async) { var edits:Array = []; - failCanRefactor({fileName: "testcases/classes/BaseClass.hx", toName: "data", pos: 516}, + failCanRename({fileName: "testcases/classes/BaseClass.hx", toName: "data", pos: 516}, "renaming not supported for Case1 testcases/classes/BaseClass.hx@514-519 (CaseLabel(val))", async); - failRefactor({fileName: "testcases/classes/BaseClass.hx", toName: "data", pos: 516}, + failRename({fileName: "testcases/classes/BaseClass.hx", toName: "data", pos: 516}, "renaming not supported for Case1 testcases/classes/BaseClass.hx@514-519 (CaseLabel(val))", async); } public function testRenameUseChildClassParentSubPart(async:Async) { var edits:Array = []; - failCanRefactor({fileName: "testcases/classes/UseChild.hx", toName: "data", pos: 222}, "could not find identifier to rename", async); - failRefactor({fileName: "testcases/classes/UseChild.hx", toName: "data", pos: 222}, "could not find identifier to rename", async); + failCanRename({fileName: "testcases/classes/UseChild.hx", toName: "data", pos: 222}, "could not find identifier to rename", async); + failRename({fileName: "testcases/classes/UseChild.hx", toName: "data", pos: 222}, "could not find identifier to rename", async); } public function testRenameBaseClassDataToData(async:Async) { var edits:Array = []; - failCanRefactor({fileName: "testcases/classes/BaseClass.hx", toName: "data", pos: 43}, "could not find identifier to rename", async); - failRefactor({fileName: "testcases/classes/BaseClass.hx", toName: "data", pos: 43}, "could not find identifier to rename", async); + failCanRename({fileName: "testcases/classes/BaseClass.hx", toName: "data", pos: 43}, "could not find identifier to rename", async); + failRename({fileName: "testcases/classes/BaseClass.hx", toName: "data", pos: 43}, "could not find identifier to rename", async); } public function testRenameBaseClassNoIdentifier(async:Async) { var edits:Array = []; - failCanRefactor({fileName: "testcases/classes/BaseClass.hx", toName: "data", pos: 103}, "could not find identifier to rename", async); - failRefactor({fileName: "testcases/classes/BaseClass.hx", toName: "data", pos: 103}, "could not find identifier to rename", async); + failCanRename({fileName: "testcases/classes/BaseClass.hx", toName: "data", pos: 103}, "could not find identifier to rename", async); + failRename({fileName: "testcases/classes/BaseClass.hx", toName: "data", pos: 103}, "could not find identifier to rename", async); } public function testRenameStaticUsingConstructorCall(async:Async) { var edits:Array = []; - failCanRefactor({fileName: "testcases/classes/StaticUsing.hx", toName: "NewChildClass", pos: 359}, + failCanRename({fileName: "testcases/classes/StaticUsing.hx", toName: "NewChildClass", pos: 359}, "renaming not supported for ChildClass testcases/classes/StaticUsing.hx@355-365 (Call(true))", async); - failRefactor({fileName: "testcases/classes/StaticUsing.hx", toName: "NewChildClass", pos: 359}, + failRename({fileName: "testcases/classes/StaticUsing.hx", toName: "NewChildClass", pos: 359}, "renaming not supported for ChildClass testcases/classes/StaticUsing.hx@355-365 (Call(true))", async); } @@ -242,28 +242,28 @@ class ClassTest extends TestBase { makeReplaceTestEdit("testcases/classes/BaseClass.hx", "data", 386, 387), makeInsertTestEdit("testcases/classes/BaseClass.hx", "this.", 407), ]; - refactorAndCheck({fileName: "testcases/classes/BaseClass.hx", toName: "data", pos: 386}, edits, async); + checkRename({fileName: "testcases/classes/BaseClass.hx", toName: "data", pos: 386}, edits, async); } public function testRenameBaseClassParamterWithShadowCase(async:Async) { var edits:Array = []; - failCanRefactor({fileName: "testcases/classes/BaseClass.hx", toName: "data", pos: 470}, 'local var "data" exists', async); - failRefactor({fileName: "testcases/classes/BaseClass.hx", toName: "data", pos: 470}, 'local var "data" exists', async); + failCanRename({fileName: "testcases/classes/BaseClass.hx", toName: "data", pos: 470}, 'local var "data" exists', async); + failRename({fileName: "testcases/classes/BaseClass.hx", toName: "data", pos: 470}, 'local var "data" exists', async); } public function testRenameChildClassExtendsBaseClass(async:Async) { var edits:Array = []; - failCanRefactor({fileName: "testcases/classes/ChildClass.hx", toName: "parentBase", pos: 47}, + failCanRename({fileName: "testcases/classes/ChildClass.hx", toName: "parentBase", pos: 47}, "renaming not supported for BaseClass testcases/classes/ChildClass.hx@43-52 (Extends)", async); - failRefactor({fileName: "testcases/classes/ChildClass.hx", toName: "parentBase", pos: 47}, + failRename({fileName: "testcases/classes/ChildClass.hx", toName: "parentBase", pos: 47}, "renaming not supported for BaseClass testcases/classes/ChildClass.hx@43-52 (Extends)", async); } public function testRenameImport(async:Async) { var edits:Array = []; - failCanRefactor({fileName: "testcases/classes/MyIdentifier.hx", toName: "refactor.Foo", pos: 44}, + failCanRename({fileName: "testcases/classes/MyIdentifier.hx", toName: "refactor.Foo", pos: 44}, "renaming not supported for refactor.discover.File testcases/classes/MyIdentifier.hx@25-47 (ImportModul)", async); - failRefactor({fileName: "testcases/classes/MyIdentifier.hx", toName: "refactor.Foo", pos: 44}, + failRename({fileName: "testcases/classes/MyIdentifier.hx", toName: "refactor.Foo", pos: 44}, "renaming not supported for refactor.discover.File testcases/classes/MyIdentifier.hx@25-47 (ImportModul)", async); } @@ -273,7 +273,7 @@ class ClassTest extends TestBase { makeReplaceTestEdit("testcases/classes/DemoClassA.hx", "wasRenamed", 529, 539), makeReplaceTestEdit("testcases/classes/DemoClassA.hx", "wasRenamed", 546, 556), ]; - refactorAndCheck({fileName: "testcases/classes/DemoClassA.hx", toName: "wasRenamed", pos: 237}, edits, async); + checkRename({fileName: "testcases/classes/DemoClassA.hx", toName: "wasRenamed", pos: 237}, edits, async); } public function testRenameDemoClassAMemberVar2(async:Async) { @@ -282,7 +282,7 @@ class ClassTest extends TestBase { makeReplaceTestEdit("testcases/classes/DemoClassA.hx", "wasRenamed", 529, 539), makeReplaceTestEdit("testcases/classes/DemoClassA.hx", "wasRenamed", 546, 556), ]; - refactorAndCheck({fileName: "testcases/classes/DemoClassA.hx", toName: "wasRenamed", pos: 531}, edits, async); + checkRename({fileName: "testcases/classes/DemoClassA.hx", toName: "wasRenamed", pos: 531}, edits, async); } public function testRenameDemoClassAMemberVar3(async:Async) { @@ -291,7 +291,7 @@ class ClassTest extends TestBase { makeReplaceTestEdit("testcases/classes/DemoClassA.hx", "wasRenamed", 529, 539), makeReplaceTestEdit("testcases/classes/DemoClassA.hx", "wasRenamed", 546, 556), ]; - refactorAndCheck({fileName: "testcases/classes/DemoClassA.hx", toName: "wasRenamed", pos: 548}, edits, async); + checkRename({fileName: "testcases/classes/DemoClassA.hx", toName: "wasRenamed", pos: 548}, edits, async); } public function testRenameDemoClassASomeValue(async:Async) { @@ -299,27 +299,27 @@ class ClassTest extends TestBase { makeReplaceTestEdit("testcases/classes/DemoClassA.hx", "wasRenamed", 59, 68), makeReplaceTestEdit("testcases/classes/DemoClassA.hx", "wasRenamed", 515, 524), ]; - refactorAndCheck({fileName: "testcases/classes/DemoClassA.hx", toName: "wasRenamed", pos: 61}, edits, async); + checkRename({fileName: "testcases/classes/DemoClassA.hx", toName: "wasRenamed", pos: 61}, edits, async); } public function testRenamePrinter(async:Async) { var edits:Array = [ makeMoveTestEdit("testcases/classes/Printer.hx", "testcases/classes/PrinterRenamed.hx"), - makeReplaceTestEdit("testcases/classes/Printer.hx", "PrinterRenamed", 383, 390), - makeReplaceTestEdit("testcases/classes/Printer.hx", "PrinterRenamed", 545, 552), - makeReplaceTestEdit("testcases/classes/Printer.hx", "PrinterRenamed", 699, 706), - makeReplaceTestEdit("testcases/classes/Printer.hx", "PrinterRenamed", 930, 937), - makeReplaceTestEdit("testcases/classes/Printer.hx", "PrinterRenamed", 1076, 1083), - makeReplaceTestEdit("testcases/classes/Printer.hx", "PrinterRenamed", 1219, 1226), - makeReplaceTestEdit("testcases/classes/import.hx", "PrinterRenamed", 80, 87), - makeReplaceTestEdit("testcases/classes/pack/UsePrinter.hx", "PrinterRenamed", 58, 65), + makeReplaceTestEdit("testcases/classes/Printer.hx", "PrinterRenamed", 345, 352), + makeReplaceTestEdit("testcases/classes/Printer.hx", "PrinterRenamed", 507, 514), + makeReplaceTestEdit("testcases/classes/Printer.hx", "PrinterRenamed", 661, 668), + makeReplaceTestEdit("testcases/classes/Printer.hx", "PrinterRenamed", 892, 899), + makeReplaceTestEdit("testcases/classes/Printer.hx", "PrinterRenamed", 1038, 1045), + makeReplaceTestEdit("testcases/classes/Printer.hx", "PrinterRenamed", 1181, 1188), + makeReplaceTestEdit("testcases/classes/import.hx", "PrinterRenamed", 88, 95), + makeReplaceTestEdit("testcases/classes/pack/UsePrinter.hx", "PrinterRenamed", 38, 45), ]; - refactorAndCheck({fileName: "testcases/classes/Printer.hx", toName: "PrinterRenamed", pos: 1222}, edits, async); + checkRename({fileName: "testcases/classes/Printer.hx", toName: "PrinterRenamed", pos: 1184}, edits, async); } public function testRenameFooX(async:Async) { var edits:Array = [makeReplaceTestEdit("testcases/classes/Foo.hx", "xRenamed", 218, 219),]; - refactorAndCheck({fileName: "testcases/classes/Foo.hx", toName: "xRenamed", pos: 218}, edits, async); + checkRename({fileName: "testcases/classes/Foo.hx", toName: "xRenamed", pos: 218}, edits, async); } public function testRenameFooResp(async:Async) { @@ -327,6 +327,6 @@ class ClassTest extends TestBase { makeReplaceTestEdit("testcases/classes/Foo.hx", "respRenamed", 170, 174), makeReplaceTestEdit("testcases/classes/Foo.hx", "respRenamed", 182, 186), ]; - refactorAndCheck({fileName: "testcases/classes/Foo.hx", toName: "respRenamed", pos: 173}, edits, async); + checkRename({fileName: "testcases/classes/Foo.hx", toName: "respRenamed", pos: 173}, edits, async); } } diff --git a/test/refactor/EnumTest.hx b/test/refactor/rename/RenameEnumTest.hx similarity index 80% rename from test/refactor/EnumTest.hx rename to test/refactor/rename/RenameEnumTest.hx index 06c04ec..cfd888a 100644 --- a/test/refactor/EnumTest.hx +++ b/test/refactor/rename/RenameEnumTest.hx @@ -1,6 +1,6 @@ -package refactor; +package refactor.rename; -class EnumTest extends TestBase { +class RenameEnumTest extends RenameTestBase { function setupClass() { setupTestSources(["testcases/enums"]); } @@ -14,7 +14,7 @@ class EnumTest extends TestBase { makeReplaceTestEdit("testcases/enums/Main.hx", "IdentType", 480, 494), makeReplaceTestEdit("testcases/enums/Main.hx", "IdentType", 2012, 2026), ]; - refactorAndCheck({fileName: "testcases/enums/IdentifierType.hx", toName: "IdentType", pos: 30}, edits, async); + checkRename({fileName: "testcases/enums/IdentifierType.hx", toName: "IdentType", pos: 30}, edits, async); } public function testRenameScopedLocal(async:Async) { @@ -26,7 +26,7 @@ class EnumTest extends TestBase { makeReplaceTestEdit("testcases/enums/Main.hx", "LocalScopeVar", 1734, 1745), makeReplaceTestEdit("testcases/enums/Main.hx", "LocalScopeVar", 1869, 1880), ]; - refactorAndCheck({fileName: "testcases/enums/IdentifierType.hx", toName: "LocalScopeVar", pos: 77}, edits, async); + checkRename({fileName: "testcases/enums/IdentifierType.hx", toName: "LocalScopeVar", pos: 77}, edits, async); } public function testRenameCall(async:Async) { @@ -35,7 +35,7 @@ class EnumTest extends TestBase { makeReplaceTestEdit("testcases/enums/Main.hx", "FunctionCall", 178, 182), makeReplaceTestEdit("testcases/enums/Main.hx", "FunctionCall", 211, 215), ]; - refactorAndCheck({fileName: "testcases/enums/IdentifierType.hx", toName: "FunctionCall", pos: 55}, edits, async); + checkRename({fileName: "testcases/enums/IdentifierType.hx", toName: "FunctionCall", pos: 55}, edits, async); } public function testRenameScopedGlobal(async:Async) { @@ -47,6 +47,6 @@ class EnumTest extends TestBase { makeReplaceTestEdit("testcases/enums/Main.hx", "GlobalScopeVar", 1798, 1810), makeReplaceTestEdit("testcases/enums/Main.hx", "GlobalScopeVar", 1904, 1916), ]; - refactorAndCheck({fileName: "testcases/enums/IdentifierType.hx", toName: "GlobalScopeVar", pos: 103}, edits, async); + checkRename({fileName: "testcases/enums/IdentifierType.hx", toName: "GlobalScopeVar", pos: 103}, edits, async); } } diff --git a/test/refactor/ImportAliasTest.hx b/test/refactor/rename/RenameImportAliasTest.hx similarity index 69% rename from test/refactor/ImportAliasTest.hx rename to test/refactor/rename/RenameImportAliasTest.hx index ec76745..03c6ff7 100644 --- a/test/refactor/ImportAliasTest.hx +++ b/test/refactor/rename/RenameImportAliasTest.hx @@ -1,6 +1,6 @@ -package refactor; +package refactor.rename; -class ImportAliasTest extends TestBase { +class RenameImportAliasTest extends RenameTestBase { function setupClass() { setupTestSources(["testcases/importalias"]); } @@ -11,7 +11,7 @@ class ImportAliasTest extends TestBase { makeReplaceTestEdit("testcases/importalias/import.hx", "typeof", 45, 53), makeReplaceTestEdit("testcases/importalias/pack/Child.hx", "typeof", 89, 97), ]; - refactorAndCheck({fileName: "testcases/importalias/import.hx", toName: "typeof", pos: 47}, edits, async); + checkRename({fileName: "testcases/importalias/import.hx", toName: "typeof", pos: 47}, edits, async); } public function testRenameMainAlias(async:Async) { @@ -19,6 +19,6 @@ class ImportAliasTest extends TestBase { makeReplaceTestEdit("testcases/importalias/Main.hx", "typeof", 45, 57), makeReplaceTestEdit("testcases/importalias/Main.hx", "typeof", 161, 173), ]; - refactorAndCheck({fileName: "testcases/importalias/Main.hx", toName: "typeof", pos: 50}, edits, async); + checkRename({fileName: "testcases/importalias/Main.hx", toName: "typeof", pos: 50}, edits, async); } } diff --git a/test/refactor/InterfaceTest.hx b/test/refactor/rename/RenameInterfaceTest.hx similarity index 82% rename from test/refactor/InterfaceTest.hx rename to test/refactor/rename/RenameInterfaceTest.hx index 3f178e5..9025771 100644 --- a/test/refactor/InterfaceTest.hx +++ b/test/refactor/rename/RenameInterfaceTest.hx @@ -1,6 +1,6 @@ -package refactor; +package refactor.rename; -class InterfaceTest extends TestBase { +class RenameInterfaceTest extends RenameTestBase { function setupClass() { setupTestSources(["testcases/interfaces"]); } @@ -13,7 +13,7 @@ class InterfaceTest extends TestBase { makeReplaceTestEdit("testcases/interfaces/pack/sub2/ISubInterface.hx", "MyInterface", 49, 59), makeReplaceTestEdit("testcases/interfaces/pack/sub2/ISubInterface.hx", "MyInterface", 94, 104), ]; - refactorAndCheck({fileName: "testcases/interfaces/IInterface.hx", toName: "MyInterface", pos: 36}, edits, async); + checkRename({fileName: "testcases/interfaces/IInterface.hx", toName: "MyInterface", pos: 36}, edits, async); } public function testMoveInterfacePackage(async:Async) { @@ -23,7 +23,7 @@ class InterfaceTest extends TestBase { makeReplaceTestEdit("testcases/interfaces/IInterface.hx", "interfaces.pack.sub2", 8, 18), makeReplaceTestEdit("testcases/interfaces/pack/sub2/ISubInterface.hx", "interfaces.pack.sub2.IInterface", 38, 59), ]; - refactorAndCheck({fileName: "testcases/interfaces/IInterface.hx", toName: "interfaces.pack.sub2", pos: 13}, edits, async); + checkRename({fileName: "testcases/interfaces/IInterface.hx", toName: "interfaces.pack.sub2", pos: 13}, edits, async); } public function testRenameInterfaceFieldDoSomething(async:Async) { @@ -35,7 +35,7 @@ class InterfaceTest extends TestBase { makeReplaceTestEdit("testcases/interfaces/pack/SecondChild.hx", "doIt", 260, 271), makeReplaceTestEdit("testcases/interfaces/pack/SecondChild.hx", "doIt", 392, 403), ]; - refactorAndCheck({fileName: "testcases/interfaces/IInterface.hx", toName: "doIt", pos: 78}, edits, async); + checkRename({fileName: "testcases/interfaces/IInterface.hx", toName: "doIt", pos: 78}, edits, async); } public function testRenameInterfaceFieldDoSomethingElse(async:Async) { @@ -50,7 +50,7 @@ class InterfaceTest extends TestBase { makeReplaceTestEdit("testcases/interfaces/pack/SecondChild.hx", "doMore", 283, 298), makeReplaceTestEdit("testcases/interfaces/pack/SecondChild.hx", "doMore", 415, 430), ]; - refactorAndCheck({fileName: "testcases/interfaces/IInterface.hx", toName: "doMore", pos: 110}, edits, async); + checkRename({fileName: "testcases/interfaces/IInterface.hx", toName: "doMore", pos: 110}, edits, async); } public function testRenameAnotherInterfaceFieldDoNothing(async:Async) { @@ -60,7 +60,7 @@ class InterfaceTest extends TestBase { makeReplaceTestEdit("testcases/interfaces/pack/sub/AnotherClass.hx", "doIt", 211, 220), makeReplaceTestEdit("testcases/interfaces/pack/sub/IAnotherInterface.hx", "doIt", 134, 143), ]; - refactorAndCheck({fileName: "testcases/interfaces/pack/sub/IAnotherInterface.hx", toName: "doIt", pos: 139}, edits, async); + checkRename({fileName: "testcases/interfaces/pack/sub/IAnotherInterface.hx", toName: "doIt", pos: 139}, edits, async); } public function testRenameAnotherInterfaceFieldDoSomething(async:Async) { @@ -68,7 +68,7 @@ class InterfaceTest extends TestBase { makeReplaceTestEdit("testcases/interfaces/pack/sub/AnotherClass.hx", "doIt", 137, 148), makeReplaceTestEdit("testcases/interfaces/pack/sub/IAnotherInterface.hx", "doIt", 70, 81), ]; - refactorAndCheck({fileName: "testcases/interfaces/pack/sub/IAnotherInterface.hx", toName: "doIt", pos: 78}, edits, async); + checkRename({fileName: "testcases/interfaces/pack/sub/IAnotherInterface.hx", toName: "doIt", pos: 78}, edits, async); } public function testRenameAnotherInterfaceFieldSomeVar(async:Async) { @@ -81,6 +81,6 @@ class InterfaceTest extends TestBase { makeReplaceTestEdit("testcases/interfaces/pack/sub/AnotherClass.hx", "state", 307, 315), makeReplaceTestEdit("testcases/interfaces/pack/sub/IAnotherInterface.hx", "state", 157, 165), ]; - refactorAndCheck({fileName: "testcases/interfaces/pack/sub/IAnotherInterface.hx", toName: "state", pos: 160}, edits, async); + checkRename({fileName: "testcases/interfaces/pack/sub/IAnotherInterface.hx", toName: "state", pos: 160}, edits, async); } } diff --git a/test/refactor/ModuleLevelStaticTest.hx b/test/refactor/rename/RenameModuleLevelStaticTest.hx similarity index 78% rename from test/refactor/ModuleLevelStaticTest.hx rename to test/refactor/rename/RenameModuleLevelStaticTest.hx index efc1ef3..4505043 100644 --- a/test/refactor/ModuleLevelStaticTest.hx +++ b/test/refactor/rename/RenameModuleLevelStaticTest.hx @@ -1,6 +1,6 @@ -package refactor; +package refactor.rename; -class ModuleLevelStaticTest extends TestBase { +class RenameModuleLevelStaticTest extends RenameTestBase { function setupClass() { setupTestSources(["testcases/modulelevelstatics"]); } @@ -14,7 +14,7 @@ class ModuleLevelStaticTest extends TestBase { makeReplaceTestEdit("testcases/modulelevelstatics/pack/Command.hx", "logText", 163, 175), makeReplaceTestEdit("testcases/modulelevelstatics/pack/Command.hx", "logText", 208, 220), ]; - refactorAndCheck({fileName: "testcases/modulelevelstatics/StaticFuncs.hx", toName: "logText", pos: 177}, edits, async); + checkRename({fileName: "testcases/modulelevelstatics/StaticFuncs.hx", toName: "logText", pos: 177}, edits, async); } public function testRenameSomeVar(async:Async) { @@ -23,6 +23,6 @@ class ModuleLevelStaticTest extends TestBase { makeReplaceTestEdit("testcases/modulelevelstatics/StaticFuncs.hx", "logText", 134, 141), makeReplaceTestEdit("testcases/modulelevelstatics/pack/Command.hx", "logText", 185, 192), ]; - refactorAndCheck({fileName: "testcases/modulelevelstatics/StaticFuncs.hx", toName: "logText", pos: 137}, edits, async); + checkRename({fileName: "testcases/modulelevelstatics/StaticFuncs.hx", toName: "logText", pos: 137}, edits, async); } } diff --git a/test/refactor/PackageTest.hx b/test/refactor/rename/RenamePackageTest.hx similarity index 79% rename from test/refactor/PackageTest.hx rename to test/refactor/rename/RenamePackageTest.hx index 1ac3d5a..c118bd1 100644 --- a/test/refactor/PackageTest.hx +++ b/test/refactor/rename/RenamePackageTest.hx @@ -1,6 +1,6 @@ -package refactor; +package refactor.rename; -class PackageTest extends TestBase { +class RenamePackageTest extends RenameTestBase { function setupClass() { setupTestSources(["testcases/packages"]); } @@ -14,7 +14,7 @@ class PackageTest extends TestBase { makeReplaceTestEdit("testcases/packages/Types.hx", "packages.sub", 8, 16), ]; - refactorAndCheck({fileName: "testcases/packages/Types.hx", toName: "packages.sub", pos: 12}, edits, async); + checkRename({fileName: "testcases/packages/Types.hx", toName: "packages.sub", pos: 12}, edits, async); } public function testRenameMoreTypesModul(async:Async) { @@ -23,7 +23,7 @@ class PackageTest extends TestBase { makeMoveTestEdit("testcases/packages/MoreTypes.hx", "testcases/packages/sub/MoreTypes.hx"), makeReplaceTestEdit("testcases/packages/MoreTypes.hx", "packages.sub", 8, 16), ]; - refactorAndCheck({fileName: "testcases/packages/MoreTypes.hx", toName: "packages.sub", pos: 12}, edits, async); + checkRename({fileName: "testcases/packages/MoreTypes.hx", toName: "packages.sub", pos: 12}, edits, async); } public function testRenameOtherTypesModul(async:Async) { @@ -34,7 +34,7 @@ class PackageTest extends TestBase { makeMoveTestEdit("testcases/packages/OtherTypes.hx", "testcases/packages/sub/OtherTypes.hx"), makeReplaceTestEdit("testcases/packages/OtherTypes.hx", "packages.sub", 8, 16), ]; - refactorAndCheck({fileName: "testcases/packages/OtherTypes.hx", toName: "packages.sub", pos: 12}, edits, async); + checkRename({fileName: "testcases/packages/OtherTypes.hx", toName: "packages.sub", pos: 12}, edits, async); } public function testRenameHelperTypesModul(async:Async) { @@ -43,7 +43,7 @@ class PackageTest extends TestBase { makeReplaceTestEdit("testcases/packages/HelperTypes.hx", "packages.sub", 8, 16), makeReplaceTestEdit("testcases/packages/import.hx", "packages.sub.HelperTypes", 26, 46), ]; - refactorAndCheck({fileName: "testcases/packages/HelperTypes.hx", toName: "packages.sub", pos: 12}, edits, async); + checkRename({fileName: "testcases/packages/HelperTypes.hx", toName: "packages.sub", pos: 12}, edits, async); } public function testRenameHelperECTypesModul(async:Async) { @@ -52,6 +52,6 @@ class PackageTest extends TestBase { makeMoveTestEdit("testcases/packages/kages/ECTypes.hx", "testcases/packages/packages/sub/ECTypes.hx"), makeReplaceTestEdit("testcases/packages/kages/ECTypes.hx", "packages.sub", 8, 13), ]; - refactorAndCheck({fileName: "testcases/packages/kages/ECTypes.hx", toName: "packages.sub", pos: 10}, edits, async); + checkRename({fileName: "testcases/packages/kages/ECTypes.hx", toName: "packages.sub", pos: 10}, edits, async); } } diff --git a/test/refactor/ScopedLocalTest.hx b/test/refactor/rename/RenameScopedLocalTest.hx similarity index 77% rename from test/refactor/ScopedLocalTest.hx rename to test/refactor/rename/RenameScopedLocalTest.hx index b247b7c..19f46b7 100644 --- a/test/refactor/ScopedLocalTest.hx +++ b/test/refactor/rename/RenameScopedLocalTest.hx @@ -1,6 +1,6 @@ -package refactor; +package refactor.rename; -class ScopedLocalTest extends TestBase { +class RenameScopedLocalTest extends RenameTestBase { function setupClass() { setupTestSources(["testcases/scopedlocal"]); } @@ -19,7 +19,7 @@ class ScopedLocalTest extends TestBase { makeReplaceTestEdit("testcases/scopedlocal/Refactor.hx", "refactorContext", 1806, 1813), makeReplaceTestEdit("testcases/scopedlocal/Refactor.hx", "refactorContext", 2017, 2024), ]; - refactorAndCheck({fileName: "testcases/scopedlocal/Refactor.hx", toName: "refactorContext", pos: 463}, edits, async); + checkRename({fileName: "testcases/scopedlocal/Refactor.hx", toName: "refactorContext", pos: 463}, edits, async); } public function testRenameScopeEndCaseCapture(async:Async) { @@ -27,7 +27,7 @@ class ScopedLocalTest extends TestBase { makeReplaceTestEdit("testcases/scopedlocal/Refactor.hx", "endPos", 1964, 1972), makeReplaceTestEdit("testcases/scopedlocal/Refactor.hx", "endPos", 2044, 2052), ]; - refactorAndCheck({fileName: "testcases/scopedlocal/Refactor.hx", toName: "endPos", pos: 1968}, edits, async); + checkRename({fileName: "testcases/scopedlocal/Refactor.hx", toName: "endPos", pos: 1968}, edits, async); } public function testRenamePackNameParameter(async:Async) { @@ -35,7 +35,7 @@ class ScopedLocalTest extends TestBase { makeReplaceTestEdit("testcases/scopedlocal/StringInterpolation.hx", "packageName", 171, 179), makeReplaceTestEdit("testcases/scopedlocal/StringInterpolation.hx", "packageName", 276, 284), ]; - refactorAndCheck({fileName: "testcases/scopedlocal/StringInterpolation.hx", toName: "packageName", pos: 175}, edits, async); + checkRename({fileName: "testcases/scopedlocal/StringInterpolation.hx", toName: "packageName", pos: 175}, edits, async); } public function testRenameBaseTypeParameter(async:Async) { @@ -45,7 +45,7 @@ class ScopedLocalTest extends TestBase { makeReplaceTestEdit("testcases/scopedlocal/StringInterpolation.hx", "base", 1124, 1132), makeReplaceTestEdit("testcases/scopedlocal/StringInterpolation.hx", "base", 1428, 1436), ]; - refactorAndCheck({fileName: "testcases/scopedlocal/StringInterpolation.hx", toName: "base", pos: 191}, edits, async); + checkRename({fileName: "testcases/scopedlocal/StringInterpolation.hx", toName: "base", pos: 191}, edits, async); } public function testRenameBaseTypeParameterFromUse(async:Async) { @@ -62,7 +62,7 @@ class ScopedLocalTest extends TestBase { makeReplaceTestEdit("testcases/scopedlocal/Refactor.hx", "refactorContext", 1806, 1813), makeReplaceTestEdit("testcases/scopedlocal/Refactor.hx", "refactorContext", 2017, 2024), ]; - refactorAndCheck({fileName: "testcases/scopedlocal/Refactor.hx", toName: "refactorContext", pos: 2020}, edits, async); + checkRename({fileName: "testcases/scopedlocal/Refactor.hx", toName: "refactorContext", pos: 2020}, edits, async); } public function testRenameFileNameParameterWithStructureFields(async:Async) { @@ -71,7 +71,7 @@ class ScopedLocalTest extends TestBase { makeReplaceTestEdit("testcases/scopedlocal/Structure.hx", "file", 182, 190), makeReplaceTestEdit("testcases/scopedlocal/Structure.hx", "file", 229, 237), ]; - refactorAndCheck({fileName: "testcases/scopedlocal/Structure.hx", toName: "file", pos: 102}, edits, async); + checkRename({fileName: "testcases/scopedlocal/Structure.hx", toName: "file", pos: 102}, edits, async); } public function testRenameFooVar(async:Async) { @@ -80,7 +80,7 @@ class ScopedLocalTest extends TestBase { makeReplaceTestEdit("testcases/scopedlocal/Refactor.hx", "file", 2139, 2142), makeReplaceTestEdit("testcases/scopedlocal/Refactor.hx", "file", 2187, 2190), ]; - refactorAndCheck({fileName: "testcases/scopedlocal/Refactor.hx", toName: "file", pos: 2105}, edits, async); + checkRename({fileName: "testcases/scopedlocal/Refactor.hx", toName: "file", pos: 2105}, edits, async); } public function testRenameFooArray(async:Async) { @@ -89,7 +89,7 @@ class ScopedLocalTest extends TestBase { makeReplaceTestEdit("testcases/scopedlocal/Refactor.hx", "file", 2139, 2142), makeReplaceTestEdit("testcases/scopedlocal/Refactor.hx", "file", 2187, 2190), ]; - refactorAndCheck({fileName: "testcases/scopedlocal/Refactor.hx", toName: "file", pos: 2140}, edits, async); + checkRename({fileName: "testcases/scopedlocal/Refactor.hx", toName: "file", pos: 2140}, edits, async); } public function testRenameFooItem(async:Async) { @@ -97,7 +97,7 @@ class ScopedLocalTest extends TestBase { makeReplaceTestEdit("testcases/scopedlocal/Refactor.hx", "file", 2132, 2135), makeReplaceTestEdit("testcases/scopedlocal/Refactor.hx", "file", 2155, 2158), ]; - refactorAndCheck({fileName: "testcases/scopedlocal/Refactor.hx", toName: "file", pos: 2133}, edits, async); + checkRename({fileName: "testcases/scopedlocal/Refactor.hx", toName: "file", pos: 2133}, edits, async); } public function testRenameFooItem2(async:Async) { @@ -105,7 +105,7 @@ class ScopedLocalTest extends TestBase { makeReplaceTestEdit("testcases/scopedlocal/Refactor.hx", "file", 2173, 2176), makeReplaceTestEdit("testcases/scopedlocal/Refactor.hx", "file", 2203, 2206), ]; - refactorAndCheck({fileName: "testcases/scopedlocal/Refactor.hx", toName: "file", pos: 2174}, edits, async); + checkRename({fileName: "testcases/scopedlocal/Refactor.hx", toName: "file", pos: 2174}, edits, async); } public function testRenameValItem(async:Async) { @@ -113,7 +113,7 @@ class ScopedLocalTest extends TestBase { makeReplaceTestEdit("testcases/scopedlocal/Refactor.hx", "file", 2180, 2183), makeReplaceTestEdit("testcases/scopedlocal/Refactor.hx", "file", 2218, 2221), ]; - refactorAndCheck({fileName: "testcases/scopedlocal/Refactor.hx", toName: "file", pos: 2181}, edits, async); + checkRename({fileName: "testcases/scopedlocal/Refactor.hx", toName: "file", pos: 2181}, edits, async); } public function testRenameParameterValue(async:Async) { @@ -121,7 +121,7 @@ class ScopedLocalTest extends TestBase { makeReplaceTestEdit("testcases/scopedlocal/Refactor.hx", "data", 2271, 2276), makeReplaceTestEdit("testcases/scopedlocal/Refactor.hx", "data", 2300, 2305), ]; - refactorAndCheck({fileName: "testcases/scopedlocal/Refactor.hx", toName: "data", pos: 2274}, edits, async); + checkRename({fileName: "testcases/scopedlocal/Refactor.hx", toName: "data", pos: 2274}, edits, async); } public function testRenameParameterValue2(async:Async) { @@ -129,7 +129,7 @@ class ScopedLocalTest extends TestBase { makeReplaceTestEdit("testcases/scopedlocal/Refactor.hx", "data", 2426, 2431), makeReplaceTestEdit("testcases/scopedlocal/Refactor.hx", "data", 2453, 2458), ]; - refactorAndCheck({fileName: "testcases/scopedlocal/Refactor.hx", toName: "data", pos: 2429}, edits, async); + checkRename({fileName: "testcases/scopedlocal/Refactor.hx", toName: "data", pos: 2429}, edits, async); } public function testRenameLocalValue(async:Async) { @@ -137,7 +137,7 @@ class ScopedLocalTest extends TestBase { makeReplaceTestEdit("testcases/scopedlocal/Refactor.hx", "data", 2290, 2295), makeReplaceTestEdit("testcases/scopedlocal/Refactor.hx", "data", 2323, 2328), ]; - refactorAndCheck({fileName: "testcases/scopedlocal/Refactor.hx", toName: "data", pos: 2291}, edits, async); + checkRename({fileName: "testcases/scopedlocal/Refactor.hx", toName: "data", pos: 2291}, edits, async); } public function testRenameLocalValue2(async:Async) { @@ -145,7 +145,7 @@ class ScopedLocalTest extends TestBase { makeReplaceTestEdit("testcases/scopedlocal/Refactor.hx", "data", 2290, 2295), makeReplaceTestEdit("testcases/scopedlocal/Refactor.hx", "data", 2323, 2328), ]; - refactorAndCheck({fileName: "testcases/scopedlocal/Refactor.hx", toName: "data", pos: 2326}, edits, async); + checkRename({fileName: "testcases/scopedlocal/Refactor.hx", toName: "data", pos: 2326}, edits, async); } public function testRenameLocalValue3(async:Async) { @@ -153,7 +153,7 @@ class ScopedLocalTest extends TestBase { makeReplaceTestEdit("testcases/scopedlocal/Refactor.hx", "data", 2445, 2450), makeReplaceTestEdit("testcases/scopedlocal/Refactor.hx", "data", 2477, 2482), ]; - refactorAndCheck({fileName: "testcases/scopedlocal/Refactor.hx", toName: "data", pos: 2447}, edits, async); + checkRename({fileName: "testcases/scopedlocal/Refactor.hx", toName: "data", pos: 2447}, edits, async); } public function testRenameLocalValue4(async:Async) { @@ -161,7 +161,7 @@ class ScopedLocalTest extends TestBase { makeReplaceTestEdit("testcases/scopedlocal/Refactor.hx", "data", 2445, 2450), makeReplaceTestEdit("testcases/scopedlocal/Refactor.hx", "data", 2477, 2482), ]; - refactorAndCheck({fileName: "testcases/scopedlocal/Refactor.hx", toName: "data", pos: 2479}, edits, async); + checkRename({fileName: "testcases/scopedlocal/Refactor.hx", toName: "data", pos: 2479}, edits, async); } public function testRenameFieldValue(async:Async) { @@ -172,6 +172,6 @@ class ScopedLocalTest extends TestBase { makeReplaceTestEdit("testcases/scopedlocal/Refactor.hx", "data", 2390, 2395), makeReplaceTestEdit("testcases/scopedlocal/Refactor.hx", "data", 2467, 2472), ]; - refactorAndCheck({fileName: "testcases/scopedlocal/Refactor.hx", toName: "data", pos: 2239}, edits, async); + checkRename({fileName: "testcases/scopedlocal/Refactor.hx", toName: "data", pos: 2239}, edits, async); } } diff --git a/test/refactor/rename/RenameTestBase.hx b/test/refactor/rename/RenameTestBase.hx new file mode 100644 index 0000000..2f6195b --- /dev/null +++ b/test/refactor/rename/RenameTestBase.hx @@ -0,0 +1,85 @@ +package refactor.rename; + +import haxe.Exception; +import haxe.PosInfos; +import js.lib.Promise; +import utest.Async; +import refactor.RefactorResult; +import refactor.Rename; +import refactor.TestEditableDocument; + +class RenameTestBase extends TestBase { + function checkRename(what:RenameWhat, edits:Array, async:Async, ?pos:PosInfos) { + try { + doCanRename(what, edits, pos).catchError(function(failure) { + Assert.fail('$failure', pos); + }).finally(function() { + async.done(); + }); + } catch (e:Exception) { + Assert.fail(e.toString(), pos); + } + } + + function failCanRename(what:RenameWhat, expected:String, async:Async, ?pos:PosInfos) { + try { + doCanRename(what, [], pos).then(function(success:RefactorResult) { + Assert.equals(expected, PrintHelper.printRefactorResult(success), pos); + }).catchError(function(failure) { + Assert.equals(expected, '$failure', pos); + }).finally(function() { + async.done(); + }); + } catch (e:Exception) { + Assert.fail(e.toString(), pos); + } + } + + function failRename(what:RenameWhat, expected:String, async:Async, ?pos:PosInfos) { + try { + doRename(what, [], pos).then(function(success:RefactorResult) { + Assert.equals(expected, PrintHelper.printRefactorResult(success), pos); + }).catchError(function(failure) { + Assert.equals(expected, '$failure', pos); + }).finally(function() { + async.done(); + }); + } catch (e:Exception) { + Assert.fail(e.toString(), pos); + } + } + + function doCanRename(what:RenameWhat, edits:Array, pos:PosInfos):Promise { + var editList:TestEditList = new TestEditList(); + return Rename.canRename({ + nameMap: usageContext.nameMap, + fileList: usageContext.fileList, + typeList: usageContext.typeList, + what: what, + verboseLog: function(text:String, ?pos:PosInfos) { + Sys.println('${pos.fileName}:${pos.lineNumber}: $text'); + }, + typer: null + }).then(function(success:CanRenameResult) { + return doRename(what, edits, pos); + }); + } + + function doRename(what:RenameWhat, edits:Array, pos:PosInfos):Promise { + var editList:TestEditList = new TestEditList(); + return Rename.rename({ + nameMap: usageContext.nameMap, + fileList: usageContext.fileList, + typeList: usageContext.typeList, + what: what, + forRealExecute: true, + docFactory: (fileName) -> editList.newDoc(fileName), + verboseLog: function(text:String, ?pos:PosInfos) { + Sys.println('${pos.fileName}:${pos.lineNumber}: $text'); + }, + typer: null + }).then(function(success:RefactorResult) { + return assertEdits(success, editList, edits, pos); + }); + } +} diff --git a/test/refactor/TypedefTest.hx b/test/refactor/rename/RenameTypedefTest.hx similarity index 78% rename from test/refactor/TypedefTest.hx rename to test/refactor/rename/RenameTypedefTest.hx index f761ed7..e05bbab 100644 --- a/test/refactor/TypedefTest.hx +++ b/test/refactor/rename/RenameTypedefTest.hx @@ -1,6 +1,6 @@ -package refactor; +package refactor.rename; -class TypedefTest extends TestBase { +class RenameTypedefTest extends RenameTestBase { function setupClass() { setupTestSources(["testcases/typedefs"]); } @@ -12,7 +12,7 @@ class TypedefTest extends TestBase { makeReplaceTestEdit("testcases/typedefs/Types.hx", "FilePos", 59, 72), makeReplaceTestEdit("testcases/typedefs/Types.hx", "FilePos", 166, 179), ]; - refactorAndCheck({fileName: "testcases/typedefs/Types.hx", toName: "FilePos", pos: 66}, edits, async); + checkRename({fileName: "testcases/typedefs/Types.hx", toName: "FilePos", pos: 66}, edits, async); } public function testRenameFilename(async:Async) { @@ -26,7 +26,7 @@ class TypedefTest extends TestBase { makeReplaceTestEdit("testcases/typedefs/Main.hx", "file", 957, 965), makeReplaceTestEdit("testcases/typedefs/Types.hx", "file", 82, 90), ]; - refactorAndCheck({fileName: "testcases/typedefs/Types.hx", toName: "file", pos: 84}, edits, async); + checkRename({fileName: "testcases/typedefs/Types.hx", toName: "file", pos: 84}, edits, async); } public function testRenameFilenameFormObjectLiteral(async:Async) { @@ -40,7 +40,7 @@ class TypedefTest extends TestBase { makeReplaceTestEdit("testcases/typedefs/Main.hx", "file", 957, 965), makeReplaceTestEdit("testcases/typedefs/Types.hx", "file", 82, 90), ]; - refactorAndCheck({fileName: "testcases/typedefs/Main.hx", toName: "file", pos: 305}, edits, async); + checkRename({fileName: "testcases/typedefs/Main.hx", toName: "file", pos: 305}, edits, async); } public function testRenameLine(async:Async) { @@ -49,7 +49,7 @@ class TypedefTest extends TestBase { makeReplaceTestEdit("testcases/typedefs/Main.hx", "lineNumber", 550, 554), makeReplaceTestEdit("testcases/typedefs/Types.hx", "lineNumber", 189, 193), ]; - refactorAndCheck({fileName: "testcases/typedefs/Types.hx", toName: "lineNumber", pos: 191}, edits, async); + checkRename({fileName: "testcases/typedefs/Types.hx", toName: "lineNumber", pos: 191}, edits, async); } public function testRenameLinefromObjectLiteral(async:Async) { @@ -58,7 +58,7 @@ class TypedefTest extends TestBase { makeReplaceTestEdit("testcases/typedefs/Main.hx", "lineNumber", 550, 554), makeReplaceTestEdit("testcases/typedefs/Types.hx", "lineNumber", 189, 193), ]; - refactorAndCheck({fileName: "testcases/typedefs/Main.hx", toName: "lineNumber", pos: 453}, edits, async); + checkRename({fileName: "testcases/typedefs/Main.hx", toName: "lineNumber", pos: 453}, edits, async); } public function testRenameLinefromObjectLiteral2(async:Async) { @@ -67,14 +67,14 @@ class TypedefTest extends TestBase { makeReplaceTestEdit("testcases/typedefs/Main.hx", "lineNumber", 550, 554), makeReplaceTestEdit("testcases/typedefs/Types.hx", "lineNumber", 189, 193), ]; - refactorAndCheck({fileName: "testcases/typedefs/Main.hx", toName: "lineNumber", pos: 552}, edits, async); + checkRename({fileName: "testcases/typedefs/Main.hx", toName: "lineNumber", pos: 552}, edits, async); } public function testRenameTypedefBase(async:Async) { var edits:Array = []; - failCanRefactor({fileName: "testcases/typedefs/Types.hx", toName: "Position", pos: 172}, + failCanRename({fileName: "testcases/typedefs/Types.hx", toName: "Position", pos: 172}, "renaming not supported for IdentifierPos testcases/typedefs/Types.hx@166-179 (TypedefBase)", async); - failRefactor({fileName: "testcases/typedefs/Types.hx", toName: "Position", pos: 172}, + failRename({fileName: "testcases/typedefs/Types.hx", toName: "Position", pos: 172}, "renaming not supported for IdentifierPos testcases/typedefs/Types.hx@166-179 (TypedefBase)", async); } @@ -86,6 +86,6 @@ class TypedefTest extends TestBase { makeReplaceTestEdit("testcases/typedefs/Main.hx", "sourceFolders", 3489, 3508), makeReplaceTestEdit("testcases/typedefs/Types.hx", "sourceFolders", 775, 794), ]; - refactorAndCheck({fileName: "testcases/typedefs/Types.hx", toName: "sourceFolders", pos: 784}, edits, async); + checkRename({fileName: "testcases/typedefs/Types.hx", toName: "sourceFolders", pos: 784}, edits, async); } } diff --git a/testcases/classes/Printer.hx b/testcases/classes/Printer.hx index c542763..37153ea 100644 --- a/testcases/classes/Printer.hx +++ b/testcases/classes/Printer.hx @@ -1,8 +1,7 @@ -package testcases.classes; +package classes; import js.Browser; -import tink.core.Future; -import tink.core.Promise; +import js.lib.Promise; @:expose class PrinterMain { public static function main():Void { @@ -59,3 +58,5 @@ class TextLoader { return Promise.resolve(text); } } + +typedef Future = Any; diff --git a/testcases/classes/import.hx b/testcases/classes/import.hx index 98ad8dd..a58bbe3 100644 --- a/testcases/classes/import.hx +++ b/testcases/classes/import.hx @@ -1,5 +1,5 @@ package classes; import refactor.discover.Identifier; -import testcases.classes.Printer; import classes.*; +import classes.Printer; diff --git a/testcases/classes/pack/UsePrinter.hx b/testcases/classes/pack/UsePrinter.hx index 5679b71..5a996a0 100644 --- a/testcases/classes/pack/UsePrinter.hx +++ b/testcases/classes/pack/UsePrinter.hx @@ -1,6 +1,6 @@ -package testcases.classes.pack; +package classes.pack; -import testcases.classes.Printer; +import classes.Printer; function usingPrinter() { new TextLoader(); From 5f09eba316332882253f030e68e21b79cc692354 Mon Sep 17 00:00:00 2001 From: AlexHaxe Date: Fri, 8 Nov 2024 18:47:35 +0100 Subject: [PATCH 03/59] added support for doc comments in extracted interfaces --- src/refactor/refactor/ExtractInterface.hx | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/src/refactor/refactor/ExtractInterface.hx b/src/refactor/refactor/ExtractInterface.hx index 02156f8..d7dcc4e 100644 --- a/src/refactor/refactor/ExtractInterface.hx +++ b/src/refactor/refactor/ExtractInterface.hx @@ -161,6 +161,16 @@ class ExtractInterface { var isPublic:Bool = false; var hasHint:Bool = false; + if (parent.previousSibling != null) { + switch (parent.previousSibling.tok) { + case Comment(s): + if (s.startsWith("*")) { + expandPos(pos, parent.previousSibling.pos); + } + default: + } + } + for (child in nameToken.children) { switch (child.tok) { case Kwd(KwdPrivate) | Kwd(KwdStatic): @@ -199,6 +209,7 @@ class ExtractInterface { return; } for (child in parent.children) { + trace(child); switch (child.tok) { case Kwd(KwdVar) | Kwd(KwdFinal) | Kwd(KwdFunction): addField(child, fields); From 4163f68888a03ad50c4f7fcfb317caa5f6659fed Mon Sep 17 00:00:00 2001 From: AlexHaxe Date: Sun, 10 Nov 2024 22:55:00 +0100 Subject: [PATCH 04/59] added format flag for edits --- .vscode/settings.json | 2 +- src/refactor/cache/IFileCache.hx | 2 +- src/refactor/discover/NameMap.hx | 5 ++++ src/refactor/edits/Changelist.hx | 16 ++++++------- src/refactor/edits/FileEdit.hx | 4 ++-- src/refactor/refactor/ExtractInterface.hx | 7 +++--- src/refactor/refactor/ExtractType.hx | 23 +++++++++++++------ src/refactor/rename/RenameAnonStructField.hx | 4 ++-- src/refactor/rename/RenameEnumField.hx | 2 +- src/refactor/rename/RenameField.hx | 2 +- src/refactor/rename/RenameHelper.hx | 12 +++++----- src/refactor/rename/RenameImportAlias.hx | 4 ++-- .../rename/RenameModuleLevelStatic.hx | 8 +++---- src/refactor/rename/RenamePackage.hx | 12 +++++----- src/refactor/rename/RenameScopedLocal.hx | 8 +++---- src/refactor/rename/RenameTypeName.hx | 8 ++++--- test/refactor/TestBase.hx | 16 ++++++------- test/refactor/TestEditableDocument.hx | 12 +++++----- test/refactor/refactor/RefactorClassTest.hx | 6 ++--- test/refactor/refactor/RefactorTypedefTest.hx | 2 +- 20 files changed, 86 insertions(+), 69 deletions(-) diff --git a/.vscode/settings.json b/.vscode/settings.json index 744e6f7..7fbb641 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -16,7 +16,7 @@ "parameterNames": true, "parameterTypes": false, "functionReturnTypes": true, - "conditionals": false + "conditionals": true }, "haxe.importsSortOrder": "stdlib -> libs -> project" } diff --git a/src/refactor/cache/IFileCache.hx b/src/refactor/cache/IFileCache.hx index d0f16a7..6e1f36f 100644 --- a/src/refactor/cache/IFileCache.hx +++ b/src/refactor/cache/IFileCache.hx @@ -1,7 +1,7 @@ package refactor.cache; -import refactor.discover.TypeList; import refactor.discover.NameMap; +import refactor.discover.TypeList; interface IFileCache { function save():Void; diff --git a/src/refactor/discover/NameMap.hx b/src/refactor/discover/NameMap.hx index 8f6d1c2..9fb1361 100644 --- a/src/refactor/discover/NameMap.hx +++ b/src/refactor/discover/NameMap.hx @@ -40,6 +40,11 @@ class NameMap { if (list == null) { map.set(key, [identifier]); } else { + for (id in list) { + if (id.pos.fileName == identifier.pos.fileName && id.pos.start == identifier.pos.start) { + return; + } + } list.push(identifier); } } diff --git a/src/refactor/edits/Changelist.hx b/src/refactor/edits/Changelist.hx index b6d29fd..849b539 100644 --- a/src/refactor/edits/Changelist.hx +++ b/src/refactor/edits/Changelist.hx @@ -63,11 +63,11 @@ class Changelist { Sys.println('* delete file "$fileName"'); case Move(newFileName): Sys.println('* rename to file "$newFileName"'); - case InsertText(text, pos): - Sys.println('* insert text "$text" @${pos.start}-${pos.end}'); + case InsertText(text, pos, format): + Sys.println('* insert text "$text" @${pos.start}-${pos.end}${format ? " with format" : ""}'); Sys.println('+++ $text'); - case ReplaceText(text, pos): - Sys.println('* replace text with "$text" @${pos.start}-${pos.end}'); + case ReplaceText(text, pos, format): + Sys.println('* replace text with "$text" @${pos.start}-${pos.end}${format ? " with format" : ""}'); printDiffLines(pos, text); case RemoveText(pos): Sys.println('* remove text @${pos.start}-${pos.end}'); @@ -83,16 +83,16 @@ class Changelist { case CreateFile(_): 0; case DeleteFile(_): 9999; case Move(_): 0; - case InsertText(_, pos): pos.start; - case ReplaceText(_, pos): pos.start; + case InsertText(_, pos, _): pos.start; + case ReplaceText(_, pos, _): pos.start; case RemoveText(pos): pos.start; }; var offsetB:Int = switch (b) { case CreateFile(_): 0; case DeleteFile(_): 9999; case Move(_): 0; - case InsertText(_, pos): pos.start; - case ReplaceText(_, pos): pos.start; + case InsertText(_, pos, _): pos.start; + case ReplaceText(_, pos, _): pos.start; case RemoveText(pos): pos.start; }; if (offsetA < offsetB) { diff --git a/src/refactor/edits/FileEdit.hx b/src/refactor/edits/FileEdit.hx index cfc362b..1bd459d 100644 --- a/src/refactor/edits/FileEdit.hx +++ b/src/refactor/edits/FileEdit.hx @@ -6,7 +6,7 @@ enum FileEdit { CreateFile(newFileName:String); DeleteFile(fileName:String); Move(newFileName:String); - ReplaceText(text:String, pos:IdentifierPos); - InsertText(text:String, pos:IdentifierPos); + ReplaceText(text:String, pos:IdentifierPos, format:Bool); + InsertText(text:String, pos:IdentifierPos, format:Bool); RemoveText(pos:IdentifierPos); } diff --git a/src/refactor/refactor/ExtractInterface.hx b/src/refactor/refactor/ExtractInterface.hx index d7dcc4e..619a741 100644 --- a/src/refactor/refactor/ExtractInterface.hx +++ b/src/refactor/refactor/ExtractInterface.hx @@ -36,11 +36,13 @@ class ExtractInterface { final fieldDefinition:String = makeFields(extractData, context, fields); final interfaceText:String = 'interface ${extractData.newTypeName} {\n' + fieldDefinition + "}"; - changelist.addChange(extractData.newFileName, InsertText(fileHeader + interfaceText, {fileName: extractData.newFileName, start: 0, end: 0}), null); + changelist.addChange(extractData.newFileName, InsertText(fileHeader + interfaceText, {fileName: extractData.newFileName, start: 0, end: 0}, true), + null); final implementsText:String = ' implements ${extractData.newTypeName}'; final pos:Position = findImplementsPos(extractData); - changelist.addChange(extractData.srcFile.name, InsertText(implementsText, {fileName: extractData.srcFile.name, start: pos.max, end: pos.max}), null); + changelist.addChange(extractData.srcFile.name, InsertText(implementsText, {fileName: extractData.srcFile.name, start: pos.max, end: pos.max}, false), + null); return Promise.resolve(changelist.execute()); } @@ -209,7 +211,6 @@ class ExtractInterface { return; } for (child in parent.children) { - trace(child); switch (child.tok) { case Kwd(KwdVar) | Kwd(KwdFinal) | Kwd(KwdFunction): addField(child, fields); diff --git a/src/refactor/refactor/ExtractType.hx b/src/refactor/refactor/ExtractType.hx index f772c57..0e6fa1e 100644 --- a/src/refactor/refactor/ExtractType.hx +++ b/src/refactor/refactor/ExtractType.hx @@ -47,7 +47,7 @@ class ExtractType { changelist.addChange(extractData.newFileName, CreateFile(extractData.newFileName), null); // copy file header, type and doc comment into new file - changelist.addChange(extractData.newFileName, InsertText(fileHeader + typeText, {fileName: extractData.newFileName, start: 0, end: 0}), null); + changelist.addChange(extractData.newFileName, InsertText(fileHeader + typeText, {fileName: extractData.newFileName, start: 0, end: 0}, true), null); // find all places using type and update their imports findImportLocations(context, extractData, changelist); @@ -129,13 +129,22 @@ class ExtractType { if (token == null) { return null; } - return switch (token.tok) { + switch (token.tok) { case Kwd(KwdAbstract) | Kwd(KwdClass) | Kwd(KwdEnum) | Kwd(KwdInterface) | Kwd(KwdTypedef): - token; + return token; case Root: - null; + return null; + default: + } + var token = token.parent; + if (token == null) { + return null; + } + switch (token.tok) { + case Kwd(KwdAbstract) | Kwd(KwdClass) | Kwd(KwdEnum) | Kwd(KwdInterface) | Kwd(KwdTypedef): + return token; default: - findTypeToken(token.parent); + return null; } } @@ -181,7 +190,7 @@ class ExtractType { final newFullName = oldPackageName + "." + extractData.name; var allUses:Array = context.nameMap.getIdentifiers(oldFullName); for (use in allUses) { - changelist.addChange(use.file.name, ReplaceText(newFullName, use.pos), use); + changelist.addChange(use.file.name, ReplaceText(newFullName, use.pos, false), use); } allUses = context.nameMap.getIdentifiers(extractData.name); var needsImport:Array = []; @@ -199,7 +208,7 @@ class ExtractType { final importNewModule = "import " + newFullName + ";\n"; for (file in needsImport) { var pos:IdentifierPos = {fileName: file.name, start: file.importInsertPos, end: file.importInsertPos}; - changelist.addChange(file.name, InsertText(importNewModule, pos), null); + changelist.addChange(file.name, InsertText(importNewModule, pos, false), null); } } } diff --git a/src/refactor/rename/RenameAnonStructField.hx b/src/refactor/rename/RenameAnonStructField.hx index f110063..b934062 100644 --- a/src/refactor/rename/RenameAnonStructField.hx +++ b/src/refactor/rename/RenameAnonStructField.hx @@ -13,7 +13,7 @@ class RenameAnonStructField { fields:Array):Promise { var changelist:Changelist = new Changelist(context); - changelist.addChange(identifier.pos.fileName, ReplaceText(context.what.toName, identifier.pos), identifier); + changelist.addChange(identifier.pos.fileName, ReplaceText(context.what.toName, identifier.pos, false), identifier); return renameFieldsOfType(context, changelist, identifier.defineType, fields, identifier.name).then(function(result):RefactorResult { return changelist.execute(); @@ -60,7 +60,7 @@ class RenameAnonStructField { continue; case Global | SamePackage | Imported | ImportedWithAlias(_) | StarImported: } - changelist.addChange(use.pos.fileName, ReplaceText(context.what.toName, use.pos), use); + changelist.addChange(use.pos.fileName, ReplaceText(context.what.toName, use.pos, false), use); } promises.push(findAllExtending(context, changelist, type, fields, fromName)); diff --git a/src/refactor/rename/RenameEnumField.hx b/src/refactor/rename/RenameEnumField.hx index 9406d63..047f8d1 100644 --- a/src/refactor/rename/RenameEnumField.hx +++ b/src/refactor/rename/RenameEnumField.hx @@ -9,7 +9,7 @@ import refactor.rename.RenameContext; class RenameEnumField { public static function refactorEnumField(context:RenameContext, file:File, identifier:Identifier):Promise { var changelist:Changelist = new Changelist(context); - changelist.addChange(identifier.pos.fileName, ReplaceText(context.what.toName, identifier.pos), identifier); + changelist.addChange(identifier.pos.fileName, ReplaceText(context.what.toName, identifier.pos, false), identifier); var packName:String = file.getPackage(); var mainModuleName:String = file.getMainModulName(); diff --git a/src/refactor/rename/RenameField.hx b/src/refactor/rename/RenameField.hx index b90dcbe..f7be50e 100644 --- a/src/refactor/rename/RenameField.hx +++ b/src/refactor/rename/RenameField.hx @@ -112,7 +112,7 @@ class RenameField { start: access.pos.start + prefix.length, end: access.pos.start + prefix.length + from.length }; - changelist.addChange(access.pos.fileName, ReplaceText(to, pos), access); + changelist.addChange(access.pos.fileName, ReplaceText(to, pos, false), access); } } diff --git a/src/refactor/rename/RenameHelper.hx b/src/refactor/rename/RenameHelper.hx index 35235ac..c952b5e 100644 --- a/src/refactor/rename/RenameHelper.hx +++ b/src/refactor/rename/RenameHelper.hx @@ -9,14 +9,14 @@ import refactor.edits.Changelist; class RenameHelper { public static function replaceTextWithPrefix(use:Identifier, prefix:String, to:String, changelist:Changelist) { if (prefix.length <= 0) { - changelist.addChange(use.pos.fileName, ReplaceText(to, use.pos), use); + changelist.addChange(use.pos.fileName, ReplaceText(to, use.pos, false), use); } else { var pos:IdentifierPos = { fileName: use.pos.fileName, start: use.pos.start + prefix.length, end: use.pos.end }; - changelist.addChange(use.pos.fileName, ReplaceText(to, pos), use); + changelist.addChange(use.pos.fileName, ReplaceText(to, pos, false), use); } } @@ -407,12 +407,12 @@ class RenameHelper { case null: case KnownType(type, _): if (use.parent.name == type.name.name) { - changelist.addChange(use.pos.fileName, ReplaceText(context.what.toName, use.pos), use); + changelist.addChange(use.pos.fileName, ReplaceText(context.what.toName, use.pos, false), use); continue; } case UnknownType(name, _): if (use.parent.name == name) { - changelist.addChange(use.pos.fileName, ReplaceText(context.what.toName, use.pos), use); + changelist.addChange(use.pos.fileName, ReplaceText(context.what.toName, use.pos, false), use); continue; } } @@ -474,7 +474,7 @@ class RenameHelper { end: use.pos.end }; pos.end = pos.start + fromName.length; - changelist.addChange(use.pos.fileName, ReplaceText(context.what.toName, pos), use); + changelist.addChange(use.pos.fileName, ReplaceText(context.what.toName, pos, false), use); } case UnknownType(_, _): } @@ -504,7 +504,7 @@ class RenameHelper { end: use.pos.end }; pos.end = pos.start + fromName.length; - changelist.addChange(use.pos.fileName, ReplaceText(context.what.toName, pos), use); + changelist.addChange(use.pos.fileName, ReplaceText(context.what.toName, pos, false), use); } case UnknownType(_, _): } diff --git a/src/refactor/rename/RenameImportAlias.hx b/src/refactor/rename/RenameImportAlias.hx index 930d2f2..e3c3f79 100644 --- a/src/refactor/rename/RenameImportAlias.hx +++ b/src/refactor/rename/RenameImportAlias.hx @@ -14,12 +14,12 @@ class RenameImportAlias { var changelist:Changelist = new Changelist(context); for (use in allUses) { if (use.file.name == file.name) { - changelist.addChange(use.pos.fileName, ReplaceText(context.what.toName, use.pos), use); + changelist.addChange(use.pos.fileName, ReplaceText(context.what.toName, use.pos,false), use); continue; } if (isImportHx) { if (use.file.importHxFile.name == file.name) { - changelist.addChange(use.pos.fileName, ReplaceText(context.what.toName, use.pos), use); + changelist.addChange(use.pos.fileName, ReplaceText(context.what.toName, use.pos, false), use); } } } diff --git a/src/refactor/rename/RenameModuleLevelStatic.hx b/src/refactor/rename/RenameModuleLevelStatic.hx index 6704ace..3faa63f 100644 --- a/src/refactor/rename/RenameModuleLevelStatic.hx +++ b/src/refactor/rename/RenameModuleLevelStatic.hx @@ -32,7 +32,7 @@ class RenameModuleLevelStatic { if ((use.pos.fileName != file.name) && (!filesWithStaticImport.contains(use.pos.fileName))) { continue; } - changelist.addChange(use.pos.fileName, ReplaceText(context.what.toName, use.pos), use); + changelist.addChange(use.pos.fileName, ReplaceText(context.what.toName, use.pos, false), use); } // all uses in files importing with package.mainmodul @@ -45,7 +45,7 @@ class RenameModuleLevelStatic { for (u in uses) { switch (u.type) { case Call(false) | Access: - changelist.addChange(u.pos.fileName, ReplaceText(context.what.toName, u.pos), u); + changelist.addChange(u.pos.fileName, ReplaceText(context.what.toName, u.pos, false), u); case ScopedLocal(_): default: } @@ -65,7 +65,7 @@ class RenameModuleLevelStatic { if (use.type.match(ImportModul)) { filesWithStaticImport.push(use.pos.fileName); } - changelist.addChange(use.pos.fileName, ReplaceText(replaceName, use.pos), use); + changelist.addChange(use.pos.fileName, ReplaceText(replaceName, use.pos, false), use); } var searchNameDot:String = '$searchName.'; var replaceNameDot:String = '$replaceName.'; @@ -76,7 +76,7 @@ class RenameModuleLevelStatic { start: use.pos.start, end: use.pos.start + searchNameDot.length } - changelist.addChange(use.pos.fileName, ReplaceText(replaceNameDot, pos), use); + changelist.addChange(use.pos.fileName, ReplaceText(replaceNameDot, pos, false), use); } } } diff --git a/src/refactor/rename/RenamePackage.hx b/src/refactor/rename/RenamePackage.hx index d8abf9e..09b75f2 100644 --- a/src/refactor/rename/RenamePackage.hx +++ b/src/refactor/rename/RenamePackage.hx @@ -17,16 +17,16 @@ class RenamePackage { var packageName:String = file.getPackage(); if (packageName.length > 0) { packageNamePrefix = file.packageIdentifier.name + "."; - changelist.addChange(file.name, ReplaceText(context.what.toName, file.packageIdentifier.pos), identifier); + changelist.addChange(file.name, ReplaceText(context.what.toName, file.packageIdentifier.pos, false), identifier); } else { - changelist.addChange(file.name, InsertText('package ${context.what.toName};\n', {fileName: file.name, start: 0, end: 0}), identifier); + changelist.addChange(file.name, InsertText('package ${context.what.toName};\n', {fileName: file.name, start: 0, end: 0}, false), identifier); } var newMainModulName:String = context.what.toName + "." + mainTypeName; var mainModule:String = packageNamePrefix + mainTypeName; var allUses:Array = context.nameMap.getIdentifiers(mainModule); for (use in allUses) { - changelist.addChange(use.pos.fileName, ReplaceText(newMainModulName, use.pos), use); + changelist.addChange(use.pos.fileName, ReplaceText(newMainModulName, use.pos, false), use); } for (type in file.typeList) { if (mainTypeName == type.name.name) { @@ -38,14 +38,14 @@ class RenamePackage { var newFullModulName:String = context.what.toName + "." + typeName; allUses = context.nameMap.getIdentifiers(fullModulName); for (use in allUses) { - changelist.addChange(use.pos.fileName, ReplaceText(newFullModulName, use.pos), use); + changelist.addChange(use.pos.fileName, ReplaceText(newFullModulName, use.pos, false), use); } fullModulName = packageNamePrefix + mainTypeName + "." + typeName; newFullModulName = context.what.toName + "." + mainTypeName + "." + typeName; allUses = context.nameMap.getIdentifiers(fullModulName); for (use in allUses) { - changelist.addChange(use.pos.fileName, ReplaceText(newFullModulName, use.pos), use); + changelist.addChange(use.pos.fileName, ReplaceText(newFullModulName, use.pos, false), use); } } var uniqueFiles:Array = []; @@ -66,7 +66,7 @@ class RenamePackage { case None: case Global | SamePackage | StarImported: var importPos:IdentifierPos = {fileName: use.pos.fileName, start: use.file.importInsertPos, end: use.file.importInsertPos} - changelist.addChange(use.pos.fileName, InsertText('import $newMainModulName;\n', importPos), use); + changelist.addChange(use.pos.fileName, InsertText('import $newMainModulName;\n', importPos, false), use); uniqueFiles.push(use.pos.fileName); case Imported: case ImportedWithAlias(_): diff --git a/src/refactor/rename/RenameScopedLocal.hx b/src/refactor/rename/RenameScopedLocal.hx index 06749f9..aff938e 100644 --- a/src/refactor/rename/RenameScopedLocal.hx +++ b/src/refactor/rename/RenameScopedLocal.hx @@ -12,7 +12,7 @@ class RenameScopedLocal { var changelist:Changelist = new Changelist(context); var identifierDot:String = identifier.name + "."; var toNameDot:String = context.what.toName + "."; - changelist.addChange(identifier.pos.fileName, ReplaceText(context.what.toName, identifier.pos), identifier); + changelist.addChange(identifier.pos.fileName, ReplaceText(context.what.toName, identifier.pos, false), identifier); var allUses:Array = identifier.defineType.findAllIdentifiers(function(ident:Identifier) { if (ident.pos.start < scopeStart) { @@ -53,7 +53,7 @@ class RenameScopedLocal { start: use.pos.start, end: use.pos.start }; - changelist.addChange(use.pos.fileName, InsertText("this.", pos), use); + changelist.addChange(use.pos.fileName, InsertText("this.", pos, false), use); case ScopedLocal(_, _): return Promise.reject('local var "${context.what.toName}" exists'); default: @@ -91,7 +91,7 @@ class RenameScopedLocal { } if (use.name == identifier.name) { // exact match - changelist.addChange(use.pos.fileName, ReplaceText(context.what.toName, use.pos), use); + changelist.addChange(use.pos.fileName, ReplaceText(context.what.toName, use.pos, false), use); } else { // starts with identifier + "." -> replace only identifier part var pos:IdentifierPos = { @@ -99,7 +99,7 @@ class RenameScopedLocal { start: use.pos.start, end: use.pos.start + identifier.pos.end - identifier.pos.start }; - changelist.addChange(use.pos.fileName, ReplaceText(context.what.toName, pos), use); + changelist.addChange(use.pos.fileName, ReplaceText(context.what.toName, pos, false), use); } } return Promise.resolve(changelist.execute()); diff --git a/src/refactor/rename/RenameTypeName.hx b/src/refactor/rename/RenameTypeName.hx index 510c1ce..232a7b5 100644 --- a/src/refactor/rename/RenameTypeName.hx +++ b/src/refactor/rename/RenameTypeName.hx @@ -21,7 +21,7 @@ class RenameTypeName { changelist.addChange(file.name, Move(newFileName), null); } // replace self - changelist.addChange(identifier.pos.fileName, ReplaceText(context.what.toName, identifier.pos), identifier); + changelist.addChange(identifier.pos.fileName, ReplaceText(context.what.toName, identifier.pos, false), identifier); var allUses:Array; // find all fully qualified modul names of type @@ -38,6 +38,8 @@ class RenameTypeName { } } allUses = context.nameMap.matchIdentifierPart(identifier.name, true); + var type = context.typeList.findTypeName("Index"); + trace(type); var changes:Array> = []; for (use in allUses) { @@ -69,7 +71,7 @@ class RenameTypeName { return; } if (use.name == identifier.name) { - changelist.addChange(use.pos.fileName, ReplaceText(context.what.toName, use.pos), use); + changelist.addChange(use.pos.fileName, ReplaceText(context.what.toName, use.pos, false), use); return; } if (use.name.startsWith('${identifier.name}.')) { @@ -78,7 +80,7 @@ class RenameTypeName { start: use.pos.start, end: use.pos.start + identifier.name.length } - changelist.addChange(use.pos.fileName, ReplaceText(context.what.toName, newPos), use); + changelist.addChange(use.pos.fileName, ReplaceText(context.what.toName, newPos, false), use); } })); } diff --git a/test/refactor/TestBase.hx b/test/refactor/TestBase.hx index f0904ed..2a69e65 100644 --- a/test/refactor/TestBase.hx +++ b/test/refactor/TestBase.hx @@ -51,10 +51,10 @@ class TestBase implements ITest { 'Delete $fileName'; case Move(newFileName): 'Move $newFileName'; - case ReplaceText(text, pos): - 'ReplaceText "$text" ${pos.fileName}@${pos.start}-${pos.end}'; - case InsertText(text, pos): - 'InsertText "$text" ${pos.fileName}@${pos.start}-${pos.end}'; + case ReplaceText(text, pos, format): + 'ReplaceText "$text" ${pos.fileName}@${pos.start}-${pos.end}${format ? " with format" : ""}'; + case InsertText(text, pos, format): + 'InsertText "$text" ${pos.fileName}@${pos.start}-${pos.end}${format ? " with format" : ""}'; case RemoveText(pos): 'RemoveText ${pos.fileName}@${pos.start}-${pos.end}'; } @@ -84,18 +84,18 @@ class TestBase implements ITest { } } - function makeReplaceTestEdit(fileName:String, text:String, start:Int, end:Int, ?pos:PosInfos):TestEdit { + function makeReplaceTestEdit(fileName:String, text:String, start:Int, end:Int, format:Bool = false, ?pos:PosInfos):TestEdit { return { fileName: fileName, - edit: ReplaceText(text, {fileName: fileName, start: start, end: end}), + edit: ReplaceText(text, {fileName: fileName, start: start, end: end}, format), pos: pos } } - function makeInsertTestEdit(fileName:String, text:String, insertPos:Int, ?pos:PosInfos):TestEdit { + function makeInsertTestEdit(fileName:String, text:String, insertPos:Int, format:Bool = false, ?pos:PosInfos):TestEdit { return { fileName: fileName, - edit: InsertText(text, {fileName: fileName, start: insertPos, end: insertPos}), + edit: InsertText(text, {fileName: fileName, start: insertPos, end: insertPos}, format), pos: pos } } diff --git a/test/refactor/TestEditableDocument.hx b/test/refactor/TestEditableDocument.hx index 7122429..2ba2416 100644 --- a/test/refactor/TestEditableDocument.hx +++ b/test/refactor/TestEditableDocument.hx @@ -22,9 +22,9 @@ class TestEditableDocument implements IEditableDocument { Assert.equals(fileName, oldFileName); case Move(newFileName): Assert.notEquals(fileName, newFileName); - case ReplaceText(_, pos): + case ReplaceText(_, pos, _): Assert.equals(fileName, pos.fileName); - case InsertText(_, pos): + case InsertText(_, pos, _): Assert.equals(fileName, pos.fileName); case RemoveText(pos): Assert.equals(fileName, pos.fileName); @@ -78,16 +78,16 @@ class TestEditList { case CreateFile(_): 0; case DeleteFile(_): 9999; case Move(_): 0; - case InsertText(_, pos): pos.start; - case ReplaceText(_, pos): pos.start; + case InsertText(_, pos, _): pos.start; + case ReplaceText(_, pos, _): pos.start; case RemoveText(pos): pos.start; }; var offsetB:Int = switch (b.edit) { case CreateFile(_): 0; case DeleteFile(_): 9999; case Move(_): 0; - case InsertText(_, pos): pos.start; - case ReplaceText(_, pos): pos.start; + case InsertText(_, pos, _): pos.start; + case ReplaceText(_, pos, _): pos.start; case RemoveText(pos): pos.start; }; if (offsetA < offsetB) { diff --git a/test/refactor/refactor/RefactorClassTest.hx b/test/refactor/refactor/RefactorClassTest.hx index 280a66a..db4ab1c 100644 --- a/test/refactor/refactor/RefactorClassTest.hx +++ b/test/refactor/refactor/RefactorClassTest.hx @@ -9,7 +9,7 @@ class RefactorClassTest extends RefactorTestBase { var edits:Array = [ makeRemoveTestEdit("testcases/classes/ChildClass.hx", 860, 901), makeCreateTestEdit("testcases/classes/ListOfChilds.hx"), - makeInsertTestEdit("testcases/classes/ListOfChilds.hx", "package classes;\n\ntypedef ListOfChilds = Array;", 0), + makeInsertTestEdit("testcases/classes/ListOfChilds.hx", "package classes;\n\ntypedef ListOfChilds = Array;", 0, true), makeInsertTestEdit("testcases/classes/pack/UseChild.hx", "import classes.ListOfChilds;\n", 23), ]; checkRefactor(RefactorExtractType, {fileName: "testcases/classes/ChildClass.hx", posStart: 873, posEnd: 873}, edits, async); @@ -28,7 +28,7 @@ class RefactorClassTest extends RefactorTestBase { + "\t\treturn Promise.resolve(text);\n" + "\t}\n" + "}", - 0), + 0, true), makeInsertTestEdit("testcases/classes/pack/UsePrinter.hx", "import classes.TextLoader;\n", 23), ]; checkRefactor(RefactorExtractType, {fileName: "testcases/classes/Printer.hx", posStart: 1273, posEnd: 1283}, edits, async); @@ -47,7 +47,7 @@ class RefactorClassTest extends RefactorTestBase { + "\tfunction doSomething5(d:Array):Void;\n" + "\tfunction doSomething6(d:Array):Void;\n" + "}", - 0), + 0, true), ]; checkRefactor(RefactorExtractInterface, {fileName: "testcases/classes/BaseClass.hx", posStart: 27, posEnd: 27}, edits, async); } diff --git a/test/refactor/refactor/RefactorTypedefTest.hx b/test/refactor/refactor/RefactorTypedefTest.hx index 3d29959..401bfed 100644 --- a/test/refactor/refactor/RefactorTypedefTest.hx +++ b/test/refactor/refactor/RefactorTypedefTest.hx @@ -29,7 +29,7 @@ class RefactorTypedefTest extends RefactorTestBase { + "\tvar maxCompletionItems:Int;\n" + "\tvar renameSourceFolders:Array;\n" + "}", - 0), + 0, true), ]; checkRefactor(RefactorExtractType, {fileName: "testcases/typedefs/Types.hx", posStart: 260, posEnd: 260}, edits, async); } From 7832912770b72b3ae0a11eae591a64b2ed8ff30f Mon Sep 17 00:00:00 2001 From: AlexHaxe Date: Sun, 10 Nov 2024 22:58:09 +0100 Subject: [PATCH 05/59] fixed missing file --- src/refactor/edits/EditableDocument.hx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/refactor/edits/EditableDocument.hx b/src/refactor/edits/EditableDocument.hx index bdbd9a3..904b118 100644 --- a/src/refactor/edits/EditableDocument.hx +++ b/src/refactor/edits/EditableDocument.hx @@ -28,11 +28,11 @@ class EditableDocument implements IEditableDocument { fileName = oldFileName; case Move(newFileName): fileName = newFileName; - case ReplaceText(text, pos): + case ReplaceText(text, pos, _): refactoredContent.add(originalContent.getString(lastPos, pos.start - lastPos)); refactoredContent.add(text); lastPos = pos.end; - case InsertText(text, pos): + case InsertText(text, pos, _): refactoredContent.add(originalContent.getString(lastPos, pos.start - lastPos)); refactoredContent.add(text); lastPos = pos.start; From 2385c12beac007d1536d0e92f059a073c3d40641 Mon Sep 17 00:00:00 2001 From: AlexHaxe Date: Wed, 13 Nov 2024 16:35:42 +0100 Subject: [PATCH 06/59] fixed arrow function with single parameter detection fixed findTokensAtPos to account for positions in whitespace added unittests --- src/refactor/discover/UsageCollector.hx | 5 +++ src/refactor/refactor/ExtractInterface.hx | 7 ++-- src/refactor/refactor/ExtractType.hx | 7 ++-- src/refactor/refactor/RefactorHelper.hx | 43 ++++++++++++++++------- test/refactor/rename/RenameClassTest.hx | 28 +++++++++++++++ testcases/classes/pack/UseChild.hx | 9 +++++ testcases/classes/pack/UseJson.hx | 14 ++++++++ 7 files changed, 95 insertions(+), 18 deletions(-) create mode 100644 testcases/classes/pack/UseJson.hx diff --git a/src/refactor/discover/UsageCollector.hx b/src/refactor/discover/UsageCollector.hx index e546af7..fae56a3 100644 --- a/src/refactor/discover/UsageCollector.hx +++ b/src/refactor/discover/UsageCollector.hx @@ -895,6 +895,7 @@ class UsageCollector { var parent:TokenTree = nameToken.parent; var lastNamePart:TokenTree = nameToken; + var parameterList:Array = []; function findAllNames(parentPart:TokenTree) { if (!parentPart.hasChildren()) { @@ -932,6 +933,9 @@ class UsageCollector { if (TokenTreeCheckUtils.isTypeParameter(child)) { typeParamLt = child; } + case Arrow: + var scopePos = child.getPos(); + type = ScopedLocal(nameToken.pos.min, scopePos.max, Parameter(parameterList)); case POpen: if (type.match(Access)) { if (parent.matches(Kwd(KwdNew))) { @@ -981,6 +985,7 @@ class UsageCollector { if (identifier == null) { identifier = new Identifier(type, name, pos, context.nameMap, context.file, context.type); } + parameterList.push(identifier); if (parentIdentifier != null) { parentIdentifier.addUse(identifier); diff --git a/src/refactor/refactor/ExtractInterface.hx b/src/refactor/refactor/ExtractInterface.hx index 619a741..ddd3034 100644 --- a/src/refactor/refactor/ExtractInterface.hx +++ b/src/refactor/refactor/ExtractInterface.hx @@ -6,6 +6,7 @@ import refactor.discover.File; import refactor.discover.Identifier; import refactor.discover.Type; import refactor.edits.Changelist; +import refactor.refactor.RefactorHelper.TokensAtPos; class ExtractInterface { public static function canRefactor(context:CanRefactorContext):CanRefactorResult { @@ -65,13 +66,13 @@ class ExtractInterface { return null; } - final token:Null = RefactorHelper.findTokenAtPos(root, context.what.posStart); - if (token == null) { + final tokens:TokensAtPos = RefactorHelper.findTokensAtPos(root, context.what.posStart); + if (tokens.before == null || tokens.after == null || tokens.before.index != tokens.after.index) { return null; } final firstImport:Null = RefactorHelper.findFirstImport(root); - final classToken:Null = token.access().parent().matches(Kwd(KwdClass)).token; + final classToken:Null = tokens.before.access().parent().matches(Kwd(KwdClass)).token; if (classToken == null) { return null; } diff --git a/src/refactor/refactor/ExtractType.hx b/src/refactor/refactor/ExtractType.hx index 0e6fa1e..6227d21 100644 --- a/src/refactor/refactor/ExtractType.hx +++ b/src/refactor/refactor/ExtractType.hx @@ -10,6 +10,7 @@ import refactor.discover.Identifier; import refactor.discover.IdentifierPos; import refactor.discover.Type; import refactor.edits.Changelist; +import refactor.refactor.RefactorHelper.TokensAtPos; class ExtractType { public static function canRefactor(context:CanRefactorContext):CanRefactorResult { @@ -73,13 +74,13 @@ class ExtractType { return null; } - final token:Null = RefactorHelper.findTokenAtPos(root, context.what.posStart); - if (token == null) { + final tokens:TokensAtPos = RefactorHelper.findTokensAtPos(root, context.what.posStart); + if (tokens.before == null || tokens.after == null || tokens.before.index != tokens.after.index) { return null; } final firstImport:Null = RefactorHelper.findFirstImport(root); - final typeToken:Null = findTypeToken(token); + final typeToken:Null = findTypeToken(tokens.before); if (typeToken == null) { return null; } diff --git a/src/refactor/refactor/RefactorHelper.hx b/src/refactor/refactor/RefactorHelper.hx index 69dfa48..7711134 100644 --- a/src/refactor/refactor/RefactorHelper.hx +++ b/src/refactor/refactor/RefactorHelper.hx @@ -4,20 +4,34 @@ import refactor.discover.File; import refactor.refactor.CanRefactorContext.ByteToCharConverterFunc; class RefactorHelper { - public static function findTokenAtPos(token:TokenTree, searchPos:Int):Null { - if (!token.hasChildren()) { - return null; - } - if (token.pos.min <= searchPos && token.pos.max > searchPos) { - return token; + public static function findTokensAtPos(root:TokenTree, searchPos:Int):TokensAtPos { + var tokens:TokensAtPos = { + before: null, + after: null } - for (child in token.children) { - var range = child.getPos(); - if (range.min <= searchPos && range.max > searchPos) { - return findTokenAtPos(child, searchPos); + var distanceBefore:Int = 10000; + var distanceAfter:Int = 10000; + + root.filterCallback(function(token:TokenTree, index:Int):FilterResult { + if (token.pos.min <= searchPos) { + var distance = searchPos - token.pos.max; + if (distanceBefore > distance) { + tokens.before = token; + distanceBefore = distance; + } } - } - return null; + + if (token.pos.max >= searchPos) { + var distance = token.pos.min - searchPos; + if (distanceAfter > distance) { + tokens.after = token; + distanceAfter = distance; + } + } + return GoDeeper; + }); + + return tokens; } public static function extractText(converter:ByteToCharConverterFunc, text:String, start:Int, end:Int):String { @@ -122,3 +136,8 @@ class RefactorHelper { return headers.shift(); } } + +typedef TokensAtPos = { + var before:Null; + var after:Null; +} diff --git a/test/refactor/rename/RenameClassTest.hx b/test/refactor/rename/RenameClassTest.hx index 61545cb..4407bf3 100644 --- a/test/refactor/rename/RenameClassTest.hx +++ b/test/refactor/rename/RenameClassTest.hx @@ -184,6 +184,10 @@ class RenameClassTest extends RenameTestBase { makeReplaceTestEdit("testcases/classes/JsonClass.hx", "JsonImporter", 319, 328), makeReplaceTestEdit("testcases/classes/JsonClass.hx", "JsonImporter", 336, 345), makeReplaceTestEdit("testcases/classes/pack/UseChild.hx", "JsonImporter", 744, 753), + makeReplaceTestEdit("testcases/classes/pack/UseChild.hx", "JsonImporter", 801, 810), + makeReplaceTestEdit("testcases/classes/pack/UseChild.hx", "JsonImporter", 864, 873), + makeReplaceTestEdit("testcases/classes/pack/UseJson.hx", "JsonImporter", 38, 47), + makeReplaceTestEdit("testcases/classes/pack/UseJson.hx", "JsonImporter", 165, 174), ]; checkRename({fileName: "testcases/classes/JsonClass.hx", toName: "JsonImporter", pos: 28}, edits, async); } @@ -329,4 +333,28 @@ class RenameClassTest extends RenameTestBase { ]; checkRename({fileName: "testcases/classes/Foo.hx", toName: "respRenamed", pos: 173}, edits, async); } + + public function testRenamePrinterMainResultArrow(async:Async) { + var edits:Array = [ + makeReplaceTestEdit("testcases/classes/Printer.hx", "resultRenamed", 446, 452), + makeReplaceTestEdit("testcases/classes/Printer.hx", "resultRenamed", 469, 475), + ]; + checkRename({fileName: "testcases/classes/Printer.hx", toName: "resultRenamed", pos: 448}, edits, async); + } + + public function testRenamePrinterMainTextArrow(async:Async) { + var edits:Array = [ + makeReplaceTestEdit("testcases/classes/Printer.hx", "textRenamed", 644, 648), + makeReplaceTestEdit("testcases/classes/Printer.hx", "textRenamed", 669, 673), + ]; + checkRename({fileName: "testcases/classes/Printer.hx", toName: "textRenamed", pos: 645}, edits, async); + } + + public function testRenamePrinterMainResultArrow2(async:Async) { + var edits:Array = [ + makeReplaceTestEdit("testcases/classes/Printer.hx", "resultRenamed", 831, 837), + makeReplaceTestEdit("testcases/classes/Printer.hx", "resultRenamed", 854, 860), + ]; + checkRename({fileName: "testcases/classes/Printer.hx", toName: "resultRenamed", pos: 833}, edits, async); + } } diff --git a/testcases/classes/pack/UseChild.hx b/testcases/classes/pack/UseChild.hx index d173a22..ff4ccc0 100644 --- a/testcases/classes/pack/UseChild.hx +++ b/testcases/classes/pack/UseChild.hx @@ -44,4 +44,13 @@ class UseChild { } function json(json:JsonClass) {} + + private function doSwitch(action:String):JsonClass { + return switch (action) { + case "test": + new JsonClass(1, "test", 2); + default: + null; + } + } } diff --git a/testcases/classes/pack/UseJson.hx b/testcases/classes/pack/UseJson.hx new file mode 100644 index 0000000..d554a44 --- /dev/null +++ b/testcases/classes/pack/UseJson.hx @@ -0,0 +1,14 @@ +package classes.pack; + +import classes.JsonClass; + +class UseJson { + private function doSwitch(action:String):Any { + return switch (action) { + case "test": + new JsonClass(1, "test", 2); + default: + null; + } + } +} From 121c3c2f8932c3c6ac29b0f2126d2f845c690818 Mon Sep 17 00:00:00 2001 From: AlexHaxe Date: Tue, 19 Nov 2024 02:26:36 +0100 Subject: [PATCH 07/59] refactored typehint data structure --- src/refactor/ITypeList.hx | 4 +- src/refactor/ITyper.hx | 2 +- src/refactor/PrintHelper.hx | 71 +++- src/refactor/TypingHelper.hx | 440 ++++++++++++++++++++ src/refactor/discover/TypeList.hx | 8 +- src/refactor/discover/UsageCollector.hx | 2 +- src/refactor/refactor/CanRefactorContext.hx | 11 +- src/refactor/rename/CanRenameContext.hx | 14 +- src/refactor/rename/RenameEnumField.hx | 4 +- src/refactor/rename/RenameField.hx | 2 +- src/refactor/rename/RenameHelper.hx | 400 ++---------------- src/refactor/rename/RenameTypeName.hx | 11 +- test/refactor/rename/RenameEnumTest.hx | 3 + 13 files changed, 554 insertions(+), 418 deletions(-) create mode 100644 src/refactor/TypingHelper.hx diff --git a/src/refactor/ITypeList.hx b/src/refactor/ITypeList.hx index 140cfdc..7f98eb2 100644 --- a/src/refactor/ITypeList.hx +++ b/src/refactor/ITypeList.hx @@ -1,7 +1,7 @@ package refactor; -import refactor.rename.RenameHelper.TypeHintType; +import refactor.discover.Type; interface ITypeList { - function makeTypeHintType(name:String):TypeHintType; + function getType(fullName:String):Null; } diff --git a/src/refactor/ITyper.hx b/src/refactor/ITyper.hx index 6685d7b..e8bcc25 100644 --- a/src/refactor/ITyper.hx +++ b/src/refactor/ITyper.hx @@ -1,6 +1,6 @@ package refactor; -import refactor.rename.RenameHelper.TypeHintType; +import refactor.TypingHelper.TypeHintType; interface ITyper { function resolveType(fileName:String, pos:Int):Promise>; diff --git a/src/refactor/PrintHelper.hx b/src/refactor/PrintHelper.hx index 39afd83..defebd4 100644 --- a/src/refactor/PrintHelper.hx +++ b/src/refactor/PrintHelper.hx @@ -1,7 +1,8 @@ package refactor; +import refactor.TypingHelper.TypeHintType; +import refactor.TypingHelper.TypeParameterList; import refactor.discover.IdentifierType; -import refactor.rename.RenameHelper.TypeHintType; class PrintHelper { public static function typeToString(identType:IdentifierType):String { @@ -11,7 +12,15 @@ class PrintHelper { case Method(isStatic): 'Method(${isStatic})'; case TypedefField(fields): - 'TypedefField(${fields})'; + final fieldnames = [ + for (field in fields) { + return switch (field) { + case Required(identifier) | Optional(identifier): + identifier.name; + } + } + ]; + 'TypedefField(${fieldnames.join(", ")})'; case StructureField(fieldNames): 'StructureField(${fieldNames})'; case EnumField(params): @@ -36,12 +45,62 @@ class PrintHelper { } } + public static function typeHintToString(hintType:TypeHintType):String { + if (hintType == null) { + return "null"; + } + return switch (hintType) { + case ClasspathType(type, paramList): + if (paramList.length > 0) { + final params = paramList.map(p -> typeHintToString(p)); + return 'ClasspathType(${type.name.name}<${params.join(", ")}>)'; + } + 'ClasspathType(${type.name.name}, <>)'; + case LibType(name, fullName, paramList): + if (paramList.length > 0) { + final params = paramList.map(p -> typeHintToString(p)); + return 'LibType($name, $fullName, <${params.join(", ")}>'; + } + 'LibType($name, $fullName, <>)'; + case FunctionType(argTypes, retVal): + final args = argTypes.map(f -> typeHintToString(f)); + if (argTypes == null) { + return 'FunctionType((${args.join(", ")}) -> Void)'; + } + return 'FunctionType((${args.join(", ")}) -> ${typeHintToString(retVal)})'; + case StructType(fieldTypes): + final fields = fieldTypes.map(f -> typeHintToString(f)); + 'StructType({${fields.join(";")}})'; + case UnknownType(name): + 'UnknownType($name)'; + } + } + public static function printTypeHint(hintType:TypeHintType):String { return switch (hintType) { - case KnownType(type, params): - 'KnownType(${type.name.name}, ${params.map((i) -> i.name)})'; - case UnknownType(name, params): - 'UnknownType($name, ${params.map((i) -> i.name)})'; + case ClasspathType(type, paramList): + if (paramList.length > 0) { + final params = paramList.map(p -> printTypeHint(p)); + return '${type.name.name}<${params.join(", ")}>'; + } + '${type.name.name}'; + case LibType(name, fullName, paramList): + if (paramList.length > 0) { + final params = paramList.map(p -> printTypeHint(p)); + return '${name}<${params.join(", ")}>'; + } + '$name'; + case FunctionType(argTypes, retVal): + final args = argTypes.map(f -> printTypeHint(f)); + if (argTypes == null) { + return '(${args.join(", ")}) -> Void'; + } + return '(${args.join(", ")}) -> ${printTypeHint(retVal)}'; + case StructType(fieldTypes): + final fields = fieldTypes.map(f -> printTypeHint(f)); + '{${fields.join(";")}}'; + case UnknownType(name): + '$name'; } } diff --git a/src/refactor/TypingHelper.hx b/src/refactor/TypingHelper.hx new file mode 100644 index 0000000..1edc244 --- /dev/null +++ b/src/refactor/TypingHelper.hx @@ -0,0 +1,440 @@ +package refactor; + +import refactor.discover.Identifier; +import refactor.discover.IdentifierPos; +import refactor.discover.Type; +import refactor.discover.TypeList; +import refactor.edits.Changelist; + +class TypingHelper { + public static function findDescendantTypes(context:CacheAndTyperContext, packName:String, baseType:Type):Array { + var types:Array = []; + var fullModulName:String = '$packName.${baseType.name.name}'; + + function pushType(newType:Type) { + for (type in types) { + if ((type.file.name == newType.file.name) && (type.name.name == newType.name.name)) { + return; + } + } + types.push(newType); + } + function searchImplementingTypes(types:Array, search:String) { + for (type in types) { + for (use in type.getIdentifiers(search)) { + switch (use.type) { + case Extends | Implements: + pushType(use.defineType); + case AbstractOver: + pushType(use.defineType); + default: + } + } + } + } + + var allUses:Array = context.nameMap.getIdentifiers(fullModulName); + for (use in allUses) { + switch (use.type) { + case ImportModul: + var search:String = switch (use.file.importsModule(baseType.file.getPackage(), baseType.file.getMainModulName(), baseType.name.name)) { + case None: + continue; + case Global | SamePackage | Imported | StarImported: + baseType.name.name; + case ImportedWithAlias(alias): + alias; + } + searchImplementingTypes(use.file.typeList, search); + case Extends | Implements: + pushType(use.defineType); + case AbstractOver: + pushType(use.defineType); + default: + } + } + allUses = context.nameMap.getIdentifiers(baseType.name.name); + for (use in allUses) { + switch (use.type) { + case Extends | Implements: + pushType(use.defineType); + case AbstractOver: + pushType(use.defineType); + default: + } + } + + for (type in types) { + for (t in findDescendantTypes(context, type.file.getPackage(), type)) { + pushType(t); + } + } + return types; + } + + public static function matchesType(context:CacheAndTyperContext, searchTypeOf:SearchTypeOf, searchType:TypeHintType):Promise { + return findTypeOfIdentifier(context, searchTypeOf).then(function(identifierType:Null):Bool { + if (identifierType == null) { + return false; + } + return typeHintsEqual(identifierType, searchType); + }); + } + + static function typeHintsEqual(typeHint1:TypeHintType, typeHint2:TypeHintType):Bool { + switch ([typeHint1, typeHint2]) { + case [LibType(name1, fullName1, params1), LibType(name2, fullName2, params2)]: + if (fullName1 != fullName2) { + return false; + } + if (params1.length != params2.length) { + return false; + } + for (i in 0...params1.length) { + if (!typeHintsEqual(params1[i], params2[i])) { + return false; + } + } + return true; + case [ClasspathType(type1, params1), ClasspathType(type2, params2)]: + if (type1.fullModuleName != type2.fullModuleName) { + return false; + } + if (params1.length != params2.length) { + return false; + } + for (i in 0...params1.length) { + if (!typeHintsEqual(params1[i], params2[i])) { + return false; + } + } + return true; + case [FunctionType(args1, retVal1), FunctionType(args2, retVal2)]: + if (args1.length != args2.length) { + return false; + } + for (i in 0...args1.length) { + if (!typeHintsEqual(args1[i], args2[i])) { + return false; + } + } + return typeHintsEqual(retVal1, retVal2); + case [StructType(fields1), StructType(fields2)]: + if (fields1.length != fields2.length) { + return false; + } + for (i in 0...fields1.length) { + if (!typeHintsEqual(fields1[i], fields2[i])) { + return false; + } + } + return true; + case [UnknownType(name1), UnknownType(name2)]: + return (name1 == name2); + default: + return false; + } + } + + public static function findTypeWithTyper(context:CacheAndTyperContext, fileName:String, pos:Int):Promise> { + if (context.typer == null) { + return Promise.reject("no typer"); + } + return context.typer.resolveType(fileName, pos); + } + + public static function findTypeOfIdentifier(context:CacheAndTyperContext, searchTypeOf:SearchTypeOf):Promise { + var parts:Array = searchTypeOf.name.split("."); + var part:String = parts.shift(); + return findFieldOrScopedLocal(context, searchTypeOf.defineType, part, searchTypeOf.pos).then(function(type:Null):Promise { + var index:Int = 0; + function findFieldForPart(partType:TypeHintType):Promise { + if (index >= parts.length) { + return Promise.resolve(partType); + } + var part:String = parts[index++]; + switch (partType) { + case null: + context.verboseLog('unable to determine type of "$part" in ${searchTypeOf.defineType?.file.name}@${searchTypeOf.pos}'); + return Promise.reject('unable to determine type of "$part" in ${searchTypeOf.defineType?.file.name}@${searchTypeOf.pos}'); + case ClasspathType(t, params): + return findField(context, t, part).then(findFieldForPart); + case LibType(t, fullPath, params): + return Promise.reject('unable to determine type of "$part" in ${searchTypeOf.defineType.name.name}@${searchTypeOf.pos}'); + case StructType(fields): + return Promise.reject('unable to determine type of "$part" in ${searchTypeOf.defineType.name.name}@${searchTypeOf.pos}'); + case FunctionType(args, retVal): + return Promise.reject('unable to determine type of "$part" in ${searchTypeOf.defineType.name.name}@${searchTypeOf.pos}'); + case UnknownType(name): + return Promise.reject('unable to determine type of "$part" in ${searchTypeOf.defineType.name.name}@${searchTypeOf.pos}'); + } + } + + return findFieldForPart(type); + }); + } + + public static function findFieldOrScopedLocal(context:CacheAndTyperContext, containerType:Type, name:String, pos:Int):Promise { + return findTypeWithTyper(context, containerType.file.name, pos).catchError(function(msg):Promise { + // trace("Haxe typer failed for " + name); + var allUses:Array = containerType.getIdentifiers(name); + var candidate:Null = null; + var fieldCandidate:Null = null; + for (use in allUses) { + switch (use.type) { + case Property | FieldVar(_) | Method(_): + fieldCandidate = use; + case TypedefField(_): + fieldCandidate = use; + case EnumField(_): + return Promise.resolve(ClasspathType(use.defineType, [])); + case ScopedLocal(scopeStart, scopeEnd, _): + if ((pos >= scopeStart) && (pos <= scopeEnd)) { + candidate = use; + } + if (pos == use.pos.start) { + candidate = use; + } + case CaseLabel(switchIdentifier): + if (use.pos.start == pos) { + return findFieldOrScopedLocal(context, containerType, switchIdentifier.name, switchIdentifier.pos.start); + } + default: + } + } + if (candidate == null) { + candidate = fieldCandidate; + } + if (candidate == null) { + return Promise.resolve(null); + } + var typeHint:Null = candidate.getTypeHint(); + switch (candidate.type) { + case ScopedLocal(_, _, ForLoop(loopIdent)): + var index:Int = loopIdent.indexOf(candidate); + var changes:Array> = []; + for (child in loopIdent) { + switch (child.type) { + case ScopedLocal(_, _, ForLoop(_)): + continue; + default: + changes.push(findTypeOfIdentifier(context, { + name: child.name, + pos: child.pos.start, + defineType: containerType + }).then(function(data:TypeHintType):Promise { + switch (data) { + case null: + case ClasspathType(_, typeParams) | LibType(_, _, typeParams): + if (typeParams.length <= index) { + return Promise.reject("not enough type parameters"); + } + return Promise.resolve(typeParams[index]); + case UnknownType(_): + trace("unknown type!!"); + case StructType(_) | FunctionType(_): + trace("TODO"); + } + return Promise.reject("not found"); + })); + } + } + + var winner:Promise = cast Promise.race(changes); + return winner.catchError(function(data:TypeHintType):Promise { + if (typeHint != null) { + return typeFromTypeHint(context, typeHint); + } + return Promise.reject("type not found"); + }); + case ScopedLocal(_, _, Parameter(params)): + if (typeHint != null) { + return typeFromTypeHint(context, typeHint).then(function(hint) { + return Promise.resolve(hint); + }); + } + var index:Int = params.indexOf(candidate); + switch (candidate.parent.type) { + case CaseLabel(switchIdentifier): + return findFieldOrScopedLocal(context, containerType, switchIdentifier.name, + switchIdentifier.pos.start).then(function(enumType:TypeHintType) { + switch (enumType) { + case null: + return Promise.resolve(null); + case ClasspathType(type, typeParams): + switch (type.name.type) { + case Enum: + var enumFields:Array = type.findAllIdentifiers((i) -> i.name == candidate.parent.name); + for (field in enumFields) { + switch (field.type) { + case EnumField(params): + if (params.length <= index) { + return Promise.resolve(null); + } + typeHint = params[index].getTypeHint(); + if (typeHint == null) { + return Promise.resolve(null); + } + return typeFromTypeHint(context, typeHint); + default: + return Promise.reject("not an enum field"); + } + } + default: + } + case LibType(_, _, _): + return Promise.resolve(null); + case FunctionType(_, _): + return Promise.resolve(null); + case StructType(_): + return Promise.resolve(null); + case UnknownType(_): + return Promise.resolve(null); + } + return Promise.resolve(enumType); + }); + default: + } + default: + } + if (typeHint != null) { + return typeFromTypeHint(context, typeHint); + } + return Promise.resolve(null); + }); + } + + public static function findField(context:CacheAndTyperContext, containerType:Type, name:String):Promise { + var allUses:Array = containerType.getIdentifiers(name); + var candidate:Null = null; + for (use in allUses) { + switch (use.type) { + case Property | FieldVar(_) | Method(_) | TypedefField(_) | EnumField(_): + candidate = use; + break; + default: + } + } + if ((candidate == null) || (candidate.uses == null)) { + switch (containerType.name.type) { + // case Abstract: + case Class: + var baseType:Null = findBaseClass(context.typeList, containerType); + if (baseType == null) { + return Promise.resolve(null); + } + + return findField(context, baseType, name); + case Typedef: + default: + } + return Promise.resolve(null); + } + for (use in candidate.uses) { + switch (use.type) { + case TypeHint: + return typeFromTypeHint(context, use); + default: + } + } + return Promise.resolve(null); + } + + public static function findBaseClass(typeList:TypeList, type:Type):Null { + var baseClasses:Array = type.findAllIdentifiers((i) -> i.type.match(Extends)); + for (base in baseClasses) { + var candidateTypes:Array = typeList.findTypeName(base.name); + for (candidate in candidateTypes) { + switch (type.file.importsModule(candidate.file.getPackage(), candidate.file.getMainModulName(), candidate.name.name)) { + case None: + case ImportedWithAlias(_): + case Global | SamePackage | Imported | StarImported: + return candidate; + } + } + } + return null; + } + + public static function typeFromTypeHint(context:CacheAndTyperContext, hint:Identifier):Promise { + if (hint.name == "Null") { + if ((hint.uses == null) || (hint.uses.length <= 0)) { + return Promise.reject(); + } + return typeFromTypeHint(context, hint.uses[0]); + } + + var parts:Array = hint.name.split("."); + var typeName:String = parts.pop(); + + var typeParams:Array = []; + if (hint.uses != null) { + for (use in hint.uses) { + switch (use.type) { + case TypedParameter: + var allTypes:Array = context.typeList.findTypeName(use.name); + if (allTypes.length <= 0) { + if (use.defineType != null) { + typeParams.push(ClasspathType(use.defineType, [])); + continue; + } + } + for (type in allTypes) { + switch (hint.file.importsModule(type.file.getPackage(), type.file.getMainModulName(), type.name.name)) { + case None: + case ImportedWithAlias(_): + case Global | SamePackage | Imported | StarImported: + // TODO recursive type params!!! + typeParams.push(ClasspathType(type, [])); + } + } + default: + } + } + } + + var allTypes:Array = context.typeList.findTypeName(typeName); + + if (parts.length > 0) { + for (type in allTypes) { + if (type.fullModuleName == hint.name) { + return Promise.resolve(ClasspathType(type, typeParams)); + } + } + + return Promise.resolve(LibType(hint.name, hint.name, typeParams)); + } + for (type in allTypes) { + switch (hint.file.importsModule(type.file.getPackage(), type.file.getMainModulName(), type.name.name)) { + case None: + case ImportedWithAlias(_): + case Global | SamePackage | Imported | StarImported: + return Promise.resolve(ClasspathType(type, typeParams)); + } + } + // TODO if there's no type found maybe it's because of an import alias + return Promise.resolve(LibType(hint.name, hint.name, typeParams)); + } +} + +typedef SearchTypeOf = { + var name:String; + var pos:Int; + var defineType:Type; +} + +enum TypeHintTypeOld { + ClasspathType(type:Type, typeParams:String); + LibType(name:String, typeParams:String); + UnknownType(name:String); +} + +enum TypeHintType { + ClasspathType(type:Type, typeParams:Array); + LibType(name:String, fullName:String, typeParams:Array); + FunctionType(args:Array, retVal:Null); + StructType(fields:Array); + UnknownType(name:String); +} + +typedef TypeParameterList = Array; diff --git a/src/refactor/discover/TypeList.hx b/src/refactor/discover/TypeList.hx index 682a75a..9561371 100644 --- a/src/refactor/discover/TypeList.hx +++ b/src/refactor/discover/TypeList.hx @@ -1,6 +1,6 @@ package refactor.discover; -import refactor.rename.RenameHelper.TypeHintType; +import refactor.TypingHelper.TypeHintType; class TypeList implements ITypeList { public final types:Map; @@ -17,9 +17,9 @@ class TypeList implements ITypeList { return Lambda.filter({iterator: types.iterator}, (t) -> t.name.name == name); } - public function makeTypeHintType(name:String):Null { - if (types.exists(name)) { - return KnownType(types.get(name), []); + public function getType(fullName:String):Null { + if (types.exists(fullName)) { + return types.get(fullName); } return null; } diff --git a/src/refactor/discover/UsageCollector.hx b/src/refactor/discover/UsageCollector.hx index fae56a3..0223e88 100644 --- a/src/refactor/discover/UsageCollector.hx +++ b/src/refactor/discover/UsageCollector.hx @@ -793,10 +793,10 @@ class UsageCollector { } function readCaseConst(context:UsageContext, identifier:Identifier, token:TokenTree, scopeEnd:Int) { + var caseIdent:Identifier = makeIdentifier(context, token, CaseLabel(identifier), identifier); if (!token.hasChildren()) { return; } - var caseIdent:Identifier = makeIdentifier(context, token, CaseLabel(identifier), identifier); var pOpen:Array = token.filterCallback(function(token:TokenTree, index:Int):FilterResult { return switch (token.tok) { case POpen: diff --git a/src/refactor/refactor/CanRefactorContext.hx b/src/refactor/refactor/CanRefactorContext.hx index 0cf8a45..9a26ce3 100644 --- a/src/refactor/refactor/CanRefactorContext.hx +++ b/src/refactor/refactor/CanRefactorContext.hx @@ -1,18 +1,9 @@ package refactor.refactor; -import refactor.ITyper; -import refactor.discover.FileList; import refactor.discover.FileReaderFunc; -import refactor.discover.NameMap; -import refactor.discover.TypeList; -typedef CanRefactorContext = { - var nameMap:NameMap; - var fileList:FileList; - var typeList:TypeList; +typedef CanRefactorContext = CacheAndTyperContext & { var what:RefactorWhat; - var verboseLog:VerboseLogger; - var typer:Null; var fileReader:FileReaderFunc; var converter:ByteToCharConverterFunc; } diff --git a/src/refactor/rename/CanRenameContext.hx b/src/refactor/rename/CanRenameContext.hx index 04f8d15..328342f 100644 --- a/src/refactor/rename/CanRenameContext.hx +++ b/src/refactor/rename/CanRenameContext.hx @@ -1,16 +1,6 @@ package refactor.rename; -import refactor.ITyper; -import refactor.VerboseLogger; -import refactor.discover.FileList; -import refactor.discover.NameMap; -import refactor.discover.TypeList; - -typedef CanRenameContext = { - var nameMap:NameMap; - var fileList:FileList; - var typeList:TypeList; +import refactor.CacheAndTyperContext; +typedef CanRenameContext = CacheAndTyperContext & { var what:RenameWhat; - var verboseLog:VerboseLogger; - var typer:Null; } diff --git a/src/refactor/rename/RenameEnumField.hx b/src/refactor/rename/RenameEnumField.hx index 047f8d1..788e759 100644 --- a/src/refactor/rename/RenameEnumField.hx +++ b/src/refactor/rename/RenameEnumField.hx @@ -40,11 +40,11 @@ class RenameEnumField { for (use in allUses) { switch (use.type) { case CaseLabel(switchIdentifier): - changes.push(RenameHelper.matchesType(context, { + changes.push(TypingHelper.matchesType(context, { name: switchIdentifier.name, pos: switchIdentifier.pos.start, defineType: switchIdentifier.defineType - }, KnownType(identifier.defineType, [])).then(function(matched:Bool) { + }, ClasspathType(identifier.defineType, [])).then(function(matched:Bool) { if (matched) { RenameHelper.replaceTextWithPrefix(use, "", context.what.toName, changelist); } diff --git a/src/refactor/rename/RenameField.hx b/src/refactor/rename/RenameField.hx index f7be50e..a57da3b 100644 --- a/src/refactor/rename/RenameField.hx +++ b/src/refactor/rename/RenameField.hx @@ -13,7 +13,7 @@ class RenameField { var changelist:Changelist = new Changelist(context); var packName:String = file.getPackage(); - var types:Array = RenameHelper.findDescendantTypes(context, packName, identifier.defineType); + var types:Array = TypingHelper.findDescendantTypes(context, packName, identifier.defineType); types.push(identifier.defineType); var changes:Array> = []; diff --git a/src/refactor/rename/RenameHelper.hx b/src/refactor/rename/RenameHelper.hx index c952b5e..387521d 100644 --- a/src/refactor/rename/RenameHelper.hx +++ b/src/refactor/rename/RenameHelper.hx @@ -1,5 +1,6 @@ package refactor.rename; +import refactor.TypingHelper; import refactor.discover.Identifier; import refactor.discover.IdentifierPos; import refactor.discover.Type; @@ -20,356 +21,6 @@ class RenameHelper { } } - public static function findDescendantTypes(context:RenameContext, packName:String, baseType:Type):Array { - var types:Array = []; - var fullModulName:String = '$packName.${baseType.name.name}'; - - function pushType(newType:Type) { - for (type in types) { - if ((type.file.name == newType.file.name) && (type.name.name == newType.name.name)) { - return; - } - } - types.push(newType); - } - function searchImplementingTypes(types:Array, search:String) { - for (type in types) { - for (use in type.getIdentifiers(search)) { - switch (use.type) { - case Extends | Implements: - pushType(use.defineType); - case AbstractOver: - pushType(use.defineType); - default: - } - } - } - } - - var allUses:Array = context.nameMap.getIdentifiers(fullModulName); - for (use in allUses) { - switch (use.type) { - case ImportModul: - var search:String = switch (use.file.importsModule(baseType.file.getPackage(), baseType.file.getMainModulName(), baseType.name.name)) { - case None: - continue; - case Global | SamePackage | Imported | StarImported: - baseType.name.name; - case ImportedWithAlias(alias): - alias; - } - searchImplementingTypes(use.file.typeList, search); - case Extends | Implements: - pushType(use.defineType); - case AbstractOver: - pushType(use.defineType); - default: - } - } - allUses = context.nameMap.getIdentifiers(baseType.name.name); - for (use in allUses) { - switch (use.type) { - case Extends | Implements: - pushType(use.defineType); - case AbstractOver: - pushType(use.defineType); - default: - } - } - - for (type in types) { - for (t in findDescendantTypes(context, type.file.getPackage(), type)) { - pushType(t); - } - } - return types; - } - - public static function matchesType(context:RenameContext, searchTypeOf:SearchTypeOf, searchType:TypeHintType):Promise { - return findTypeOfIdentifier(context, searchTypeOf).then(function(identifierType:Null):Bool { - if (identifierType == null) { - return false; - } - switch ([identifierType, searchType]) { - case [UnknownType(name1, params1), UnknownType(name2, params2)]: - if (name1 != name2) { - return false; - } - if (params1.length != params2.length) { - return false; - } - for (index in 0...params1.length) { - if (params1[index].name != params2[index].name) { - return false; - } - } - return true; - case [KnownType(type1, params1), KnownType(type2, params2)]: - if (type1.fullModuleName != type2.fullModuleName) { - return false; - } - if (params1.length != params2.length) { - return false; - } - for (index in 0...params1.length) { - if (params1[index].name != params2[index].name) { - return false; - } - } - return true; - default: - context.verboseLog('types do not match for static extension ${searchTypeOf.name}:${identifierType.printTypeHint()} != ${searchType.printTypeHint()}'); - return false; - } - }); - } - - static function findTypeWithTyper(context:RenameContext, fileName:String, pos:Int):Promise> { - if (context.typer == null) { - return Promise.reject("no typer"); - } - return context.typer.resolveType(fileName, pos); - } - - public static function findTypeOfIdentifier(context:RenameContext, searchTypeOf:SearchTypeOf):Promise { - var parts:Array = searchTypeOf.name.split("."); - - var part:String = parts.shift(); - return findFieldOrScopedLocal(context, searchTypeOf.defineType, part, searchTypeOf.pos).then(function(type:Null):Promise { - var index:Int = 0; - function findFieldForPart(partType:TypeHintType):Promise { - if (index >= parts.length) { - return Promise.resolve(partType); - } - var part:String = parts[index++]; - switch (partType) { - case null: - context.verboseLog('unable to determine type of "$part" in ${searchTypeOf.defineType?.file.name}@${searchTypeOf.pos}'); - return Promise.reject('unable to determine type of "$part" in ${searchTypeOf.defineType?.file.name}@${searchTypeOf.pos}'); - case KnownType(t, params): - return findField(context, t, part).then(findFieldForPart); - case UnknownType(name, _): - return Promise.reject('unable to determine type of "$part" in ${searchTypeOf.defineType.name.name}@${searchTypeOf.pos}'); - } - } - - return findFieldForPart(type); - }); - } - - public static function findFieldOrScopedLocal(context:RenameContext, containerType:Type, name:String, pos:Int):Promise { - return findTypeWithTyper(context, containerType.file.name, pos).catchError(function(msg):Promise { - var allUses:Array = containerType.getIdentifiers(name); - var candidate:Null = null; - var fieldCandidate:Null = null; - for (use in allUses) { - switch (use.type) { - case Property | FieldVar(_) | Method(_): - fieldCandidate = use; - case TypedefField(_): - fieldCandidate = use; - case EnumField(_): - return Promise.resolve(KnownType(use.defineType, [])); - case ScopedLocal(scopeStart, scopeEnd, _): - if ((pos >= scopeStart) && (pos <= scopeEnd)) { - candidate = use; - } - if (pos == use.pos.start) { - candidate = use; - } - case CaseLabel(switchIdentifier): - if (use.pos.start == pos) { - return findFieldOrScopedLocal(context, containerType, switchIdentifier.name, switchIdentifier.pos.start); - } - default: - } - } - if (candidate == null) { - candidate = fieldCandidate; - } - if (candidate == null) { - return Promise.resolve(null); - } - - var typeHint:Null = candidate.getTypeHint(); - switch (candidate.type) { - case ScopedLocal(_, _, ForLoop(loopIdent)): - var index:Int = loopIdent.indexOf(candidate); - var changes:Array> = []; - for (child in loopIdent) { - switch (child.type) { - case ScopedLocal(_, _, ForLoop(_)): - continue; - default: - changes.push(findTypeOfIdentifier(context, { - name: child.name, - pos: child.pos.start, - defineType: containerType - }).then(function(data:TypeHintType):Promise { - switch (data) { - case null: - case KnownType(_, typeParams) | UnknownType(_, typeParams): - if (typeParams.length <= index) { - return Promise.reject("not enough type parameters"); - } - return typeFromTypeHint(context, typeParams[index]); - } - return Promise.reject("not found"); - })); - } - } - - var winner:Promise = cast Promise.race(changes); - return winner.catchError(function(data:TypeHintType):Promise { - if (typeHint != null) { - return typeFromTypeHint(context, typeHint); - } - return Promise.reject("type not found"); - }); - case ScopedLocal(_, _, Parameter(params)): - if (typeHint != null) { - return typeFromTypeHint(context, typeHint); - } - - var index:Int = params.indexOf(candidate); - switch (candidate.parent.type) { - case CaseLabel(switchIdentifier): - return findFieldOrScopedLocal(context, containerType, switchIdentifier.name, - switchIdentifier.pos.start).then(function(enumType:TypeHintType) { - switch (enumType) { - case null: - return Promise.resolve(null); - case KnownType(type, typeParams): - switch (type.name.type) { - case Enum: - var enumFields:Array = type.findAllIdentifiers((i) -> i.name == candidate.parent.name); - for (field in enumFields) { - switch (field.type) { - case EnumField(params): - if (params.length <= index) { - return Promise.resolve(null); - } - typeHint = params[index].getTypeHint(); - if (typeHint == null) { - return Promise.resolve(null); - } - return typeFromTypeHint(context, typeHint); - default: - return Promise.reject("not an enum field"); - } - } - default: - } - case UnknownType(_, _): - return Promise.resolve(null); - } - return Promise.resolve(enumType); - }); - default: - } - default: - } - if (typeHint != null) { - return typeFromTypeHint(context, typeHint); - } - return Promise.resolve(null); - }); - } - - public static function findField(context:RenameContext, containerType:Type, name:String):Promise { - var allUses:Array = containerType.getIdentifiers(name); - var candidate:Null = null; - for (use in allUses) { - switch (use.type) { - case Property | FieldVar(_) | Method(_) | TypedefField(_) | EnumField(_): - candidate = use; - break; - default: - } - } - if ((candidate == null) || (candidate.uses == null)) { - switch (containerType.name.type) { - // case Abstract: - case Class: - var baseType:Null = findBaseClass(context.typeList, containerType); - if (baseType == null) { - return Promise.resolve(null); - } - return findField(context, baseType, name); - case Typedef: - default: - } - return Promise.resolve(null); - } - for (use in candidate.uses) { - switch (use.type) { - case TypeHint: - return typeFromTypeHint(context, use); - default: - } - } - return Promise.resolve(null); - } - - public static function findBaseClass(typeList:TypeList, type:Type):Null { - var baseClasses:Array = type.findAllIdentifiers((i) -> i.type.match(Extends)); - for (base in baseClasses) { - var candidateTypes:Array = typeList.findTypeName(base.name); - for (candidate in candidateTypes) { - switch (type.file.importsModule(candidate.file.getPackage(), candidate.file.getMainModulName(), candidate.name.name)) { - case None: - case ImportedWithAlias(_): - case Global | SamePackage | Imported | StarImported: - return candidate; - } - } - } - return null; - } - - public static function typeFromTypeHint(context:RenameContext, hint:Identifier):Promise { - if (hint.name == "Null") { - if ((hint.uses == null) || (hint.uses.length <= 0)) { - return Promise.reject(); - } - return typeFromTypeHint(context, hint.uses[0]); - } - - var parts:Array = hint.name.split("."); - var typeName:String = parts.pop(); - - var typeParams:Array = []; - if (hint.uses != null) { - for (use in hint.uses) { - switch (use.type) { - case TypedParameter: - typeParams.push(use); - default: - } - } - } - - var allTypes:Array = context.typeList.findTypeName(typeName); - if (parts.length > 0) { - for (type in allTypes) { - if (type.fullModuleName == hint.name) { - return Promise.resolve(KnownType(type, typeParams)); - } - } - return Promise.resolve(UnknownType(hint.name, typeParams)); - } - for (type in allTypes) { - switch (hint.file.importsModule(type.file.getPackage(), type.file.getMainModulName(), type.name.name)) { - case None: - case ImportedWithAlias(_): - case Global | SamePackage | Imported | StarImported: - return Promise.resolve(KnownType(type, typeParams)); - } - } - // TODO if there's no type found maybe it's because of an import alias - return Promise.resolve(UnknownType(hint.name, typeParams)); - } - public static function replaceStaticExtension(context:RenameContext, changelist:Changelist, identifier:Identifier):Promise { var allUses:Array = context.nameMap.matchIdentifierPart(identifier.name, true); @@ -392,7 +43,7 @@ class RenameHelper { for (use in firstParam.uses) { switch (use.type) { case TypeHint: - changes.push(RenameHelper.typeFromTypeHint(context, use).then(function(firstParamType):Promise { + changes.push(TypingHelper.typeFromTypeHint(context, use).then(function(firstParamType):Promise { if (firstParamType == null) { context.verboseLog("could not find type of first parameter for static extension"); return Promise.resolve(null); @@ -405,25 +56,33 @@ class RenameHelper { if (use.parent != null) { switch (firstParamType) { case null: - case KnownType(type, _): + case ClasspathType(type, _): if (use.parent.name == type.name.name) { changelist.addChange(use.pos.fileName, ReplaceText(context.what.toName, use.pos, false), use); continue; } - case UnknownType(name, _): + case LibType(name, _): if (use.parent.name == name) { changelist.addChange(use.pos.fileName, ReplaceText(context.what.toName, use.pos, false), use); continue; } + case StructType(fields): + trace("TODO"); + continue; + case FunctionType(args, retVal): + trace("TODO"); + continue; + case UnknownType(name): + continue; } } object = use.name; } else { object = use.name.substr(0, use.name.length - identifier.name.length - 1); } - // TODO check for using as well! - innerChanges.push(RenameHelper.matchesType(context, { + + innerChanges.push(TypingHelper.matchesType(context, { name: object, pos: use.pos.start, defineType: use.defineType @@ -460,10 +119,10 @@ class RenameHelper { pos: use.pos.start, defineType: use.defineType }; - return RenameHelper.findTypeOfIdentifier(context, search).then(function(typeHint:TypeHintType) { + return TypingHelper.findTypeOfIdentifier(context, search).then(function(typeHint:TypeHintType) { switch (typeHint) { case null: - case KnownType(type, _): + case ClasspathType(type, _): for (t in types) { if (t != type) { continue; @@ -476,7 +135,10 @@ class RenameHelper { pos.end = pos.start + fromName.length; changelist.addChange(use.pos.fileName, ReplaceText(context.what.toName, pos, false), use); } - case UnknownType(_, _): + case LibType(_, _) | UnknownType(_): + trace("TODO"); + case FunctionType(_, _) | StructType(_): + trace("TODO"); } }); } @@ -490,10 +152,10 @@ class RenameHelper { pos: posClosing, defineType: use.defineType }; - return RenameHelper.findTypeOfIdentifier(context, search).then(function(typeHint:TypeHintType) { + return TypingHelper.findTypeOfIdentifier(context, search).then(function(typeHint:TypeHintType) { switch (typeHint) { case null: - case KnownType(type, _): + case ClasspathType(type, _): for (t in types) { if (t != type) { continue; @@ -506,21 +168,11 @@ class RenameHelper { pos.end = pos.start + fromName.length; changelist.addChange(use.pos.fileName, ReplaceText(context.what.toName, pos, false), use); } - case UnknownType(_, _): + case LibType(_, _) | UnknownType(_): + trace("TODO " + typeHint.typeHintToString()); + case FunctionType(_, _) | StructType(_): + trace("TODO " + typeHint.typeHintToString()); } }); } } - -typedef SearchTypeOf = { - var name:String; - var pos:Int; - var defineType:Type; -} - -enum TypeHintType { - KnownType(type:Type, typeParams:TypeParameterList); - UnknownType(name:String, typeParams:TypeParameterList); -} - -typedef TypeParameterList = Array; diff --git a/src/refactor/rename/RenameTypeName.hx b/src/refactor/rename/RenameTypeName.hx index 232a7b5..9c9ed1c 100644 --- a/src/refactor/rename/RenameTypeName.hx +++ b/src/refactor/rename/RenameTypeName.hx @@ -2,12 +2,12 @@ package refactor.rename; import haxe.io.Path; import refactor.RefactorResult; +import refactor.TypingHelper; import refactor.discover.File; import refactor.discover.Identifier; import refactor.discover.IdentifierPos; import refactor.edits.Changelist; import refactor.rename.RenameContext; -import refactor.rename.RenameHelper.TypeHintType; class RenameTypeName { public static function refactorTypeName(context:RenameContext, file:File, identifier:Identifier):Promise { @@ -39,7 +39,6 @@ class RenameTypeName { } allUses = context.nameMap.matchIdentifierPart(identifier.name, true); var type = context.typeList.findTypeName("Index"); - trace(type); var changes:Array> = []; for (use in allUses) { @@ -56,18 +55,20 @@ class RenameTypeName { case Global | SamePackage | Imported | StarImported: } final searchName:String = if (use.name.startsWith(identifier.name)) identifier.name; else identifier.defineType.fullModuleName; - changes.push(RenameHelper.findTypeOfIdentifier(context, { + changes.push(TypingHelper.findTypeOfIdentifier(context, { name: searchName, pos: use.pos.start, defineType: use.defineType }).then(function(typeHint:TypeHintType) { switch (typeHint) { case null: - case KnownType(type, _): + case ClasspathType(type, _): if (type.fullModuleName != identifier.defineType.fullModuleName) { return; } - case UnknownType(_): + case LibType(_) | UnknownType(_): + return; + case StructType(_) | FunctionType(_, _): return; } if (use.name == identifier.name) { diff --git a/test/refactor/rename/RenameEnumTest.hx b/test/refactor/rename/RenameEnumTest.hx index cfd888a..509fdd5 100644 --- a/test/refactor/rename/RenameEnumTest.hx +++ b/test/refactor/rename/RenameEnumTest.hx @@ -34,6 +34,9 @@ class RenameEnumTest extends RenameTestBase { makeReplaceTestEdit("testcases/enums/IdentifierType.hx", "FunctionCall", 53, 57), makeReplaceTestEdit("testcases/enums/Main.hx", "FunctionCall", 178, 182), makeReplaceTestEdit("testcases/enums/Main.hx", "FunctionCall", 211, 215), + makeReplaceTestEdit("testcases/enums/Main.hx", "FunctionCall", 1101, 1105), + makeReplaceTestEdit("testcases/enums/Main.hx", "FunctionCall", 1395, 1399), + makeReplaceTestEdit("testcases/enums/Main.hx", "FunctionCall", 1702, 1706), ]; checkRename({fileName: "testcases/enums/IdentifierType.hx", toName: "FunctionCall", pos: 55}, edits, async); } From c5ed08b325fa8bfa89d63bc57cef8a5aa9e07369 Mon Sep 17 00:00:00 2001 From: AlexHaxe Date: Tue, 19 Nov 2024 02:28:43 +0100 Subject: [PATCH 08/59] fixed missing file --- src/refactor/CacheAndTyperContext.hx | 15 +++++++++++++++ 1 file changed, 15 insertions(+) create mode 100644 src/refactor/CacheAndTyperContext.hx diff --git a/src/refactor/CacheAndTyperContext.hx b/src/refactor/CacheAndTyperContext.hx new file mode 100644 index 0000000..6ea454c --- /dev/null +++ b/src/refactor/CacheAndTyperContext.hx @@ -0,0 +1,15 @@ +package refactor; + +import refactor.ITyper; +import refactor.VerboseLogger; +import refactor.discover.FileList; +import refactor.discover.NameMap; +import refactor.discover.TypeList; + +typedef CacheAndTyperContext = { + var nameMap:NameMap; + var fileList:FileList; + var typeList:TypeList; + var verboseLog:VerboseLogger; + var typer:Null; +} From bcb4112911ca3d70bf847c951381282a272add18 Mon Sep 17 00:00:00 2001 From: AlexHaxe Date: Tue, 19 Nov 2024 23:39:38 +0100 Subject: [PATCH 09/59] added ExtractMethod --- CHANGELOG.md | 4 +- src/refactor/Refactoring.hx | 13 +- src/refactor/refactor/ExtractMethod.hx | 610 ++++++++++++++++++ src/refactor/refactor/RefactorType.hx | 3 +- test/TestMain.hx | 2 + .../refactor/RefactorExtractMethodTest.hx | 51 ++ test/refactor/rename/RenameClassTest.hx | 5 +- testcases/methods/Main.hx | 30 + 8 files changed, 710 insertions(+), 8 deletions(-) create mode 100644 src/refactor/refactor/ExtractMethod.hx create mode 100644 test/refactor/refactor/RefactorExtractMethodTest.hx create mode 100644 testcases/methods/Main.hx diff --git a/CHANGELOG.md b/CHANGELOG.md index 330bb38..314c4cd 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,16 +2,18 @@ ## dev branch / next version (2.x.x) -## 2.4.0 (2024-11-) +## 2.4.0 (2024-) - added invalidateFile / removeFile to allow rescan in case of file changes or deletion - added support for create and delete file operations as edits - added ExtractType refactor module - added ExtractInterface refactor module +- added ExtractMethod refactor module - changed Refactor class to Rename - changed getFullModulName call to fullModuleName property - fixed type name renaming not changing in use locations - fixed discovery of arrow functions as type hints +- refactored typehint data structure ## 2.3.1 (2024-11-01) diff --git a/src/refactor/Refactoring.hx b/src/refactor/Refactoring.hx index 3dea300..92cafde 100644 --- a/src/refactor/Refactoring.hx +++ b/src/refactor/Refactoring.hx @@ -3,6 +3,7 @@ package refactor; import refactor.refactor.CanRefactorContext; import refactor.refactor.CanRefactorResult; import refactor.refactor.ExtractInterface; +import refactor.refactor.ExtractMethod; import refactor.refactor.ExtractType; import refactor.refactor.RefactorContext; import refactor.refactor.RefactorType; @@ -10,20 +11,24 @@ import refactor.refactor.RefactorType; class Refactoring { public static function canRefactor(refactorType:RefactorType, context:CanRefactorContext):CanRefactorResult { switch (refactorType) { - case RefactorExtractType: - return ExtractType.canRefactor(context); case RefactorExtractInterface: return ExtractInterface.canRefactor(context); + case RefactorExtractMethod: + return ExtractMethod.canRefactor(context); + case RefactorExtractType: + return ExtractType.canRefactor(context); } return null; } public static function doRefactor(refactorType:RefactorType, context:RefactorContext):Promise { switch (refactorType) { - case RefactorExtractType: - return ExtractType.doRefactor(context); case RefactorExtractInterface: return ExtractInterface.doRefactor(context); + case RefactorExtractMethod: + return ExtractMethod.doRefactor(context); + case RefactorExtractType: + return ExtractType.doRefactor(context); } return Promise.resolve(RefactorResult.Unsupported("no refactor type selected")); } diff --git a/src/refactor/refactor/ExtractMethod.hx b/src/refactor/refactor/ExtractMethod.hx new file mode 100644 index 0000000..0b88791 --- /dev/null +++ b/src/refactor/refactor/ExtractMethod.hx @@ -0,0 +1,610 @@ +package refactor.refactor; + +import refactor.TypingHelper.TypeHintType; +import refactor.discover.File; +import refactor.discover.Identifier; +import refactor.edits.Changelist; +import refactor.refactor.RefactorHelper.TokensAtPos; + +class ExtractMethod { + public static function canRefactor(context:CanRefactorContext):CanRefactorResult { + final extractData = makeExtractMethodData(context); + if (extractData == null) { + return Unsupported; + } + return Supported('Extract Method as ${extractData.newMethodName}'); + } + + public static function doRefactor(context:RefactorContext):Promise { + final extractData = makeExtractMethodData(context); + if (extractData == null) { + return Promise.reject("failed to collect extract method data"); + } + final changelist:Changelist = new Changelist(context); + + var neededIdentifiers:Array = findParameters(extractData, context); + + var returnType = findReturnType(extractData, context, neededIdentifiers); + if (returnType == Invalid) { + return Promise.reject("could not extract method from selected code"); + } + + var parameterList:String = ""; + var returnTypeHint:String = ""; + + var parameterPromise = Promise.all(makeParameterList(extractData, context, neededIdentifiers)).then(function(params):Promise { + parameterList = params.join(", "); + return Promise.resolve(true); + }); + var returnHintPromise = makeReturnTypeHint(extractData, context, returnType).then(function(typeHint) { + returnTypeHint = typeHint; + return Promise.resolve(true); + }); + + return Promise.all([parameterPromise, returnHintPromise]).then(function(_) { + final extractedCall:String = makeCallSite(extractData, context, neededIdentifiers, returnType); + + final staticModifier = extractData.isStatic ? "static " : ""; + + final functionDefinition:String = '${staticModifier}function ${extractData.newMethodName}($parameterList)$returnTypeHint'; + final body:String = makeBody(extractData, context, returnType); + + changelist.addChange(context.what.fileName, + ReplaceText(extractedCall, {fileName: context.what.fileName, start: extractData.startToken.pos.min, end: extractData.endToken.pos.max}, true), + null); + + changelist.addChange(context.what.fileName, + InsertText(functionDefinition + body, {fileName: context.what.fileName, start: extractData.newMethodOffset, end: extractData.newMethodOffset}, + true), + null); + return changelist.execute(); + }); + } + + static function makeExtractMethodData(context:CanRefactorContext):Null { + final fileContent = context.fileReader(context.what.fileName); + var content:String; + var root:TokenTree; + switch (fileContent) { + case Text(_): + return null; + case Token(tokens, text): + content = text; + root = tokens; + } + if (root == null) { + return null; + } + if (content == null) { + return null; + } + + final file:Null = context.fileList.getFile(context.what.fileName); + if (file == null) { + return null; + } + final tokensStart:TokensAtPos = RefactorHelper.findTokensAtPos(root, context.what.posStart); + final tokensEnd:TokensAtPos = RefactorHelper.findTokensAtPos(root, context.what.posEnd); + if (tokensStart.after == null) { + return null; + } + if (tokensEnd.before == null) { + return null; + } + + final tokenStart:Null = tokensStart.after; + final tokenEnd:Null = tokensEnd.before; + + if (tokenStart == null || tokenEnd == null) { + return null; + } + if (tokenStart.index >= tokenEnd.index) { + return null; + } + switch (tokenStart.tok) { + case Kwd(KwdFunction): + return null; + default: + } + + final tokenEndLast:Null = TokenTreeCheckUtils.getLastToken(tokenEnd); + if (tokenEndLast == null) { + return null; + } + if (tokenEnd.index != tokenEndLast.index) { + return null; + } + + if (!shareSameParent(tokenStart, tokenEnd)) { + return null; + } + var parentFunction:Null = findParentFunction(tokenStart); + if (parentFunction == null) { + return null; + } + var parentParentFunction:Null; + while ((parentParentFunction = findParentFunction(parentFunction)) != null) { + parentFunction = parentParentFunction; + } + + var isStatic = parentFunction.access().firstChild().firstOf(Kwd(KwdStatic)).exists(); + var isSingleExpr = isSingleExpression(tokenStart, tokenEnd); + + final lastToken:Null = TokenTreeCheckUtils.getLastToken(parentFunction); + if (lastToken == null) { + return null; + } + final newMethodOffset = lastToken.pos.max + 1; + final nameToken:Null = parentFunction.getFirstChild(); + if (nameToken == null) { + return null; + } + final name:String = nameToken.toString(); + final newMethodName = '${name}Extract'; + + return { + content: content, + root: root, + startToken: tokenStart, + endToken: tokenEnd, + newMethodOffset: newMethodOffset, + newMethodName: newMethodName, + functionToken: parentFunction, + isStatic: isStatic, + isSingleExpr: isSingleExpr, + }; + } + + static function findParameters(extractData:ExtractMethodData, context:RefactorContext) { + final file:Null = context.fileList.getFile(context.what.fileName); + if (file == null) { + return []; + } + final functionIdentifier = file.getIdentifier(extractData.functionToken.getFirstChild().pos.min); + if (functionIdentifier == null) { + return []; + } + final allIdentifiersBefore = functionIdentifier.findAllIdentifiers(identifier -> { + if ((identifier.pos.start < extractData.functionToken.pos.min) || (identifier.pos.start >= extractData.startToken.pos.min)) { + return false; + } + if (identifier.name.contains(".")) { + return false; + } + switch (identifier.type) { + case ScopedLocal(scopeStart, scopeEnd, _): + if (scopeEnd <= extractData.startToken.pos.min) { + return false; + } + if (scopeStart > extractData.endToken.pos.max) { + return false; + } + return true; + default: + return false; + } + }); + + final scopedInside:Array = []; + final allUsesInside = functionIdentifier.findAllIdentifiers(identifier -> { + if ((identifier.pos.start < extractData.startToken.pos.min) || (identifier.pos.start > extractData.endToken.pos.min)) { + return false; + } + switch (identifier.type) { + case ScopedLocal(_): + scopedInside.push(identifier); + return false; + default: + } + return true; + }); + final avail:Map = new Map(); + for (use in allIdentifiersBefore) { + avail.set(use.name, use); + } + var neededIdentifiers:Array = []; + for (use in allUsesInside) { + final parts = use.name.split("."); + final first = parts.shift(); + var shadowed = false; + for (scoped in scopedInside) { + if (scoped.name != first) { + continue; + } + switch (scoped.type) { + case ScopedLocal(scopeStart, scopeEnd, _): + if (scopeStart <= use.pos.start && scopeEnd >= use.pos.end) { + shadowed = true; + break; + } + default: + } + } + if (shadowed) { + continue; + } + + if (!avail.exists(first)) { + continue; + } + final id = avail.get(first); + if (neededIdentifiers.contains(id)) { + continue; + } + neededIdentifiers.push(id); + } + + return neededIdentifiers; + } + + static function shareSameParent(tokenA:TokenTree, tokenB:TokenTree):Bool { + var parentA = tokenA.parent; + if (parentA == null) { + return false; + } + var parentB = tokenB.parent; + while (true) { + if (parentB == null) { + return false; + } + if (parentA.index == parentB.index) { + return true; + } + parentB = parentB.parent; + } + } + + static function isSingleExpression(tokenStart:TokenTree, tokenEnd:TokenTree):Bool { + var fullPos = tokenStart.getPos(); + return (tokenEnd.pos.min >= fullPos.min) && (tokenEnd.pos.max <= fullPos.max); + } + + static function findParentFunction(token:TokenTree):Null { + var parent:Null = token.parent; + while (parent != null && parent.tok != null) { + switch parent.tok { + case Kwd(KwdFunction): + return parent; + default: + } + parent = parent.parent; + } + return null; + } + + static function findReturnType(extractData:ExtractMethodData, context:RefactorContext, neededParams:Array):ReturnType { + final assignedVars:Array = []; + final parent = extractData.startToken.parent; + + if (usedAsExpression(parent)) { + if (extractData.isSingleExpr) { + return ReturnAsExpression; + } else { + return Invalid; + } + } + + final allReturns = parent.filterCallback(function(token:TokenTree, index:Int):FilterResult { + if (token.pos.max < extractData.startToken.pos.min) { + return GoDeeper; + } + if (token.pos.min > extractData.endToken.pos.max) { + return SkipSubtree; + } + switch (token.tok) { + case Kwd(KwdFunction) | Arrow: + return SkipSubtree; + case Kwd(KwdReturn): + return FoundSkipSubtree; + case Const(CIdent(s)): + var child = token.getFirstChild(); + if (child != null) { + switch (child.tok) { + case Binop(OpAssign) | Binop(OpAssignOp(_)): + assignedVars.push(s); + default: + } + } + default: + } + return GoDeeper; + }); + if (allReturns.length > 0) { + var lastReturn = allReturns[allReturns.length - 1]; + if (isSingleExpression(lastReturn, extractData.endToken)) { + return ReturnIsLast(lastReturn); + } + } + + final modifiedIdentifiers:Array = []; + for (identifier in neededParams) { + if (assignedVars.contains(identifier.name)) { + modifiedIdentifiers.push(identifier); + trace(identifier.name + " modified"); + } + } + + if (allReturns.length == 0) { + return NoReturn(modifiedIdentifiers); + } + for (ret in allReturns) { + var child = ret.getFirstChild(); + if (child == null) { + return Invalid; + } + switch (child.tok) { + case Semicolon: + return EmptyReturn(modifiedIdentifiers); + default: + if (modifiedIdentifiers.length > 0) { + return Invalid; + } + return OpenEndedReturn(allReturns); + } + } + return Invalid; + } + + static function makeParameterList(extractData:ExtractMethodData, context:RefactorContext, neededIdentifiers:Array):Array> { + var promises:Array> = []; + for (identifier in neededIdentifiers) { + final promise = findTypeOfIdentifier(context, identifier).then(function(typeHint):Promise { + trace("typehint resolved: " + PrintHelper.typeHintToString(typeHint)); + return Promise.resolve(buildParameter(identifier, typeHint)); + }); + + promises.push(promise); + } + return promises; + } + + static function findTypeOfIdentifier(context:RefactorContext, identifier:Identifier):Promise { + var hint = identifier.getTypeHint(); + if (hint != null) { + return TypingHelper.typeFromTypeHint(context, hint); + } + return TypingHelper.findTypeOfIdentifier(context, { + name: identifier.name, + pos: identifier.pos.end - 1, + defineType: identifier.defineType + }); + } + + static function makeCallSite(extractData:ExtractMethodData, context:RefactorContext, neededIdentifiers:Array, returnType:ReturnType):String { + final callParams:String = neededIdentifiers.map(i -> i.name).join(", "); + return switch (returnType) { + case NoReturn(assignments): + switch (assignments.length) { + case 0: + '${extractData.newMethodName}($callParams);\n'; + case 1: + '${assignments[0].name} = ${extractData.newMethodName}($callParams);\n'; + default: + final assignData = assignments.map(a -> '${a.name} = data.${a.name};').join("\n"); + '{\nfinal data = ${extractData.newMethodName}($callParams);\n' + assignData + "}\n"; + } + case ReturnAsExpression: + '${extractData.newMethodName}($callParams)'; + case ReturnIsLast(_): + 'return ${extractData.newMethodName}($callParams);\n'; + case EmptyReturn(assignments): + switch (assignments.length) { + case 0: + 'if (!${extractData.newMethodName}($callParams)) {\nreturn;\n}\n'; + case 1: + 'switch (${extractData.newMethodName}($callParams)) {\n' + + 'case None:\n' + + 'return;\n' + + 'case Some(data):\n' + + '${assignments[0].name} = data;\n}\n'; + default: + final assignData = assignments.map(a -> '${a.name} = data.${a.name};').join("\n"); + '{\nfinal data =${extractData.newMethodName}($callParams);\n' + assignData + "}\n"; + 'switch (${extractData.newMethodName}($callParams)) {\n' + + 'case None:\n' + + 'return;\n' + + 'case Some(data):\n' + + '${assignData}}\n'; + } + case OpenEndedReturn(_): + 'switch (${extractData.newMethodName}($callParams)) {\n' + + 'case None:\n' + + 'case Some(data):\n' + + 'return data;\n}\n'; + case Invalid: + ""; + } + } + + static function makeReturnTypeHint(extractData:ExtractMethodData, context:RefactorContext, returnType:ReturnType):Promise { + switch (returnType) { + case NoReturn(assignments): + switch (assignments.length) { + case 0: + return Promise.resolve(":Void"); + case 1: + return findTypeOfIdentifier(context, assignments[0]).then(function(typeHint):Promise { + if (typeHint == null) { + return Promise.resolve(""); + } + return Promise.resolve(":" + typeHint.printTypeHint()); + }); + default: + var promises:Array> = []; + for (assign in assignments) { + promises.push(findTypeOfIdentifier(context, assign).then(function(typeHint):Promise { + if (typeHint == null) { + return Promise.resolve(assign.name + ":Any"); + } + return Promise.resolve(":" + typeHint.printTypeHint()); + })); + } + return Promise.all(promises).then(function(fields) { + return Promise.resolve(":{" + fields.join(", ") + "}"); + }); + } + case ReturnAsExpression: + return TypingHelper.findTypeWithTyper(context, context.what.fileName, extractData.endToken.pos.max - 1) + .then(function(typeHint):Promise { + if (typeHint == null) { + return Promise.resolve(""); + } + return Promise.resolve(":" + typeHint.printTypeHint()); + }); + case ReturnIsLast(lastReturnToken): + final pos = lastReturnToken.getPos(); + return TypingHelper.findTypeWithTyper(context, context.what.fileName, pos.max - 2).then(function(typeHint):Promise { + if (typeHint == null) { + return Promise.resolve(""); + } + return Promise.resolve(":" + typeHint.printTypeHint()); + }); + case EmptyReturn(assignments): + switch (assignments.length) { + case 0: + return Promise.resolve(":Bool"); + case 1: + return findTypeOfIdentifier(context, assignments[0]).then(function(typeHint):Promise { + if (typeHint == null) { + return Promise.resolve(""); + } + return Promise.resolve(":haxe.ds.Option<" + typeHint.printTypeHint() + ">"); + }); + default: + var promises:Array> = []; + for (assign in assignments) { + promises.push(findTypeOfIdentifier(context, assign).then(function(typeHint):Promise { + if (typeHint == null) { + return Promise.resolve(assign.name + ":Any"); + } + return Promise.resolve(":" + typeHint.printTypeHint()); + })); + } + return Promise.all(promises).then(function(fields) { + return Promise.resolve(":haxe.ds.Option<{" + fields.join(", ") + "}>"); + }); + } + case OpenEndedReturn(returnTokens): + var token = returnTokens.shift(); + if (token == null) { + return Promise.resolve(""); + } + final pos = token.getPos(); + return TypingHelper.findTypeWithTyper(context, context.what.fileName, pos.max - 2).then(function(typeHint):Promise { + if (typeHint == null) { + return Promise.resolve(""); + } + return Promise.resolve(":haxe.ds.Option<" + typeHint.printTypeHint() + ">"); + }); + case Invalid: + return Promise.resolve(""); + } + return Promise.resolve(""); + } + + static function makeBody(extractData:ExtractMethodData, context:RefactorContext, returnType:ReturnType):String { + final selectedSnippet = RefactorHelper.extractText(context.converter, extractData.content, extractData.startToken.pos.min, + extractData.endToken.pos.max); + return switch (returnType) { + case NoReturn(assignments): + switch (assignments.length) { + case 0: + " {\n" + selectedSnippet + "\n}\n"; + case 1: + final returnAssigmentVar = '\nreturn ${assignments[0].name};'; + " {\n" + selectedSnippet + returnAssigmentVar + "\n}\n"; + default: + final returnAssigments = "\nreturn {\n" + assignments.map(a -> '${a.name}: ${a.name},\n') + "};"; + " {\n" + selectedSnippet + returnAssigments + "\n}\n"; + } + case ReturnAsExpression: + " {\n" + "return " + selectedSnippet + "\n}\n"; + case ReturnIsLast(_): + " {\n" + selectedSnippet + "\n}\n"; + case EmptyReturn(assignments): + switch (assignments.length) { + case 0: + final reg:EReg = ~/^([ \t]*)return[ \t]*;/gm; + final replacedSnippet = reg.map(selectedSnippet, f -> f.matched(1) + "return false;"); + " {\n" + replacedSnippet + "\nreturn true;\n}\n"; + case 1: + final reg:EReg = ~/^([ \t]*)return[ \t]*;/gm; + final replacedSnippet = reg.map(selectedSnippet, f -> f.matched(1) + "return None;"); + final returnAssigmentVar = '\nreturn Some(${assignments[0].name});'; + " {\n" + replacedSnippet + returnAssigmentVar + "\n}\n"; + default: + final reg:EReg = ~/^([ \t]*)return[ \t]*;/gm; + final replacedSnippet = reg.map(selectedSnippet, f -> f.matched(1) + "return None;"); + final returnAssigments = "\nreturn Some({\n" + assignments.map(a -> '${a.name}: ${a.name},\n') + "});"; + " {\n" + replacedSnippet + returnAssigments + "\n}\n"; + } + case OpenEndedReturn(returnTokens): + var startOffset = context.converter(extractData.content, extractData.startToken.pos.min); + var snippet = selectedSnippet; + returnTokens.reverse(); + for (token in returnTokens) { + var pos = token.getPos(); + var returnStart = context.converter(extractData.content, pos.min); + var returnEnd = context.converter(extractData.content, pos.max); + returnStart -= startOffset; + returnEnd -= startOffset; + var before = snippet.substring(0, returnStart); + var after = snippet.substring(returnEnd); + var retValue = snippet.substring(returnStart + 7, returnEnd - 1); + snippet = before + "return Some(" + retValue + ");" + after; + } + " {\n" + snippet + "\nreturn None;\n}\n"; + case Invalid: + ""; + } + } + + static function buildParameter(identifier:Identifier, typeHint:TypeHintType):String { + if (typeHint == null) { + return '${identifier.name}:Any'; + } + return '${identifier.name}:${PrintHelper.printTypeHint(typeHint)}'; + } + + static function usedAsExpression(token:Null):Bool { + if (token == null) { + return false; + } + return switch (token.tok) { + case Kwd(KwdReturn): + true; + case Binop(_): + true; + case POpen: + true; + default: + return false; + } + } +} + +private typedef NewFunctionParameter = { + final call:String; + final param:String; +} + +typedef ExtractMethodData = { + var content:String; + var root:TokenTree; + var startToken:TokenTree; + var endToken:TokenTree; + var newMethodName:String; + var newMethodOffset:Int; + var functionToken:TokenTree; + var isStatic:Bool; + var isSingleExpr:Bool; +} + +enum ReturnType { + NoReturn(assignments:Array); + ReturnAsExpression; + ReturnIsLast(lastReturnToken:TokenTree); + EmptyReturn(assignments:Array); + OpenEndedReturn(returnTokens:Array); + Invalid; +} diff --git a/src/refactor/refactor/RefactorType.hx b/src/refactor/refactor/RefactorType.hx index 4362e7e..3834fb9 100644 --- a/src/refactor/refactor/RefactorType.hx +++ b/src/refactor/refactor/RefactorType.hx @@ -1,6 +1,7 @@ package refactor.refactor; enum RefactorType { - RefactorExtractType; RefactorExtractInterface; + RefactorExtractMethod; + RefactorExtractType; } diff --git a/test/TestMain.hx b/test/TestMain.hx index b5e36ec..1b454b7 100644 --- a/test/TestMain.hx +++ b/test/TestMain.hx @@ -1,4 +1,5 @@ import refactor.refactor.RefactorClassTest; +import refactor.refactor.RefactorExtractMethodTest; import refactor.refactor.RefactorTypedefTest; import refactor.rename.RenameClassTest; import refactor.rename.RenameEnumTest; @@ -24,6 +25,7 @@ class TestMain { new RenameTypedefTest(), new RefactorClassTest(), new RefactorTypedefTest(), + new RefactorExtractMethodTest(), ]; var runner:Runner = new Runner(); diff --git a/test/refactor/refactor/RefactorExtractMethodTest.hx b/test/refactor/refactor/RefactorExtractMethodTest.hx new file mode 100644 index 0000000..3dc06bd --- /dev/null +++ b/test/refactor/refactor/RefactorExtractMethodTest.hx @@ -0,0 +1,51 @@ +package refactor.refactor; + +class RefactorExtractMethodTest extends RefactorTestBase { + function setupClass() { + setupTestSources(["testcases/methods"]); + } + + function testSimpleNoReturns(async:Async) { + var edits:Array = [ + makeReplaceTestEdit("testcases/methods/Main.hx", "noReturnsExtract();\n", 94, 131, true), + makeInsertTestEdit("testcases/methods/Main.hx", + "function noReturnsExtract():Void {\n" + + "trace(\"hello 2\");\n" + + "\t\ttrace(\"hello 3\");\n" + + "}\n", 155, true), + ]; + checkRefactor(RefactorExtractMethod, {fileName: "testcases/methods/Main.hx", posStart: 93, posEnd: 131}, edits, async); + } + + function testSimpleNoReturnsStatic(async:Async) { + var edits:Array = [ + makeReplaceTestEdit("testcases/methods/Main.hx", "noReturnsStaticExtract();\n", 222, 259, true), + makeInsertTestEdit("testcases/methods/Main.hx", + "static function noReturnsStaticExtract():Void {\n" + + "trace(\"hello 2\");\n" + + "\t\ttrace(\"hello 3\");\n" + + "}\n", 283, true), + ]; + checkRefactor(RefactorExtractMethod, {fileName: "testcases/methods/Main.hx", posStart: 221, posEnd: 259}, edits, async); + } + + function testEmptyReturns(async:Async) { + var edits:Array = [ + makeReplaceTestEdit("testcases/methods/Main.hx", "if (!emptyReturnsExtract(cond1, cond2)) {\n" + "return;\n" + "}\n", 342, 439, true), + makeInsertTestEdit("testcases/methods/Main.hx", + "function emptyReturnsExtract(cond1:Bool, cond2:Bool):Bool {\n" + + "if (cond1) {\n" + + "\t\t\treturn false;\n" + + "\t\t}\n" + + "\t\tif (cond2) {\n" + + "\t\t\treturn false;\n" + + "\t\t}\n" + + "\t\ttrace(\"hello 1\");\n" + + "\t\ttrace(\"hello 2\");\n" + + "return true;\n" + + "}\n", + 483, true), + ]; + checkRefactor(RefactorExtractMethod, {fileName: "testcases/methods/Main.hx", posStart: 341, posEnd: 439}, edits, async); + } +} diff --git a/test/refactor/rename/RenameClassTest.hx b/test/refactor/rename/RenameClassTest.hx index 4407bf3..1e891ed 100644 --- a/test/refactor/rename/RenameClassTest.hx +++ b/test/refactor/rename/RenameClassTest.hx @@ -151,7 +151,7 @@ class RenameClassTest extends RenameTestBase { checkRename({fileName: "testcases/classes/MyIdentifier.hx", toName: "position", pos: 254}, edits, async); } - // requires external typer since built-in will not resolve array access + // TODO requires external typer since built-in will not resolve array access // public function testRenameIdentifierName(async:Async) { // var edits:Array = [ // makeReplaceTestEdit("testcases/classes/ChildClass.hx", "id", 454, 458), @@ -161,8 +161,9 @@ class RenameClassTest extends RenameTestBase { // makeReplaceTestEdit("testcases/classes/ChildClass.hx", "id", 796, 800), // makeReplaceTestEdit("testcases/classes/MyIdentifier.hx", "id", 228, 232), // ]; - // refactorAndCheck({fileName: "testcases/classes/MyIdentifier.hx", toName: "id", pos: 229}, edits, async); + // checkRename({fileName: "testcases/classes/MyIdentifier.hx", toName: "id", pos: 229}, edits, async); // } + public function testRenameJsonClassWidth(async:Async) { var edits:Array = [ makeReplaceTestEdit("testcases/classes/JsonClass.hx", "jsonWidth", 72, 77), diff --git a/testcases/methods/Main.hx b/testcases/methods/Main.hx new file mode 100644 index 0000000..7f3a30f --- /dev/null +++ b/testcases/methods/Main.hx @@ -0,0 +1,30 @@ +package testcases.methods; + +class Main { + public function noReturns() { + trace("hello 1"); + trace("hello 2"); + trace("hello 3"); + trace("hello 4"); + } + + public static function noReturnsStatic() { + trace("hello 1"); + trace("hello 2"); + trace("hello 3"); + trace("hello 4"); + } + + public function emptyReturns(cond1:Bool, cond2:Bool) { + if (cond1) { + return; + } + if (cond2) { + return; + } + trace("hello 1"); + trace("hello 2"); + trace("hello 3"); + trace("hello 4"); + } +} From 03bac6ca8392f8a35166db812c04cad19ce3c914 Mon Sep 17 00:00:00 2001 From: AlexHaxe Date: Wed, 20 Nov 2024 00:44:01 +0100 Subject: [PATCH 10/59] added testcases from vshaxe/vshaxe#632 --- .../refactor/RefactorExtractMethodTest.hx | 35 ++++++++++++++++++ testcases/methods/Main.hx | 37 +++++++++++++++++++ 2 files changed, 72 insertions(+) diff --git a/test/refactor/refactor/RefactorExtractMethodTest.hx b/test/refactor/refactor/RefactorExtractMethodTest.hx index 3dc06bd..fdb4d23 100644 --- a/test/refactor/refactor/RefactorExtractMethodTest.hx +++ b/test/refactor/refactor/RefactorExtractMethodTest.hx @@ -48,4 +48,39 @@ class RefactorExtractMethodTest extends RefactorTestBase { ]; checkRefactor(RefactorExtractMethod, {fileName: "testcases/methods/Main.hx", posStart: 341, posEnd: 439}, edits, async); } + + function testCalculateTotal(async:Async) { + var edits:Array = [ + makeReplaceTestEdit("testcases/methods/Main.hx", "total = calculateTotalExtract(items, total);\n", 569, 720, true), + makeInsertTestEdit("testcases/methods/Main.hx", + "function calculateTotalExtract(items:Array, total:Float):Float {\n" + + "// Selected code block to extract\n" + + "\t\tfor (item in items) {\n" + + "\t\t\tvar price = item.price;\n" + + "\t\t\tvar quantity = item.quantity;\n" + + "\t\t\ttotal += price * quantity;\n" + + "\t\t}\n" + + "return total;\n" + + "}\n", + 741, true), + ]; + checkRefactor(RefactorExtractMethod, {fileName: "testcases/methods/Main.hx", posStart: 568, posEnd: 720}, edits, async); + } + + function testProcessUser(async:Async) { + var edits:Array = [ + makeReplaceTestEdit("testcases/methods/Main.hx", "processUserExtract(name, age);\n", 844, 980, true), + makeInsertTestEdit("testcases/methods/Main.hx", + "function processUserExtract(name:String, age:Int):Void {\n" + + "// Selected code block to extract\n" + + "\t\tvar greeting = \"Hello, \" + name;\n" + + "\t\tif (age < 18) {\n" + + "\t\t\tgreeting += \" (minor)\";\n" + + "\t\t}\n" + + "\t\ttrace(greeting);\n" + + "}\n", + 994, true), + ]; + checkRefactor(RefactorExtractMethod, {fileName: "testcases/methods/Main.hx", posStart: 843, posEnd: 980}, edits, async); + } } diff --git a/testcases/methods/Main.hx b/testcases/methods/Main.hx index 7f3a30f..d4f850f 100644 --- a/testcases/methods/Main.hx +++ b/testcases/methods/Main.hx @@ -27,4 +27,41 @@ class Main { trace("hello 3"); trace("hello 4"); } + + public function calculateTotal(items:Array):Float { + var total:Float = 0; + + // Selected code block to extract + for (item in items) { + var price = item.price; + var quantity = item.quantity; + total += price * quantity; + } + + return total; + } + + public function processUser(user:User) { + var name:String = user.name; + var age:Int = user.age; + + // Selected code block to extract + var greeting = "Hello, " + name; + if (age < 18) { + greeting += " (minor)"; + } + trace(greeting); + + // ... + } +} + +typedef Item = { + var price:Float; + var quantity:Float; +} + +typedef User = { + var name:String; + var age:Int; } From 04a3f7f35738b557961f8e253784b9fad26cb251 Mon Sep 17 00:00:00 2001 From: AlexHaxe Date: Sat, 23 Nov 2024 02:20:36 +0100 Subject: [PATCH 11/59] refactored code generation for ExtractMethod added test cases WIP support for different code layouts/configurations when extracting methods --- src/refactor/TypingHelper.hx | 8 +- src/refactor/refactor/ExtractMethod.hx | 446 ++++++++---------- .../refactormethod/CodeGenAsExpression.hx | 45 ++ .../refactor/refactormethod/CodeGenBase.hx | 21 + .../refactormethod/CodeGenEmptyReturn.hx | 89 ++++ .../refactormethod/CodeGenNoReturn.hx | 96 ++++ .../refactormethod/CodeGenOpenEnded.hx | 53 +++ .../refactormethod/CodeGenReturnIsLast.hx | 38 ++ .../refactor/refactormethod/ICodeGen.hx | 7 + src/refactor/rename/RenameHelper.hx | 2 - test/refactor/TestBase.hx | 11 +- test/refactor/TestTyper.hx | 28 ++ test/refactor/refactor/RefactorClassTest.hx | 18 +- .../refactor/RefactorExtractMethodTest.hx | 204 +++++++- test/refactor/refactor/RefactorTestBase.hx | 4 +- test/refactor/refactor/RefactorTypedefTest.hx | 30 +- test/refactor/rename/RenameTestBase.hx | 22 +- testcases/methods/AgeChecker.hx | 14 + testcases/methods/ArrayHandler.hx | 12 + testcases/methods/Main.hx | 16 + testcases/methods/Math.hx | 10 + testcases/methods/NameProcessor.hx | 12 + testcases/methods/PersonHandler.hx | 8 + 23 files changed, 860 insertions(+), 334 deletions(-) create mode 100644 src/refactor/refactor/refactormethod/CodeGenAsExpression.hx create mode 100644 src/refactor/refactor/refactormethod/CodeGenBase.hx create mode 100644 src/refactor/refactor/refactormethod/CodeGenEmptyReturn.hx create mode 100644 src/refactor/refactor/refactormethod/CodeGenNoReturn.hx create mode 100644 src/refactor/refactor/refactormethod/CodeGenOpenEnded.hx create mode 100644 src/refactor/refactor/refactormethod/CodeGenReturnIsLast.hx create mode 100644 src/refactor/refactor/refactormethod/ICodeGen.hx create mode 100644 test/refactor/TestTyper.hx create mode 100644 testcases/methods/AgeChecker.hx create mode 100644 testcases/methods/ArrayHandler.hx create mode 100644 testcases/methods/Math.hx create mode 100644 testcases/methods/NameProcessor.hx create mode 100644 testcases/methods/PersonHandler.hx diff --git a/src/refactor/TypingHelper.hx b/src/refactor/TypingHelper.hx index 1edc244..d623e6c 100644 --- a/src/refactor/TypingHelper.hx +++ b/src/refactor/TypingHelper.hx @@ -138,7 +138,7 @@ class TypingHelper { public static function findTypeWithTyper(context:CacheAndTyperContext, fileName:String, pos:Int):Promise> { if (context.typer == null) { - return Promise.reject("no typer"); + return Promise.reject("no typer for " + fileName + "@" + pos); } return context.typer.resolveType(fileName, pos); } @@ -423,12 +423,6 @@ typedef SearchTypeOf = { var defineType:Type; } -enum TypeHintTypeOld { - ClasspathType(type:Type, typeParams:String); - LibType(name:String, typeParams:String); - UnknownType(name:String); -} - enum TypeHintType { ClasspathType(type:Type, typeParams:Array); LibType(name:String, fullName:String, typeParams:Array); diff --git a/src/refactor/refactor/ExtractMethod.hx b/src/refactor/refactor/ExtractMethod.hx index 0b88791..90866c6 100644 --- a/src/refactor/refactor/ExtractMethod.hx +++ b/src/refactor/refactor/ExtractMethod.hx @@ -5,6 +5,12 @@ import refactor.discover.File; import refactor.discover.Identifier; import refactor.edits.Changelist; import refactor.refactor.RefactorHelper.TokensAtPos; +import refactor.refactor.refactormethod.CodeGenAsExpression; +import refactor.refactor.refactormethod.CodeGenEmptyReturn; +import refactor.refactor.refactormethod.CodeGenNoReturn; +import refactor.refactor.refactormethod.CodeGenOpenEnded; +import refactor.refactor.refactormethod.CodeGenReturnIsLast; +import refactor.refactor.refactormethod.ICodeGen; class ExtractMethod { public static function canRefactor(context:CanRefactorContext):CanRefactorResult { @@ -18,45 +24,60 @@ class ExtractMethod { public static function doRefactor(context:RefactorContext):Promise { final extractData = makeExtractMethodData(context); if (extractData == null) { - return Promise.reject("failed to collect extract method data"); + return Promise.resolve(RefactorResult.Unsupported("failed to collect extract method data")); } final changelist:Changelist = new Changelist(context); - var neededIdentifiers:Array = findParameters(extractData, context); + // identifier of top-level containing function + final functionIdentifier = getFunctionIdentifier(extractData, context); + if (functionIdentifier == null) { + return Promise.resolve(RefactorResult.Unsupported("failed to find identifier of containing function")); + } + + // find all parameters for extracted method + // e.g. all scoped vars used inside selected code + var neededIdentifiers:Array = findParameters(extractData, context, functionIdentifier); - var returnType = findReturnType(extractData, context, neededIdentifiers); - if (returnType == Invalid) { - return Promise.reject("could not extract method from selected code"); + // determine type of selected code + var codeGen:Null = findCodeGen(extractData, context, functionIdentifier, neededIdentifiers); + if (codeGen == null) { + return Promise.resolve(RefactorResult.Unsupported("could not extract method from selected code - no codegen")); } var parameterList:String = ""; var returnTypeHint:String = ""; + // resolve all parameter types either from typehint or using a hover request with Haxe server var parameterPromise = Promise.all(makeParameterList(extractData, context, neededIdentifiers)).then(function(params):Promise { parameterList = params.join(", "); return Promise.resolve(true); }); - var returnHintPromise = makeReturnTypeHint(extractData, context, returnType).then(function(typeHint) { + // resolve function return typehint + + var returnHintPromise = codeGen.makeReturnTypeHint().then(function(typeHint) { returnTypeHint = typeHint; return Promise.resolve(true); }); + // all necessary types resolved return Promise.all([parameterPromise, returnHintPromise]).then(function(_) { - final extractedCall:String = makeCallSite(extractData, context, neededIdentifiers, returnType); - - final staticModifier = extractData.isStatic ? "static " : ""; - - final functionDefinition:String = '${staticModifier}function ${extractData.newMethodName}($parameterList)$returnTypeHint'; - final body:String = makeBody(extractData, context, returnType); + // replace selected code with call to newly extracted method + final extractedCall:String = codeGen.makeCallSite(); changelist.addChange(context.what.fileName, ReplaceText(extractedCall, {fileName: context.what.fileName, start: extractData.startToken.pos.min, end: extractData.endToken.pos.max}, true), null); + // insert new method with function signature and body after current function + final staticModifier = extractData.isStatic ? "static " : ""; + final functionDefinition:String = '${staticModifier}function ${extractData.newMethodName}($parameterList)$returnTypeHint'; + final body:String = codeGen.makeBody(); + changelist.addChange(context.what.fileName, InsertText(functionDefinition + body, {fileName: context.what.fileName, start: extractData.newMethodOffset, end: extractData.newMethodOffset}, true), null); + return changelist.execute(); }); } @@ -83,6 +104,7 @@ class ExtractMethod { if (file == null) { return null; } + // find corresponding tokens in tokentree, selection start/end in whitespace final tokensStart:TokensAtPos = RefactorHelper.findTokensAtPos(root, context.what.posStart); final tokensEnd:TokensAtPos = RefactorHelper.findTokensAtPos(root, context.what.posEnd); if (tokensStart.after == null) { @@ -101,6 +123,7 @@ class ExtractMethod { if (tokenStart.index >= tokenEnd.index) { return null; } + // currently not supporting extracting an inner function switch (tokenStart.tok) { case Kwd(KwdFunction): return null; @@ -115,9 +138,12 @@ class ExtractMethod { return null; } + // extracting only works if parent of start token is also grand…parent of end token if (!shareSameParent(tokenStart, tokenEnd)) { return null; } + + // find top-level containing function var parentFunction:Null = findParentFunction(tokenStart); if (parentFunction == null) { return null; @@ -135,6 +161,8 @@ class ExtractMethod { return null; } final newMethodOffset = lastToken.pos.max + 1; + + // suggest name + Extract for new method name final nameToken:Null = parentFunction.getFirstChild(); if (nameToken == null) { return null; @@ -155,35 +183,33 @@ class ExtractMethod { }; } - static function findParameters(extractData:ExtractMethodData, context:RefactorContext) { - final file:Null = context.fileList.getFile(context.what.fileName); - if (file == null) { - return []; - } - final functionIdentifier = file.getIdentifier(extractData.functionToken.getFirstChild().pos.min); - if (functionIdentifier == null) { - return []; + static function shareSameParent(tokenA:TokenTree, tokenB:TokenTree):Bool { + var parentA = tokenA.parent; + if (parentA == null) { + return false; } - final allIdentifiersBefore = functionIdentifier.findAllIdentifiers(identifier -> { - if ((identifier.pos.start < extractData.functionToken.pos.min) || (identifier.pos.start >= extractData.startToken.pos.min)) { - return false; - } - if (identifier.name.contains(".")) { + var parentB = tokenB.parent; + while (true) { + if (parentB == null) { return false; } - switch (identifier.type) { - case ScopedLocal(scopeStart, scopeEnd, _): - if (scopeEnd <= extractData.startToken.pos.min) { - return false; - } - if (scopeStart > extractData.endToken.pos.max) { - return false; - } - return true; - default: - return false; + if (parentA.index == parentB.index) { + return true; } - }); + parentB = parentB.parent; + } + } + + static function getFunctionIdentifier(extractData:ExtractMethodData, context:RefactorContext):Null { + final file:Null = context.fileList.getFile(context.what.fileName); + if (file == null) { + return null; + } + return file.getIdentifier(extractData.functionToken.getFirstChild().pos.min); + } + + static function findParameters(extractData:ExtractMethodData, context:RefactorContext, functionIdentifier:Identifier) { + final allIdentifiersBefore = getScopedBeforeSelected(extractData, context, functionIdentifier); final scopedInside:Array = []; final allUsesInside = functionIdentifier.findAllIdentifiers(identifier -> { @@ -237,26 +263,53 @@ class ExtractMethod { return neededIdentifiers; } - static function shareSameParent(tokenA:TokenTree, tokenB:TokenTree):Bool { - var parentA = tokenA.parent; - if (parentA == null) { - return false; - } - var parentB = tokenB.parent; - while (true) { - if (parentB == null) { + static function getScopedBeforeSelected(extractData:ExtractMethodData, context:RefactorContext, functionIdentifier:Identifier):Array { + return functionIdentifier.findAllIdentifiers(identifier -> { + if ((identifier.pos.start < extractData.functionToken.pos.min) || (identifier.pos.start >= extractData.startToken.pos.min)) { return false; } - if (parentA.index == parentB.index) { - return true; + if (identifier.name.contains(".")) { + return false; } - parentB = parentB.parent; - } + switch (identifier.type) { + case ScopedLocal(scopeStart, scopeEnd, _): + if (scopeEnd <= extractData.startToken.pos.min) { + return false; + } + if (scopeStart > extractData.endToken.pos.max) { + return false; + } + return true; + default: + return false; + } + }); + } + + static function getScopedInsideSelected(extractData:ExtractMethodData, context:RefactorContext, functionIdentifier:Identifier):Array { + return functionIdentifier.findAllIdentifiers(identifier -> { + if ((identifier.pos.start < extractData.startToken.pos.min) || (identifier.pos.start > extractData.endToken.pos.min)) { + return false; + } + switch (identifier.type) { + case ScopedLocal(_): + return true; + default: + } + return false; + }); } static function isSingleExpression(tokenStart:TokenTree, tokenEnd:TokenTree):Bool { var fullPos = tokenStart.getPos(); - return (tokenEnd.pos.min >= fullPos.min) && (tokenEnd.pos.max <= fullPos.max); + if ((tokenEnd.pos.min >= fullPos.min) && (tokenEnd.pos.max <= fullPos.max)) { + return true; + } + if (tokenEnd.matches(Semicolon)) { + return (tokenEnd.pos.min == fullPos.max); + } + + return false; } static function findParentFunction(token:TokenTree):Null { @@ -272,18 +325,21 @@ class ExtractMethod { return null; } - static function findReturnType(extractData:ExtractMethodData, context:RefactorContext, neededParams:Array):ReturnType { + static function findCodeGen(extractData:ExtractMethodData, context:RefactorContext, functionIdentifier:Identifier, + neededIdentifiers:Array):Null { final assignedVars:Array = []; final parent = extractData.startToken.parent; if (usedAsExpression(parent)) { if (extractData.isSingleExpr) { - return ReturnAsExpression; + return new CodeGenAsExpression(extractData, context, neededIdentifiers); } else { - return Invalid; + return null; } } + final leakingVars = findAdditionalScopedVars(extractData, context, functionIdentifier, neededIdentifiers); + final allReturns = parent.filterCallback(function(token:TokenTree, index:Int):FilterResult { if (token.pos.max < extractData.startToken.pos.min) { return GoDeeper; @@ -312,44 +368,108 @@ class ExtractMethod { if (allReturns.length > 0) { var lastReturn = allReturns[allReturns.length - 1]; if (isSingleExpression(lastReturn, extractData.endToken)) { - return ReturnIsLast(lastReturn); + return new CodeGenReturnIsLast(extractData, context, neededIdentifiers, lastReturn); } } final modifiedIdentifiers:Array = []; - for (identifier in neededParams) { + for (identifier in neededIdentifiers) { if (assignedVars.contains(identifier.name)) { modifiedIdentifiers.push(identifier); - trace(identifier.name + " modified"); } } if (allReturns.length == 0) { - return NoReturn(modifiedIdentifiers); + return new CodeGenNoReturn(extractData, context, neededIdentifiers, modifiedIdentifiers, leakingVars); } for (ret in allReturns) { var child = ret.getFirstChild(); if (child == null) { - return Invalid; + trace("xxx"); + return null; } switch (child.tok) { case Semicolon: - return EmptyReturn(modifiedIdentifiers); + return new CodeGenEmptyReturn(extractData, context, neededIdentifiers, modifiedIdentifiers, leakingVars); default: if (modifiedIdentifiers.length > 0) { - return Invalid; + trace("xxx"); + return null; } - return OpenEndedReturn(allReturns); + return new CodeGenOpenEnded(extractData, context, neededIdentifiers, allReturns); } } - return Invalid; + trace("xxx"); + return null; + } + + static function findAdditionalScopedVars(extractData:ExtractMethodData, context:RefactorContext, functionIdentifier:Identifier, + neededParams:Array):Array { + // find all scoped identifiers that are valid beyond user's selection + final allVarsInsideSelection = getScopedInsideSelected(extractData, context, functionIdentifier); + final varsValidAfterSelection:Map = new Map(); + for (identifier in allVarsInsideSelection) { + switch (identifier.type) { + case ScopedLocal(scopeStart, scopeEnd, _): + if (scopeEnd > context.what.posEnd) { + varsValidAfterSelection.set(identifier.name, identifier); + } + default: + } + } + + // find all identifier uses after user's selection that share same name + final varShadows:Map = new Map(); + final allIdentifierUses:Array = functionIdentifier.findAllIdentifiers(identifier -> { + if (identifier.pos.start < extractData.endToken.pos.max) { + return false; + } + final parts = identifier.name.split("."); + final part = parts.shift(); + if (!varsValidAfterSelection.exists(part)) { + return false; + } + final scoped:Identifier = varsValidAfterSelection.get(part); + switch (scoped.type) { + case ScopedLocal(_, scopeEnd, _): + if (identifier.pos.start > scopeEnd) { + return false; + } + default: + return false; + } + switch (identifier.type) { + case ScopedLocal(_): + // new scoped identifier that shadows the one from selection + varShadows.set(identifier.name, identifier); + return false; + default: + } + return true; + }); + + // apply shadows + final scopedVarUses:Array = []; + for (use in allIdentifierUses) { + final parts = use.name.split("."); + final part = parts.shift(); + if (varShadows.exists(part)) { + final shadow = varShadows.get(part); + if ((use.pos.start >= shadow.pos.start) && (use.pos.end <= shadow.pos.end)) { + continue; + } + } + scopedVarUses.push(varsValidAfterSelection.get(part)); + trace("leaking " + varsValidAfterSelection.get(part)); + } + return scopedVarUses; } static function makeParameterList(extractData:ExtractMethodData, context:RefactorContext, neededIdentifiers:Array):Array> { var promises:Array> = []; for (identifier in neededIdentifiers) { final promise = findTypeOfIdentifier(context, identifier).then(function(typeHint):Promise { - trace("typehint resolved: " + PrintHelper.typeHintToString(typeHint)); + trace("typehint resolved: " + identifier + " " + PrintHelper.typeHintToString(typeHint)); return Promise.resolve(buildParameter(identifier, typeHint)); }); @@ -358,7 +478,7 @@ class ExtractMethod { return promises; } - static function findTypeOfIdentifier(context:RefactorContext, identifier:Identifier):Promise { + public static function findTypeOfIdentifier(context:RefactorContext, identifier:Identifier):Promise { var hint = identifier.getTypeHint(); if (hint != null) { return TypingHelper.typeFromTypeHint(context, hint); @@ -370,195 +490,6 @@ class ExtractMethod { }); } - static function makeCallSite(extractData:ExtractMethodData, context:RefactorContext, neededIdentifiers:Array, returnType:ReturnType):String { - final callParams:String = neededIdentifiers.map(i -> i.name).join(", "); - return switch (returnType) { - case NoReturn(assignments): - switch (assignments.length) { - case 0: - '${extractData.newMethodName}($callParams);\n'; - case 1: - '${assignments[0].name} = ${extractData.newMethodName}($callParams);\n'; - default: - final assignData = assignments.map(a -> '${a.name} = data.${a.name};').join("\n"); - '{\nfinal data = ${extractData.newMethodName}($callParams);\n' + assignData + "}\n"; - } - case ReturnAsExpression: - '${extractData.newMethodName}($callParams)'; - case ReturnIsLast(_): - 'return ${extractData.newMethodName}($callParams);\n'; - case EmptyReturn(assignments): - switch (assignments.length) { - case 0: - 'if (!${extractData.newMethodName}($callParams)) {\nreturn;\n}\n'; - case 1: - 'switch (${extractData.newMethodName}($callParams)) {\n' - + 'case None:\n' - + 'return;\n' - + 'case Some(data):\n' - + '${assignments[0].name} = data;\n}\n'; - default: - final assignData = assignments.map(a -> '${a.name} = data.${a.name};').join("\n"); - '{\nfinal data =${extractData.newMethodName}($callParams);\n' + assignData + "}\n"; - 'switch (${extractData.newMethodName}($callParams)) {\n' - + 'case None:\n' - + 'return;\n' - + 'case Some(data):\n' - + '${assignData}}\n'; - } - case OpenEndedReturn(_): - 'switch (${extractData.newMethodName}($callParams)) {\n' - + 'case None:\n' - + 'case Some(data):\n' - + 'return data;\n}\n'; - case Invalid: - ""; - } - } - - static function makeReturnTypeHint(extractData:ExtractMethodData, context:RefactorContext, returnType:ReturnType):Promise { - switch (returnType) { - case NoReturn(assignments): - switch (assignments.length) { - case 0: - return Promise.resolve(":Void"); - case 1: - return findTypeOfIdentifier(context, assignments[0]).then(function(typeHint):Promise { - if (typeHint == null) { - return Promise.resolve(""); - } - return Promise.resolve(":" + typeHint.printTypeHint()); - }); - default: - var promises:Array> = []; - for (assign in assignments) { - promises.push(findTypeOfIdentifier(context, assign).then(function(typeHint):Promise { - if (typeHint == null) { - return Promise.resolve(assign.name + ":Any"); - } - return Promise.resolve(":" + typeHint.printTypeHint()); - })); - } - return Promise.all(promises).then(function(fields) { - return Promise.resolve(":{" + fields.join(", ") + "}"); - }); - } - case ReturnAsExpression: - return TypingHelper.findTypeWithTyper(context, context.what.fileName, extractData.endToken.pos.max - 1) - .then(function(typeHint):Promise { - if (typeHint == null) { - return Promise.resolve(""); - } - return Promise.resolve(":" + typeHint.printTypeHint()); - }); - case ReturnIsLast(lastReturnToken): - final pos = lastReturnToken.getPos(); - return TypingHelper.findTypeWithTyper(context, context.what.fileName, pos.max - 2).then(function(typeHint):Promise { - if (typeHint == null) { - return Promise.resolve(""); - } - return Promise.resolve(":" + typeHint.printTypeHint()); - }); - case EmptyReturn(assignments): - switch (assignments.length) { - case 0: - return Promise.resolve(":Bool"); - case 1: - return findTypeOfIdentifier(context, assignments[0]).then(function(typeHint):Promise { - if (typeHint == null) { - return Promise.resolve(""); - } - return Promise.resolve(":haxe.ds.Option<" + typeHint.printTypeHint() + ">"); - }); - default: - var promises:Array> = []; - for (assign in assignments) { - promises.push(findTypeOfIdentifier(context, assign).then(function(typeHint):Promise { - if (typeHint == null) { - return Promise.resolve(assign.name + ":Any"); - } - return Promise.resolve(":" + typeHint.printTypeHint()); - })); - } - return Promise.all(promises).then(function(fields) { - return Promise.resolve(":haxe.ds.Option<{" + fields.join(", ") + "}>"); - }); - } - case OpenEndedReturn(returnTokens): - var token = returnTokens.shift(); - if (token == null) { - return Promise.resolve(""); - } - final pos = token.getPos(); - return TypingHelper.findTypeWithTyper(context, context.what.fileName, pos.max - 2).then(function(typeHint):Promise { - if (typeHint == null) { - return Promise.resolve(""); - } - return Promise.resolve(":haxe.ds.Option<" + typeHint.printTypeHint() + ">"); - }); - case Invalid: - return Promise.resolve(""); - } - return Promise.resolve(""); - } - - static function makeBody(extractData:ExtractMethodData, context:RefactorContext, returnType:ReturnType):String { - final selectedSnippet = RefactorHelper.extractText(context.converter, extractData.content, extractData.startToken.pos.min, - extractData.endToken.pos.max); - return switch (returnType) { - case NoReturn(assignments): - switch (assignments.length) { - case 0: - " {\n" + selectedSnippet + "\n}\n"; - case 1: - final returnAssigmentVar = '\nreturn ${assignments[0].name};'; - " {\n" + selectedSnippet + returnAssigmentVar + "\n}\n"; - default: - final returnAssigments = "\nreturn {\n" + assignments.map(a -> '${a.name}: ${a.name},\n') + "};"; - " {\n" + selectedSnippet + returnAssigments + "\n}\n"; - } - case ReturnAsExpression: - " {\n" + "return " + selectedSnippet + "\n}\n"; - case ReturnIsLast(_): - " {\n" + selectedSnippet + "\n}\n"; - case EmptyReturn(assignments): - switch (assignments.length) { - case 0: - final reg:EReg = ~/^([ \t]*)return[ \t]*;/gm; - final replacedSnippet = reg.map(selectedSnippet, f -> f.matched(1) + "return false;"); - " {\n" + replacedSnippet + "\nreturn true;\n}\n"; - case 1: - final reg:EReg = ~/^([ \t]*)return[ \t]*;/gm; - final replacedSnippet = reg.map(selectedSnippet, f -> f.matched(1) + "return None;"); - final returnAssigmentVar = '\nreturn Some(${assignments[0].name});'; - " {\n" + replacedSnippet + returnAssigmentVar + "\n}\n"; - default: - final reg:EReg = ~/^([ \t]*)return[ \t]*;/gm; - final replacedSnippet = reg.map(selectedSnippet, f -> f.matched(1) + "return None;"); - final returnAssigments = "\nreturn Some({\n" + assignments.map(a -> '${a.name}: ${a.name},\n') + "});"; - " {\n" + replacedSnippet + returnAssigments + "\n}\n"; - } - case OpenEndedReturn(returnTokens): - var startOffset = context.converter(extractData.content, extractData.startToken.pos.min); - var snippet = selectedSnippet; - returnTokens.reverse(); - for (token in returnTokens) { - var pos = token.getPos(); - var returnStart = context.converter(extractData.content, pos.min); - var returnEnd = context.converter(extractData.content, pos.max); - returnStart -= startOffset; - returnEnd -= startOffset; - var before = snippet.substring(0, returnStart); - var after = snippet.substring(returnEnd); - var retValue = snippet.substring(returnStart + 7, returnEnd - 1); - snippet = before + "return Some(" + retValue + ");" + after; - } - " {\n" + snippet + "\nreturn None;\n}\n"; - case Invalid: - ""; - } - } - static function buildParameter(identifier:Identifier, typeHint:TypeHintType):String { if (typeHint == null) { return '${identifier.name}:Any'; @@ -599,12 +530,3 @@ typedef ExtractMethodData = { var isStatic:Bool; var isSingleExpr:Bool; } - -enum ReturnType { - NoReturn(assignments:Array); - ReturnAsExpression; - ReturnIsLast(lastReturnToken:TokenTree); - EmptyReturn(assignments:Array); - OpenEndedReturn(returnTokens:Array); - Invalid; -} diff --git a/src/refactor/refactor/refactormethod/CodeGenAsExpression.hx b/src/refactor/refactor/refactormethod/CodeGenAsExpression.hx new file mode 100644 index 0000000..6327619 --- /dev/null +++ b/src/refactor/refactor/refactormethod/CodeGenAsExpression.hx @@ -0,0 +1,45 @@ +package refactor.refactor.refactormethod; + +import refactor.discover.Identifier; +import refactor.refactor.ExtractMethod.ExtractMethodData; + +class CodeGenAsExpression extends CodeGenBase { + public function new(extractData:ExtractMethodData, context:RefactorContext, neededIdentifiers:Array) { + super(extractData, context, neededIdentifiers); + } + + public function makeCallSite():String { + final callParams:String = neededIdentifiers.map(i -> i.name).join(", "); + if (extractData.endToken.matches(Semicolon)) { + return '${extractData.newMethodName}($callParams);\n'; + } + return '${extractData.newMethodName}($callParams)'; + } + + public function makeReturnTypeHint():Promise { + var parent = extractData.startToken.parent; + switch (parent.tok) { + case Binop(OpAssign) | Binop(OpAssignOp(_)): + parent = parent.parent; + return TypingHelper.findTypeWithTyper(context, context.what.fileName, parent.pos.max - 1).then(function(typeHint):Promise { + if (typeHint == null) { + return Promise.resolve(""); + } + return Promise.resolve(":" + typeHint.printTypeHint()); + }); + default: + } + return TypingHelper.findTypeWithTyper(context, context.what.fileName, extractData.endToken.pos.max - 1).then(function(typeHint):Promise { + if (typeHint == null) { + return Promise.resolve(""); + } + return Promise.resolve(":" + typeHint.printTypeHint()); + }); + } + + public function makeBody():String { + final selectedSnippet = RefactorHelper.extractText(context.converter, extractData.content, extractData.startToken.pos.min, + extractData.endToken.pos.max); + return " {\n" + "return " + selectedSnippet + "\n}\n"; + } +} diff --git a/src/refactor/refactor/refactormethod/CodeGenBase.hx b/src/refactor/refactor/refactormethod/CodeGenBase.hx new file mode 100644 index 0000000..edb0e45 --- /dev/null +++ b/src/refactor/refactor/refactormethod/CodeGenBase.hx @@ -0,0 +1,21 @@ +package refactor.refactor.refactormethod; + +import refactor.TypingHelper.TypeHintType; +import refactor.discover.Identifier; +import refactor.refactor.ExtractMethod.ExtractMethodData; + +abstract class CodeGenBase implements ICodeGen { + final extractData:ExtractMethodData; + final context:RefactorContext; + final neededIdentifiers:Array; + + public function new(extractData:ExtractMethodData, context:RefactorContext, neededIdentifiers:Array) { + this.extractData = extractData; + this.context = context; + this.neededIdentifiers = neededIdentifiers; + } + + function findTypeOfIdentifier(identifier:Identifier):Promise { + return ExtractMethod.findTypeOfIdentifier(context, identifier); + } +} diff --git a/src/refactor/refactor/refactormethod/CodeGenEmptyReturn.hx b/src/refactor/refactor/refactormethod/CodeGenEmptyReturn.hx new file mode 100644 index 0000000..48e34b6 --- /dev/null +++ b/src/refactor/refactor/refactormethod/CodeGenEmptyReturn.hx @@ -0,0 +1,89 @@ +package refactor.refactor.refactormethod; + +import refactor.discover.Identifier; +import refactor.refactor.ExtractMethod.ExtractMethodData; + +class CodeGenEmptyReturn extends CodeGenBase { + final assignments:Array; + final vars:Array; + + public function new(extractData:ExtractMethodData, context:RefactorContext, neededIdentifiers:Array, assignments:Array, + vars:Array) { + super(extractData, context, neededIdentifiers); + + this.assignments = assignments; + this.vars = vars; + } + + public function makeCallSite():String { + final callParams:String = neededIdentifiers.map(i -> i.name).join(", "); + return switch [assignments.length, vars.length] { + case [0, 0]: + 'if (!${extractData.newMethodName}($callParams)) {\nreturn;\n}\n'; + case [1, 0]: + 'switch (${extractData.newMethodName}($callParams)) {\n' + + 'case None:\n' + + 'return;\n' + + 'case Some(data):\n' + + '${assignments[0].name} = data;\n}\n'; + case [_, 0]: + final assignData = assignments.map(a -> '${a.name} = data.${a.name};').join("\n"); + '{\nfinal data =${extractData.newMethodName}($callParams);\n' + assignData + "}\n"; + 'switch (${extractData.newMethodName}($callParams)) {\n' + + 'case None:\n' + + 'return;\n' + + 'case Some(data):\n' + + '${assignData}}\n'; + case [_, _]: + "TODO please implement!! :)"; + } + } + + public function makeReturnTypeHint():Promise { + return switch (assignments.length) { + case 0: + return Promise.resolve(":Bool"); + case 1: + return findTypeOfIdentifier(assignments[0]).then(function(typeHint):Promise { + if (typeHint == null) { + return Promise.resolve(""); + } + return Promise.resolve(":haxe.ds.Option<" + typeHint.printTypeHint() + ">"); + }); + default: + var promises:Array> = []; + for (assign in assignments) { + promises.push(findTypeOfIdentifier(assign).then(function(typeHint):Promise { + if (typeHint == null) { + return Promise.resolve(assign.name + ":Any"); + } + return Promise.resolve(":" + typeHint.printTypeHint()); + })); + } + return Promise.all(promises).then(function(fields) { + return Promise.resolve(":haxe.ds.Option<{" + fields.join(", ") + "}>"); + }); + } + } + + public function makeBody():String { + final selectedSnippet = RefactorHelper.extractText(context.converter, extractData.content, extractData.startToken.pos.min, + extractData.endToken.pos.max); + return switch (assignments.length) { + case 0: + final reg:EReg = ~/^([ \t]*)return[ \t]*;/gm; + final replacedSnippet = reg.map(selectedSnippet, f -> f.matched(1) + "return false;"); + " {\n" + replacedSnippet + "\nreturn true;\n}\n"; + case 1: + final reg:EReg = ~/^([ \t]*)return[ \t]*;/gm; + final replacedSnippet = reg.map(selectedSnippet, f -> f.matched(1) + "return None;"); + final returnAssigmentVar = '\nreturn Some(${assignments[0].name});'; + " {\n" + replacedSnippet + returnAssigmentVar + "\n}\n"; + default: + final reg:EReg = ~/^([ \t]*)return[ \t]*;/gm; + final replacedSnippet = reg.map(selectedSnippet, f -> f.matched(1) + "return None;"); + final returnAssigments = "\nreturn Some({\n" + assignments.map(a -> '${a.name}: ${a.name},\n') + "});"; + " {\n" + replacedSnippet + returnAssigments + "\n}\n"; + } + } +} diff --git a/src/refactor/refactor/refactormethod/CodeGenNoReturn.hx b/src/refactor/refactor/refactormethod/CodeGenNoReturn.hx new file mode 100644 index 0000000..8f90bc4 --- /dev/null +++ b/src/refactor/refactor/refactormethod/CodeGenNoReturn.hx @@ -0,0 +1,96 @@ +package refactor.refactor.refactormethod; + +import refactor.discover.Identifier; +import refactor.refactor.ExtractMethod.ExtractMethodData; + +class CodeGenNoReturn extends CodeGenBase { + final assignments:Array; + final vars:Array; + + public function new(extractData:ExtractMethodData, context:RefactorContext, neededIdentifiers:Array, assignments:Array, + vars:Array) { + super(extractData, context, neededIdentifiers); + + this.assignments = assignments; + this.vars = vars; + } + + public function makeCallSite():String { + final callParams:String = neededIdentifiers.map(i -> i.name).join(", "); + return switch [assignments.length, vars.length] { + case [0, 0]: + '${extractData.newMethodName}($callParams);\n'; + case [0, 1]: + 'var ${vars[0].name} = ${extractData.newMethodName}($callParams);\n'; + case [1, 0]: + '${assignments[0].name} = ${extractData.newMethodName}($callParams);\n'; + case [_, _]: + final dataVars = vars.map(v -> 'var ${v.name};').join("\n"); + final assignData = assignments.map(a -> '${a.name} = data.${a.name};').join("\n"); + final assignVars = vars.map(v -> '${v.name} = data.${v.name};').join("\n"); + '$dataVars\n{\nfinal data = ${extractData.newMethodName}($callParams);\n' + assignData + assignVars + "}\n"; + } + } + + public function makeReturnTypeHint():Promise { + return switch [assignments.length, vars.length] { + case [0, 0]: + return Promise.resolve(":Void"); + case [0, 1]: + return findTypeOfIdentifier(vars[0]).then(function(typeHint):Promise { + if (typeHint == null) { + return Promise.resolve(""); + } + return Promise.resolve(":" + typeHint.printTypeHint()); + }); + case [1, 0]: + return findTypeOfIdentifier(assignments[0]).then(function(typeHint):Promise { + if (typeHint == null) { + return Promise.resolve(""); + } + return Promise.resolve(":" + typeHint.printTypeHint()); + }); + case [_, _]: + var promises:Array> = []; + for (assign in assignments) { + promises.push(findTypeOfIdentifier(assign).then(function(typeHint):Promise { + if (typeHint == null) { + return Promise.resolve(assign.name + ":Any"); + } + return Promise.resolve(assign.name + ":" + typeHint.printTypeHint()); + })); + } + for (v in vars) { + promises.push(findTypeOfIdentifier(v).then(function(typeHint):Promise { + if (typeHint == null) { + return Promise.resolve(v.name + ":Any"); + } + return Promise.resolve(v.name + ":" + typeHint.printTypeHint()); + })); + } + return Promise.all(promises).then(function(fields) { + return Promise.resolve(":{" + fields.join(", ") + "}"); + }); + } + } + + public function makeBody():String { + final selectedSnippet = RefactorHelper.extractText(context.converter, extractData.content, extractData.startToken.pos.min, + extractData.endToken.pos.max); + return switch [assignments.length, vars.length] { + case [0, 0]: + " {\n" + selectedSnippet + "\n}\n"; + case [0, 1]: + final returnAssigmentVar = '\nreturn ${vars[0].name};'; + " {\n" + selectedSnippet + returnAssigmentVar + "\n}\n"; + case [1, 0]: + final returnAssigmentVar = '\nreturn ${assignments[0].name};'; + " {\n" + selectedSnippet + returnAssigmentVar + "\n}\n"; + case [_, _]: + final assignData = assignments.map(a -> '${a.name}: ${a.name},').join("\n"); + final assignVars = vars.map(v -> '${v.name}: ${v.name},').join("\n"); + final returnAssigments = "\nreturn {\n" + assignData + assignVars + "};"; + " {\n" + selectedSnippet + returnAssigments + "\n}\n"; + } + } +} diff --git a/src/refactor/refactor/refactormethod/CodeGenOpenEnded.hx b/src/refactor/refactor/refactormethod/CodeGenOpenEnded.hx new file mode 100644 index 0000000..cde2428 --- /dev/null +++ b/src/refactor/refactor/refactormethod/CodeGenOpenEnded.hx @@ -0,0 +1,53 @@ +package refactor.refactor.refactormethod; + +import refactor.discover.Identifier; +import refactor.refactor.ExtractMethod.ExtractMethodData; + +class CodeGenOpenEnded extends CodeGenBase { + final returnTokens:Array; + + public function new(extractData:ExtractMethodData, context:RefactorContext, neededIdentifiers:Array, returnTokens:Array) { + super(extractData, context, neededIdentifiers); + + this.returnTokens = returnTokens; + } + + public function makeCallSite():String { + final callParams:String = neededIdentifiers.map(i -> i.name).join(", "); + return 'switch (${extractData.newMethodName}($callParams)) {\n' + 'case None:\n' + 'case Some(data):\n' + 'return data;\n}\n'; + } + + public function makeReturnTypeHint():Promise { + var token = returnTokens.shift(); + if (token == null) { + return Promise.resolve(""); + } + final pos = token.getPos(); + return TypingHelper.findTypeWithTyper(context, context.what.fileName, pos.max - 2).then(function(typeHint):Promise { + if (typeHint == null) { + return Promise.resolve(""); + } + return Promise.resolve(":haxe.ds.Option<" + typeHint.printTypeHint() + ">"); + }); + } + + public function makeBody():String { + final selectedSnippet = RefactorHelper.extractText(context.converter, extractData.content, extractData.startToken.pos.min, + extractData.endToken.pos.max); + var startOffset = context.converter(extractData.content, extractData.startToken.pos.min); + var snippet = selectedSnippet; + returnTokens.reverse(); + for (token in returnTokens) { + var pos = token.getPos(); + var returnStart = context.converter(extractData.content, pos.min); + var returnEnd = context.converter(extractData.content, pos.max); + returnStart -= startOffset; + returnEnd -= startOffset; + var before = snippet.substring(0, returnStart); + var after = snippet.substring(returnEnd); + var retValue = snippet.substring(returnStart + 7, returnEnd - 1); + snippet = before + "return Some(" + retValue + ");" + after; + } + return " {\n" + snippet + "\nreturn None;\n}\n"; + } +} diff --git a/src/refactor/refactor/refactormethod/CodeGenReturnIsLast.hx b/src/refactor/refactor/refactormethod/CodeGenReturnIsLast.hx new file mode 100644 index 0000000..c764210 --- /dev/null +++ b/src/refactor/refactor/refactormethod/CodeGenReturnIsLast.hx @@ -0,0 +1,38 @@ +package refactor.refactor.refactormethod; + +import refactor.discover.Identifier; +import refactor.refactor.ExtractMethod.ExtractMethodData; + +class CodeGenReturnIsLast extends CodeGenBase { + final lastReturnToken:Null; + + public function new(extractData:ExtractMethodData, context:RefactorContext, neededIdentifiers:Array, lastReturnToken:TokenTree) { + super(extractData, context, neededIdentifiers); + + this.lastReturnToken = lastReturnToken; + } + + public function makeCallSite():String { + final callParams:String = neededIdentifiers.map(i -> i.name).join(", "); + return 'return ${extractData.newMethodName}($callParams);\n'; + } + + public function makeReturnTypeHint():Promise { + if (lastReturnToken == null) { + return Promise.resolve(""); + } + final pos = lastReturnToken.getPos(); + return TypingHelper.findTypeWithTyper(context, context.what.fileName, pos.max - 2).then(function(typeHint):Promise { + if (typeHint == null) { + return Promise.resolve(""); + } + return Promise.resolve(":" + typeHint.printTypeHint()); + }); + } + + public function makeBody():String { + final selectedSnippet = RefactorHelper.extractText(context.converter, extractData.content, extractData.startToken.pos.min, + extractData.endToken.pos.max); + return " {\n" + selectedSnippet + "\n}\n"; + } +} diff --git a/src/refactor/refactor/refactormethod/ICodeGen.hx b/src/refactor/refactor/refactormethod/ICodeGen.hx new file mode 100644 index 0000000..41f3cd7 --- /dev/null +++ b/src/refactor/refactor/refactormethod/ICodeGen.hx @@ -0,0 +1,7 @@ +package refactor.refactor.refactormethod; + +interface ICodeGen { + function makeCallSite():String; + function makeReturnTypeHint():Promise; + function makeBody():String; +} diff --git a/src/refactor/rename/RenameHelper.hx b/src/refactor/rename/RenameHelper.hx index 387521d..222d654 100644 --- a/src/refactor/rename/RenameHelper.hx +++ b/src/refactor/rename/RenameHelper.hx @@ -136,9 +136,7 @@ class RenameHelper { changelist.addChange(use.pos.fileName, ReplaceText(context.what.toName, pos, false), use); } case LibType(_, _) | UnknownType(_): - trace("TODO"); case FunctionType(_, _) | StructType(_): - trace("TODO"); } }); } diff --git a/test/refactor/TestBase.hx b/test/refactor/TestBase.hx index 2a69e65..f65778b 100644 --- a/test/refactor/TestBase.hx +++ b/test/refactor/TestBase.hx @@ -4,6 +4,7 @@ import haxe.PosInfos; import js.lib.Promise; import refactor.RefactorResult; import refactor.TestEditableDocument; +import refactor.TypingHelper.TypeHintType; import refactor.discover.FileList; import refactor.discover.NameMap; import refactor.discover.TraverseSources; @@ -14,8 +15,11 @@ import refactor.edits.FileEdit; class TestBase implements ITest { var usageContext:UsageContext; + var typer:TestTyper; - public function new() {} + public function new() { + typer = new TestTyper(); + } function setupTestSources(srcFolders:Array) { usageContext = { @@ -36,6 +40,7 @@ class TestBase implements ITest { @:access(refactor.discover.NameMap) function setup() { + typer.clear(); for (key => list in usageContext.nameMap.names) { for (identifier in list) { identifier.edited = false; @@ -43,6 +48,10 @@ class TestBase implements ITest { } } + public function addTypeHint(fileName:String, pos:Int, typeHint:TypeHintType) { + typer.addTypeHint(fileName, pos, typeHint); + } + function fileEditToString(edit:FileEdit):String { return switch (edit) { case CreateFile(newFileName): diff --git a/test/refactor/TestTyper.hx b/test/refactor/TestTyper.hx new file mode 100644 index 0000000..1cc8f95 --- /dev/null +++ b/test/refactor/TestTyper.hx @@ -0,0 +1,28 @@ +package refactor; + +import js.lib.Promise; +import refactor.TypingHelper.TypeHintType; + +using refactor.PrintHelper; + +class TestTyper implements ITyper { + var typeHints:Map; + + public function new() { + typeHints = new Map(); + } + + public function clear() { + typeHints.clear(); + } + + public function addTypeHint(fileName:String, pos:Int, typeHint:TypeHintType) { + typeHints.set('$fileName@$pos', typeHint); + } + + public function resolveType(fileName:String, pos:Int):Promise> { + var typeHint:TypeHintType = typeHints.get('$fileName@$pos'); + trace('[TestTyper] resolving $fileName@$pos -> ${typeHint.typeHintToString()}'); + return Promise.resolve(typeHint); + } +} diff --git a/test/refactor/refactor/RefactorClassTest.hx b/test/refactor/refactor/RefactorClassTest.hx index db4ab1c..3edd6a8 100644 --- a/test/refactor/refactor/RefactorClassTest.hx +++ b/test/refactor/refactor/RefactorClassTest.hx @@ -23,10 +23,10 @@ class RefactorClassTest extends RefactorTestBase { "package classes;\n\n" + "import js.lib.Promise;\n\n" + "class TextLoader {\n" - + "\tpublic function new() {}\n\n" - + "\tpublic function load(text:String):Promise {\n" - + "\t\treturn Promise.resolve(text);\n" - + "\t}\n" + + " public function new() {}\n\n" + + " public function load(text:String):Promise {\n" + + " return Promise.resolve(text);\n" + + " }\n" + "}", 0, true), makeInsertTestEdit("testcases/classes/pack/UsePrinter.hx", "import classes.TextLoader;\n", 23), @@ -41,11 +41,11 @@ class RefactorClassTest extends RefactorTestBase { makeInsertTestEdit("testcases/classes/IBaseClass.hx", "package classes;\n\n" + "interface IBaseClass {\n" - + "\tfunction doSomething(data:Array):Void;\n" - + "\tfunction doSomething3(d:Array):Void;\n" - + "\tfunction doSomething4(d:Array):Void;\n" - + "\tfunction doSomething5(d:Array):Void;\n" - + "\tfunction doSomething6(d:Array):Void;\n" + + " function doSomething(data:Array):Void;\n" + + " function doSomething3(d:Array):Void;\n" + + " function doSomething4(d:Array):Void;\n" + + " function doSomething5(d:Array):Void;\n" + + " function doSomething6(d:Array):Void;\n" + "}", 0, true), ]; diff --git a/test/refactor/refactor/RefactorExtractMethodTest.hx b/test/refactor/refactor/RefactorExtractMethodTest.hx index fdb4d23..1ce937b 100644 --- a/test/refactor/refactor/RefactorExtractMethodTest.hx +++ b/test/refactor/refactor/RefactorExtractMethodTest.hx @@ -1,5 +1,7 @@ package refactor.refactor; +import refactor.TypingHelper.TypeHintType; + class RefactorExtractMethodTest extends RefactorTestBase { function setupClass() { setupTestSources(["testcases/methods"]); @@ -11,7 +13,7 @@ class RefactorExtractMethodTest extends RefactorTestBase { makeInsertTestEdit("testcases/methods/Main.hx", "function noReturnsExtract():Void {\n" + "trace(\"hello 2\");\n" - + "\t\ttrace(\"hello 3\");\n" + + " trace(\"hello 3\");\n" + "}\n", 155, true), ]; checkRefactor(RefactorExtractMethod, {fileName: "testcases/methods/Main.hx", posStart: 93, posEnd: 131}, edits, async); @@ -23,7 +25,7 @@ class RefactorExtractMethodTest extends RefactorTestBase { makeInsertTestEdit("testcases/methods/Main.hx", "static function noReturnsStaticExtract():Void {\n" + "trace(\"hello 2\");\n" - + "\t\ttrace(\"hello 3\");\n" + + " trace(\"hello 3\");\n" + "}\n", 283, true), ]; checkRefactor(RefactorExtractMethod, {fileName: "testcases/methods/Main.hx", posStart: 221, posEnd: 259}, edits, async); @@ -35,13 +37,13 @@ class RefactorExtractMethodTest extends RefactorTestBase { makeInsertTestEdit("testcases/methods/Main.hx", "function emptyReturnsExtract(cond1:Bool, cond2:Bool):Bool {\n" + "if (cond1) {\n" - + "\t\t\treturn false;\n" - + "\t\t}\n" - + "\t\tif (cond2) {\n" - + "\t\t\treturn false;\n" - + "\t\t}\n" - + "\t\ttrace(\"hello 1\");\n" - + "\t\ttrace(\"hello 2\");\n" + + " return false;\n" + + " }\n" + + " if (cond2) {\n" + + " return false;\n" + + " }\n" + + " trace(\"hello 1\");\n" + + " trace(\"hello 2\");\n" + "return true;\n" + "}\n", 483, true), @@ -55,11 +57,11 @@ class RefactorExtractMethodTest extends RefactorTestBase { makeInsertTestEdit("testcases/methods/Main.hx", "function calculateTotalExtract(items:Array, total:Float):Float {\n" + "// Selected code block to extract\n" - + "\t\tfor (item in items) {\n" - + "\t\t\tvar price = item.price;\n" - + "\t\t\tvar quantity = item.quantity;\n" - + "\t\t\ttotal += price * quantity;\n" - + "\t\t}\n" + + " for (item in items) {\n" + + " var price = item.price;\n" + + " var quantity = item.quantity;\n" + + " total += price * quantity;\n" + + " }\n" + "return total;\n" + "}\n", 741, true), @@ -67,20 +69,174 @@ class RefactorExtractMethodTest extends RefactorTestBase { checkRefactor(RefactorExtractMethod, {fileName: "testcases/methods/Main.hx", posStart: 568, posEnd: 720}, edits, async); } - function testProcessUser(async:Async) { + function testCalculateTotalWithLastReturn(async:Async) { var edits:Array = [ - makeReplaceTestEdit("testcases/methods/Main.hx", "processUserExtract(name, age);\n", 844, 980, true), + makeReplaceTestEdit("testcases/methods/Main.hx", "return calculateTotalExtract(items, total);\n", 569, 737, true), makeInsertTestEdit("testcases/methods/Main.hx", - "function processUserExtract(name:String, age:Int):Void {\n" + "function calculateTotalExtract(items:Array, total:Float):Float {\n" + "// Selected code block to extract\n" - + "\t\tvar greeting = \"Hello, \" + name;\n" - + "\t\tif (age < 18) {\n" - + "\t\t\tgreeting += \" (minor)\";\n" - + "\t\t}\n" - + "\t\ttrace(greeting);\n" + + " for (item in items) {\n" + + " var price = item.price;\n" + + " var quantity = item.quantity;\n" + + " total += price * quantity;\n" + + " }\n" + + "\n" + + " return total;\n" + + "}\n", + 741, true), + ]; + addTypeHint("testcases/methods/Main.hx", 735, LibType("Float", "Float", [])); + checkRefactor(RefactorExtractMethod, {fileName: "testcases/methods/Main.hx", posStart: 568, posEnd: 737}, edits, async); + } + + function testProcessUser2(async:Async) { + var edits:Array = [ + makeReplaceTestEdit("testcases/methods/Main.hx", "return calculateTotal2Extract(items, total);\n", 1081, 1295, true), + makeInsertTestEdit("testcases/methods/Main.hx", + "function calculateTotal2Extract(items:Array, total:Float) {\n" + + "// Selected code block to extract\n" + + " for (item in items) {\n" + + " var price = item.price;\n" + + " var quantity = item.quantity;\n" + + " if (quantity < 0) {\n" + + " return total;\n" + + " }\n" + + " total += price * quantity;\n" + + " }\n\n" + + " return total;\n" + + "}\n", + 1299, true), + ]; + checkRefactor(RefactorExtractMethod, {fileName: "testcases/methods/Main.hx", posStart: 1080, posEnd: 1295}, edits, async); + } + + // function testProcessUser3(async:Async) { + // var edits:Array = [ + // makeReplaceTestEdit("testcases/methods/Main.hx", "return calculateTotal2Extract(items, total);\n", 1081, 1295, true), + // makeInsertTestEdit("testcases/methods/Main.hx", + // "function calculateTotal2Extract(items:Array, total:Float) {\n" + // + "// Selected code block to extract\n" + // + " for (item in items) {\n" + // + " var price = item.price;\n" + // + " var quantity = item.quantity;\n" + // + " if (quantity < 0) {\n" + // + " return total;\n" + // + " }\n" + // + " total += price * quantity;\n" + // + " }\n\n" + // + " return total;\n" + // + "}\n", + // 1299, true), + // ]; + // checkRefactor(RefactorExtractMethod, {fileName: "testcases/methods/Main.hx", posStart: 1080, posEnd: 1278}, edits, async); + // } + + function testCalculateMath(async:Async) { + var edits:Array = [ + makeReplaceTestEdit("testcases/methods/Math.hx", "calculateExtract(a, b);\n", 106, 122, true), + makeInsertTestEdit("testcases/methods/Math.hx", "function calculateExtract(a:Int, b:Int):Int {\n" + + "return a * b + (a - b);\n" + + "}\n", 143, true), + ]; + addTypeHint("testcases/methods/Math.hx", 71, LibType("Int", "Int", [])); + addTypeHint("testcases/methods/Math.hx", 84, LibType("Int", "Int", [])); + addTypeHint("testcases/methods/Math.hx", 102, LibType("Int", "Int", [])); + checkRefactor(RefactorExtractMethod, {fileName: "testcases/methods/Math.hx", posStart: 106, posEnd: 122}, edits, async); + } + + function testCalculateMathWithVar(async:Async) { + var edits:Array = [ + makeReplaceTestEdit("testcases/methods/Math.hx", "var result = calculateExtract(a, b);\n", 93, 122, true), + makeInsertTestEdit("testcases/methods/Math.hx", + "function calculateExtract(a:Int, b:Int):Int {\n" + + "var result = a * b + (a - b);\n" + + "return result;\n" + + "}\n", 143, true), + ]; + addTypeHint("testcases/methods/Math.hx", 71, LibType("Int", "Int", [])); + addTypeHint("testcases/methods/Math.hx", 84, LibType("Int", "Int", [])); + addTypeHint("testcases/methods/Math.hx", 102, LibType("Int", "Int", [])); + checkRefactor(RefactorExtractMethod, {fileName: "testcases/methods/Math.hx", posStart: 93, posEnd: 122}, edits, async); + } + + function testNameProcessor(async:Async) { + var edits:Array = [ + makeReplaceTestEdit("testcases/methods/NameProcessor.hx", "var upperNames = processExtract(names);\n", 113, 201, true), + makeInsertTestEdit("testcases/methods/NameProcessor.hx", + "function processExtract(names:Array):Array {\n" + + "var upperNames = [];\n" + + " for (name in names) {\n" + + " upperNames.push(name.toUpperCase());\n" + + " }\n" + + "return upperNames;\n" + + "}\n", + 226, true), + ]; + addTypeHint("testcases/methods/NameProcessor.hx", 82, LibType("Array", "Array", [LibType("String", "String", [])])); + addTypeHint("testcases/methods/NameProcessor.hx", 126, LibType("Array", "Array", [UnknownType("?")])); + checkRefactor(RefactorExtractMethod, {fileName: "testcases/methods/NameProcessor.hx", posStart: 112, posEnd: 201}, edits, async); + } + + function testArrayHandler(async:Async) { + var edits:Array = [ + makeReplaceTestEdit("testcases/methods/ArrayHandler.hx", "var sum = handleExtract(numbers);\n", 105, 157, true), + makeInsertTestEdit("testcases/methods/ArrayHandler.hx", + "function handleExtract(numbers:Array):Int {\n" + + "var sum = 0;\n" + + " for (n in numbers) {\n" + + " sum += n;\n" + + " }\n" + + "return sum;\n" + + "}\n", + 175, true), + ]; + addTypeHint("testcases/methods/ArrayHandler.hx", 82, LibType("Array", "Array", [LibType("Int", "Int", [])])); + addTypeHint("testcases/methods/ArrayHandler.hx", 111, LibType("Int", "Int", [])); + checkRefactor(RefactorExtractMethod, {fileName: "testcases/methods/ArrayHandler.hx", posStart: 104, posEnd: 157}, edits, async); + } + + function testAgeChecker(async:Async) { + var edits:Array = [ + makeReplaceTestEdit("testcases/methods/AgeChecker.hx", "message = checkExtract(age, message);\n", 105, 179, true), + makeInsertTestEdit("testcases/methods/AgeChecker.hx", + "function checkExtract(age:Int, message:String):String {\n" + + "if (age < 18) {\n" + + " message = \"Minor\";\n" + + " } else {\n" + + " message = \"Adult\";\n" + + " }\n" + + "return message;\n" + + "}\n", + 201, true), + ]; + addTypeHint("testcases/methods/AgeChecker.hx", 75, LibType("Int", "Int", [])); + addTypeHint("testcases/methods/AgeChecker.hx", 95, LibType("String", "String", [])); + checkRefactor(RefactorExtractMethod, {fileName: "testcases/methods/AgeChecker.hx", posStart: 104, posEnd: 179}, edits, async); + } + + function testPersonHandler(async:Async) { + var edits:Array = [ + makeReplaceTestEdit("testcases/methods/PersonHandler.hx", "handleExtract(person);\n", 113, 161, true), + makeInsertTestEdit("testcases/methods/PersonHandler.hx", + "function handleExtract(person:Any) {\n" + "return \"Name: \" + person.name + \", Age: \" + person.age;\n" + "}\n", 180, true), + ]; + addTypeHint("testcases/methods/PersonHandler.hx", 75, LibType("Int", "Int", [])); + addTypeHint("testcases/methods/PersonHandler.hx", 95, LibType("String", "String", [])); + checkRefactor(RefactorExtractMethod, {fileName: "testcases/methods/PersonHandler.hx", posStart: 113, posEnd: 161}, edits, async); + } + + function testPersonHandlerWithVar(async:Async) { + var edits:Array = [ + makeReplaceTestEdit("testcases/methods/PersonHandler.hx", "var info = handleExtract(person);\n", 102, 161, true), + makeInsertTestEdit("testcases/methods/PersonHandler.hx", + "function handleExtract(person:Any) {\n" + + "var info = \"Name: \" + person.name + \", Age: \" + person.age;\n" + + "return info;\n" + "}\n", - 994, true), + 180, true), ]; - checkRefactor(RefactorExtractMethod, {fileName: "testcases/methods/Main.hx", posStart: 843, posEnd: 980}, edits, async); + addTypeHint("testcases/methods/PersonHandler.hx", 75, LibType("Int", "Int", [])); + addTypeHint("testcases/methods/PersonHandler.hx", 95, LibType("String", "String", [])); + checkRefactor(RefactorExtractMethod, {fileName: "testcases/methods/PersonHandler.hx", posStart: 101, posEnd: 161}, edits, async); } } diff --git a/test/refactor/refactor/RefactorTestBase.hx b/test/refactor/refactor/RefactorTestBase.hx index 606e356..0791970 100644 --- a/test/refactor/refactor/RefactorTestBase.hx +++ b/test/refactor/refactor/RefactorTestBase.hx @@ -2,8 +2,6 @@ package refactor.refactor; import haxe.Exception; import haxe.PosInfos; -import haxe.io.Path; -import js.html.AbortController; import js.lib.Promise; import byte.ByteData; import haxeparser.HaxeLexer; @@ -90,7 +88,7 @@ class RefactorTestBase extends TestBase { verboseLog: function(text:String, ?pos:PosInfos) { Sys.println('${pos.fileName}:${pos.lineNumber}: $text'); }, - typer: null, + typer: typer, converter: (string, byteOffset) -> byteOffset, fileReader: testFileReader, }).then(function(success:RefactorResult) { diff --git a/test/refactor/refactor/RefactorTypedefTest.hx b/test/refactor/refactor/RefactorTypedefTest.hx index 401bfed..8ea1b53 100644 --- a/test/refactor/refactor/RefactorTypedefTest.hx +++ b/test/refactor/refactor/RefactorTypedefTest.hx @@ -13,21 +13,21 @@ class RefactorTypedefTest extends RefactorTestBase { "package typedefs;\n\n" + "import haxe.extern.EitherType;\n\n" + "typedef UserConfig = {\n" - + "\tvar enableCodeLens:Bool;\n" - + "\tvar enableDiagnostics:Bool;\n" - + "\tvar enableServerView:Bool;\n" - + "\tvar enableSignatureHelpDocumentation:Bool;\n" - + "\tvar diagnosticsPathFilter:String;\n" - + "\tvar displayPort:EitherType;\n" - + "\tvar buildCompletionCache:Bool;\n" - + "\tvar enableCompletionCacheWarning:Bool;\n" - + "\tvar useLegacyCompletion:Bool;\n" - + "\tvar codeGeneration:CodeGenerationConfig;\n" - + "\tvar exclude:Array;\n" - + "\tvar postfixCompletion:PostfixCompletionConfig;\n" - + "\tvar importsSortOrder:ImportsSortOrderConfig;\n" - + "\tvar maxCompletionItems:Int;\n" - + "\tvar renameSourceFolders:Array;\n" + + " var enableCodeLens:Bool;\n" + + " var enableDiagnostics:Bool;\n" + + " var enableServerView:Bool;\n" + + " var enableSignatureHelpDocumentation:Bool;\n" + + " var diagnosticsPathFilter:String;\n" + + " var displayPort:EitherType;\n" + + " var buildCompletionCache:Bool;\n" + + " var enableCompletionCacheWarning:Bool;\n" + + " var useLegacyCompletion:Bool;\n" + + " var codeGeneration:CodeGenerationConfig;\n" + + " var exclude:Array;\n" + + " var postfixCompletion:PostfixCompletionConfig;\n" + + " var importsSortOrder:ImportsSortOrderConfig;\n" + + " var maxCompletionItems:Int;\n" + + " var renameSourceFolders:Array;\n" + "}", 0, true), ]; diff --git a/test/refactor/rename/RenameTestBase.hx b/test/refactor/rename/RenameTestBase.hx index 2f6195b..0c9c2d0 100644 --- a/test/refactor/rename/RenameTestBase.hx +++ b/test/refactor/rename/RenameTestBase.hx @@ -9,9 +9,9 @@ import refactor.Rename; import refactor.TestEditableDocument; class RenameTestBase extends TestBase { - function checkRename(what:RenameWhat, edits:Array, async:Async, ?pos:PosInfos) { + function checkRename(what:RenameWhat, edits:Array, async:Async, withTyper:Bool = false, ?pos:PosInfos) { try { - doCanRename(what, edits, pos).catchError(function(failure) { + doCanRename(what, edits, withTyper, pos).catchError(function(failure) { Assert.fail('$failure', pos); }).finally(function() { async.done(); @@ -21,9 +21,9 @@ class RenameTestBase extends TestBase { } } - function failCanRename(what:RenameWhat, expected:String, async:Async, ?pos:PosInfos) { + function failCanRename(what:RenameWhat, expected:String, async:Async, withTyper:Bool = false, ?pos:PosInfos) { try { - doCanRename(what, [], pos).then(function(success:RefactorResult) { + doCanRename(what, [], withTyper, pos).then(function(success:RefactorResult) { Assert.equals(expected, PrintHelper.printRefactorResult(success), pos); }).catchError(function(failure) { Assert.equals(expected, '$failure', pos); @@ -35,9 +35,9 @@ class RenameTestBase extends TestBase { } } - function failRename(what:RenameWhat, expected:String, async:Async, ?pos:PosInfos) { + function failRename(what:RenameWhat, expected:String, async:Async, withTyper:Bool = false, ?pos:PosInfos) { try { - doRename(what, [], pos).then(function(success:RefactorResult) { + doRename(what, [], withTyper, pos).then(function(success:RefactorResult) { Assert.equals(expected, PrintHelper.printRefactorResult(success), pos); }).catchError(function(failure) { Assert.equals(expected, '$failure', pos); @@ -49,7 +49,7 @@ class RenameTestBase extends TestBase { } } - function doCanRename(what:RenameWhat, edits:Array, pos:PosInfos):Promise { + function doCanRename(what:RenameWhat, edits:Array, withTyper:Bool = false, pos:PosInfos):Promise { var editList:TestEditList = new TestEditList(); return Rename.canRename({ nameMap: usageContext.nameMap, @@ -59,13 +59,13 @@ class RenameTestBase extends TestBase { verboseLog: function(text:String, ?pos:PosInfos) { Sys.println('${pos.fileName}:${pos.lineNumber}: $text'); }, - typer: null + typer: withTyper ? typer : null, }).then(function(success:CanRenameResult) { - return doRename(what, edits, pos); + return doRename(what, edits, withTyper, pos); }); } - function doRename(what:RenameWhat, edits:Array, pos:PosInfos):Promise { + function doRename(what:RenameWhat, edits:Array, withTyper:Bool = false, pos:PosInfos):Promise { var editList:TestEditList = new TestEditList(); return Rename.rename({ nameMap: usageContext.nameMap, @@ -77,7 +77,7 @@ class RenameTestBase extends TestBase { verboseLog: function(text:String, ?pos:PosInfos) { Sys.println('${pos.fileName}:${pos.lineNumber}: $text'); }, - typer: null + typer: withTyper ? typer : null, }).then(function(success:RefactorResult) { return assertEdits(success, editList, edits, pos); }); diff --git a/testcases/methods/AgeChecker.hx b/testcases/methods/AgeChecker.hx new file mode 100644 index 0000000..cb1a5f7 --- /dev/null +++ b/testcases/methods/AgeChecker.hx @@ -0,0 +1,14 @@ +package testcases.methods; + +class AgeChecker { + function check() { + var age = 15; + var message = ""; + if (age < 18) { + message = "Minor"; + } else { + message = "Adult"; + } + trace(message); + } +} diff --git a/testcases/methods/ArrayHandler.hx b/testcases/methods/ArrayHandler.hx new file mode 100644 index 0000000..cd232fc --- /dev/null +++ b/testcases/methods/ArrayHandler.hx @@ -0,0 +1,12 @@ +package testcases.methods; + +class ArrayHandler { + function handle() { + var numbers = [1, 2, 3, 4, 5]; + var sum = 0; + for (n in numbers) { + sum += n; + } + trace(sum); + } +} diff --git a/testcases/methods/Main.hx b/testcases/methods/Main.hx index d4f850f..50f121f 100644 --- a/testcases/methods/Main.hx +++ b/testcases/methods/Main.hx @@ -54,6 +54,22 @@ class Main { // ... } + + public function calculateTotal2(items:Array):Float { + var total:Float = 0; + + // Selected code block to extract + for (item in items) { + var price = item.price; + var quantity = item.quantity; + if (quantity < 0) { + return total; + } + total += price * quantity; + } + + return total; + } } typedef Item = { diff --git a/testcases/methods/Math.hx b/testcases/methods/Math.hx new file mode 100644 index 0000000..013b509 --- /dev/null +++ b/testcases/methods/Math.hx @@ -0,0 +1,10 @@ +package testcases.methods; + +class Math { + function calculate() { + var a = 5; + var b = 3; + var result = a * b + (a - b); + trace(result); + } +} diff --git a/testcases/methods/NameProcessor.hx b/testcases/methods/NameProcessor.hx new file mode 100644 index 0000000..7c99b32 --- /dev/null +++ b/testcases/methods/NameProcessor.hx @@ -0,0 +1,12 @@ +package testcases.methods; + +class NameProcessor { + function process() { + var names = ["John", "Jane", "Bob"]; + var upperNames = []; + for (name in names) { + upperNames.push(name.toUpperCase()); + } + trace(upperNames); + } +} diff --git a/testcases/methods/PersonHandler.hx b/testcases/methods/PersonHandler.hx new file mode 100644 index 0000000..2135103 --- /dev/null +++ b/testcases/methods/PersonHandler.hx @@ -0,0 +1,8 @@ +package testcases.methods; + +class PersonHandler { + function handle(person:{name:String, age:Int}) { + var info = "Name: " + person.name + ", Age: " + person.age; + trace(info); + } +} From 19b2667de5d5302e6f3a7e650efcb7771d11082d Mon Sep 17 00:00:00 2001 From: AlexHaxe Date: Sun, 24 Nov 2024 01:25:21 +0100 Subject: [PATCH 12/59] refactored typing refactored CodeGens added more cases for open ended return types --- src/refactor/CacheAndTyperContext.hx | 2 +- src/refactor/PrintHelper.hx | 7 +- src/refactor/discover/TypeList.hx | 5 +- src/refactor/import.hx | 2 + src/refactor/refactor/ExtractMethod.hx | 50 ++--- src/refactor/refactor/RefactorHelper.hx | 2 +- .../CodeGenAsExpression.hx | 13 +- .../refactor/extractmethod/CodeGenBase.hx | 42 ++++ .../extractmethod/CodeGenEmptyReturn.hx | 120 +++++++++++ .../CodeGenNoReturn.hx | 15 +- .../extractmethod/CodeGenOpenEnded.hx | 198 ++++++++++++++++++ .../CodeGenReturnIsLast.hx | 7 +- .../extractmethod/ExtractMethodData.hx | 13 ++ .../ICodeGen.hx | 2 +- .../refactor/refactormethod/CodeGenBase.hx | 21 -- .../refactormethod/CodeGenEmptyReturn.hx | 89 -------- .../refactormethod/CodeGenOpenEnded.hx | 53 ----- src/refactor/rename/RenameHelper.hx | 5 +- src/refactor/rename/RenameTypeName.hx | 3 +- src/refactor/{ => typing}/ITypeList.hx | 2 +- src/refactor/{ => typing}/ITyper.hx | 4 +- src/refactor/typing/TypeHintType.hx | 11 + src/refactor/{ => typing}/TypingHelper.hx | 20 +- test/refactor/TestBase.hx | 2 +- test/refactor/TestTyper.hx | 3 +- .../refactor/RefactorExtractMethodTest.hx | 138 +++++++++--- testcases/methods/Container.hx | 12 ++ testcases/methods/MacroTools.hx | 19 ++ testcases/methods/Matcher.hx | 12 ++ 29 files changed, 606 insertions(+), 266 deletions(-) rename src/refactor/refactor/{refactormethod => extractmethod}/CodeGenAsExpression.hx (84%) create mode 100644 src/refactor/refactor/extractmethod/CodeGenBase.hx create mode 100644 src/refactor/refactor/extractmethod/CodeGenEmptyReturn.hx rename src/refactor/refactor/{refactormethod => extractmethod}/CodeGenNoReturn.hx (87%) create mode 100644 src/refactor/refactor/extractmethod/CodeGenOpenEnded.hx rename src/refactor/refactor/{refactormethod => extractmethod}/CodeGenReturnIsLast.hx (87%) create mode 100644 src/refactor/refactor/extractmethod/ExtractMethodData.hx rename src/refactor/refactor/{refactormethod => extractmethod}/ICodeGen.hx (76%) delete mode 100644 src/refactor/refactor/refactormethod/CodeGenBase.hx delete mode 100644 src/refactor/refactor/refactormethod/CodeGenEmptyReturn.hx delete mode 100644 src/refactor/refactor/refactormethod/CodeGenOpenEnded.hx rename src/refactor/{ => typing}/ITypeList.hx (80%) rename src/refactor/{ => typing}/ITyper.hx (61%) create mode 100644 src/refactor/typing/TypeHintType.hx rename src/refactor/{ => typing}/TypingHelper.hx (96%) create mode 100644 testcases/methods/Container.hx create mode 100644 testcases/methods/MacroTools.hx create mode 100644 testcases/methods/Matcher.hx diff --git a/src/refactor/CacheAndTyperContext.hx b/src/refactor/CacheAndTyperContext.hx index 6ea454c..21e745a 100644 --- a/src/refactor/CacheAndTyperContext.hx +++ b/src/refactor/CacheAndTyperContext.hx @@ -1,10 +1,10 @@ package refactor; -import refactor.ITyper; import refactor.VerboseLogger; import refactor.discover.FileList; import refactor.discover.NameMap; import refactor.discover.TypeList; +import refactor.typing.ITyper; typedef CacheAndTyperContext = { var nameMap:NameMap; diff --git a/src/refactor/PrintHelper.hx b/src/refactor/PrintHelper.hx index defebd4..e233d7b 100644 --- a/src/refactor/PrintHelper.hx +++ b/src/refactor/PrintHelper.hx @@ -1,8 +1,8 @@ package refactor; -import refactor.TypingHelper.TypeHintType; -import refactor.TypingHelper.TypeParameterList; import refactor.discover.IdentifierType; +import refactor.typing.TypeHintType; +import refactor.typing.TypingHelper.TypeParameterList; class PrintHelper { public static function typeToString(identType:IdentifierType):String { @@ -95,6 +95,9 @@ class PrintHelper { if (argTypes == null) { return '(${args.join(", ")}) -> Void'; } + if (argTypes.length == 1) { + return '${args.join(", ")} -> ${printTypeHint(retVal)}'; + } return '(${args.join(", ")}) -> ${printTypeHint(retVal)}'; case StructType(fieldTypes): final fields = fieldTypes.map(f -> printTypeHint(f)); diff --git a/src/refactor/discover/TypeList.hx b/src/refactor/discover/TypeList.hx index 9561371..7eeaa09 100644 --- a/src/refactor/discover/TypeList.hx +++ b/src/refactor/discover/TypeList.hx @@ -1,6 +1,7 @@ package refactor.discover; -import refactor.TypingHelper.TypeHintType; +import refactor.typing.ITypeList; +import refactor.typing.TypeHintType; class TypeList implements ITypeList { public final types:Map; @@ -14,7 +15,7 @@ class TypeList implements ITypeList { } public function findTypeName(name:String):Array { - return Lambda.filter({iterator: types.iterator}, (t) -> t.name.name == name); + return Lambda.filter({iterator: types.iterator}, t -> t.name.name == name); } public function getType(fullName:String):Null { diff --git a/src/refactor/import.hx b/src/refactor/import.hx index 010e49e..3b3289b 100644 --- a/src/refactor/import.hx +++ b/src/refactor/import.hx @@ -1,6 +1,8 @@ import haxe.macro.Expr; import js.lib.Promise; import haxeparser.Data; +import refactor.typing.TypeHintType; +import refactor.typing.TypingHelper; import tokentree.TokenTree; import tokentree.TokenTreeAccessHelper; import tokentree.utils.TokenTreeCheckUtils; diff --git a/src/refactor/refactor/ExtractMethod.hx b/src/refactor/refactor/ExtractMethod.hx index 90866c6..3ca7419 100644 --- a/src/refactor/refactor/ExtractMethod.hx +++ b/src/refactor/refactor/ExtractMethod.hx @@ -1,16 +1,17 @@ package refactor.refactor; -import refactor.TypingHelper.TypeHintType; import refactor.discover.File; import refactor.discover.Identifier; import refactor.edits.Changelist; import refactor.refactor.RefactorHelper.TokensAtPos; -import refactor.refactor.refactormethod.CodeGenAsExpression; -import refactor.refactor.refactormethod.CodeGenEmptyReturn; -import refactor.refactor.refactormethod.CodeGenNoReturn; -import refactor.refactor.refactormethod.CodeGenOpenEnded; -import refactor.refactor.refactormethod.CodeGenReturnIsLast; -import refactor.refactor.refactormethod.ICodeGen; +import refactor.refactor.extractmethod.CodeGenAsExpression; +import refactor.refactor.extractmethod.CodeGenEmptyReturn; +import refactor.refactor.extractmethod.CodeGenNoReturn; +import refactor.refactor.extractmethod.CodeGenOpenEnded; +import refactor.refactor.extractmethod.CodeGenReturnIsLast; +import refactor.refactor.extractmethod.ExtractMethodData; +import refactor.refactor.extractmethod.ICodeGen; +import refactor.typing.TypeHintType; class ExtractMethod { public static function canRefactor(context:CanRefactorContext):CanRefactorResult { @@ -127,6 +128,19 @@ class ExtractMethod { switch (tokenStart.tok) { case Kwd(KwdFunction): return null; + case Const(_): + if (tokenStart.hasChildren()) { + final child = tokenStart.getFirstChild(); + if (child.matches(Arrow)) { + return null; + } + } + case POpen: + switch (TokenTreeCheckUtils.getPOpenType(tokenStart)) { + case Parameter: + return null; + default: + } default: } @@ -385,21 +399,15 @@ class ExtractMethod { for (ret in allReturns) { var child = ret.getFirstChild(); if (child == null) { - trace("xxx"); return null; } switch (child.tok) { case Semicolon: - return new CodeGenEmptyReturn(extractData, context, neededIdentifiers, modifiedIdentifiers, leakingVars); + return new CodeGenEmptyReturn(extractData, context, neededIdentifiers, allReturns, modifiedIdentifiers, leakingVars); default: - if (modifiedIdentifiers.length > 0) { - trace("xxx"); - return null; - } - return new CodeGenOpenEnded(extractData, context, neededIdentifiers, allReturns); + return new CodeGenOpenEnded(extractData, context, neededIdentifiers, allReturns, modifiedIdentifiers, leakingVars); } } - trace("xxx"); return null; } @@ -518,15 +526,3 @@ private typedef NewFunctionParameter = { final call:String; final param:String; } - -typedef ExtractMethodData = { - var content:String; - var root:TokenTree; - var startToken:TokenTree; - var endToken:TokenTree; - var newMethodName:String; - var newMethodOffset:Int; - var functionToken:TokenTree; - var isStatic:Bool; - var isSingleExpr:Bool; -} diff --git a/src/refactor/refactor/RefactorHelper.hx b/src/refactor/refactor/RefactorHelper.hx index 7711134..2685734 100644 --- a/src/refactor/refactor/RefactorHelper.hx +++ b/src/refactor/refactor/RefactorHelper.hx @@ -21,7 +21,7 @@ class RefactorHelper { } } - if (token.pos.max >= searchPos) { + if (token.pos.max > searchPos) { var distance = token.pos.min - searchPos; if (distanceAfter > distance) { tokens.after = token; diff --git a/src/refactor/refactor/refactormethod/CodeGenAsExpression.hx b/src/refactor/refactor/extractmethod/CodeGenAsExpression.hx similarity index 84% rename from src/refactor/refactor/refactormethod/CodeGenAsExpression.hx rename to src/refactor/refactor/extractmethod/CodeGenAsExpression.hx index 6327619..f595183 100644 --- a/src/refactor/refactor/refactormethod/CodeGenAsExpression.hx +++ b/src/refactor/refactor/extractmethod/CodeGenAsExpression.hx @@ -1,7 +1,6 @@ -package refactor.refactor.refactormethod; +package refactor.refactor.extractmethod; import refactor.discover.Identifier; -import refactor.refactor.ExtractMethod.ExtractMethodData; class CodeGenAsExpression extends CodeGenBase { public function new(extractData:ExtractMethodData, context:RefactorContext, neededIdentifiers:Array) { @@ -10,10 +9,14 @@ class CodeGenAsExpression extends CodeGenBase { public function makeCallSite():String { final callParams:String = neededIdentifiers.map(i -> i.name).join(", "); - if (extractData.endToken.matches(Semicolon)) { - return '${extractData.newMethodName}($callParams);\n'; + final call = '${extractData.newMethodName}($callParams)'; + + return switch (extractData.endToken.tok) { + case Semicolon | BrClose: + '$call;\n'; + default: + '$call'; } - return '${extractData.newMethodName}($callParams)'; } public function makeReturnTypeHint():Promise { diff --git a/src/refactor/refactor/extractmethod/CodeGenBase.hx b/src/refactor/refactor/extractmethod/CodeGenBase.hx new file mode 100644 index 0000000..62f28ab --- /dev/null +++ b/src/refactor/refactor/extractmethod/CodeGenBase.hx @@ -0,0 +1,42 @@ +package refactor.refactor.extractmethod; + +import refactor.discover.Identifier; +import refactor.typing.TypeHintType; + +abstract class CodeGenBase implements ICodeGen { + final extractData:ExtractMethodData; + final context:RefactorContext; + final neededIdentifiers:Array; + + public function new(extractData:ExtractMethodData, context:RefactorContext, neededIdentifiers:Array) { + this.extractData = extractData; + this.context = context; + this.neededIdentifiers = neededIdentifiers; + } + + function findTypeOfIdentifier(identifier:Identifier):Promise { + return ExtractMethod.findTypeOfIdentifier(context, identifier); + } + + function replaceReturnValues(returnTokens:Array, callback:ReturnValueCallback):String { + final selectedSnippet = RefactorHelper.extractText(context.converter, extractData.content, extractData.startToken.pos.min, + extractData.endToken.pos.max); + var startOffset = context.converter(extractData.content, extractData.startToken.pos.min); + var snippet = selectedSnippet; + returnTokens.reverse(); + for (token in returnTokens) { + var pos = token.getPos(); + var returnStart = context.converter(extractData.content, pos.min); + var returnEnd = context.converter(extractData.content, pos.max); + returnStart -= startOffset; + returnEnd -= startOffset; + var before = snippet.substring(0, returnStart); + var after = snippet.substring(returnEnd); + var retValue = snippet.substring(returnStart + 7, returnEnd - 1); + snippet = before + "return " + callback(retValue) + ";" + after; + } + return snippet; + } +} + +typedef ReturnValueCallback = (value:String) -> String; diff --git a/src/refactor/refactor/extractmethod/CodeGenEmptyReturn.hx b/src/refactor/refactor/extractmethod/CodeGenEmptyReturn.hx new file mode 100644 index 0000000..30bb6d5 --- /dev/null +++ b/src/refactor/refactor/extractmethod/CodeGenEmptyReturn.hx @@ -0,0 +1,120 @@ +package refactor.refactor.extractmethod; + +import refactor.discover.Identifier; + +class CodeGenEmptyReturn extends CodeGenBase { + final returnTokens:Array; + + final assignments:Array; + final vars:Array; + + public function new(extractData:ExtractMethodData, context:RefactorContext, neededIdentifiers:Array, returnTokens:Array, + assignments:Array, vars:Array) { + super(extractData, context, neededIdentifiers); + + this.returnTokens = returnTokens; + this.assignments = assignments; + this.vars = vars; + } + + public function makeCallSite():String { + final callParams:String = neededIdentifiers.map(i -> i.name).join(", "); + final call = '${extractData.newMethodName}($callParams)'; + + return switch [assignments.length, vars.length] { + case [0, 0]: + 'if (!$call) {\nreturn;\n}\n'; + case [1, 0]: + 'switch ($call) {\n' + + 'case None:\n' + + 'return;\n' + + 'case Some(data):\n' + + '${assignments[0].name} = data;\n}\n'; + case [0, 1]: + 'var ${vars[0].name};\n' + + 'switch ($call) {\n' + + 'case None:\n' + + 'return;\n' + + 'case Some(data):\n' + + '${vars[0].name} = data;\n}\n'; + case [_, _]: + final dataVars = vars.map(v -> 'var ${v.name};').join("\n"); + final assignData = assignments.map(a -> '${a.name} = data.${a.name};').join("\n"); + final varsData = vars.map(v -> '${v.name} = data.${v.name};').join("\n"); + dataVars + + 'switch ($call) {\n' + + "case None:\n" + + "return;\n" + + "case Some(data):\n" + + '${assignData}${varsData}' + + "}\n"; + } + } + + public function makeReturnTypeHint():Promise { + return switch [assignments.length, vars.length] { + case [0, 0]: + return Promise.resolve(":Bool"); + case [1, 0]: + return findTypeOfIdentifier(assignments[0]).then(function(typeHint):Promise { + if (typeHint == null) { + return Promise.resolve(""); + } + return Promise.resolve(":haxe.ds.Option<" + typeHint.printTypeHint() + ">"); + }); + case [0, 1]: + return findTypeOfIdentifier(vars[0]).then(function(typeHint):Promise { + if (typeHint == null) { + return Promise.resolve(""); + } + return Promise.resolve(":haxe.ds.Option<" + typeHint.printTypeHint() + ">"); + }); + case [_, _]: + var promises:Array> = []; + for (assign in assignments) { + promises.push(findTypeOfIdentifier(assign).then(function(typeHint):Promise { + if (typeHint == null) { + return Promise.resolve(assign.name + ":Any"); + } + return Promise.resolve(assign.name + ":" + typeHint.printTypeHint()); + })); + } + for (v in vars) { + promises.push(findTypeOfIdentifier(v).then(function(typeHint):Promise { + if (typeHint == null) { + return Promise.resolve(v.name + ":Any"); + } + return Promise.resolve(v.name + ":" + typeHint.printTypeHint()); + })); + } + return Promise.all(promises).then(function(fields) { + return Promise.resolve(":haxe.ds.Option<{" + fields.join(", ") + "}>"); + }); + } + } + + public function makeBody():String { + final selectedSnippet = RefactorHelper.extractText(context.converter, extractData.content, extractData.startToken.pos.min, + extractData.endToken.pos.max); + + return switch [assignments.length, vars.length] { + case [0, 0]: + final snippet = replaceReturnValues(returnTokens, value -> 'false'); + " {\n" + snippet + "\nreturn true;\n}\n"; + case [1, 0]: + final snippet = replaceReturnValues(returnTokens, value -> 'None'); + final returnAssigmentVar = '\nreturn Some(${assignments[0].name});'; + " {\n" + snippet + returnAssigmentVar + "\n}\n"; + case [0, 1]: + final snippet = replaceReturnValues(returnTokens, value -> 'None'); + final returnLocalVar = '\nreturn Some(${vars[0].name});'; + " {\n" + snippet + returnLocalVar + "\n}\n"; + case [_, _]: + final snippet = replaceReturnValues(returnTokens, value -> 'None'); + final assignData = assignments.map(a -> '${a.name}: ${a.name},\n'); + final varsData = vars.map(v -> '${v.name}: ${v.name},\n'); + final returnAssigments = "\nreturn Some({\n" + assignData + varsData + "});"; + " {\n" + snippet + returnAssigments + "\n}\n"; + } + } +} diff --git a/src/refactor/refactor/refactormethod/CodeGenNoReturn.hx b/src/refactor/refactor/extractmethod/CodeGenNoReturn.hx similarity index 87% rename from src/refactor/refactor/refactormethod/CodeGenNoReturn.hx rename to src/refactor/refactor/extractmethod/CodeGenNoReturn.hx index 8f90bc4..20c4752 100644 --- a/src/refactor/refactor/refactormethod/CodeGenNoReturn.hx +++ b/src/refactor/refactor/extractmethod/CodeGenNoReturn.hx @@ -1,7 +1,6 @@ -package refactor.refactor.refactormethod; +package refactor.refactor.extractmethod; import refactor.discover.Identifier; -import refactor.refactor.ExtractMethod.ExtractMethodData; class CodeGenNoReturn extends CodeGenBase { final assignments:Array; @@ -17,25 +16,27 @@ class CodeGenNoReturn extends CodeGenBase { public function makeCallSite():String { final callParams:String = neededIdentifiers.map(i -> i.name).join(", "); + final call = '${extractData.newMethodName}($callParams)'; + return switch [assignments.length, vars.length] { case [0, 0]: - '${extractData.newMethodName}($callParams);\n'; + '$call;\n'; case [0, 1]: - 'var ${vars[0].name} = ${extractData.newMethodName}($callParams);\n'; + 'var ${vars[0].name} = $call;\n'; case [1, 0]: - '${assignments[0].name} = ${extractData.newMethodName}($callParams);\n'; + '${assignments[0].name} = $call;\n'; case [_, _]: final dataVars = vars.map(v -> 'var ${v.name};').join("\n"); final assignData = assignments.map(a -> '${a.name} = data.${a.name};').join("\n"); final assignVars = vars.map(v -> '${v.name} = data.${v.name};').join("\n"); - '$dataVars\n{\nfinal data = ${extractData.newMethodName}($callParams);\n' + assignData + assignVars + "}\n"; + '$dataVars\n{\nfinal data = $call;\n' + assignData + assignVars + "}\n"; } } public function makeReturnTypeHint():Promise { return switch [assignments.length, vars.length] { case [0, 0]: - return Promise.resolve(":Void"); + return Promise.resolve(""); case [0, 1]: return findTypeOfIdentifier(vars[0]).then(function(typeHint):Promise { if (typeHint == null) { diff --git a/src/refactor/refactor/extractmethod/CodeGenOpenEnded.hx b/src/refactor/refactor/extractmethod/CodeGenOpenEnded.hx new file mode 100644 index 0000000..b762668 --- /dev/null +++ b/src/refactor/refactor/extractmethod/CodeGenOpenEnded.hx @@ -0,0 +1,198 @@ +package refactor.refactor.extractmethod; + +import refactor.discover.Identifier; + +class CodeGenOpenEnded extends CodeGenBase { + final returnTokens:Array; + final assignments:Array; + final vars:Array; + + public function new(extractData:ExtractMethodData, context:RefactorContext, neededIdentifiers:Array, returnTokens:Array, + assignments:Array, vars:Array) { + super(extractData, context, neededIdentifiers); + + this.returnTokens = returnTokens; + this.assignments = assignments; + this.vars = vars; + } + + public function makeCallSite():String { + final callParams:String = neededIdentifiers.map(i -> i.name).join(", "); + final call = '${extractData.newMethodName}($callParams)'; + + return switch [assignments.length, vars.length] { + case [0, 0]: + 'switch ($call) {\n' + "case Some(data):\n" + "return data;\n" + "case None:\n" + "}\n"; + case [1, 0]: + '{\nfinal result = $call;\n' + + "switch (result.ret) {\n" + + "case Some(data):\n" + + "return data;\n" + + "case None:\n" + + '${assignments[0].name} = result.data;\n' + + "}\n" + + "}\n"; + case [0, 1]: + 'var ${vars[0].name};\n' + + '{\nfinal result = $call;\n' + + "switch (result.ret) {\n" + + "case Some(data):\n" + + "return data;\n" + + "case None:\n" + + '${vars[0].name} = result.data;\n' + + "}\n" + + "}\n"; + case [_, _]: + final dataVars = vars.map(v -> 'var ${v.name};').join("\n"); + final assignData = assignments.map(a -> '${a.name} = result.data.${a.name};').join("\n"); + final varsData = vars.map(v -> '${v.name} = result.data.${v.name};').join("\n"); + '$dataVars' + + '{\nfinal result = $call;\n' + + "switch (result.ret) {\n" + + "case Some(data):\n" + + "return data;\n" + + "case None:\n" + + '${assignData}${varsData}' + + "}\n" + + "}\n"; + } + } + + public function makeReturnTypeHint():Promise { + return switch [assignments.length, vars.length] { + case [0, 0]: + return parentTypeHint().then(function(typeHint):Promise { + if (typeHint == null) { + return Promise.resolve(""); + } + return Promise.resolve(":haxe.ds.Option<" + typeHint.printTypeHint() + ">"); + }); + case [1, 0]: + final promises:Array> = []; + promises.push(parentTypeHint().then(function(typeHint):Promise { + if (typeHint == null) { + return Promise.resolve(""); + } + return Promise.resolve("ret:haxe.ds.Option<" + typeHint.printTypeHint() + ">"); + })); + promises.push(findTypeOfIdentifier(assignments[0]).then(function(typeHint):Promise { + if (typeHint == null) { + return Promise.resolve(""); + } + return Promise.resolve("?data:" + typeHint.printTypeHint()); + })); + return Promise.all(promises).then(function(fields) { + return Promise.resolve(":{" + fields.join(", ") + "}"); + }); + case [0, 1]: + final promises:Array> = []; + promises.push(parentTypeHint().then(function(typeHint):Promise { + if (typeHint == null) { + return Promise.resolve(""); + } + return Promise.resolve("ret:haxe.ds.Option<" + typeHint.printTypeHint() + ">"); + })); + promises.push(findTypeOfIdentifier(vars[0]).then(function(typeHint):Promise { + if (typeHint == null) { + return Promise.resolve(""); + } + return Promise.resolve("?data:" + typeHint.printTypeHint()); + })); + return Promise.all(promises).then(function(fields) { + return Promise.resolve(":{" + fields.join(", ") + "}"); + }); + case [_, _]: + final returnPromise = parentTypeHint().then(function(typeHint):Promise { + if (typeHint == null) { + return Promise.resolve(""); + } + return Promise.resolve("ret:haxe.ds.Option<" + typeHint.printTypeHint() + ">"); + }); + final promises:Array> = []; + for (assign in assignments) { + promises.push(findTypeOfIdentifier(assign).then(function(typeHint):Promise { + if (typeHint == null) { + return Promise.resolve(assign.name + ":Any"); + } + return Promise.resolve(assign.name + ":" + typeHint.printTypeHint()); + })); + } + for (v in vars) { + promises.push(findTypeOfIdentifier(v).then(function(typeHint):Promise { + if (typeHint == null) { + return Promise.resolve(v.name + ":Any"); + } + return Promise.resolve(v.name + ":" + typeHint.printTypeHint()); + })); + } + final fieldsPromise = Promise.all(promises).then(function(fields) { + return Promise.resolve("?data:{" + fields.join(", ") + "}"); + }); + return Promise.all([returnPromise, fieldsPromise]).then(function(fields) { + return Promise.resolve(":{" + fields.join(", ") + "}"); + }); + } + } + + function parentTypeHint():Promise { + var func:Null = findParentFunction(); + if (func == null) { + return Promise.reject("failed to find return type of selected code"); + } + return TypingHelper.findTypeWithTyper(context, context.what.fileName, func.pos.max - 1).then(function(typeHint) { + return switch (typeHint) { + case null | ClasspathType(_) | LibType(_) | StructType(_) | UnknownType(_): + Promise.resolve(typeHint); + case FunctionType(args, retVal): + Promise.resolve(retVal); + } + }); + } + + function findParentFunction():Null { + var token = extractData.startToken.parent; + while (true) { + switch (token.tok) { + case Kwd(KwdFunction): + var child = token.getFirstChild(); + if (child == null) { + return null; + } + switch (child.tok) { + case Const(_): + return child; + default: + return token; + } + case Arrow: + return token; + case Root: + return null; + default: + token = token.parent; + } + } + } + + public function makeBody():String { + return switch [assignments.length, vars.length] { + case [0, 0]: + final snippet = replaceReturnValues(returnTokens, value -> 'Some($value)'); + return " {\n" + snippet + "\nreturn None;\n}\n"; + case [1, 0]: + final snippet = replaceReturnValues(returnTokens, value -> '{ret: Some($value)}'); + final returnData = '{ret: None, data: ${assignments[0].name}}'; + return " {\n" + snippet + '\nreturn $returnData;\n}\n'; + case [0, 1]: + final snippet = replaceReturnValues(returnTokens, value -> '{ret: Some($value)}'); + final returnData = '{ret: None, data: ${vars[0].name}}'; + return " {\n" + snippet + '\nreturn $returnData;\n}\n'; + case [_, _]: + final snippet = replaceReturnValues(returnTokens, value -> '{ret: Some($value)}'); + final assignData = assignments.map(a -> '${a.name}: ${a.name},\n'); + final varsData = vars.map(v -> '${v.name}: ${v.name},\n'); + final returnData = '{ret: None, data: {$assignData$varsData}}'; + return " {\n" + snippet + '\nreturn $returnData;\n}\n'; + } + } +} diff --git a/src/refactor/refactor/refactormethod/CodeGenReturnIsLast.hx b/src/refactor/refactor/extractmethod/CodeGenReturnIsLast.hx similarity index 87% rename from src/refactor/refactor/refactormethod/CodeGenReturnIsLast.hx rename to src/refactor/refactor/extractmethod/CodeGenReturnIsLast.hx index c764210..88a2e90 100644 --- a/src/refactor/refactor/refactormethod/CodeGenReturnIsLast.hx +++ b/src/refactor/refactor/extractmethod/CodeGenReturnIsLast.hx @@ -1,7 +1,6 @@ -package refactor.refactor.refactormethod; +package refactor.refactor.extractmethod; import refactor.discover.Identifier; -import refactor.refactor.ExtractMethod.ExtractMethodData; class CodeGenReturnIsLast extends CodeGenBase { final lastReturnToken:Null; @@ -14,7 +13,9 @@ class CodeGenReturnIsLast extends CodeGenBase { public function makeCallSite():String { final callParams:String = neededIdentifiers.map(i -> i.name).join(", "); - return 'return ${extractData.newMethodName}($callParams);\n'; + final call = '${extractData.newMethodName}($callParams)'; + + return 'return $call;\n'; } public function makeReturnTypeHint():Promise { diff --git a/src/refactor/refactor/extractmethod/ExtractMethodData.hx b/src/refactor/refactor/extractmethod/ExtractMethodData.hx new file mode 100644 index 0000000..2468112 --- /dev/null +++ b/src/refactor/refactor/extractmethod/ExtractMethodData.hx @@ -0,0 +1,13 @@ +package refactor.refactor.extractmethod; + +typedef ExtractMethodData = { + var content:String; + var root:TokenTree; + var startToken:TokenTree; + var endToken:TokenTree; + var newMethodName:String; + var newMethodOffset:Int; + var functionToken:TokenTree; + var isStatic:Bool; + var isSingleExpr:Bool; +} diff --git a/src/refactor/refactor/refactormethod/ICodeGen.hx b/src/refactor/refactor/extractmethod/ICodeGen.hx similarity index 76% rename from src/refactor/refactor/refactormethod/ICodeGen.hx rename to src/refactor/refactor/extractmethod/ICodeGen.hx index 41f3cd7..132514e 100644 --- a/src/refactor/refactor/refactormethod/ICodeGen.hx +++ b/src/refactor/refactor/extractmethod/ICodeGen.hx @@ -1,4 +1,4 @@ -package refactor.refactor.refactormethod; +package refactor.refactor.extractmethod; interface ICodeGen { function makeCallSite():String; diff --git a/src/refactor/refactor/refactormethod/CodeGenBase.hx b/src/refactor/refactor/refactormethod/CodeGenBase.hx deleted file mode 100644 index edb0e45..0000000 --- a/src/refactor/refactor/refactormethod/CodeGenBase.hx +++ /dev/null @@ -1,21 +0,0 @@ -package refactor.refactor.refactormethod; - -import refactor.TypingHelper.TypeHintType; -import refactor.discover.Identifier; -import refactor.refactor.ExtractMethod.ExtractMethodData; - -abstract class CodeGenBase implements ICodeGen { - final extractData:ExtractMethodData; - final context:RefactorContext; - final neededIdentifiers:Array; - - public function new(extractData:ExtractMethodData, context:RefactorContext, neededIdentifiers:Array) { - this.extractData = extractData; - this.context = context; - this.neededIdentifiers = neededIdentifiers; - } - - function findTypeOfIdentifier(identifier:Identifier):Promise { - return ExtractMethod.findTypeOfIdentifier(context, identifier); - } -} diff --git a/src/refactor/refactor/refactormethod/CodeGenEmptyReturn.hx b/src/refactor/refactor/refactormethod/CodeGenEmptyReturn.hx deleted file mode 100644 index 48e34b6..0000000 --- a/src/refactor/refactor/refactormethod/CodeGenEmptyReturn.hx +++ /dev/null @@ -1,89 +0,0 @@ -package refactor.refactor.refactormethod; - -import refactor.discover.Identifier; -import refactor.refactor.ExtractMethod.ExtractMethodData; - -class CodeGenEmptyReturn extends CodeGenBase { - final assignments:Array; - final vars:Array; - - public function new(extractData:ExtractMethodData, context:RefactorContext, neededIdentifiers:Array, assignments:Array, - vars:Array) { - super(extractData, context, neededIdentifiers); - - this.assignments = assignments; - this.vars = vars; - } - - public function makeCallSite():String { - final callParams:String = neededIdentifiers.map(i -> i.name).join(", "); - return switch [assignments.length, vars.length] { - case [0, 0]: - 'if (!${extractData.newMethodName}($callParams)) {\nreturn;\n}\n'; - case [1, 0]: - 'switch (${extractData.newMethodName}($callParams)) {\n' - + 'case None:\n' - + 'return;\n' - + 'case Some(data):\n' - + '${assignments[0].name} = data;\n}\n'; - case [_, 0]: - final assignData = assignments.map(a -> '${a.name} = data.${a.name};').join("\n"); - '{\nfinal data =${extractData.newMethodName}($callParams);\n' + assignData + "}\n"; - 'switch (${extractData.newMethodName}($callParams)) {\n' - + 'case None:\n' - + 'return;\n' - + 'case Some(data):\n' - + '${assignData}}\n'; - case [_, _]: - "TODO please implement!! :)"; - } - } - - public function makeReturnTypeHint():Promise { - return switch (assignments.length) { - case 0: - return Promise.resolve(":Bool"); - case 1: - return findTypeOfIdentifier(assignments[0]).then(function(typeHint):Promise { - if (typeHint == null) { - return Promise.resolve(""); - } - return Promise.resolve(":haxe.ds.Option<" + typeHint.printTypeHint() + ">"); - }); - default: - var promises:Array> = []; - for (assign in assignments) { - promises.push(findTypeOfIdentifier(assign).then(function(typeHint):Promise { - if (typeHint == null) { - return Promise.resolve(assign.name + ":Any"); - } - return Promise.resolve(":" + typeHint.printTypeHint()); - })); - } - return Promise.all(promises).then(function(fields) { - return Promise.resolve(":haxe.ds.Option<{" + fields.join(", ") + "}>"); - }); - } - } - - public function makeBody():String { - final selectedSnippet = RefactorHelper.extractText(context.converter, extractData.content, extractData.startToken.pos.min, - extractData.endToken.pos.max); - return switch (assignments.length) { - case 0: - final reg:EReg = ~/^([ \t]*)return[ \t]*;/gm; - final replacedSnippet = reg.map(selectedSnippet, f -> f.matched(1) + "return false;"); - " {\n" + replacedSnippet + "\nreturn true;\n}\n"; - case 1: - final reg:EReg = ~/^([ \t]*)return[ \t]*;/gm; - final replacedSnippet = reg.map(selectedSnippet, f -> f.matched(1) + "return None;"); - final returnAssigmentVar = '\nreturn Some(${assignments[0].name});'; - " {\n" + replacedSnippet + returnAssigmentVar + "\n}\n"; - default: - final reg:EReg = ~/^([ \t]*)return[ \t]*;/gm; - final replacedSnippet = reg.map(selectedSnippet, f -> f.matched(1) + "return None;"); - final returnAssigments = "\nreturn Some({\n" + assignments.map(a -> '${a.name}: ${a.name},\n') + "});"; - " {\n" + replacedSnippet + returnAssigments + "\n}\n"; - } - } -} diff --git a/src/refactor/refactor/refactormethod/CodeGenOpenEnded.hx b/src/refactor/refactor/refactormethod/CodeGenOpenEnded.hx deleted file mode 100644 index cde2428..0000000 --- a/src/refactor/refactor/refactormethod/CodeGenOpenEnded.hx +++ /dev/null @@ -1,53 +0,0 @@ -package refactor.refactor.refactormethod; - -import refactor.discover.Identifier; -import refactor.refactor.ExtractMethod.ExtractMethodData; - -class CodeGenOpenEnded extends CodeGenBase { - final returnTokens:Array; - - public function new(extractData:ExtractMethodData, context:RefactorContext, neededIdentifiers:Array, returnTokens:Array) { - super(extractData, context, neededIdentifiers); - - this.returnTokens = returnTokens; - } - - public function makeCallSite():String { - final callParams:String = neededIdentifiers.map(i -> i.name).join(", "); - return 'switch (${extractData.newMethodName}($callParams)) {\n' + 'case None:\n' + 'case Some(data):\n' + 'return data;\n}\n'; - } - - public function makeReturnTypeHint():Promise { - var token = returnTokens.shift(); - if (token == null) { - return Promise.resolve(""); - } - final pos = token.getPos(); - return TypingHelper.findTypeWithTyper(context, context.what.fileName, pos.max - 2).then(function(typeHint):Promise { - if (typeHint == null) { - return Promise.resolve(""); - } - return Promise.resolve(":haxe.ds.Option<" + typeHint.printTypeHint() + ">"); - }); - } - - public function makeBody():String { - final selectedSnippet = RefactorHelper.extractText(context.converter, extractData.content, extractData.startToken.pos.min, - extractData.endToken.pos.max); - var startOffset = context.converter(extractData.content, extractData.startToken.pos.min); - var snippet = selectedSnippet; - returnTokens.reverse(); - for (token in returnTokens) { - var pos = token.getPos(); - var returnStart = context.converter(extractData.content, pos.min); - var returnEnd = context.converter(extractData.content, pos.max); - returnStart -= startOffset; - returnEnd -= startOffset; - var before = snippet.substring(0, returnStart); - var after = snippet.substring(returnEnd); - var retValue = snippet.substring(returnStart + 7, returnEnd - 1); - snippet = before + "return Some(" + retValue + ");" + after; - } - return " {\n" + snippet + "\nreturn None;\n}\n"; - } -} diff --git a/src/refactor/rename/RenameHelper.hx b/src/refactor/rename/RenameHelper.hx index 222d654..48cc2f1 100644 --- a/src/refactor/rename/RenameHelper.hx +++ b/src/refactor/rename/RenameHelper.hx @@ -1,11 +1,12 @@ package refactor.rename; -import refactor.TypingHelper; import refactor.discover.Identifier; import refactor.discover.IdentifierPos; import refactor.discover.Type; import refactor.discover.TypeList; import refactor.edits.Changelist; +import refactor.typing.TypeHintType; +import refactor.typing.TypingHelper; class RenameHelper { public static function replaceTextWithPrefix(use:Identifier, prefix:String, to:String, changelist:Changelist) { @@ -67,10 +68,8 @@ class RenameHelper { continue; } case StructType(fields): - trace("TODO"); continue; case FunctionType(args, retVal): - trace("TODO"); continue; case UnknownType(name): continue; diff --git a/src/refactor/rename/RenameTypeName.hx b/src/refactor/rename/RenameTypeName.hx index 9c9ed1c..0efe721 100644 --- a/src/refactor/rename/RenameTypeName.hx +++ b/src/refactor/rename/RenameTypeName.hx @@ -2,12 +2,13 @@ package refactor.rename; import haxe.io.Path; import refactor.RefactorResult; -import refactor.TypingHelper; import refactor.discover.File; import refactor.discover.Identifier; import refactor.discover.IdentifierPos; import refactor.edits.Changelist; import refactor.rename.RenameContext; +import refactor.typing.TypeHintType; +import refactor.typing.TypingHelper; class RenameTypeName { public static function refactorTypeName(context:RenameContext, file:File, identifier:Identifier):Promise { diff --git a/src/refactor/ITypeList.hx b/src/refactor/typing/ITypeList.hx similarity index 80% rename from src/refactor/ITypeList.hx rename to src/refactor/typing/ITypeList.hx index 7f98eb2..1fa2c20 100644 --- a/src/refactor/ITypeList.hx +++ b/src/refactor/typing/ITypeList.hx @@ -1,4 +1,4 @@ -package refactor; +package refactor.typing; import refactor.discover.Type; diff --git a/src/refactor/ITyper.hx b/src/refactor/typing/ITyper.hx similarity index 61% rename from src/refactor/ITyper.hx rename to src/refactor/typing/ITyper.hx index e8bcc25..9ff1a0f 100644 --- a/src/refactor/ITyper.hx +++ b/src/refactor/typing/ITyper.hx @@ -1,6 +1,4 @@ -package refactor; - -import refactor.TypingHelper.TypeHintType; +package refactor.typing; interface ITyper { function resolveType(fileName:String, pos:Int):Promise>; diff --git a/src/refactor/typing/TypeHintType.hx b/src/refactor/typing/TypeHintType.hx new file mode 100644 index 0000000..68a45bb --- /dev/null +++ b/src/refactor/typing/TypeHintType.hx @@ -0,0 +1,11 @@ +package refactor.typing; + +import refactor.discover.Type; + +enum TypeHintType { + ClasspathType(type:Type, typeParams:Array); + LibType(name:String, fullName:String, typeParams:Array); + FunctionType(args:Array, retVal:Null); + StructType(fields:Array); + UnknownType(name:String); +} diff --git a/src/refactor/TypingHelper.hx b/src/refactor/typing/TypingHelper.hx similarity index 96% rename from src/refactor/TypingHelper.hx rename to src/refactor/typing/TypingHelper.hx index d623e6c..4a3e0d9 100644 --- a/src/refactor/TypingHelper.hx +++ b/src/refactor/typing/TypingHelper.hx @@ -1,10 +1,8 @@ -package refactor; +package refactor.typing; import refactor.discover.Identifier; -import refactor.discover.IdentifierPos; import refactor.discover.Type; import refactor.discover.TypeList; -import refactor.edits.Changelist; class TypingHelper { public static function findDescendantTypes(context:CacheAndTyperContext, packName:String, baseType:Type):Array { @@ -231,9 +229,7 @@ class TypingHelper { } return Promise.resolve(typeParams[index]); case UnknownType(_): - trace("unknown type!!"); case StructType(_) | FunctionType(_): - trace("TODO"); } return Promise.reject("not found"); })); @@ -374,10 +370,8 @@ class TypingHelper { case TypedParameter: var allTypes:Array = context.typeList.findTypeName(use.name); if (allTypes.length <= 0) { - if (use.defineType != null) { - typeParams.push(ClasspathType(use.defineType, [])); - continue; - } + typeParams.push(LibType(use.name, use.name, [])); + continue; } for (type in allTypes) { switch (hint.file.importsModule(type.file.getPackage(), type.file.getMainModulName(), type.name.name)) { @@ -423,12 +417,4 @@ typedef SearchTypeOf = { var defineType:Type; } -enum TypeHintType { - ClasspathType(type:Type, typeParams:Array); - LibType(name:String, fullName:String, typeParams:Array); - FunctionType(args:Array, retVal:Null); - StructType(fields:Array); - UnknownType(name:String); -} - typedef TypeParameterList = Array; diff --git a/test/refactor/TestBase.hx b/test/refactor/TestBase.hx index f65778b..e2cc622 100644 --- a/test/refactor/TestBase.hx +++ b/test/refactor/TestBase.hx @@ -4,7 +4,6 @@ import haxe.PosInfos; import js.lib.Promise; import refactor.RefactorResult; import refactor.TestEditableDocument; -import refactor.TypingHelper.TypeHintType; import refactor.discover.FileList; import refactor.discover.NameMap; import refactor.discover.TraverseSources; @@ -12,6 +11,7 @@ import refactor.discover.TypeList; import refactor.discover.UsageCollector; import refactor.discover.UsageContext; import refactor.edits.FileEdit; +import refactor.typing.TypeHintType; class TestBase implements ITest { var usageContext:UsageContext; diff --git a/test/refactor/TestTyper.hx b/test/refactor/TestTyper.hx index 1cc8f95..b367a2e 100644 --- a/test/refactor/TestTyper.hx +++ b/test/refactor/TestTyper.hx @@ -1,7 +1,8 @@ package refactor; import js.lib.Promise; -import refactor.TypingHelper.TypeHintType; +import refactor.typing.ITyper; +import refactor.typing.TypeHintType; using refactor.PrintHelper; diff --git a/test/refactor/refactor/RefactorExtractMethodTest.hx b/test/refactor/refactor/RefactorExtractMethodTest.hx index 1ce937b..1376f7f 100644 --- a/test/refactor/refactor/RefactorExtractMethodTest.hx +++ b/test/refactor/refactor/RefactorExtractMethodTest.hx @@ -1,7 +1,5 @@ package refactor.refactor; -import refactor.TypingHelper.TypeHintType; - class RefactorExtractMethodTest extends RefactorTestBase { function setupClass() { setupTestSources(["testcases/methods"]); @@ -10,11 +8,11 @@ class RefactorExtractMethodTest extends RefactorTestBase { function testSimpleNoReturns(async:Async) { var edits:Array = [ makeReplaceTestEdit("testcases/methods/Main.hx", "noReturnsExtract();\n", 94, 131, true), - makeInsertTestEdit("testcases/methods/Main.hx", - "function noReturnsExtract():Void {\n" + makeInsertTestEdit("testcases/methods/Main.hx", "function noReturnsExtract() {\n" + "trace(\"hello 2\");\n" + " trace(\"hello 3\");\n" - + "}\n", 155, true), + + "}\n", + 155, true), ]; checkRefactor(RefactorExtractMethod, {fileName: "testcases/methods/Main.hx", posStart: 93, posEnd: 131}, edits, async); } @@ -23,7 +21,7 @@ class RefactorExtractMethodTest extends RefactorTestBase { var edits:Array = [ makeReplaceTestEdit("testcases/methods/Main.hx", "noReturnsStaticExtract();\n", 222, 259, true), makeInsertTestEdit("testcases/methods/Main.hx", - "static function noReturnsStaticExtract():Void {\n" + "static function noReturnsStaticExtract() {\n" + "trace(\"hello 2\");\n" + " trace(\"hello 3\");\n" + "}\n", 283, true), @@ -89,7 +87,7 @@ class RefactorExtractMethodTest extends RefactorTestBase { checkRefactor(RefactorExtractMethod, {fileName: "testcases/methods/Main.hx", posStart: 568, posEnd: 737}, edits, async); } - function testProcessUser2(async:Async) { + function testProcessUserWithLastReturn(async:Async) { var edits:Array = [ makeReplaceTestEdit("testcases/methods/Main.hx", "return calculateTotal2Extract(items, total);\n", 1081, 1295, true), makeInsertTestEdit("testcases/methods/Main.hx", @@ -110,26 +108,37 @@ class RefactorExtractMethodTest extends RefactorTestBase { checkRefactor(RefactorExtractMethod, {fileName: "testcases/methods/Main.hx", posStart: 1080, posEnd: 1295}, edits, async); } - // function testProcessUser3(async:Async) { - // var edits:Array = [ - // makeReplaceTestEdit("testcases/methods/Main.hx", "return calculateTotal2Extract(items, total);\n", 1081, 1295, true), - // makeInsertTestEdit("testcases/methods/Main.hx", - // "function calculateTotal2Extract(items:Array, total:Float) {\n" - // + "// Selected code block to extract\n" - // + " for (item in items) {\n" - // + " var price = item.price;\n" - // + " var quantity = item.quantity;\n" - // + " if (quantity < 0) {\n" - // + " return total;\n" - // + " }\n" - // + " total += price * quantity;\n" - // + " }\n\n" - // + " return total;\n" - // + "}\n", - // 1299, true), - // ]; - // checkRefactor(RefactorExtractMethod, {fileName: "testcases/methods/Main.hx", posStart: 1080, posEnd: 1278}, edits, async); - // } + function testProcessUserWithUseAfterSelection(async:Async) { + var edits:Array = [ + makeReplaceTestEdit("testcases/methods/Main.hx", + "{\n" + + "final result = calculateTotal2Extract(items, total);\n" + + "switch (result.ret) {\n" + + "case Some(data):\n" + + "return data;\n" + + "case None:\n" + + "total = result.data;\n" + + "}\n" + + "}\n", + 1081, 1278, true), + makeInsertTestEdit("testcases/methods/Main.hx", + "function calculateTotal2Extract(items:Array, total:Float):{ret:haxe.ds.Option, ?data:Float} {\n" + + "// Selected code block to extract\n" + + " for (item in items) {\n" + + " var price = item.price;\n" + + " var quantity = item.quantity;\n" + + " if (quantity < 0) {\n" + + " return {ret: Some(total)};\n" + + " }\n" + + " total += price * quantity;\n" + + " }\n" + + "return {ret: None, data: total};\n" + + "}\n", + 1299, true), + ]; + addTypeHint("testcases/methods/Main.hx", 1026, FunctionType([LibType("Array", "Array", [LibType("Item", "Item", [])])], LibType("Float", "Float", []))); + checkRefactor(RefactorExtractMethod, {fileName: "testcases/methods/Main.hx", posStart: 1080, posEnd: 1278}, edits, async); + } function testCalculateMath(async:Async) { var edits:Array = [ @@ -239,4 +248,79 @@ class RefactorExtractMethodTest extends RefactorTestBase { addTypeHint("testcases/methods/PersonHandler.hx", 95, LibType("String", "String", [])); checkRefactor(RefactorExtractMethod, {fileName: "testcases/methods/PersonHandler.hx", posStart: 101, posEnd: 161}, edits, async); } + + function testContainer(async:Async) { + var edits:Array = [ + makeReplaceTestEdit("testcases/methods/Container.hx", "var result = processExtract(items, converter);\n", 108, 239, true), + makeInsertTestEdit("testcases/methods/Container.hx", + "function processExtract(items:Array, converter:T -> String):Array {\n" + + "var result = new Array();\n" + + " for (item in items) {\n" + + " var converted = converter(item);\n" + + " result.push('[${converted}]');\n" + + " }\n" + + "return result;\n" + + "}\n", + 260, true), + ]; + addTypeHint("testcases/methods/Container.hx", 91, FunctionType([LibType("T", "T", [])], LibType("String", "String", []))); + addTypeHint("testcases/methods/Container.hx", 117, LibType("Array", "Array", [LibType("String", "String", [])])); + checkRefactor(RefactorExtractMethod, {fileName: "testcases/methods/Container.hx", posStart: 107, posEnd: 239}, edits, async); + } + + function testMacroTools(async:Async) { + var edits:Array = [ + makeReplaceTestEdit("testcases/methods/MacroTools.hx", "return buildExtract(fields);\n", 195, 353, true), + makeInsertTestEdit("testcases/methods/MacroTools.hx", + "static function buildExtract(fields:Array):Array {\n" + + "for (field in fields) {\n" + + " switch field.kind {\n" + + " case FFun(f):\n" + + " var expr = f.expr;\n" + + " // Complex manipulation...\n" + + " default:\n" + + " }\n" + + " }\n" + + " return fields;\n" + + "}\n", + 357, true), + ]; + addTypeHint("testcases/methods/MacroTools.hx", 163, LibType("Array", "Array", [LibType("Field", "Field", [])])); + addTypeHint("testcases/methods/MacroTools.hx", 351, LibType("Array", "Array", [LibType("Field", "Field", [])])); + checkRefactor(RefactorExtractMethod, {fileName: "testcases/methods/MacroTools.hx", posStart: 195, posEnd: 353}, edits, async); + } + + function testMatcherOnlySwitch(async:Async) { + var edits:Array = [ + makeReplaceTestEdit("testcases/methods/Matcher.hx", "processExtract(value);\n", 84, 284, true), + makeInsertTestEdit("testcases/methods/Matcher.hx", + "function processExtract(value:Any) {\n" + + "return switch value {\n" + + " case Int(i) if (i > 0): 'Positive: $i';\n" + + " case String(s) if (s.length > 0): 'NonEmpty: $s';\n" + + " case Array(a) if (a.length > 0): 'HasElements: ${a.length}';\n" + + " case _: 'Unknown';\n" + + " }\n" + + "}\n", + 288, true), + ]; + checkRefactor(RefactorExtractMethod, {fileName: "testcases/methods/Matcher.hx", posStart: 84, posEnd: 284}, edits, async); + } + + function testMatcherWithReturn(async:Async) { + var edits:Array = [ + makeReplaceTestEdit("testcases/methods/Matcher.hx", "return processExtract(value);\n", 77, 284, true), + makeInsertTestEdit("testcases/methods/Matcher.hx", + "function processExtract(value:Any) {\n" + + "return switch value {\n" + + " case Int(i) if (i > 0): 'Positive: $i';\n" + + " case String(s) if (s.length > 0): 'NonEmpty: $s';\n" + + " case Array(a) if (a.length > 0): 'HasElements: ${a.length}';\n" + + " case _: 'Unknown';\n" + + " }\n" + + "}\n", + 288, true), + ]; + checkRefactor(RefactorExtractMethod, {fileName: "testcases/methods/Matcher.hx", posStart: 76, posEnd: 284}, edits, async); + } } diff --git a/testcases/methods/Container.hx b/testcases/methods/Container.hx new file mode 100644 index 0000000..90bed64 --- /dev/null +++ b/testcases/methods/Container.hx @@ -0,0 +1,12 @@ +package testcases.methods; + +class Container { + function process(items:Array, converter:T->String) { + var result = new Array(); + for (item in items) { + var converted = converter(item); + result.push('[${converted}]'); + } + trace(result); + } +} diff --git a/testcases/methods/MacroTools.hx b/testcases/methods/MacroTools.hx new file mode 100644 index 0000000..f54a794 --- /dev/null +++ b/testcases/methods/MacroTools.hx @@ -0,0 +1,19 @@ +package testcases.methods; + +import haxe.macro.Context; +import haxe.macro.Expr; + +class MacroTools { + macro public static function build():Array { + var fields = Context.getBuildFields(); + for (field in fields) { + switch field.kind { + case FFun(f): + var expr = f.expr; + // Complex manipulation... + default: + } + } + return fields; + } +} diff --git a/testcases/methods/Matcher.hx b/testcases/methods/Matcher.hx new file mode 100644 index 0000000..84cb1c3 --- /dev/null +++ b/testcases/methods/Matcher.hx @@ -0,0 +1,12 @@ +package testcases.methods; + +class Matcher { + function process(value:Any) { + return switch value { + case Int(i) if (i > 0): 'Positive: $i'; + case String(s) if (s.length > 0): 'NonEmpty: $s'; + case Array(a) if (a.length > 0): 'HasElements: ${a.length}'; + case _: 'Unknown'; + } + } +} From 5bc1166fa27c3865982dbdb974f79699e07a2c00 Mon Sep 17 00:00:00 2001 From: AlexHaxe Date: Sun, 24 Nov 2024 17:12:00 +0100 Subject: [PATCH 13/59] fixed wrong position for hover requests fixed duplicated identifiers for return values modified codegens --- src/refactor/CacheAndTyperContext.hx | 5 ++ src/refactor/Cli.hx | 4 +- src/refactor/refactor/CanRefactorContext.hx | 6 --- src/refactor/refactor/ExtractMethod.hx | 10 +++- src/refactor/refactor/RefactorHelper.hx | 2 +- .../refactor/extractmethod/CodeGenBase.hx | 43 ++++++++++++++++ .../extractmethod/CodeGenEmptyReturn.hx | 16 ++---- .../refactor/extractmethod/CodeGenNoReturn.hx | 10 ++-- .../extractmethod/CodeGenOpenEnded.hx | 50 ++----------------- .../extractmethod/CodeGenReturnIsLast.hx | 12 +---- src/refactor/typing/TypingHelper.hx | 6 +++ test/refactor/TestBase.hx | 31 ++++++++++++ .../refactor/RefactorExtractMethodTest.hx | 29 +++++++++++ test/refactor/refactor/RefactorTestBase.hx | 34 +------------ test/refactor/rename/RenameTestBase.hx | 4 ++ 15 files changed, 146 insertions(+), 116 deletions(-) diff --git a/src/refactor/CacheAndTyperContext.hx b/src/refactor/CacheAndTyperContext.hx index 21e745a..c8ec85c 100644 --- a/src/refactor/CacheAndTyperContext.hx +++ b/src/refactor/CacheAndTyperContext.hx @@ -2,6 +2,7 @@ package refactor; import refactor.VerboseLogger; import refactor.discover.FileList; +import refactor.discover.FileReaderFunc; import refactor.discover.NameMap; import refactor.discover.TypeList; import refactor.typing.ITyper; @@ -12,4 +13,8 @@ typedef CacheAndTyperContext = { var typeList:TypeList; var verboseLog:VerboseLogger; var typer:Null; + var fileReader:FileReaderFunc; + var converter:ByteToCharConverterFunc; } + +typedef ByteToCharConverterFunc = (string:String, byteOffset:Int) -> Int; diff --git a/src/refactor/Cli.hx b/src/refactor/Cli.hx index ff77d07..f81b042 100644 --- a/src/refactor/Cli.hx +++ b/src/refactor/Cli.hx @@ -118,7 +118,9 @@ class Cli { forRealExecute: execute && forReal, docFactory: EditableDocument.new, verboseLog: verboseLog, - typer: null + typer: null, + fileReader: null, + converter: null, }); result.then(function(result:RefactorResult) { switch (result) { diff --git a/src/refactor/refactor/CanRefactorContext.hx b/src/refactor/refactor/CanRefactorContext.hx index 9a26ce3..ef7ff83 100644 --- a/src/refactor/refactor/CanRefactorContext.hx +++ b/src/refactor/refactor/CanRefactorContext.hx @@ -1,11 +1,5 @@ package refactor.refactor; -import refactor.discover.FileReaderFunc; - typedef CanRefactorContext = CacheAndTyperContext & { var what:RefactorWhat; - var fileReader:FileReaderFunc; - var converter:ByteToCharConverterFunc; } - -typedef ByteToCharConverterFunc = (string:String, byteOffset:Int) -> Int; diff --git a/src/refactor/refactor/ExtractMethod.hx b/src/refactor/refactor/ExtractMethod.hx index 3ca7419..0acd015 100644 --- a/src/refactor/refactor/ExtractMethod.hx +++ b/src/refactor/refactor/ExtractMethod.hx @@ -372,6 +372,8 @@ class ExtractMethod { switch (child.tok) { case Binop(OpAssign) | Binop(OpAssignOp(_)): assignedVars.push(s); + case Unop(OpIncrement) | Unop(OpDecrement): + assignedVars.push(s); default: } } @@ -382,7 +384,7 @@ class ExtractMethod { if (allReturns.length > 0) { var lastReturn = allReturns[allReturns.length - 1]; if (isSingleExpression(lastReturn, extractData.endToken)) { - return new CodeGenReturnIsLast(extractData, context, neededIdentifiers, lastReturn); + return new CodeGenReturnIsLast(extractData, context, neededIdentifiers); } } @@ -467,7 +469,11 @@ class ExtractMethod { continue; } } - scopedVarUses.push(varsValidAfterSelection.get(part)); + final scopedVar = varsValidAfterSelection.get(part); + if (scopedVarUses.contains(scopedVar)) { + continue; + } + scopedVarUses.push(scopedVar); trace("leaking " + varsValidAfterSelection.get(part)); } return scopedVarUses; diff --git a/src/refactor/refactor/RefactorHelper.hx b/src/refactor/refactor/RefactorHelper.hx index 2685734..ce8c8eb 100644 --- a/src/refactor/refactor/RefactorHelper.hx +++ b/src/refactor/refactor/RefactorHelper.hx @@ -1,7 +1,7 @@ package refactor.refactor; +import refactor.CacheAndTyperContext.ByteToCharConverterFunc; import refactor.discover.File; -import refactor.refactor.CanRefactorContext.ByteToCharConverterFunc; class RefactorHelper { public static function findTokensAtPos(root:TokenTree, searchPos:Int):TokensAtPos { diff --git a/src/refactor/refactor/extractmethod/CodeGenBase.hx b/src/refactor/refactor/extractmethod/CodeGenBase.hx index 62f28ab..639330f 100644 --- a/src/refactor/refactor/extractmethod/CodeGenBase.hx +++ b/src/refactor/refactor/extractmethod/CodeGenBase.hx @@ -37,6 +37,49 @@ abstract class CodeGenBase implements ICodeGen { } return snippet; } + + function parentTypeHint():Promise { + var func:Null = findParentFunction(); + if (func == null) { + return Promise.reject("failed to find return type of selected code"); + } + return TypingHelper.findTypeWithTyper(context, context.what.fileName, func.pos.max - 1).then(function(typeHint) { + return switch (typeHint) { + case null | ClasspathType(_) | LibType(_) | StructType(_) | UnknownType(_): + Promise.resolve(typeHint); + case FunctionType(args, retVal): + Promise.resolve(retVal); + } + }); + } + + function findParentFunction():Null { + var token = extractData.startToken.parent; + while (true) { + if (token == null) { + return null; + } + switch (token.tok) { + case Kwd(KwdFunction): + var child = token.getFirstChild(); + if (child == null) { + return null; + } + switch (child.tok) { + case Const(_): + return child; + default: + return token; + } + case Arrow: + return token; + case Root: + return null; + default: + token = token.parent; + } + } + } } typedef ReturnValueCallback = (value:String) -> String; diff --git a/src/refactor/refactor/extractmethod/CodeGenEmptyReturn.hx b/src/refactor/refactor/extractmethod/CodeGenEmptyReturn.hx index 30bb6d5..5d20b28 100644 --- a/src/refactor/refactor/extractmethod/CodeGenEmptyReturn.hx +++ b/src/refactor/refactor/extractmethod/CodeGenEmptyReturn.hx @@ -39,15 +39,8 @@ class CodeGenEmptyReturn extends CodeGenBase { + '${vars[0].name} = data;\n}\n'; case [_, _]: final dataVars = vars.map(v -> 'var ${v.name};').join("\n"); - final assignData = assignments.map(a -> '${a.name} = data.${a.name};').join("\n"); - final varsData = vars.map(v -> '${v.name} = data.${v.name};').join("\n"); - dataVars - + 'switch ($call) {\n' - + "case None:\n" - + "return;\n" - + "case Some(data):\n" - + '${assignData}${varsData}' - + "}\n"; + final assignData = assignments.concat(vars).map(a -> '${a.name} = data.${a.name};').join("\n"); + dataVars + 'switch ($call) {\n' + "case None:\n" + "return;\n" + "case Some(data):\n" + '${assignData}\n' + "}\n"; } } @@ -111,9 +104,8 @@ class CodeGenEmptyReturn extends CodeGenBase { " {\n" + snippet + returnLocalVar + "\n}\n"; case [_, _]: final snippet = replaceReturnValues(returnTokens, value -> 'None'); - final assignData = assignments.map(a -> '${a.name}: ${a.name},\n'); - final varsData = vars.map(v -> '${v.name}: ${v.name},\n'); - final returnAssigments = "\nreturn Some({\n" + assignData + varsData + "});"; + final assignData = assignments.concat(vars).map(a -> '${a.name}: ${a.name},\n'); + final returnAssigments = "\nreturn Some({\n" + assignData + "\n});"; " {\n" + snippet + returnAssigments + "\n}\n"; } } diff --git a/src/refactor/refactor/extractmethod/CodeGenNoReturn.hx b/src/refactor/refactor/extractmethod/CodeGenNoReturn.hx index 20c4752..92c1840 100644 --- a/src/refactor/refactor/extractmethod/CodeGenNoReturn.hx +++ b/src/refactor/refactor/extractmethod/CodeGenNoReturn.hx @@ -27,9 +27,8 @@ class CodeGenNoReturn extends CodeGenBase { '${assignments[0].name} = $call;\n'; case [_, _]: final dataVars = vars.map(v -> 'var ${v.name};').join("\n"); - final assignData = assignments.map(a -> '${a.name} = data.${a.name};').join("\n"); - final assignVars = vars.map(v -> '${v.name} = data.${v.name};').join("\n"); - '$dataVars\n{\nfinal data = $call;\n' + assignData + assignVars + "}\n"; + final assignData = assignments.concat(vars).map(a -> '${a.name} = data.${a.name};').join("\n"); + '$dataVars\n{\nfinal data = $call;\n' + assignData + "\n}\n"; } } @@ -88,9 +87,8 @@ class CodeGenNoReturn extends CodeGenBase { final returnAssigmentVar = '\nreturn ${assignments[0].name};'; " {\n" + selectedSnippet + returnAssigmentVar + "\n}\n"; case [_, _]: - final assignData = assignments.map(a -> '${a.name}: ${a.name},').join("\n"); - final assignVars = vars.map(v -> '${v.name}: ${v.name},').join("\n"); - final returnAssigments = "\nreturn {\n" + assignData + assignVars + "};"; + final assignData = assignments.concat(vars).map(a -> '${a.name}: ${a.name},').join("\n"); + final returnAssigments = "\nreturn {\n" + assignData + "\n};"; " {\n" + selectedSnippet + returnAssigments + "\n}\n"; } } diff --git a/src/refactor/refactor/extractmethod/CodeGenOpenEnded.hx b/src/refactor/refactor/extractmethod/CodeGenOpenEnded.hx index b762668..0213c9e 100644 --- a/src/refactor/refactor/extractmethod/CodeGenOpenEnded.hx +++ b/src/refactor/refactor/extractmethod/CodeGenOpenEnded.hx @@ -44,15 +44,14 @@ class CodeGenOpenEnded extends CodeGenBase { + "}\n"; case [_, _]: final dataVars = vars.map(v -> 'var ${v.name};').join("\n"); - final assignData = assignments.map(a -> '${a.name} = result.data.${a.name};').join("\n"); - final varsData = vars.map(v -> '${v.name} = result.data.${v.name};').join("\n"); + final assignData = assignments.concat(vars).map(a -> '${a.name} = result.data.${a.name};').join("\n"); '$dataVars' + '{\nfinal result = $call;\n' + "switch (result.ret) {\n" + "case Some(data):\n" + "return data;\n" + "case None:\n" - + '${assignData}${varsData}' + + '${assignData}\n' + "}\n" + "}\n"; } @@ -134,46 +133,6 @@ class CodeGenOpenEnded extends CodeGenBase { } } - function parentTypeHint():Promise { - var func:Null = findParentFunction(); - if (func == null) { - return Promise.reject("failed to find return type of selected code"); - } - return TypingHelper.findTypeWithTyper(context, context.what.fileName, func.pos.max - 1).then(function(typeHint) { - return switch (typeHint) { - case null | ClasspathType(_) | LibType(_) | StructType(_) | UnknownType(_): - Promise.resolve(typeHint); - case FunctionType(args, retVal): - Promise.resolve(retVal); - } - }); - } - - function findParentFunction():Null { - var token = extractData.startToken.parent; - while (true) { - switch (token.tok) { - case Kwd(KwdFunction): - var child = token.getFirstChild(); - if (child == null) { - return null; - } - switch (child.tok) { - case Const(_): - return child; - default: - return token; - } - case Arrow: - return token; - case Root: - return null; - default: - token = token.parent; - } - } - } - public function makeBody():String { return switch [assignments.length, vars.length] { case [0, 0]: @@ -189,9 +148,8 @@ class CodeGenOpenEnded extends CodeGenBase { return " {\n" + snippet + '\nreturn $returnData;\n}\n'; case [_, _]: final snippet = replaceReturnValues(returnTokens, value -> '{ret: Some($value)}'); - final assignData = assignments.map(a -> '${a.name}: ${a.name},\n'); - final varsData = vars.map(v -> '${v.name}: ${v.name},\n'); - final returnData = '{ret: None, data: {$assignData$varsData}}'; + final assignData = assignments.concat(vars).map(a -> '${a.name}: ${a.name},\n'); + final returnData = '{ret: None, data: {\n$assignData\n}}'; return " {\n" + snippet + '\nreturn $returnData;\n}\n'; } } diff --git a/src/refactor/refactor/extractmethod/CodeGenReturnIsLast.hx b/src/refactor/refactor/extractmethod/CodeGenReturnIsLast.hx index 88a2e90..65f06c1 100644 --- a/src/refactor/refactor/extractmethod/CodeGenReturnIsLast.hx +++ b/src/refactor/refactor/extractmethod/CodeGenReturnIsLast.hx @@ -3,12 +3,8 @@ package refactor.refactor.extractmethod; import refactor.discover.Identifier; class CodeGenReturnIsLast extends CodeGenBase { - final lastReturnToken:Null; - - public function new(extractData:ExtractMethodData, context:RefactorContext, neededIdentifiers:Array, lastReturnToken:TokenTree) { + public function new(extractData:ExtractMethodData, context:RefactorContext, neededIdentifiers:Array) { super(extractData, context, neededIdentifiers); - - this.lastReturnToken = lastReturnToken; } public function makeCallSite():String { @@ -19,11 +15,7 @@ class CodeGenReturnIsLast extends CodeGenBase { } public function makeReturnTypeHint():Promise { - if (lastReturnToken == null) { - return Promise.resolve(""); - } - final pos = lastReturnToken.getPos(); - return TypingHelper.findTypeWithTyper(context, context.what.fileName, pos.max - 2).then(function(typeHint):Promise { + return parentTypeHint().then(function(typeHint):Promise { if (typeHint == null) { return Promise.resolve(""); } diff --git a/src/refactor/typing/TypingHelper.hx b/src/refactor/typing/TypingHelper.hx index 4a3e0d9..5d5d28a 100644 --- a/src/refactor/typing/TypingHelper.hx +++ b/src/refactor/typing/TypingHelper.hx @@ -138,6 +138,12 @@ class TypingHelper { if (context.typer == null) { return Promise.reject("no typer for " + fileName + "@" + pos); } + switch (context.fileReader(fileName)) { + case Text(text): + pos = context.converter(text, pos); + case Token(root, text): + pos = context.converter(text, pos); + } return context.typer.resolveType(fileName, pos); } diff --git a/test/refactor/TestBase.hx b/test/refactor/TestBase.hx index e2cc622..2876aa3 100644 --- a/test/refactor/TestBase.hx +++ b/test/refactor/TestBase.hx @@ -1,9 +1,16 @@ package refactor; +import haxe.Exception; import haxe.PosInfos; import js.lib.Promise; +import byte.ByteData; +import haxeparser.HaxeLexer; +import hxparse.ParserError; +import tokentree.TokenTree; +import tokentree.TokenTreeBuilder; import refactor.RefactorResult; import refactor.TestEditableDocument; +import refactor.discover.FileContentType; import refactor.discover.FileList; import refactor.discover.NameMap; import refactor.discover.TraverseSources; @@ -137,4 +144,28 @@ class TestBase implements ITest { } return Promise.resolve(success); } + + function fileReader(path:String):FileContentType { + final text = sys.io.File.getContent(path); + var root:Null = null; + try { + final content = ByteData.ofString(text); + final lexer = new HaxeLexer(content, path); + var t:haxeparser.Data.Token = lexer.token(haxeparser.HaxeLexer.tok); + + final tokens:Array = []; + while (t.tok != Eof) { + tokens.push(t); + t = lexer.token(haxeparser.HaxeLexer.tok); + } + root = TokenTreeBuilder.buildTokenTree(tokens, content, TypeLevel); + return Token(root, text); + } catch (e:ParserError) { + throw 'failed to parse ${path} - ParserError: $e (${e.pos})'; + } catch (e:LexerError) { + throw 'failed to parse ${path} - LexerError: ${e.msg} (${e.pos})'; + } catch (e:Exception) { + throw 'failed to parse ${path} - ${e.details()}'; + } + } } diff --git a/test/refactor/refactor/RefactorExtractMethodTest.hx b/test/refactor/refactor/RefactorExtractMethodTest.hx index 1376f7f..4403fb6 100644 --- a/test/refactor/refactor/RefactorExtractMethodTest.hx +++ b/test/refactor/refactor/RefactorExtractMethodTest.hx @@ -67,6 +67,33 @@ class RefactorExtractMethodTest extends RefactorTestBase { checkRefactor(RefactorExtractMethod, {fileName: "testcases/methods/Main.hx", posStart: 568, posEnd: 720}, edits, async); } + function testCalculateTotalJustVars(async:Async) { + var edits:Array = [ + makeReplaceTestEdit("testcases/methods/Main.hx", + "var price;\n" + + "var quantity;\n" + + "{\nfinal data = calculateTotalExtract(item);\n" + + "price = data.price;\n" + + "quantity = data.quantity;\n" + + "}\n", + 630, 686, true), + makeInsertTestEdit("testcases/methods/Main.hx", + "function calculateTotalExtract(item:Item):{price:Float, quantity:Float} {\n" + + "var price = item.price;\n" + + " var quantity = item.quantity;\n" + + "return {\n" + + "price: price,\n" + + "quantity: quantity,\n" + + "};\n" + + "}\n", + 741, true), + ]; + addTypeHint("testcases/methods/Main.hx", 613, LibType("Item", "Item", [])); + addTypeHint("testcases/methods/Main.hx", 638, LibType("Float", "Float", [])); + addTypeHint("testcases/methods/Main.hx", 668, LibType("Float", "Float", [])); + checkRefactor(RefactorExtractMethod, {fileName: "testcases/methods/Main.hx", posStart: 629, posEnd: 686}, edits, async); + } + function testCalculateTotalWithLastReturn(async:Async) { var edits:Array = [ makeReplaceTestEdit("testcases/methods/Main.hx", "return calculateTotalExtract(items, total);\n", 569, 737, true), @@ -83,6 +110,7 @@ class RefactorExtractMethodTest extends RefactorTestBase { + "}\n", 741, true), ]; + addTypeHint("testcases/methods/Main.hx", 514, LibType("Float", "Float", [])); addTypeHint("testcases/methods/Main.hx", 735, LibType("Float", "Float", [])); checkRefactor(RefactorExtractMethod, {fileName: "testcases/methods/Main.hx", posStart: 568, posEnd: 737}, edits, async); } @@ -285,6 +313,7 @@ class RefactorExtractMethodTest extends RefactorTestBase { + "}\n", 357, true), ]; + addTypeHint("testcases/methods/MacroTools.hx", 133, LibType("Array", "Array", [LibType("Field", "Field", [])])); addTypeHint("testcases/methods/MacroTools.hx", 163, LibType("Array", "Array", [LibType("Field", "Field", [])])); addTypeHint("testcases/methods/MacroTools.hx", 351, LibType("Array", "Array", [LibType("Field", "Field", [])])); checkRefactor(RefactorExtractMethod, {fileName: "testcases/methods/MacroTools.hx", posStart: 195, posEnd: 353}, edits, async); diff --git a/test/refactor/refactor/RefactorTestBase.hx b/test/refactor/refactor/RefactorTestBase.hx index 0791970..83aca25 100644 --- a/test/refactor/refactor/RefactorTestBase.hx +++ b/test/refactor/refactor/RefactorTestBase.hx @@ -3,15 +3,9 @@ package refactor.refactor; import haxe.Exception; import haxe.PosInfos; import js.lib.Promise; -import byte.ByteData; -import haxeparser.HaxeLexer; -import hxparse.ParserError; -import tokentree.TokenTree; -import tokentree.TokenTreeBuilder; import utest.Async; import refactor.RefactorResult; import refactor.TestEditableDocument; -import refactor.discover.FileContentType; class RefactorTestBase extends TestBase { function checkRefactor(refactorType:RefactorType, what:RefactorWhat, edits:Array, async:Async, ?pos:PosInfos) { @@ -66,7 +60,7 @@ class RefactorTestBase extends TestBase { }, typer: null, converter: (string, byteOffset) -> byteOffset, - fileReader: testFileReader, + fileReader: fileReader, }); return switch (result) { case Unsupported: @@ -90,33 +84,9 @@ class RefactorTestBase extends TestBase { }, typer: typer, converter: (string, byteOffset) -> byteOffset, - fileReader: testFileReader, + fileReader: fileReader, }).then(function(success:RefactorResult) { return assertEdits(success, editList, edits, pos); }); } } - -function testFileReader(path:String):FileContentType { - final text = sys.io.File.getContent(path); - var root:Null = null; - try { - final content = ByteData.ofString(text); - final lexer = new HaxeLexer(content, path); - var t:haxeparser.Data.Token = lexer.token(haxeparser.HaxeLexer.tok); - - final tokens:Array = []; - while (t.tok != Eof) { - tokens.push(t); - t = lexer.token(haxeparser.HaxeLexer.tok); - } - root = TokenTreeBuilder.buildTokenTree(tokens, content, TypeLevel); - return Token(root, text); - } catch (e:ParserError) { - throw 'failed to parse ${path} - ParserError: $e (${e.pos})'; - } catch (e:LexerError) { - throw 'failed to parse ${path} - LexerError: ${e.msg} (${e.pos})'; - } catch (e:Exception) { - throw 'failed to parse ${path} - ${e.details()}'; - } -} diff --git a/test/refactor/rename/RenameTestBase.hx b/test/refactor/rename/RenameTestBase.hx index 0c9c2d0..7085f6d 100644 --- a/test/refactor/rename/RenameTestBase.hx +++ b/test/refactor/rename/RenameTestBase.hx @@ -60,6 +60,8 @@ class RenameTestBase extends TestBase { Sys.println('${pos.fileName}:${pos.lineNumber}: $text'); }, typer: withTyper ? typer : null, + converter: (string, byteOffset) -> byteOffset, + fileReader: fileReader, }).then(function(success:CanRenameResult) { return doRename(what, edits, withTyper, pos); }); @@ -78,6 +80,8 @@ class RenameTestBase extends TestBase { Sys.println('${pos.fileName}:${pos.lineNumber}: $text'); }, typer: withTyper ? typer : null, + converter: (string, byteOffset) -> byteOffset, + fileReader: fileReader, }).then(function(success:RefactorResult) { return assertEdits(success, editList, edits, pos); }); From 7e161397241009b557c4c8e2edfbcd490b561b70 Mon Sep 17 00:00:00 2001 From: AlexHaxe Date: Sun, 24 Nov 2024 21:58:53 +0100 Subject: [PATCH 14/59] added testcases fixed discovery of identifiers in if conditions --- src/refactor/discover/UsageCollector.hx | 2 + src/refactor/refactor/ExtractMethod.hx | 16 +++++--- .../refactor/RefactorExtractMethodTest.hx | 41 +++++++++++++++++++ test/refactor/rename/RenameClassTest.hx | 8 ++++ testcases/methods/Main.hx | 16 ++++++++ 5 files changed, 78 insertions(+), 5 deletions(-) diff --git a/src/refactor/discover/UsageCollector.hx b/src/refactor/discover/UsageCollector.hx index 0223e88..9132500 100644 --- a/src/refactor/discover/UsageCollector.hx +++ b/src/refactor/discover/UsageCollector.hx @@ -932,6 +932,8 @@ class UsageCollector { case Binop(OpLt): if (TokenTreeCheckUtils.isTypeParameter(child)) { typeParamLt = child; + } else { + pOpenToken.push(child); } case Arrow: var scopePos = child.getPos(); diff --git a/src/refactor/refactor/ExtractMethod.hx b/src/refactor/refactor/ExtractMethod.hx index 0acd015..116a6fe 100644 --- a/src/refactor/refactor/ExtractMethod.hx +++ b/src/refactor/refactor/ExtractMethod.hx @@ -285,7 +285,7 @@ class ExtractMethod { if (identifier.name.contains(".")) { return false; } - switch (identifier.type) { + return switch (identifier.type) { case ScopedLocal(scopeStart, scopeEnd, _): if (scopeEnd <= extractData.startToken.pos.min) { return false; @@ -293,9 +293,9 @@ class ExtractMethod { if (scopeStart > extractData.endToken.pos.max) { return false; } - return true; + true; default: - return false; + false; } }); } @@ -388,12 +388,13 @@ class ExtractMethod { } } - final modifiedIdentifiers:Array = []; + var modifiedCandidates:Map = new Map(); for (identifier in neededIdentifiers) { if (assignedVars.contains(identifier.name)) { - modifiedIdentifiers.push(identifier); + modifiedCandidates.set(identifier.name, identifier); } } + final modifiedIdentifiers = findIdentifiersUsedAfterSelection(extractData, functionIdentifier, modifiedCandidates); if (allReturns.length == 0) { return new CodeGenNoReturn(extractData, context, neededIdentifiers, modifiedIdentifiers, leakingVars); @@ -428,6 +429,11 @@ class ExtractMethod { } } + return findIdentifiersUsedAfterSelection(extractData, functionIdentifier, varsValidAfterSelection); + } + + static function findIdentifiersUsedAfterSelection(extractData:ExtractMethodData, functionIdentifier:Identifier, + varsValidAfterSelection:Map):Array { // find all identifier uses after user's selection that share same name final varShadows:Map = new Map(); final allIdentifierUses:Array = functionIdentifier.findAllIdentifiers(identifier -> { diff --git a/test/refactor/refactor/RefactorExtractMethodTest.hx b/test/refactor/refactor/RefactorExtractMethodTest.hx index 4403fb6..cb50996 100644 --- a/test/refactor/refactor/RefactorExtractMethodTest.hx +++ b/test/refactor/refactor/RefactorExtractMethodTest.hx @@ -168,6 +168,47 @@ class RefactorExtractMethodTest extends RefactorTestBase { checkRefactor(RefactorExtractMethod, {fileName: "testcases/methods/Main.hx", posStart: 1080, posEnd: 1278}, edits, async); } + function testCalcConditionalLevel(async:Async) { + var edits:Array = [ + makeReplaceTestEdit("testcases/methods/Main.hx", "count = calcConditionalLevelExtract(token, count);\n", 1388, 1543, true), + makeInsertTestEdit("testcases/methods/Main.hx", + "function calcConditionalLevelExtract(token:tokentree.TokenTree, count:Int):Int {\n" + + "while ((token != null) && (token.tok != Root)) {\n" + + " switch (token.tok) {\n" + + " case Sharp(\"if\"):\n" + + " count++;\n" + + " default:\n" + + " }\n" + + " token = token.parent;\n" + + " }\n" + + "return count;\n" + + "}\n", + 1600, true), + ]; + checkRefactor(RefactorExtractMethod, {fileName: "testcases/methods/Main.hx", posStart: 1387, posEnd: 1543}, edits, async); + } + + function testCalcConditionalLevelWithVar(async:Async) { + var edits:Array = [ + makeReplaceTestEdit("testcases/methods/Main.hx", "var count = calcConditionalLevelExtract(token);\n", 1366, 1543, true), + makeInsertTestEdit("testcases/methods/Main.hx", + "function calcConditionalLevelExtract(token:tokentree.TokenTree):Int {\n" + + "var count:Int = -1;\n" + + " while ((token != null) && (token.tok != Root)) {\n" + + " switch (token.tok) {\n" + + " case Sharp(\"if\"):\n" + + " count++;\n" + + " default:\n" + + " }\n" + + " token = token.parent;\n" + + " }\n" + + "return count;\n" + + "}\n", + 1600, true), + ]; + checkRefactor(RefactorExtractMethod, {fileName: "testcases/methods/Main.hx", posStart: 1365, posEnd: 1543}, edits, async); + } + function testCalculateMath(async:Async) { var edits:Array = [ makeReplaceTestEdit("testcases/methods/Math.hx", "calculateExtract(a, b);\n", 106, 122, true), diff --git a/test/refactor/rename/RenameClassTest.hx b/test/refactor/rename/RenameClassTest.hx index 1e891ed..28c7602 100644 --- a/test/refactor/rename/RenameClassTest.hx +++ b/test/refactor/rename/RenameClassTest.hx @@ -103,6 +103,14 @@ class RenameClassTest extends RenameTestBase { checkRename({fileName: "testcases/classes/ChildClass.hx", toName: "ChildList", pos: 872}, edits, async); } + public function testRenameScopeStart(async:Async) { + var edits:Array = [ + makeReplaceTestEdit("testcases/classes/ChildClass.hx", "scopeStartRenamed", 255, 265), + makeReplaceTestEdit("testcases/classes/ChildClass.hx", "scopeStartRenamed", 404, 414), + ]; + checkRename({fileName: "testcases/classes/ChildClass.hx", toName: "scopeStartRenamed", pos: 259}, edits, async); + } + public function testRenameStaticExtentionSum(async:Async) { var edits:Array = [ makeReplaceTestEdit("testcases/classes/ChildHelper.hx", "sumChilds", 62, 65), diff --git a/testcases/methods/Main.hx b/testcases/methods/Main.hx index 50f121f..53ae470 100644 --- a/testcases/methods/Main.hx +++ b/testcases/methods/Main.hx @@ -70,6 +70,22 @@ class Main { return total; } + + function calcConditionalLevel(token:tokentree.TokenTree):Int { + var count:Int = -1; + while ((token != null) && (token.tok != Root)) { + switch (token.tok) { + case Sharp("if"): + count++; + default: + } + token = token.parent; + } + if (count <= 0) { + return 0; + } + return count; + } } typedef Item = { From ed57f56724ef3b79e54659692619cf8aceed39bb Mon Sep 17 00:00:00 2001 From: AlexHaxe Date: Mon, 25 Nov 2024 01:25:51 +0100 Subject: [PATCH 15/59] fixed type hint for return expression extracts --- src/refactor/refactor/extractmethod/CodeGenAsExpression.hx | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/src/refactor/refactor/extractmethod/CodeGenAsExpression.hx b/src/refactor/refactor/extractmethod/CodeGenAsExpression.hx index f595183..b48dae0 100644 --- a/src/refactor/refactor/extractmethod/CodeGenAsExpression.hx +++ b/src/refactor/refactor/extractmethod/CodeGenAsExpression.hx @@ -30,6 +30,13 @@ class CodeGenAsExpression extends CodeGenBase { } return Promise.resolve(":" + typeHint.printTypeHint()); }); + case Kwd(KwdReturn): + return parentTypeHint().then(function(typeHint):Promise { + if (typeHint == null) { + return Promise.resolve(""); + } + return Promise.resolve(":" + typeHint.printTypeHint()); + }); default: } return TypingHelper.findTypeWithTyper(context, context.what.fileName, extractData.endToken.pos.max - 1).then(function(typeHint):Promise { From d2b9aa9df445ee7ffe091f6fc4396b519930ca45 Mon Sep 17 00:00:00 2001 From: AlexHaxe Date: Mon, 25 Nov 2024 20:01:25 +0100 Subject: [PATCH 16/59] fixed modifier removal for ExtractInterface added testcases fixed Null handling --- src/refactor/refactor/ExtractInterface.hx | 28 ++++++++- src/refactor/rename/RenameHelper.hx | 39 ++++++++----- src/refactor/typing/TypingHelper.hx | 17 +++++- .../refactor/RefactorExtractMethodTest.hx | 58 +++++++++++++++++++ 4 files changed, 126 insertions(+), 16 deletions(-) diff --git a/src/refactor/refactor/ExtractInterface.hx b/src/refactor/refactor/ExtractInterface.hx index ddd3034..249605f 100644 --- a/src/refactor/refactor/ExtractInterface.hx +++ b/src/refactor/refactor/ExtractInterface.hx @@ -291,7 +291,33 @@ class ExtractInterface { for (field in fields) { buf.add("\t"); var text:String = RefactorHelper.extractText(context.converter, extractData.content, field.pos.min, field.pos.max); - text = text.replace(" public ", " "); + var index = text.lastIndexOf("function "); + if (index < 0) { + index = text.lastIndexOf("var "); + } + if (index < 0) { + index = text.lastIndexOf("final "); + } + if (index > 0) { + var commentIndex:Int = text.lastIndexOf("*/", index); + var comment = ""; + if (commentIndex < 0) { + commentIndex = 0; + } + if (commentIndex > 0) { + comment = text.substr(0, commentIndex); + } + var modifier = text.substring(commentIndex, index); + final funcSignature = text.substr(index); + modifier = modifier.replace("public", ""); + modifier = modifier.replace("inline", ""); + modifier = modifier.replace("override", ""); + modifier = modifier.replace("abstract", ""); + text = comment + modifier + funcSignature; + if (!funcSignature.endsWith(";")) { + text += ";"; + } + } buf.add(text); if (field.isSharp) { buf.add("\n"); diff --git a/src/refactor/rename/RenameHelper.hx b/src/refactor/rename/RenameHelper.hx index 48cc2f1..4f93a2d 100644 --- a/src/refactor/rename/RenameHelper.hx +++ b/src/refactor/rename/RenameHelper.hx @@ -113,6 +113,18 @@ class RenameHelper { } name = name.substr(0, index); + function addMatchToChangelist(type:Type) { + if (!types.contains(type)) { + return; + } + var pos:IdentifierPos = { + fileName: use.pos.fileName, + start: use.pos.start + name.length + 1, + end: use.pos.end + }; + pos.end = pos.start + fromName.length; + changelist.addChange(use.pos.fileName, ReplaceText(context.what.toName, pos, false), use); + } var search:SearchTypeOf = { name: name, pos: use.pos.start, @@ -122,20 +134,21 @@ class RenameHelper { switch (typeHint) { case null: case ClasspathType(type, _): - for (t in types) { - if (t != type) { - continue; - } - var pos:IdentifierPos = { - fileName: use.pos.fileName, - start: use.pos.start + name.length + 1, - end: use.pos.end - }; - pos.end = pos.start + fromName.length; - changelist.addChange(use.pos.fileName, ReplaceText(context.what.toName, pos, false), use); + addMatchToChangelist(type); + case LibType(name, fullName, params): + if (name != "Null") { + return; } - case LibType(_, _) | UnknownType(_): - case FunctionType(_, _) | StructType(_): + if (params == null || params.length != 1) { + return; + } + switch (params[0]) { + case ClasspathType(type, _): + addMatchToChangelist(type); + case LibType(_) | FunctionType(_) | StructType(_) | UnknownType(_): + } + + case UnknownType(_) | FunctionType(_, _) | StructType(_): } }); } diff --git a/src/refactor/typing/TypingHelper.hx b/src/refactor/typing/TypingHelper.hx index 5d5d28a..5c74bc8 100644 --- a/src/refactor/typing/TypingHelper.hx +++ b/src/refactor/typing/TypingHelper.hx @@ -164,7 +164,18 @@ class TypingHelper { case ClasspathType(t, params): return findField(context, t, part).then(findFieldForPart); case LibType(t, fullPath, params): - return Promise.reject('unable to determine type of "$part" in ${searchTypeOf.defineType.name.name}@${searchTypeOf.pos}'); + if (t != "Null") { + return Promise.reject('unable to determine type of "$part" in ${searchTypeOf.defineType.name.name}@${searchTypeOf.pos}'); + } + if (params == null || params.length != 1) { + return Promise.reject('unable to determine type of "$part" in ${searchTypeOf.defineType.name.name}@${searchTypeOf.pos}'); + } + switch (params[0]) { + case ClasspathType(t, typeParams): + return findField(context, t, part).then(findFieldForPart); + case LibType(_) | FunctionType(_) | StructType(_) | UnknownType(_): + return Promise.reject('unable to determine type of "$part" in ${searchTypeOf.defineType.name.name}@${searchTypeOf.pos}'); + } case StructType(fields): return Promise.reject('unable to determine type of "$part" in ${searchTypeOf.defineType.name.name}@${searchTypeOf.pos}'); case FunctionType(args, retVal): @@ -363,7 +374,9 @@ class TypingHelper { if ((hint.uses == null) || (hint.uses.length <= 0)) { return Promise.reject(); } - return typeFromTypeHint(context, hint.uses[0]); + return typeFromTypeHint(context, hint.uses[0]).then(function(typeHint) { + return Promise.resolve(LibType("Null", "Null", [typeHint])); + }); } var parts:Array = hint.name.split("."); diff --git a/test/refactor/refactor/RefactorExtractMethodTest.hx b/test/refactor/refactor/RefactorExtractMethodTest.hx index cb50996..d297513 100644 --- a/test/refactor/refactor/RefactorExtractMethodTest.hx +++ b/test/refactor/refactor/RefactorExtractMethodTest.hx @@ -209,6 +209,64 @@ class RefactorExtractMethodTest extends RefactorTestBase { checkRefactor(RefactorExtractMethod, {fileName: "testcases/methods/Main.hx", posStart: 1365, posEnd: 1543}, edits, async); } + function testDemoSimple(async:Async) { + var edits:Array = [ + makeReplaceTestEdit("testcases/methods/Demo.hx", "doSomethingExtract();\n", 161, 252, true), + makeInsertTestEdit("testcases/methods/Demo.hx", + "function doSomethingExtract() {\n" + + "doNothing();\n" + + " trace(\"I'm here\");\n" + + " doNothing();\n" + + " trace(\"yep, still here\");\n" + + " doNothing();\n" + + "}\n", + 467, true), + ]; + checkRefactor(RefactorExtractMethod, {fileName: "testcases/methods/Demo.hx", posStart: 160, posEnd: 252}, edits, async); + } + + function testDemoSwitch(async:Async) { + var edits:Array = [ + makeReplaceTestEdit("testcases/methods/Demo.hx", "doSomethingExtract(cond, val, text);\n", 262, 463, true), + makeInsertTestEdit("testcases/methods/Demo.hx", + "function doSomethingExtract(cond:Bool, val:Int, text:Null) {\n" + + "return switch [cond, val] {\n" + + " case [true, 0]:\n" + + " std.Math.random() * val;\n" + + " case [true, _]:\n" + + " val + val;\n" + + " case [false, 10]:\n" + + " Std.parseFloat(text);\n" + + " case [_, _]:\n" + + " std.Math.NEGATIVE_INFINITY;\n" + + " }\n" + + "}\n", + 467, true), + ]; + checkRefactor(RefactorExtractMethod, {fileName: "testcases/methods/Demo.hx", posStart: 262, posEnd: 463}, edits, async); + } + + function testDemoReturnSwitch(async:Async) { + var edits:Array = [ + makeReplaceTestEdit("testcases/methods/Demo.hx", "return doSomethingExtract(cond, val, text);\n", 255, 463, true), + makeInsertTestEdit("testcases/methods/Demo.hx", + "function doSomethingExtract(cond:Bool, val:Int, text:Null) {\n" + + "return switch [cond, val] {\n" + + " case [true, 0]:\n" + + " std.Math.random() * val;\n" + + " case [true, _]:\n" + + " val + val;\n" + + " case [false, 10]:\n" + + " Std.parseFloat(text);\n" + + " case [_, _]:\n" + + " std.Math.NEGATIVE_INFINITY;\n" + + " }\n" + + "}\n", + 467, true), + ]; + checkRefactor(RefactorExtractMethod, {fileName: "testcases/methods/Demo.hx", posStart: 254, posEnd: 463}, edits, async); + } + function testCalculateMath(async:Async) { var edits:Array = [ makeReplaceTestEdit("testcases/methods/Math.hx", "calculateExtract(a, b);\n", 106, 122, true), From e943f0cc1a456f74f9720939388ef9418fe891cd Mon Sep 17 00:00:00 2001 From: AlexHaxe Date: Mon, 25 Nov 2024 20:23:29 +0100 Subject: [PATCH 17/59] fixed failing tests added testcases --- .../refactor/RefactorExtractMethodTest.hx | 51 +++++++++++++++++++ test/refactor/refactor/RefactorTestBase.hx | 6 ++- test/refactor/rename/RenameClassTest.hx | 18 +++---- test/refactor/rename/RenameTestBase.hx | 6 ++- test/refactor/rename/RenameTypedefTest.hx | 2 +- 5 files changed, 69 insertions(+), 14 deletions(-) diff --git a/test/refactor/refactor/RefactorExtractMethodTest.hx b/test/refactor/refactor/RefactorExtractMethodTest.hx index d297513..b6fc70a 100644 --- a/test/refactor/refactor/RefactorExtractMethodTest.hx +++ b/test/refactor/refactor/RefactorExtractMethodTest.hx @@ -267,6 +267,57 @@ class RefactorExtractMethodTest extends RefactorTestBase { checkRefactor(RefactorExtractMethod, {fileName: "testcases/methods/Demo.hx", posStart: 254, posEnd: 463}, edits, async); } + function testDemoCodeAndSwitch(async:Async) { + var edits:Array = [ + makeReplaceTestEdit("testcases/methods/Demo.hx", "return doSomethingExtract(cond, val, text);\n", 161, 463, true), + makeInsertTestEdit("testcases/methods/Demo.hx", + "function doSomethingExtract(cond:Bool, val:Int, text:Null) {\n" + + "doNothing();\n" + + " trace(\"I'm here\");\n" + + " doNothing();\n" + + " trace(\"yep, still here\");\n" + + " doNothing();\n" + + " return switch [cond, val] {\n" + + " case [true, 0]:\n" + + " std.Math.random() * val;\n" + + " case [true, _]:\n" + + " val + val;\n" + + " case [false, 10]:\n" + + " Std.parseFloat(text);\n" + + " case [_, _]:\n" + + " std.Math.NEGATIVE_INFINITY;\n" + + " }\n" + + "}\n", + 467, true), + ]; + checkRefactor(RefactorExtractMethod, {fileName: "testcases/methods/Demo.hx", posStart: 160, posEnd: 463}, edits, async); + } + + function testDemoConditionAndCode(async:Async) { + var edits:Array = [ + makeReplaceTestEdit("testcases/methods/Demo.hx", + "switch (doSomethingExtract(cond, text)) {\n" + + "case Some(data):\n" + + "return data;\n" + + "case None:\n" + + "}\n", 112, 252, true), + makeInsertTestEdit("testcases/methods/Demo.hx", + "function doSomethingExtract(cond:Bool, text:Null) {\n" + + "if (cond && text == null) {\n" + + " return Some(0.0);\n" + + " }\n" + + " doNothing();\n" + + " trace(\"I'm here\");\n" + + " doNothing();\n" + + " trace(\"yep, still here\");\n" + + " doNothing();\n" + + "return None;\n" + + "}\n", + 467, true), + ]; + checkRefactor(RefactorExtractMethod, {fileName: "testcases/methods/Demo.hx", posStart: 111, posEnd: 252}, edits, async); + } + function testCalculateMath(async:Async) { var edits:Array = [ makeReplaceTestEdit("testcases/methods/Math.hx", "calculateExtract(a, b);\n", 106, 122, true), diff --git a/test/refactor/refactor/RefactorTestBase.hx b/test/refactor/refactor/RefactorTestBase.hx index 83aca25..df11f4a 100644 --- a/test/refactor/refactor/RefactorTestBase.hx +++ b/test/refactor/refactor/RefactorTestBase.hx @@ -20,14 +20,16 @@ class RefactorTestBase extends TestBase { } } - function failCanRefactor(refactorType:RefactorType, what:RefactorWhat, expected:String, async:Async, ?pos:PosInfos) { + function failCanRefactor(refactorType:RefactorType, what:RefactorWhat, expected:String, ?async:Async, ?pos:PosInfos) { try { doCanRefactor(refactorType, what, [], pos).then(function(success:RefactorResult) { Assert.equals(expected, PrintHelper.printRefactorResult(success), pos); }).catchError(function(failure) { Assert.equals(expected, '$failure', pos); }).finally(function() { - async.done(); + if (async != null) { + async.done(); + } }); } catch (e:Exception) { Assert.fail(e.toString(), pos); diff --git a/test/refactor/rename/RenameClassTest.hx b/test/refactor/rename/RenameClassTest.hx index 28c7602..a465df8 100644 --- a/test/refactor/rename/RenameClassTest.hx +++ b/test/refactor/rename/RenameClassTest.hx @@ -212,40 +212,40 @@ class RenameClassTest extends RenameTestBase { public function testRenameBaseClassParamterWithShadowLocalVar(async:Async) { var edits:Array = []; - failCanRename({fileName: "testcases/classes/BaseClass.hx", toName: "data", pos: 298}, 'local var "data" exists', async); + failCanRename({fileName: "testcases/classes/BaseClass.hx", toName: "data", pos: 298}, 'local var "data" exists'); failRename({fileName: "testcases/classes/BaseClass.hx", toName: "data", pos: 298}, 'local var "data" exists', async); } public function testRenameBaseClassCaseLabel(async:Async) { var edits:Array = []; failCanRename({fileName: "testcases/classes/BaseClass.hx", toName: "data", pos: 516}, - "renaming not supported for Case1 testcases/classes/BaseClass.hx@514-519 (CaseLabel(val))", async); + "renaming not supported for Case1 testcases/classes/BaseClass.hx@514-519 (CaseLabel(val))"); failRename({fileName: "testcases/classes/BaseClass.hx", toName: "data", pos: 516}, "renaming not supported for Case1 testcases/classes/BaseClass.hx@514-519 (CaseLabel(val))", async); } public function testRenameUseChildClassParentSubPart(async:Async) { var edits:Array = []; - failCanRename({fileName: "testcases/classes/UseChild.hx", toName: "data", pos: 222}, "could not find identifier to rename", async); + failCanRename({fileName: "testcases/classes/UseChild.hx", toName: "data", pos: 222}, "could not find identifier to rename"); failRename({fileName: "testcases/classes/UseChild.hx", toName: "data", pos: 222}, "could not find identifier to rename", async); } public function testRenameBaseClassDataToData(async:Async) { var edits:Array = []; - failCanRename({fileName: "testcases/classes/BaseClass.hx", toName: "data", pos: 43}, "could not find identifier to rename", async); + failCanRename({fileName: "testcases/classes/BaseClass.hx", toName: "data", pos: 43}, "could not find identifier to rename"); failRename({fileName: "testcases/classes/BaseClass.hx", toName: "data", pos: 43}, "could not find identifier to rename", async); } public function testRenameBaseClassNoIdentifier(async:Async) { var edits:Array = []; - failCanRename({fileName: "testcases/classes/BaseClass.hx", toName: "data", pos: 103}, "could not find identifier to rename", async); + failCanRename({fileName: "testcases/classes/BaseClass.hx", toName: "data", pos: 103}, "could not find identifier to rename"); failRename({fileName: "testcases/classes/BaseClass.hx", toName: "data", pos: 103}, "could not find identifier to rename", async); } public function testRenameStaticUsingConstructorCall(async:Async) { var edits:Array = []; failCanRename({fileName: "testcases/classes/StaticUsing.hx", toName: "NewChildClass", pos: 359}, - "renaming not supported for ChildClass testcases/classes/StaticUsing.hx@355-365 (Call(true))", async); + "renaming not supported for ChildClass testcases/classes/StaticUsing.hx@355-365 (Call(true))"); failRename({fileName: "testcases/classes/StaticUsing.hx", toName: "NewChildClass", pos: 359}, "renaming not supported for ChildClass testcases/classes/StaticUsing.hx@355-365 (Call(true))", async); } @@ -260,14 +260,14 @@ class RenameClassTest extends RenameTestBase { public function testRenameBaseClassParamterWithShadowCase(async:Async) { var edits:Array = []; - failCanRename({fileName: "testcases/classes/BaseClass.hx", toName: "data", pos: 470}, 'local var "data" exists', async); + failCanRename({fileName: "testcases/classes/BaseClass.hx", toName: "data", pos: 470}, 'local var "data" exists'); failRename({fileName: "testcases/classes/BaseClass.hx", toName: "data", pos: 470}, 'local var "data" exists', async); } public function testRenameChildClassExtendsBaseClass(async:Async) { var edits:Array = []; failCanRename({fileName: "testcases/classes/ChildClass.hx", toName: "parentBase", pos: 47}, - "renaming not supported for BaseClass testcases/classes/ChildClass.hx@43-52 (Extends)", async); + "renaming not supported for BaseClass testcases/classes/ChildClass.hx@43-52 (Extends)"); failRename({fileName: "testcases/classes/ChildClass.hx", toName: "parentBase", pos: 47}, "renaming not supported for BaseClass testcases/classes/ChildClass.hx@43-52 (Extends)", async); } @@ -275,7 +275,7 @@ class RenameClassTest extends RenameTestBase { public function testRenameImport(async:Async) { var edits:Array = []; failCanRename({fileName: "testcases/classes/MyIdentifier.hx", toName: "refactor.Foo", pos: 44}, - "renaming not supported for refactor.discover.File testcases/classes/MyIdentifier.hx@25-47 (ImportModul)", async); + "renaming not supported for refactor.discover.File testcases/classes/MyIdentifier.hx@25-47 (ImportModul)"); failRename({fileName: "testcases/classes/MyIdentifier.hx", toName: "refactor.Foo", pos: 44}, "renaming not supported for refactor.discover.File testcases/classes/MyIdentifier.hx@25-47 (ImportModul)", async); } diff --git a/test/refactor/rename/RenameTestBase.hx b/test/refactor/rename/RenameTestBase.hx index 7085f6d..d7c8771 100644 --- a/test/refactor/rename/RenameTestBase.hx +++ b/test/refactor/rename/RenameTestBase.hx @@ -21,14 +21,16 @@ class RenameTestBase extends TestBase { } } - function failCanRename(what:RenameWhat, expected:String, async:Async, withTyper:Bool = false, ?pos:PosInfos) { + function failCanRename(what:RenameWhat, expected:String, ?async:Async, withTyper:Bool = false, ?pos:PosInfos) { try { doCanRename(what, [], withTyper, pos).then(function(success:RefactorResult) { Assert.equals(expected, PrintHelper.printRefactorResult(success), pos); }).catchError(function(failure) { Assert.equals(expected, '$failure', pos); }).finally(function() { - async.done(); + if (async != null) { + async.done(); + } }); } catch (e:Exception) { Assert.fail(e.toString(), pos); diff --git a/test/refactor/rename/RenameTypedefTest.hx b/test/refactor/rename/RenameTypedefTest.hx index e05bbab..33a5094 100644 --- a/test/refactor/rename/RenameTypedefTest.hx +++ b/test/refactor/rename/RenameTypedefTest.hx @@ -73,7 +73,7 @@ class RenameTypedefTest extends RenameTestBase { public function testRenameTypedefBase(async:Async) { var edits:Array = []; failCanRename({fileName: "testcases/typedefs/Types.hx", toName: "Position", pos: 172}, - "renaming not supported for IdentifierPos testcases/typedefs/Types.hx@166-179 (TypedefBase)", async); + "renaming not supported for IdentifierPos testcases/typedefs/Types.hx@166-179 (TypedefBase)"); failRename({fileName: "testcases/typedefs/Types.hx", toName: "Position", pos: 172}, "renaming not supported for IdentifierPos testcases/typedefs/Types.hx@166-179 (TypedefBase)", async); } From 109c048ec5c661aca82425053c7e147a5f7a05e0 Mon Sep 17 00:00:00 2001 From: AlexHaxe Date: Mon, 25 Nov 2024 20:24:54 +0100 Subject: [PATCH 18/59] fixed missing testcase file --- testcases/methods/Demo.hx | 26 ++++++++++++++++++++++++++ 1 file changed, 26 insertions(+) create mode 100644 testcases/methods/Demo.hx diff --git a/testcases/methods/Demo.hx b/testcases/methods/Demo.hx new file mode 100644 index 0000000..e37b03f --- /dev/null +++ b/testcases/methods/Demo.hx @@ -0,0 +1,26 @@ +package testcases.methods; + +class Demo { + function doSomething(cond:Bool, val:Int, text:Null):Float { + if (cond && text == null) { + return 0.0; + } + doNothing(); + trace("I'm here"); + doNothing(); + trace("yep, still here"); + doNothing(); + return switch [cond, val] { + case [true, 0]: + std.Math.random() * val; + case [true, _]: + val + val; + case [false, 10]: + Std.parseFloat(text); + case [_, _]: + std.Math.NEGATIVE_INFINITY; + } + } + + function doNothing() {} +} From d327396ebc3ed65699311bda3359012fdcbce772 Mon Sep 17 00:00:00 2001 From: AlexHaxe Date: Mon, 25 Nov 2024 20:37:36 +0100 Subject: [PATCH 19/59] fixed Demo testcases --- test/refactor/refactor/RefactorExtractMethodTest.hx | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/test/refactor/refactor/RefactorExtractMethodTest.hx b/test/refactor/refactor/RefactorExtractMethodTest.hx index b6fc70a..7bc797e 100644 --- a/test/refactor/refactor/RefactorExtractMethodTest.hx +++ b/test/refactor/refactor/RefactorExtractMethodTest.hx @@ -229,7 +229,7 @@ class RefactorExtractMethodTest extends RefactorTestBase { var edits:Array = [ makeReplaceTestEdit("testcases/methods/Demo.hx", "doSomethingExtract(cond, val, text);\n", 262, 463, true), makeInsertTestEdit("testcases/methods/Demo.hx", - "function doSomethingExtract(cond:Bool, val:Int, text:Null) {\n" + "function doSomethingExtract(cond:Bool, val:Int, text:Null):Float {\n" + "return switch [cond, val] {\n" + " case [true, 0]:\n" + " std.Math.random() * val;\n" @@ -243,6 +243,7 @@ class RefactorExtractMethodTest extends RefactorTestBase { + "}\n", 467, true), ]; + addTypeHint("testcases/methods/Demo.hx", 61, LibType("Float", "Float", [])); checkRefactor(RefactorExtractMethod, {fileName: "testcases/methods/Demo.hx", posStart: 262, posEnd: 463}, edits, async); } @@ -250,7 +251,7 @@ class RefactorExtractMethodTest extends RefactorTestBase { var edits:Array = [ makeReplaceTestEdit("testcases/methods/Demo.hx", "return doSomethingExtract(cond, val, text);\n", 255, 463, true), makeInsertTestEdit("testcases/methods/Demo.hx", - "function doSomethingExtract(cond:Bool, val:Int, text:Null) {\n" + "function doSomethingExtract(cond:Bool, val:Int, text:Null):Float {\n" + "return switch [cond, val] {\n" + " case [true, 0]:\n" + " std.Math.random() * val;\n" @@ -264,6 +265,7 @@ class RefactorExtractMethodTest extends RefactorTestBase { + "}\n", 467, true), ]; + addTypeHint("testcases/methods/Demo.hx", 61, LibType("Float", "Float", [])); checkRefactor(RefactorExtractMethod, {fileName: "testcases/methods/Demo.hx", posStart: 254, posEnd: 463}, edits, async); } @@ -271,7 +273,7 @@ class RefactorExtractMethodTest extends RefactorTestBase { var edits:Array = [ makeReplaceTestEdit("testcases/methods/Demo.hx", "return doSomethingExtract(cond, val, text);\n", 161, 463, true), makeInsertTestEdit("testcases/methods/Demo.hx", - "function doSomethingExtract(cond:Bool, val:Int, text:Null) {\n" + "function doSomethingExtract(cond:Bool, val:Int, text:Null):Float {\n" + "doNothing();\n" + " trace(\"I'm here\");\n" + " doNothing();\n" @@ -290,6 +292,7 @@ class RefactorExtractMethodTest extends RefactorTestBase { + "}\n", 467, true), ]; + addTypeHint("testcases/methods/Demo.hx", 61, LibType("Float", "Float", [])); checkRefactor(RefactorExtractMethod, {fileName: "testcases/methods/Demo.hx", posStart: 160, posEnd: 463}, edits, async); } @@ -302,7 +305,7 @@ class RefactorExtractMethodTest extends RefactorTestBase { + "case None:\n" + "}\n", 112, 252, true), makeInsertTestEdit("testcases/methods/Demo.hx", - "function doSomethingExtract(cond:Bool, text:Null) {\n" + "function doSomethingExtract(cond:Bool, text:Null):haxe.ds.Option {\n" + "if (cond && text == null) {\n" + " return Some(0.0);\n" + " }\n" @@ -315,6 +318,7 @@ class RefactorExtractMethodTest extends RefactorTestBase { + "}\n", 467, true), ]; + addTypeHint("testcases/methods/Demo.hx", 61, LibType("Float", "Float", [])); checkRefactor(RefactorExtractMethod, {fileName: "testcases/methods/Demo.hx", posStart: 111, posEnd: 252}, edits, async); } From 94e8203205372d18a7c9f742641cf680ff5788e2 Mon Sep 17 00:00:00 2001 From: AlexHaxe Date: Mon, 25 Nov 2024 23:51:21 +0100 Subject: [PATCH 20/59] fixed extracting from functions with type parameters added testcase --- src/refactor/refactor/ExtractMethod.hx | 16 ++++++++++++++- .../refactor/RefactorExtractMethodTest.hx | 17 +++++++++++++++- testcases/methods/TypeProcessor.hx | 20 +++++++++++++++++++ 3 files changed, 51 insertions(+), 2 deletions(-) create mode 100644 testcases/methods/TypeProcessor.hx diff --git a/src/refactor/refactor/ExtractMethod.hx b/src/refactor/refactor/ExtractMethod.hx index 116a6fe..02f049d 100644 --- a/src/refactor/refactor/ExtractMethod.hx +++ b/src/refactor/refactor/ExtractMethod.hx @@ -71,7 +71,8 @@ class ExtractMethod { // insert new method with function signature and body after current function final staticModifier = extractData.isStatic ? "static " : ""; - final functionDefinition:String = '${staticModifier}function ${extractData.newMethodName}($parameterList)$returnTypeHint'; + final functionName = makeFunctionName(extractData, context); + final functionDefinition:String = '${staticModifier}function $functionName($parameterList)$returnTypeHint'; final body:String = codeGen.makeBody(); changelist.addChange(context.what.fileName, @@ -83,6 +84,19 @@ class ExtractMethod { }); } + static function makeFunctionName(extractData:ExtractMethodData, context:RefactorContext):String { + final functionName = extractData.functionToken.access().firstChild().token; + if (functionName == null) { + return extractData.newMethodName; + } + final functionTypeParameter = functionName.access().firstOf(Binop(OpLt)).token; + if (functionTypeParameter == null) { + return extractData.newMethodName; + } + final pos = functionTypeParameter.getPos(); + return extractData.newMethodName + RefactorHelper.extractText(context.converter, extractData.content, pos.min, pos.max); + } + static function makeExtractMethodData(context:CanRefactorContext):Null { final fileContent = context.fileReader(context.what.fileName); var content:String; diff --git a/test/refactor/refactor/RefactorExtractMethodTest.hx b/test/refactor/refactor/RefactorExtractMethodTest.hx index 7bc797e..766d517 100644 --- a/test/refactor/refactor/RefactorExtractMethodTest.hx +++ b/test/refactor/refactor/RefactorExtractMethodTest.hx @@ -435,7 +435,7 @@ class RefactorExtractMethodTest extends RefactorTestBase { var edits:Array = [ makeReplaceTestEdit("testcases/methods/Container.hx", "var result = processExtract(items, converter);\n", 108, 239, true), makeInsertTestEdit("testcases/methods/Container.hx", - "function processExtract(items:Array, converter:T -> String):Array {\n" + "function processExtract(items:Array, converter:T -> String):Array {\n" + "var result = new Array();\n" + " for (item in items) {\n" + " var converted = converter(item);\n" @@ -506,4 +506,19 @@ class RefactorExtractMethodTest extends RefactorTestBase { ]; checkRefactor(RefactorExtractMethod, {fileName: "testcases/methods/Matcher.hx", posStart: 76, posEnd: 284}, edits, async); } + + function testTypeProcessor(async:Async) { + var edits:Array = [ + makeReplaceTestEdit("testcases/methods/TypeProcessor.hx", "processExtract(item, compare);\n", 114, 221, true), + makeInsertTestEdit("testcases/methods/TypeProcessor.hx", + "function processExtract(item:T, compare:U) {\n" + + "var result = item.process();\n" + + " if (compare.compareTo(result) > 0) {\n" + + " item.update(compare.getValue());\n" + + " }\n" + + "}\n", + 225, true), + ]; + checkRefactor(RefactorExtractMethod, {fileName: "testcases/methods/TypeProcessor.hx", posStart: 113, posEnd: 221}, edits, async); + } } diff --git a/testcases/methods/TypeProcessor.hx b/testcases/methods/TypeProcessor.hx new file mode 100644 index 0000000..c856cee --- /dev/null +++ b/testcases/methods/TypeProcessor.hx @@ -0,0 +1,20 @@ +package testcases.methods; + +class TypeProcessor { + function process(item:T, compare:U) { + var result = item.process(); + if (compare.compareTo(result) > 0) { + item.update(compare.getValue()); + } + } +} + +interface Base { + function process():Float; + function update(value:Float):Void; +} + +interface IComparable { + function compareTo(value:Float):Float; + function getValue():Float; +} From 3087cd23799389d0240ffb8a17abc5b221302be0 Mon Sep 17 00:00:00 2001 From: AlexHaxe Date: Tue, 26 Nov 2024 00:05:11 +0100 Subject: [PATCH 21/59] added testcase --- .../refactor/RefactorExtractMethodTest.hx | 18 ++++++++++++++++++ testcases/methods/FunctionProcessor.hx | 11 +++++++++++ 2 files changed, 29 insertions(+) create mode 100644 testcases/methods/FunctionProcessor.hx diff --git a/test/refactor/refactor/RefactorExtractMethodTest.hx b/test/refactor/refactor/RefactorExtractMethodTest.hx index 766d517..3cfe6b1 100644 --- a/test/refactor/refactor/RefactorExtractMethodTest.hx +++ b/test/refactor/refactor/RefactorExtractMethodTest.hx @@ -521,4 +521,22 @@ class RefactorExtractMethodTest extends RefactorTestBase { ]; checkRefactor(RefactorExtractMethod, {fileName: "testcases/methods/TypeProcessor.hx", posStart: 113, posEnd: 221}, edits, async); } + + function testFunctionProcessor(async:Async) { + var edits:Array = [ + makeReplaceTestEdit("testcases/methods/FunctionProcessor.hx", "processExtract(callback, value, text);\n", 143, 211, true), + makeInsertTestEdit("testcases/methods/FunctionProcessor.hx", + "function processExtract(callback:(Int, String) -> Bool, value:Int, text:String) {\n" + + "if (callback(value, text)) {\n" + + " trace('Success: $value, $text');\n" + + " }\n" + + "}\n", + 215, true), + ]; + addTypeHint("testcases/methods/FunctionProcessor.hx", 79, + FunctionType([LibType("Int", "Int", []), LibType("String", "String", [])], LibType("Bool", "Bool", []))); + addTypeHint("testcases/methods/FunctionProcessor.hx", 112, LibType("Int", "Int", [])); + addTypeHint("testcases/methods/FunctionProcessor.hx", 129, LibType("String", "String", [])); + checkRefactor(RefactorExtractMethod, {fileName: "testcases/methods/FunctionProcessor.hx", posStart: 142, posEnd: 211}, edits, async); + } } diff --git a/testcases/methods/FunctionProcessor.hx b/testcases/methods/FunctionProcessor.hx new file mode 100644 index 0000000..d729d2b --- /dev/null +++ b/testcases/methods/FunctionProcessor.hx @@ -0,0 +1,11 @@ +package testcases.methods; + +class FunctionProcessor { + function process(callback:Int->String->Bool) { + var value = 42; + var text = "test"; + if (callback(value, text)) { + trace('Success: $value, $text'); + } + } +} From df26bfadc31a1d1177e61093df51af33426c557b Mon Sep 17 00:00:00 2001 From: AlexHaxe Date: Tue, 26 Nov 2024 01:33:22 +0100 Subject: [PATCH 22/59] added more testcases --- .../refactor/RefactorExtractMethodTest.hx | 120 ++++++++++++++++++ testcases/methods/ArrayTools.hx | 12 ++ testcases/methods/ExceptionHandler.hx | 35 +++++ testcases/methods/MetadataProcessor.hx | 25 ++++ 4 files changed, 192 insertions(+) create mode 100644 testcases/methods/ArrayTools.hx create mode 100644 testcases/methods/ExceptionHandler.hx create mode 100644 testcases/methods/MetadataProcessor.hx diff --git a/test/refactor/refactor/RefactorExtractMethodTest.hx b/test/refactor/refactor/RefactorExtractMethodTest.hx index 3cfe6b1..ade5581 100644 --- a/test/refactor/refactor/RefactorExtractMethodTest.hx +++ b/test/refactor/refactor/RefactorExtractMethodTest.hx @@ -539,4 +539,124 @@ class RefactorExtractMethodTest extends RefactorTestBase { addTypeHint("testcases/methods/FunctionProcessor.hx", 129, LibType("String", "String", [])); checkRefactor(RefactorExtractMethod, {fileName: "testcases/methods/FunctionProcessor.hx", posStart: 142, posEnd: 211}, edits, async); } + + function testArrayTools(async:Async) { + var edits:Array = [ + makeReplaceTestEdit("testcases/methods/ArrayTools.hx", "return processItemsExtract(arr, fn);\n", 117, 231, true), + makeInsertTestEdit("testcases/methods/ArrayTools.hx", + "static function processItemsExtract(arr:Array, fn:T -> Bool):Array {\n" + + "var results = new Array();\n" + + " for (item in arr) {\n" + + " if (fn(item))\n" + + " results.push(item);\n" + + " }\n" + + " return results;\n" + + "}\n", + 235, true), + ]; + addTypeHint("testcases/methods/ArrayTools.hx", 82, LibType("Array", "Array", [LibType("T", "T", [])])); + addTypeHint("testcases/methods/ArrayTools.hx", 102, FunctionType([LibType("T", "T", [])], LibType("Bool", "Bool", []))); + checkRefactor(RefactorExtractMethod, {fileName: "testcases/methods/ArrayTools.hx", posStart: 115, posEnd: 231}, edits, async); + } + + function testExceptionHandlerTry(async:Async) { + var edits:Array = [ + makeReplaceTestEdit("testcases/methods/ExceptionHandler.hx", "processExtract();\n", 86, 152, true), + makeInsertTestEdit("testcases/methods/ExceptionHandler.hx", + "function processExtract() {\n" + + "var data = getData();\n" + + " validateData(data);\n" + + " processData(data);\n" + + "}\n", 258, true), + ]; + checkRefactor(RefactorExtractMethod, {fileName: "testcases/methods/ExceptionHandler.hx", posStart: 85, posEnd: 152}, edits, async); + } + + function testExceptionHandlerCatch(async:Async) { + var edits:Array = [ + makeReplaceTestEdit("testcases/methods/ExceptionHandler.hx", "processExtract();\n", 193, 250, true), + makeInsertTestEdit("testcases/methods/ExceptionHandler.hx", + "function processExtract() {\n" + + "logError(e);\n" + + " throw new ProcessingException(e.message);\n" + + "}\n", 258, true), + ]; + checkRefactor(RefactorExtractMethod, {fileName: "testcases/methods/ExceptionHandler.hx", posStart: 192, posEnd: 250}, edits, async); + } + + function testMetadataProcessor(async:Async) { + var edits:Array = [ + makeReplaceTestEdit("testcases/methods/MetadataProcessor.hx", "processExtract(meta, results);\n", 220, 575, true), + makeInsertTestEdit("testcases/methods/MetadataProcessor.hx", + "function processExtract(meta:Dynamic>>, results:Map>) {\n" + + "for (field in Reflect.fields(meta)) {\n" + + " var fieldMeta = Reflect.field(meta, field);\n" + + " if (Reflect.hasField(fieldMeta, \"meta\")) {\n" + + " var metaValues = Reflect.field(fieldMeta, \"meta\");\n" + + " if (Std.isOfType(metaValues, Array)) {\n" + + " var values = cast(metaValues, Array);\n" + + " results.set(field, [for (v in values) Std.string(v)]);\n" + + " }\n" + + " }\n" + + " }\n" + + "}\n", + 676, true), + ]; + addTypeHint("testcases/methods/MetadataProcessor.hx", 132, LibType("Dynamic", "Dynamic", [ + LibType("Dynamic", "Dynamic", [LibType("Array", "Array", [LibType("Dynamic", "Dynamic", [])])]) + ])); + addTypeHint("testcases/methods/MetadataProcessor.hx", 179, LibType("Map", "Map", [ + LibType("String", "String", []), + LibType("Array", "Array", [LibType("String", "String", [])]) + ])); + checkRefactor(RefactorExtractMethod, {fileName: "testcases/methods/MetadataProcessor.hx", posStart: 219, posEnd: 575}, edits, async); + } + + function testMetadataProcessorWithResults(async:Async) { + var edits:Array = [ + makeReplaceTestEdit("testcases/methods/MetadataProcessor.hx", "var results = processExtract(meta);\n", 169, 575, true), + makeInsertTestEdit("testcases/methods/MetadataProcessor.hx", + "function processExtract(meta:Dynamic>>):Map> {\n" + + "var results = new Map>();\n\n" + + " for (field in Reflect.fields(meta)) {\n" + + " var fieldMeta = Reflect.field(meta, field);\n" + + " if (Reflect.hasField(fieldMeta, \"meta\")) {\n" + + " var metaValues = Reflect.field(fieldMeta, \"meta\");\n" + + " if (Std.isOfType(metaValues, Array)) {\n" + + " var values = cast(metaValues, Array);\n" + + " results.set(field, [for (v in values) Std.string(v)]);\n" + + " }\n" + + " }\n" + + " }\n" + + "return results;\n" + + "}\n", + 676, true), + ]; + addTypeHint("testcases/methods/MetadataProcessor.hx", 132, LibType("Dynamic", "Dynamic", [ + LibType("Dynamic", "Dynamic", [LibType("Array", "Array", [LibType("Dynamic", "Dynamic", [])])]) + ])); + addTypeHint("testcases/methods/MetadataProcessor.hx", 179, LibType("Map", "Map", [ + LibType("String", "String", []), + LibType("Array", "Array", [LibType("String", "String", [])]) + ])); + checkRefactor(RefactorExtractMethod, {fileName: "testcases/methods/MetadataProcessor.hx", posStart: 168, posEnd: 575}, edits, async); + } + + function testMetadataProcessorPrint(async:Async) { + var edits:Array = [ + makeReplaceTestEdit("testcases/methods/MetadataProcessor.hx", "processExtract(results);\n", 579, 672, true), + makeInsertTestEdit("testcases/methods/MetadataProcessor.hx", + "function processExtract(results:Map>) {\n" + + "for (field => values in results) {\n" + + " trace('Field: $field, Meta: ${values.join(\", \")}');\n" + + " }\n" + + "}\n", + 676, true), + ]; + addTypeHint("testcases/methods/MetadataProcessor.hx", 179, LibType("Map", "Map", [ + LibType("String", "String", []), + LibType("Array", "Array", [LibType("String", "String", [])]) + ])); + checkRefactor(RefactorExtractMethod, {fileName: "testcases/methods/MetadataProcessor.hx", posStart: 578, posEnd: 672}, edits, async); + } } diff --git a/testcases/methods/ArrayTools.hx b/testcases/methods/ArrayTools.hx new file mode 100644 index 0000000..d707f5c --- /dev/null +++ b/testcases/methods/ArrayTools.hx @@ -0,0 +1,12 @@ +package testcases.methods; + +class ArrayTools { + public static function processItems(arr:Array, fn:T->Bool) { + var results = new Array(); + for (item in arr) { + if (fn(item)) + results.push(item); + } + return results; + } +} diff --git a/testcases/methods/ExceptionHandler.hx b/testcases/methods/ExceptionHandler.hx new file mode 100644 index 0000000..d483516 --- /dev/null +++ b/testcases/methods/ExceptionHandler.hx @@ -0,0 +1,35 @@ +package testcases.methods; + +class ExceptionHandler { + function process() { + try { + var data = getData(); + validateData(data); + processData(data); + } catch (e:InvalidDataException) { + logError(e); + throw new ProcessingException(e.message); + } + } + + function getData():DataType { + return {text: "test", value: 100}; + } + + function validateData(data:DataType):Void {} + + function processData(data:DataType):Void {} + + function logError(e:InvalidDataException):Void {} +} + +typedef DataType = { + var text:String; + var value:Int; +}; + +typedef InvalidDataException = haxe.Exception; + +class ProcessingException { + public function new(message:String) {} +} diff --git a/testcases/methods/MetadataProcessor.hx b/testcases/methods/MetadataProcessor.hx new file mode 100644 index 0000000..a8355e8 --- /dev/null +++ b/testcases/methods/MetadataProcessor.hx @@ -0,0 +1,25 @@ +package testcases.methods; + +class MetadataProcessor { + @:meta(test) + function process() { + var cls = Type.getClass(this); + var meta = haxe.rtti.Meta.getFields(cls); + var results = new Map>(); + + for (field in Reflect.fields(meta)) { + var fieldMeta = Reflect.field(meta, field); + if (Reflect.hasField(fieldMeta, "meta")) { + var metaValues = Reflect.field(fieldMeta, "meta"); + if (Std.isOfType(metaValues, Array)) { + var values = cast(metaValues, Array); + results.set(field, [for (v in values) Std.string(v)]); + } + } + } + + for (field => values in results) { + trace('Field: $field, Meta: ${values.join(", ")}'); + } + } +} From 44ab547e595b406281e9689e88105b069f6d1500 Mon Sep 17 00:00:00 2001 From: AlexHaxe Date: Tue, 26 Nov 2024 13:18:26 +0100 Subject: [PATCH 23/59] fixed code gen for empty return or throw as last expressions of selection --- src/refactor/refactor/ExtractMethod.hx | 45 +++++++---- .../refactor/extractmethod/CodeGenNoReturn.hx | 2 +- .../extractmethod/CodeGenReturnIsLast.hx | 15 +++- .../refactor/RefactorExtractMethodTest.hx | 77 +++++++++++++++---- testcases/methods/ExceptionHandler.hx | 21 +++++ testcases/methods/Main.hx | 14 ++++ 6 files changed, 144 insertions(+), 30 deletions(-) diff --git a/src/refactor/refactor/ExtractMethod.hx b/src/refactor/refactor/ExtractMethod.hx index 02f049d..8b48517 100644 --- a/src/refactor/refactor/ExtractMethod.hx +++ b/src/refactor/refactor/ExtractMethod.hx @@ -368,6 +368,7 @@ class ExtractMethod { final leakingVars = findAdditionalScopedVars(extractData, context, functionIdentifier, neededIdentifiers); + final allThrows:Array = []; final allReturns = parent.filterCallback(function(token:TokenTree, index:Int):FilterResult { if (token.pos.max < extractData.startToken.pos.min) { return GoDeeper; @@ -380,6 +381,8 @@ class ExtractMethod { return SkipSubtree; case Kwd(KwdReturn): return FoundSkipSubtree; + case Kwd(KwdThrow): + allThrows.push(token); case Const(CIdent(s)): var child = token.getFirstChild(); if (child != null) { @@ -395,10 +398,18 @@ class ExtractMethod { } return GoDeeper; }); + var returnIsEmpty:Bool = allReturns.length == 0; if (allReturns.length > 0) { var lastReturn = allReturns[allReturns.length - 1]; + returnIsEmpty = isReturnEmpty(lastReturn); if (isSingleExpression(lastReturn, extractData.endToken)) { - return new CodeGenReturnIsLast(extractData, context, neededIdentifiers); + return new CodeGenReturnIsLast(extractData, context, neededIdentifiers, returnIsEmpty); + } + } + if (allThrows.length > 0) { + var lastThrow = allThrows[allThrows.length - 1]; + if (isSingleExpression(lastThrow, extractData.endToken)) { + return new CodeGenReturnIsLast(extractData, context, neededIdentifiers, returnIsEmpty); } } @@ -413,19 +424,27 @@ class ExtractMethod { if (allReturns.length == 0) { return new CodeGenNoReturn(extractData, context, neededIdentifiers, modifiedIdentifiers, leakingVars); } - for (ret in allReturns) { - var child = ret.getFirstChild(); - if (child == null) { - return null; - } - switch (child.tok) { - case Semicolon: - return new CodeGenEmptyReturn(extractData, context, neededIdentifiers, allReturns, modifiedIdentifiers, leakingVars); - default: - return new CodeGenOpenEnded(extractData, context, neededIdentifiers, allReturns, modifiedIdentifiers, leakingVars); - } + if (returnIsEmpty) { + return new CodeGenEmptyReturn(extractData, context, neededIdentifiers, allReturns, modifiedIdentifiers, leakingVars); + } else { + return new CodeGenOpenEnded(extractData, context, neededIdentifiers, allReturns, modifiedIdentifiers, leakingVars); + } + } + + static function isReturnEmpty(token:TokenTree):Bool { + if (!token.matches(Kwd(KwdReturn))) { + return false; + } + var child = token.getFirstChild(); + if (child == null) { + return false; + } + return switch (child.tok) { + case Semicolon: + true; + default: + false; } - return null; } static function findAdditionalScopedVars(extractData:ExtractMethodData, context:RefactorContext, functionIdentifier:Identifier, diff --git a/src/refactor/refactor/extractmethod/CodeGenNoReturn.hx b/src/refactor/refactor/extractmethod/CodeGenNoReturn.hx index 92c1840..157126e 100644 --- a/src/refactor/refactor/extractmethod/CodeGenNoReturn.hx +++ b/src/refactor/refactor/extractmethod/CodeGenNoReturn.hx @@ -35,7 +35,7 @@ class CodeGenNoReturn extends CodeGenBase { public function makeReturnTypeHint():Promise { return switch [assignments.length, vars.length] { case [0, 0]: - return Promise.resolve(""); + return Promise.resolve(":Void"); case [0, 1]: return findTypeOfIdentifier(vars[0]).then(function(typeHint):Promise { if (typeHint == null) { diff --git a/src/refactor/refactor/extractmethod/CodeGenReturnIsLast.hx b/src/refactor/refactor/extractmethod/CodeGenReturnIsLast.hx index 65f06c1..bd4c82a 100644 --- a/src/refactor/refactor/extractmethod/CodeGenReturnIsLast.hx +++ b/src/refactor/refactor/extractmethod/CodeGenReturnIsLast.hx @@ -3,18 +3,27 @@ package refactor.refactor.extractmethod; import refactor.discover.Identifier; class CodeGenReturnIsLast extends CodeGenBase { - public function new(extractData:ExtractMethodData, context:RefactorContext, neededIdentifiers:Array) { + final returnEmpty:Bool; + + public function new(extractData:ExtractMethodData, context:RefactorContext, neededIdentifiers:Array, returnEmpty:Bool) { super(extractData, context, neededIdentifiers); + this.returnEmpty = returnEmpty; } public function makeCallSite():String { final callParams:String = neededIdentifiers.map(i -> i.name).join(", "); final call = '${extractData.newMethodName}($callParams)'; - - return 'return $call;\n'; + if (returnEmpty) { + return '$call;\n'; + } else { + return 'return $call;\n'; + } } public function makeReturnTypeHint():Promise { + if (returnEmpty) { + return Promise.resolve(":Void"); + } return parentTypeHint().then(function(typeHint):Promise { if (typeHint == null) { return Promise.resolve(""); diff --git a/test/refactor/refactor/RefactorExtractMethodTest.hx b/test/refactor/refactor/RefactorExtractMethodTest.hx index ade5581..8811266 100644 --- a/test/refactor/refactor/RefactorExtractMethodTest.hx +++ b/test/refactor/refactor/RefactorExtractMethodTest.hx @@ -8,11 +8,11 @@ class RefactorExtractMethodTest extends RefactorTestBase { function testSimpleNoReturns(async:Async) { var edits:Array = [ makeReplaceTestEdit("testcases/methods/Main.hx", "noReturnsExtract();\n", 94, 131, true), - makeInsertTestEdit("testcases/methods/Main.hx", "function noReturnsExtract() {\n" + makeInsertTestEdit("testcases/methods/Main.hx", + "function noReturnsExtract():Void {\n" + "trace(\"hello 2\");\n" + " trace(\"hello 3\");\n" - + "}\n", - 155, true), + + "}\n", 155, true), ]; checkRefactor(RefactorExtractMethod, {fileName: "testcases/methods/Main.hx", posStart: 93, posEnd: 131}, edits, async); } @@ -21,7 +21,7 @@ class RefactorExtractMethodTest extends RefactorTestBase { var edits:Array = [ makeReplaceTestEdit("testcases/methods/Main.hx", "noReturnsStaticExtract();\n", 222, 259, true), makeInsertTestEdit("testcases/methods/Main.hx", - "static function noReturnsStaticExtract() {\n" + "static function noReturnsStaticExtract():Void {\n" + "trace(\"hello 2\");\n" + " trace(\"hello 3\");\n" + "}\n", 283, true), @@ -209,11 +209,27 @@ class RefactorExtractMethodTest extends RefactorTestBase { checkRefactor(RefactorExtractMethod, {fileName: "testcases/methods/Main.hx", posStart: 1365, posEnd: 1543}, edits, async); } + function testAllEmptyReturns(async:Async) { + var edits:Array = [ + makeReplaceTestEdit("testcases/methods/Main.hx", "allEmptyReturnsExtract();\n", 1722, 1809, true), + makeInsertTestEdit("testcases/methods/Main.hx", + "function allEmptyReturnsExtract():Void {\n" + + "trace(\"hello 1\");\n" + + " trace(\"hello 2\");\n" + + " trace(\"hello 3\");\n" + + " trace(\"hello 4\");\n" + + " return;\n" + + "}\n", + 1813, true), + ]; + checkRefactor(RefactorExtractMethod, {fileName: "testcases/methods/Main.hx", posStart: 1721, posEnd: 1809}, edits, async); + } + function testDemoSimple(async:Async) { var edits:Array = [ makeReplaceTestEdit("testcases/methods/Demo.hx", "doSomethingExtract();\n", 161, 252, true), makeInsertTestEdit("testcases/methods/Demo.hx", - "function doSomethingExtract() {\n" + "function doSomethingExtract():Void {\n" + "doNothing();\n" + " trace(\"I'm here\");\n" + " doNothing();\n" @@ -511,7 +527,7 @@ class RefactorExtractMethodTest extends RefactorTestBase { var edits:Array = [ makeReplaceTestEdit("testcases/methods/TypeProcessor.hx", "processExtract(item, compare);\n", 114, 221, true), makeInsertTestEdit("testcases/methods/TypeProcessor.hx", - "function processExtract(item:T, compare:U) {\n" + "function processExtract(item:T, compare:U):Void {\n" + "var result = item.process();\n" + " if (compare.compareTo(result) > 0) {\n" + " item.update(compare.getValue());\n" @@ -526,7 +542,7 @@ class RefactorExtractMethodTest extends RefactorTestBase { var edits:Array = [ makeReplaceTestEdit("testcases/methods/FunctionProcessor.hx", "processExtract(callback, value, text);\n", 143, 211, true), makeInsertTestEdit("testcases/methods/FunctionProcessor.hx", - "function processExtract(callback:(Int, String) -> Bool, value:Int, text:String) {\n" + "function processExtract(callback:(Int, String) -> Bool, value:Int, text:String):Void {\n" + "if (callback(value, text)) {\n" + " trace('Success: $value, $text');\n" + " }\n" @@ -563,11 +579,11 @@ class RefactorExtractMethodTest extends RefactorTestBase { var edits:Array = [ makeReplaceTestEdit("testcases/methods/ExceptionHandler.hx", "processExtract();\n", 86, 152, true), makeInsertTestEdit("testcases/methods/ExceptionHandler.hx", - "function processExtract() {\n" + "function processExtract():Void {\n" + "var data = getData();\n" + " validateData(data);\n" + " processData(data);\n" - + "}\n", 258, true), + + "}\n", 795, true), ]; checkRefactor(RefactorExtractMethod, {fileName: "testcases/methods/ExceptionHandler.hx", posStart: 85, posEnd: 152}, edits, async); } @@ -576,19 +592,54 @@ class RefactorExtractMethodTest extends RefactorTestBase { var edits:Array = [ makeReplaceTestEdit("testcases/methods/ExceptionHandler.hx", "processExtract();\n", 193, 250, true), makeInsertTestEdit("testcases/methods/ExceptionHandler.hx", - "function processExtract() {\n" + "function processExtract():Void {\n" + "logError(e);\n" + " throw new ProcessingException(e.message);\n" - + "}\n", 258, true), + + "}\n", 795, true), ]; + addTypeHint("testcases/methods/ExceptionHandler.hx", 69, LibType("Void", "Void", [])); checkRefactor(RefactorExtractMethod, {fileName: "testcases/methods/ExceptionHandler.hx", posStart: 192, posEnd: 250}, edits, async); } + function testExceptionHandlerTryWithThrow(async:Async) { + var edits:Array = [ + makeReplaceTestEdit("testcases/methods/ExceptionHandler.hx", "processExtract();\n", 266, 404, true), + makeInsertTestEdit("testcases/methods/ExceptionHandler.hx", + "function processExtract():Void {\n" + + "var data = getData();\n" + + " if (data.value > 10) {\n" + + " return;\n" + + " }\n" + + " throw new InvalidDataException(\"value should not be smaller than 11\");\n" + + "}\n", + 795, true), + ]; + checkRefactor(RefactorExtractMethod, {fileName: "testcases/methods/ExceptionHandler.hx", posStart: 265, posEnd: 404}, edits, async); + } + + function testExceptionHandlerTryWithThrowNotLast(async:Async) { + var edits:Array = [ + makeReplaceTestEdit("testcases/methods/ExceptionHandler.hx", "processExtract();\n", 518, 689, true), + makeInsertTestEdit("testcases/methods/ExceptionHandler.hx", + "function processExtract():Void {\n" + + "var data = getData();\n" + + " if (data.value > 10) {\n" + + " throw new InvalidDataException(\"value should not be larger than 10\");\n" + + " }\n" + + " validateData(data);\n" + + " processData(data);\n" + + "}\n", + 795, true), + ]; + addTypeHint("testcases/methods/ExceptionHandler.hx", 69, LibType("Void", "Void", [])); + checkRefactor(RefactorExtractMethod, {fileName: "testcases/methods/ExceptionHandler.hx", posStart: 517, posEnd: 689}, edits, async); + } + function testMetadataProcessor(async:Async) { var edits:Array = [ makeReplaceTestEdit("testcases/methods/MetadataProcessor.hx", "processExtract(meta, results);\n", 220, 575, true), makeInsertTestEdit("testcases/methods/MetadataProcessor.hx", - "function processExtract(meta:Dynamic>>, results:Map>) {\n" + "function processExtract(meta:Dynamic>>, results:Map>):Void {\n" + "for (field in Reflect.fields(meta)) {\n" + " var fieldMeta = Reflect.field(meta, field);\n" + " if (Reflect.hasField(fieldMeta, \"meta\")) {\n" @@ -646,7 +697,7 @@ class RefactorExtractMethodTest extends RefactorTestBase { var edits:Array = [ makeReplaceTestEdit("testcases/methods/MetadataProcessor.hx", "processExtract(results);\n", 579, 672, true), makeInsertTestEdit("testcases/methods/MetadataProcessor.hx", - "function processExtract(results:Map>) {\n" + "function processExtract(results:Map>):Void {\n" + "for (field => values in results) {\n" + " trace('Field: $field, Meta: ${values.join(\", \")}');\n" + " }\n" diff --git a/testcases/methods/ExceptionHandler.hx b/testcases/methods/ExceptionHandler.hx index d483516..dceea37 100644 --- a/testcases/methods/ExceptionHandler.hx +++ b/testcases/methods/ExceptionHandler.hx @@ -10,6 +10,27 @@ class ExceptionHandler { logError(e); throw new ProcessingException(e.message); } + try { + var data = getData(); + if (data.value > 10) { + return; + } + throw new InvalidDataException("value should not be smaller than 11"); + } catch (e:InvalidDataException) { + logError(e); + throw new ProcessingException(e.message); + } + try { + var data = getData(); + if (data.value > 10) { + throw new InvalidDataException("value should not be larger than 10"); + } + validateData(data); + processData(data); + } catch (e:InvalidDataException) { + logError(e); + throw new ProcessingException(e.message); + } } function getData():DataType { diff --git a/testcases/methods/Main.hx b/testcases/methods/Main.hx index 53ae470..7f55246 100644 --- a/testcases/methods/Main.hx +++ b/testcases/methods/Main.hx @@ -86,6 +86,20 @@ class Main { } return count; } + + public function allEmptyReturns(cond1:Bool, cond2:Bool) { + if (cond1) { + return; + } + if (cond2) { + return; + } + trace("hello 1"); + trace("hello 2"); + trace("hello 3"); + trace("hello 4"); + return; + } } typedef Item = { From 3e225fb7ed470522b27d2bb82a66c1ffdc40e06e Mon Sep 17 00:00:00 2001 From: AlexHaxe Date: Tue, 26 Nov 2024 17:00:42 +0100 Subject: [PATCH 24/59] fixed parameter collection from string interpolation --- src/refactor/discover/UsageCollector.hx | 31 ++++++++++++++-- src/refactor/refactor/ExtractMethod.hx | 35 ++++++++++++++++++- .../refactor/RefactorExtractMethodTest.hx | 11 ++++++ 3 files changed, 74 insertions(+), 3 deletions(-) diff --git a/src/refactor/discover/UsageCollector.hx b/src/refactor/discover/UsageCollector.hx index 9132500..3f69bc2 100644 --- a/src/refactor/discover/UsageCollector.hx +++ b/src/refactor/discover/UsageCollector.hx @@ -283,11 +283,20 @@ class UsageCollector { start: token.pos.min + 1, end: token.pos.max - 1 }; - identifier.addUse(new Identifier(StringConst, s, pos, context.nameMap, context.file, context.type)); + final newIdentifier = new Identifier(StringConst, s, pos, context.nameMap, context.file, context.type); + final parentIdentifier = findParentIdentifier(context, token); + if (parentIdentifier != null) { + parentIdentifier.addUse(newIdentifier); + } + identifier.addUse(newIdentifier); } SkipSubtree; case Const(CString(s, SingleQuotes)): - readStringInterpolation(context, identifier, token, s); + var parentIdentifier = findParentIdentifier(context, token); + if (parentIdentifier == null) { + parentIdentifier = identifier; + } + readStringInterpolation(context, parentIdentifier, token, s); SkipSubtree; default: GoDeeper; @@ -331,6 +340,24 @@ class UsageCollector { } } + function findParentIdentifier(context:UsageContext, stringToken:TokenTree):Null { + var parent = stringToken.parent; + while (true) { + switch (parent.tok) { + case Kwd(KwdFunction) | Kwd(KwdVar) | Kwd(KwdFinal) | Kwd(KwdAbstract) | Kwd(KwdClass) | Kwd(KwdEnum) | Kwd(KwdInterface) | Kwd(KwdTypedef): + var child = parent.getFirstChild(); + if (child != null) { + return context.type.findIdentifier(child.pos.min); + } + case Root | null: + break; + default: + } + parent = parent.parent; + } + return null; + } + function isDollarEscaped(text:String, index:Int):Bool { var escaped:Bool = false; while (--index >= 0) { diff --git a/src/refactor/refactor/ExtractMethod.hx b/src/refactor/refactor/ExtractMethod.hx index 8b48517..1bd87f6 100644 --- a/src/refactor/refactor/ExtractMethod.hx +++ b/src/refactor/refactor/ExtractMethod.hx @@ -216,14 +216,47 @@ class ExtractMethod { if (parentA == null) { return false; } + switch (parentA.tok) { + case POpen: + final closeToken = parentA.access().firstOf(PClose).token; + if (closeToken == null) { + return false; + } + if (closeToken.index < tokenB.index) { + return false; + } + case BrOpen: + final closeToken = parentA.access().firstOf(BrClose).token; + if (closeToken == null) { + return false; + } + if (closeToken.index < tokenB.index) { + return false; + } + case BkOpen: + final closeToken = parentA.access().firstOf(BkClose).token; + if (closeToken == null) { + return false; + } + if (closeToken.index < tokenB.index) { + return false; + } + default: + } var parentB = tokenB.parent; + var oldParentB = tokenB; while (true) { if (parentB == null) { return false; } if (parentA.index == parentB.index) { - return true; + final lastToken = TokenTreeCheckUtils.getLastToken(oldParentB); + if (lastToken == null) { + return false; + } + return (lastToken.index <= tokenB.index); } + oldParentB = parentB; parentB = parentB.parent; } } diff --git a/test/refactor/refactor/RefactorExtractMethodTest.hx b/test/refactor/refactor/RefactorExtractMethodTest.hx index 8811266..6a92952 100644 --- a/test/refactor/refactor/RefactorExtractMethodTest.hx +++ b/test/refactor/refactor/RefactorExtractMethodTest.hx @@ -225,6 +225,17 @@ class RefactorExtractMethodTest extends RefactorTestBase { checkRefactor(RefactorExtractMethod, {fileName: "testcases/methods/Main.hx", posStart: 1721, posEnd: 1809}, edits, async); } + function testStringInterpolation(async:Async) { + var edits:Array = [ + makeReplaceTestEdit("testcases/methods/Main.hx", "return interpolationExtract(data);\n", 1884, 1937, true), + makeInsertTestEdit("testcases/methods/Main.hx", + "static function interpolationExtract(data:Dynamic):String {\n" + "return cast '${data.a}_${data.b}_${data.c}_${false}';\n" + "}\n", 1941, + true), + ]; + addTypeHint("testcases/methods/Main.hx", 1857, LibType("String", "String", [])); + checkRefactor(RefactorExtractMethod, {fileName: "testcases/methods/Main.hx", posStart: 1887, posEnd: 1937}, edits, async); + } + function testDemoSimple(async:Async) { var edits:Array = [ makeReplaceTestEdit("testcases/methods/Demo.hx", "doSomethingExtract();\n", 161, 252, true), From c00ba7b69b047270c5c2afe8f3a10a64c54f281b Mon Sep 17 00:00:00 2001 From: AlexHaxe Date: Tue, 26 Nov 2024 19:42:45 +0100 Subject: [PATCH 25/59] added testcase --- testcases/methods/Main.hx | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/testcases/methods/Main.hx b/testcases/methods/Main.hx index 7f55246..465dedb 100644 --- a/testcases/methods/Main.hx +++ b/testcases/methods/Main.hx @@ -100,6 +100,10 @@ class Main { trace("hello 4"); return; } + + public static inline function interpolation(data:Dynamic):String { + return cast '${data.a}_${data.b}_${data.c}_${false}'; + } } typedef Item = { From 5497f7fa0c46e53237a554cf829b4bf0d241f544 Mon Sep 17 00:00:00 2001 From: AlexHaxe Date: Wed, 27 Nov 2024 20:09:48 +0100 Subject: [PATCH 26/59] added support for local function extraction --- src/refactor/refactor/ExtractMethod.hx | 109 +++++++++++++++--- src/refactor/refactor/RefactorHelper.hx | 2 +- .../extractmethod/ExtractMethodData.hx | 7 ++ .../refactor/RefactorExtractMethodTest.hx | 44 +++++++ testcases/methods/LambdaExample.hx | 34 ++++++ 5 files changed, 180 insertions(+), 16 deletions(-) create mode 100644 testcases/methods/LambdaExample.hx diff --git a/src/refactor/refactor/ExtractMethod.hx b/src/refactor/refactor/ExtractMethod.hx index 1bd87f6..034d965 100644 --- a/src/refactor/refactor/ExtractMethod.hx +++ b/src/refactor/refactor/ExtractMethod.hx @@ -6,6 +6,7 @@ import refactor.edits.Changelist; import refactor.refactor.RefactorHelper.TokensAtPos; import refactor.refactor.extractmethod.CodeGenAsExpression; import refactor.refactor.extractmethod.CodeGenEmptyReturn; +import refactor.refactor.extractmethod.CodeGenLocalFunction; import refactor.refactor.extractmethod.CodeGenNoReturn; import refactor.refactor.extractmethod.CodeGenOpenEnded; import refactor.refactor.extractmethod.CodeGenReturnIsLast; @@ -37,10 +38,12 @@ class ExtractMethod { // find all parameters for extracted method // e.g. all scoped vars used inside selected code - var neededIdentifiers:Array = findParameters(extractData, context, functionIdentifier); + final neededIdentifiers:Array = findParameters(extractData, context, functionIdentifier); + + final localFunctionParameters:Array = findLocalParameters(extractData, context, functionIdentifier); // determine type of selected code - var codeGen:Null = findCodeGen(extractData, context, functionIdentifier, neededIdentifiers); + final codeGen:Null = findCodeGen(extractData, context, functionIdentifier, neededIdentifiers, localFunctionParameters); if (codeGen == null) { return Promise.resolve(RefactorResult.Unsupported("could not extract method from selected code - no codegen")); } @@ -49,12 +52,13 @@ class ExtractMethod { var returnTypeHint:String = ""; // resolve all parameter types either from typehint or using a hover request with Haxe server - var parameterPromise = Promise.all(makeParameterList(extractData, context, neededIdentifiers)).then(function(params):Promise { - parameterList = params.join(", "); - return Promise.resolve(true); - }); - // resolve function return typehint + var parameterPromise = Promise.all(makeParameterList(extractData, context, localFunctionParameters.concat(neededIdentifiers))) + .then(function(params):Promise { + parameterList = params.join(", "); + return Promise.resolve(true); + }); + // resolve function return typehint var returnHintPromise = codeGen.makeReturnTypeHint().then(function(typeHint) { returnTypeHint = typeHint; return Promise.resolve(true); @@ -64,7 +68,6 @@ class ExtractMethod { return Promise.all([parameterPromise, returnHintPromise]).then(function(_) { // replace selected code with call to newly extracted method final extractedCall:String = codeGen.makeCallSite(); - changelist.addChange(context.what.fileName, ReplaceText(extractedCall, {fileName: context.what.fileName, start: extractData.startToken.pos.min, end: extractData.endToken.pos.max}, true), null); @@ -138,21 +141,34 @@ class ExtractMethod { if (tokenStart.index >= tokenEnd.index) { return null; } - // currently not supporting extracting an inner function + + // inner function detection + var functionType:LocalFunctionType = NoFunction; switch (tokenStart.tok) { case Kwd(KwdFunction): - return null; + final child = tokenStart.getFirstChild(); + if (child == null) { + return null; + } + switch (child.tok) { + case Const(_): + functionType = Named; + case POpen: + functionType = Unnamed; + default: + return null; + } case Const(_): if (tokenStart.hasChildren()) { final child = tokenStart.getFirstChild(); if (child.matches(Arrow)) { - return null; + functionType = Unnamed; } } case POpen: switch (TokenTreeCheckUtils.getPOpenType(tokenStart)) { case Parameter: - return null; + functionType = Unnamed; default: } default: @@ -168,6 +184,7 @@ class ExtractMethod { // extracting only works if parent of start token is also grand…parent of end token if (!shareSameParent(tokenStart, tokenEnd)) { + trace("xxx"); return null; } @@ -208,6 +225,7 @@ class ExtractMethod { functionToken: parentFunction, isStatic: isStatic, isSingleExpr: isSingleExpr, + functionType: functionType, }; } @@ -269,6 +287,60 @@ class ExtractMethod { return file.getIdentifier(extractData.functionToken.getFirstChild().pos.min); } + static function findLocalParameters(extractData:ExtractMethodData, context:RefactorContext, functionIdentifier:Identifier):Array { + if (extractData.functionType == NoFunction) { + return []; + } + final localFunctionParameters:Array = []; + switch (extractData.startToken.tok) { + case Kwd(KwdFunction): + var pOpenToken = extractData.startToken.getFirstChild(); + if (pOpenToken == null) { + return []; + } + switch (pOpenToken.tok) { + case Const(CIdent(_)): + pOpenToken = pOpenToken.getFirstChild(); + case POpen: + default: + return []; + } + for (child in pOpenToken.children) { + switch (child.tok) { + case Const(CIdent(_)): + final param = functionIdentifier.findIdentifier(child.pos.min); + if (param != null) { + localFunctionParameters.push(param); + } + case PClose: + break; + default: + } + } + case Const(_): + final singleParam = functionIdentifier.findIdentifier(extractData.startToken.pos.min); + if (singleParam == null) { + return []; + } + localFunctionParameters.push(singleParam); + case POpen: + for (child in extractData.startToken.children) { + switch (child.tok) { + case Const(CIdent(_)): + final param = functionIdentifier.findIdentifier(child.pos.min); + if (param != null) { + localFunctionParameters.push(param); + } + case PClose: + break; + default: + } + } + default: + } + return localFunctionParameters; + } + static function findParameters(extractData:ExtractMethodData, context:RefactorContext, functionIdentifier:Identifier) { final allIdentifiersBefore = getScopedBeforeSelected(extractData, context, functionIdentifier); @@ -386,12 +458,12 @@ class ExtractMethod { return null; } - static function findCodeGen(extractData:ExtractMethodData, context:RefactorContext, functionIdentifier:Identifier, - neededIdentifiers:Array):Null { + static function findCodeGen(extractData:ExtractMethodData, context:RefactorContext, functionIdentifier:Identifier, neededIdentifiers:Array, + localFunctionIdentifiers:Array):Null { final assignedVars:Array = []; final parent = extractData.startToken.parent; - if (usedAsExpression(parent)) { + if (extractData.functionType == NoFunction && usedAsExpression(parent)) { if (extractData.isSingleExpr) { return new CodeGenAsExpression(extractData, context, neededIdentifiers); } else { @@ -453,6 +525,13 @@ class ExtractMethod { } } final modifiedIdentifiers = findIdentifiersUsedAfterSelection(extractData, functionIdentifier, modifiedCandidates); + if (extractData.functionType != NoFunction) { + if (modifiedIdentifiers.length > 0) { + // assigments to closures are not supported + return null; + } + return new CodeGenLocalFunction(extractData, context, neededIdentifiers, localFunctionIdentifiers, returnIsEmpty); + } if (allReturns.length == 0) { return new CodeGenNoReturn(extractData, context, neededIdentifiers, modifiedIdentifiers, leakingVars); diff --git a/src/refactor/refactor/RefactorHelper.hx b/src/refactor/refactor/RefactorHelper.hx index ce8c8eb..785a5e2 100644 --- a/src/refactor/refactor/RefactorHelper.hx +++ b/src/refactor/refactor/RefactorHelper.hx @@ -13,7 +13,7 @@ class RefactorHelper { var distanceAfter:Int = 10000; root.filterCallback(function(token:TokenTree, index:Int):FilterResult { - if (token.pos.min <= searchPos) { + if (token.pos.min < searchPos) { var distance = searchPos - token.pos.max; if (distanceBefore > distance) { tokens.before = token; diff --git a/src/refactor/refactor/extractmethod/ExtractMethodData.hx b/src/refactor/refactor/extractmethod/ExtractMethodData.hx index 2468112..7a66b65 100644 --- a/src/refactor/refactor/extractmethod/ExtractMethodData.hx +++ b/src/refactor/refactor/extractmethod/ExtractMethodData.hx @@ -10,4 +10,11 @@ typedef ExtractMethodData = { var functionToken:TokenTree; var isStatic:Bool; var isSingleExpr:Bool; + var functionType:LocalFunctionType; +} + +enum LocalFunctionType { + NoFunction; + Named; + Unnamed; } diff --git a/test/refactor/refactor/RefactorExtractMethodTest.hx b/test/refactor/refactor/RefactorExtractMethodTest.hx index 6a92952..5e39f53 100644 --- a/test/refactor/refactor/RefactorExtractMethodTest.hx +++ b/test/refactor/refactor/RefactorExtractMethodTest.hx @@ -721,4 +721,48 @@ class RefactorExtractMethodTest extends RefactorTestBase { ])); checkRefactor(RefactorExtractMethod, {fileName: "testcases/methods/MetadataProcessor.hx", posStart: 578, posEnd: 672}, edits, async); } + + function testLambdaExample(async:Async) { + var edits:Array = [ + makeReplaceTestEdit("testcases/methods/LambdaExample.hx", "processExtract", 156, 230, true), + makeInsertTestEdit("testcases/methods/LambdaExample.hx", + "function processExtract(n:Int):Int {\n" + + "var temp = n * multiplier;\n" + + " return temp + this.multiplier;\n" + + "}\n", 236, true), + ]; + addTypeHint("testcases/methods/LambdaExample.hx", 156, LibType("Int", "Int", [])); + addTypeHint("testcases/methods/LambdaExample.hx", 159, LibType("Int", "Int", [])); + checkRefactor(RefactorExtractMethod, {fileName: "testcases/methods/LambdaExample.hx", posStart: 156, posEnd: 230}, edits, async); + } + + function testLambdaExampleCallback(async:Async) { + var edits:Array = [ + makeReplaceTestEdit("testcases/methods/LambdaExample.hx", "processCallbackExtract", 281, 337, true), + makeInsertTestEdit("testcases/methods/LambdaExample.hx", + "function processCallbackExtract(n:Int, m:Int):Int {\n" + + "var temp = n * m;\n" + + " return temp + m;\n" + + "}\n", 343, true), + ]; + addTypeHint("testcases/methods/LambdaExample.hx", 282, LibType("Int", "Int", [])); + addTypeHint("testcases/methods/LambdaExample.hx", 285, LibType("Int", "Int", [])); + addTypeHint("testcases/methods/LambdaExample.hx", 289, LibType("Int", "Int", [])); + checkRefactor(RefactorExtractMethod, {fileName: "testcases/methods/LambdaExample.hx", posStart: 281, posEnd: 337}, edits, async); + } + + function testLambdaExampleCallbackFunc(async:Async) { + var edits:Array = [ + makeReplaceTestEdit("testcases/methods/LambdaExample.hx", "processCallbackFuncExtract", 392, 453, true), + makeInsertTestEdit("testcases/methods/LambdaExample.hx", + "function processCallbackFuncExtract(n:Int, m:Int):Int {\n" + + "var temp = n * m;\n" + + " return temp + m;\n" + + "}\n", 459, true), + ]; + addTypeHint("testcases/methods/LambdaExample.hx", 401, LibType("Int", "Int", [])); + addTypeHint("testcases/methods/LambdaExample.hx", 404, LibType("Int", "Int", [])); + addTypeHint("testcases/methods/LambdaExample.hx", 399, LibType("Int", "Int", [])); + checkRefactor(RefactorExtractMethod, {fileName: "testcases/methods/LambdaExample.hx", posStart: 392, posEnd: 453}, edits, async); + } } diff --git a/testcases/methods/LambdaExample.hx b/testcases/methods/LambdaExample.hx new file mode 100644 index 0000000..3a9525a --- /dev/null +++ b/testcases/methods/LambdaExample.hx @@ -0,0 +1,34 @@ +package testcases.methods; + +class LambdaExample { + private var multiplier = 2; + + function process() { + var numbers = [1, 2, 3]; + var result = numbers.map(n -> { + var temp = n * multiplier; + return temp + this.multiplier; + }); + } + + function processCallback() { + processItem((n, m) -> { + var temp = n * m; + return temp + m; + }); + } + + function processCallbackFunc() { + processItem(function(n, m) { + var temp = n * m; + return temp + m; + }); + } + + function processItem(cb:ProcessCallback) { + var numbers = [1, 2, 3]; + var result = numbers.map(n -> cb(n, multiplier)); + } +} + +typedef ProcessCallback = (n:Int, m:Int) -> Int; From a24e2a250b462e655873f26d7548cca9f3f72657 Mon Sep 17 00:00:00 2001 From: AlexHaxe Date: Wed, 27 Nov 2024 20:12:37 +0100 Subject: [PATCH 27/59] fixed missing code gen --- .../extractmethod/CodeGenLocalFunction.hx | 123 ++++++++++++++++++ 1 file changed, 123 insertions(+) create mode 100644 src/refactor/refactor/extractmethod/CodeGenLocalFunction.hx diff --git a/src/refactor/refactor/extractmethod/CodeGenLocalFunction.hx b/src/refactor/refactor/extractmethod/CodeGenLocalFunction.hx new file mode 100644 index 0000000..4c63400 --- /dev/null +++ b/src/refactor/refactor/extractmethod/CodeGenLocalFunction.hx @@ -0,0 +1,123 @@ +package refactor.refactor.extractmethod; + +import refactor.discover.Identifier; + +class CodeGenLocalFunction extends CodeGenBase { + final localParams:Array; + final returnIsEmpty:Bool; + + public function new(extractData:ExtractMethodData, context:RefactorContext, neededIdentifiers:Array, localParams:Array, + returnIsEmpty:Bool) { + super(extractData, context, neededIdentifiers); + this.localParams = localParams; + this.returnIsEmpty = returnIsEmpty; + } + + public function makeCallSite():String { + return switch (extractData.functionType) { + case NoFunction: + ""; + case Named: + ""; + case Unnamed if (neededIdentifiers.length == 0): + extractData.newMethodName; + case Unnamed if (returnIsEmpty): + final outerParams:String = localParams.map(i -> i.name).join(", "); + final innerParams:String = localParams.concat(neededIdentifiers).map(i -> i.name).join(", "); + 'function($outerParams) { ${extractData.newMethodName}($innerParams); }'; + case Unnamed: + final outerParams:String = localParams.map(i -> i.name).join(", "); + final innerParams:String = localParams.concat(neededIdentifiers).map(i -> i.name).join(", "); + 'function($outerParams) { return ${extractData.newMethodName}($innerParams); }'; + } + } + + public function makeReturnTypeHint():Promise { + return functionHint().then(function(typeHint):Promise { + if (typeHint == null) { + return Promise.resolve(""); + } + return Promise.resolve(":" + typeHint.printTypeHint()); + }); + } + + function functionHint():Promise { + var func:Null = switch (extractData.functionType) { + case NoFunction: + null; + case Named: + extractData.startToken.access().firstChild().token; + case Unnamed: + switch (extractData.startToken.tok) { + case Kwd(KwdFunction): + extractData.startToken; + case POpen | Const(_): + extractData.startToken.access().firstOf(Arrow).token; + default: + null; + } + } + if (func == null) { + return Promise.reject("failed to find return type of selected code"); + } + return TypingHelper.findTypeWithTyper(context, context.what.fileName, func.pos.max - 1).then(function(typeHint) { + return switch (typeHint) { + case null | ClasspathType(_) | LibType(_) | StructType(_) | UnknownType(_): + Promise.resolve(typeHint); + case FunctionType(args, retVal): + Promise.resolve(retVal); + } + }); + } + + public function makeBody():String { + var body:Null = switch (extractData.functionType) { + case NoFunction: + null; + case Named: + extractData.startToken.access().firstChild().firstOf(BrOpen).token; + case Unnamed: + switch (extractData.startToken.tok) { + case Kwd(KwdFunction): + extractData.startToken.access().firstOf(BrOpen).token; + case POpen | Const(_): + extractData.startToken.access().firstOf(Arrow).firstChild().token; + default: + null; + } + } + if (body == null) { + return ""; + } + + var pos:Position = switch (body.tok) { + case BrOpen: + final firstChild = body.getFirstChild(); + if (firstChild == null) { + return ""; + } + final brClose = body.access().firstOf(BrClose).token; + if (brClose == null) { + return ""; + } + final prev = brClose.previousSibling; + var endPos = brClose.pos.min - 1; + if (prev != null) { + final lastChild = TokenTreeCheckUtils.getLastToken(prev); + if (lastChild != null) { + endPos = lastChild.pos.max; + } + } + { + file: body.pos.file, + min: firstChild.pos.min, + max: endPos + } + default: + body.getPos(); + } + + final selectedSnippet = RefactorHelper.extractText(context.converter, extractData.content, pos.min, pos.max); + return " {\n" + selectedSnippet + "\n}\n"; + } +} From 6fb14295b160cba3cd98f31a10c6688356781ac8 Mon Sep 17 00:00:00 2001 From: AlexHaxe Date: Wed, 27 Nov 2024 20:16:49 +0100 Subject: [PATCH 28/59] updated json2object for latest nightly --- haxe_libraries/json2object.hxml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/haxe_libraries/json2object.hxml b/haxe_libraries/json2object.hxml index 897b5a3..5ed25d9 100644 --- a/haxe_libraries/json2object.hxml +++ b/haxe_libraries/json2object.hxml @@ -1,4 +1,4 @@ -# @install: lix --silent download "haxelib:/json2object#3.11.0" into json2object/3.11.0/haxelib +# @install: lix --silent download "gh://github.com/elnabo/json2object#a75859de1e966c09e73591b6c9186086c143fe60" into json2object/3.11.0/github/a75859de1e966c09e73591b6c9186086c143fe60 -lib hxjsonast --cp ${HAXE_LIBCACHE}/json2object/3.11.0/haxelib/src +-cp ${HAXE_LIBCACHE}/json2object/3.11.0/github/a75859de1e966c09e73591b6c9186086c143fe60/src -D json2object=3.11.0 \ No newline at end of file From f1acf21d0bcc9146741a850edc6fd1dc6f5bf665 Mon Sep 17 00:00:00 2001 From: AlexHaxe Date: Thu, 28 Nov 2024 20:43:21 +0100 Subject: [PATCH 29/59] added testcases changed resolve to reject in error cases --- src/refactor/PrintHelper.hx | 17 +++++++- src/refactor/Refactoring.hx | 2 +- src/refactor/Rename.hx | 30 ++++++------- src/refactor/refactor/ExtractInterface.hx | 2 +- src/refactor/refactor/ExtractMethod.hx | 12 ++---- src/refactor/refactor/ExtractType.hx | 2 +- test/refactor/refactor/RefactorClassTest.hx | 31 +++++++++++++ .../refactor/RefactorExtractMethodTest.hx | 43 +++++++++++++++++++ test/refactor/refactor/RefactorTestBase.hx | 2 +- test/refactor/rename/RenameTestBase.hx | 4 +- testcases/classes/StaticUsing.hx | 3 ++ testcases/methods/LambdaExample.hx | 5 +++ 12 files changed, 123 insertions(+), 30 deletions(-) diff --git a/src/refactor/PrintHelper.hx b/src/refactor/PrintHelper.hx index e233d7b..2cb4c1c 100644 --- a/src/refactor/PrintHelper.hx +++ b/src/refactor/PrintHelper.hx @@ -107,7 +107,7 @@ class PrintHelper { } } - public static function printRefactorResult(result:RefactorResult):String { + public static function printRenameResult(result:RefactorResult):String { return switch (result) { case NoChange: "nothing to do"; @@ -121,4 +121,19 @@ class PrintHelper { "rename successful"; } } + + public static function printRefactorResult(result:RefactorResult):String { + return switch (result) { + case NoChange: + "nothing to do"; + case NotFound: + "could not find identifier to refactor"; + case Unsupported(name): + "refactor not supported for " + name; + case DryRun: + "dry run - no changes were made"; + case Done: + "refactor successful"; + } + } } diff --git a/src/refactor/Refactoring.hx b/src/refactor/Refactoring.hx index 92cafde..05891e4 100644 --- a/src/refactor/Refactoring.hx +++ b/src/refactor/Refactoring.hx @@ -30,6 +30,6 @@ class Refactoring { case RefactorExtractType: return ExtractType.doRefactor(context); } - return Promise.resolve(RefactorResult.Unsupported("no refactor type selected")); + return Promise.reject("no refactor type selected"); } } diff --git a/src/refactor/Rename.hx b/src/refactor/Rename.hx index 8b69126..058dbba 100644 --- a/src/refactor/Rename.hx +++ b/src/refactor/Rename.hx @@ -19,11 +19,11 @@ class Rename { public static function canRename(context:CanRenameContext):Promise { var file:Null = context.fileList.getFile(context.what.fileName); if (file == null) { - return Promise.reject(RefactorResult.NotFound.printRefactorResult()); + return Promise.reject(RefactorResult.NotFound.printRenameResult()); } var identifier:Identifier = file.getIdentifier(context.what.pos); if (identifier == null) { - return Promise.reject(RefactorResult.NotFound.printRefactorResult()); + return Promise.reject(RefactorResult.NotFound.printRenameResult()); } return switch (identifier.type) { case PackageName | ImportAlias | Abstract | Class | Enum | Interface | Typedef | ModuleLevelStaticVar | ModuleLevelStaticMethod | Property | @@ -32,11 +32,11 @@ class Rename { Promise.resolve({name: identifier.name, pos: identifier.pos}); case ImportModul | UsingModul | Extends | Implements | AbstractOver | AbstractFrom | AbstractTo | TypeHint | StringConst | TypedParameter | TypedefBase | Call(true) | CaseLabel(_): - Promise.reject(RefactorResult.Unsupported(identifier.toString()).printRefactorResult()); + Promise.reject(RefactorResult.Unsupported(identifier.toString()).printRenameResult()); case Call(false) | Access | ArrayAccess(_) | ForIterator: var candidate:Null = findActualWhat(context, file, identifier); if (candidate == null) { - return Promise.reject(RefactorResult.Unsupported(identifier.toString()).printRefactorResult()); + return Promise.reject(RefactorResult.Unsupported(identifier.toString()).printRenameResult()); } if (identifier.name.startsWith(candidate.name)) { var pos:IdentifierPos = { @@ -54,28 +54,28 @@ class Rename { } return Promise.resolve({name: candidate.name, pos: pos}); } - Promise.reject(RefactorResult.Unsupported(identifier.toString()).printRefactorResult()); + Promise.reject(RefactorResult.Unsupported(identifier.toString()).printRenameResult()); } } public static function rename(context:RenameContext):Promise { var file:Null = context.fileList.getFile(context.what.fileName); if (file == null) { - return Promise.reject(RefactorResult.NotFound.printRefactorResult()); + return Promise.reject(RefactorResult.NotFound.printRenameResult()); } var identifier:Identifier = file.getIdentifier(context.what.pos); if (identifier == null) { - return Promise.reject(RefactorResult.NotFound.printRefactorResult()); + return Promise.reject(RefactorResult.NotFound.printRenameResult()); } if (identifier.name == context.what.toName) { - return Promise.reject(RefactorResult.NotFound.printRefactorResult()); + return Promise.reject(RefactorResult.NotFound.printRenameResult()); } return switch (identifier.type) { case PackageName: context.verboseLog('rename package name "${identifier.name}" to "${context.what.toName}"'); RenamePackage.refactorPackageName(context, file, identifier); case ImportModul | UsingModul: - Promise.reject(RefactorResult.Unsupported(identifier.toString()).printRefactorResult()); + Promise.reject(RefactorResult.Unsupported(identifier.toString()).printRenameResult()); case ImportAlias: context.verboseLog('rename import alias "${identifier.name}" to "${context.what.toName}"'); RenameImportAlias.refactorImportAlias(context, file, identifier); @@ -86,7 +86,7 @@ class Rename { context.verboseLog('rename module level static "${identifier.name}" to "${context.what.toName}"'); RenameModuleLevelStatic.refactorModuleLevelStatic(context, file, identifier); case Extends | Implements | AbstractOver | AbstractFrom | AbstractTo | TypeHint | StringConst: - Promise.reject(RefactorResult.Unsupported(identifier.toString()).printRefactorResult()); + Promise.reject(RefactorResult.Unsupported(identifier.toString()).printRenameResult()); case Property: context.verboseLog('rename property "${identifier.name}" to "${context.what.toName}"'); RenameField.refactorField(context, file, identifier, false); @@ -97,9 +97,9 @@ class Rename { context.verboseLog('rename class method "${identifier.name}" to "${context.what.toName}"'); RenameField.refactorField(context, file, identifier, isStatic); case TypedParameter: - Promise.reject(RefactorResult.Unsupported(identifier.toString()).printRefactorResult()); + Promise.reject(RefactorResult.Unsupported(identifier.toString()).printRenameResult()); case TypedefBase: - Promise.reject(RefactorResult.Unsupported(identifier.toString()).printRefactorResult()); + Promise.reject(RefactorResult.Unsupported(identifier.toString()).printRenameResult()); case TypedefField(fields): RenameAnonStructField.refactorAnonStructField(context, file, identifier, fields); case StructureField(fields): @@ -111,17 +111,17 @@ class Rename { context.verboseLog('rename enum field "${identifier.name}" to "${context.what.toName}"'); RenameEnumField.refactorEnumField(context, file, identifier); case Call(true): - Promise.reject(RefactorResult.Unsupported(identifier.toString()).printRefactorResult()); + Promise.reject(RefactorResult.Unsupported(identifier.toString()).printRenameResult()); case Call(false) | Access | ArrayAccess(_) | ForIterator: context.verboseLog('rename "${identifier.name}" at call/access location - trying to find definition'); var candidate:Null = findActualWhat(context, file, identifier); if (candidate == null) { - return Promise.reject(RefactorResult.Unsupported(identifier.toString()).printRefactorResult()); + return Promise.reject(RefactorResult.Unsupported(identifier.toString()).printRenameResult()); } context.what.pos = candidate.pos.start; rename(context); case CaseLabel(_): - Promise.reject(RefactorResult.Unsupported(identifier.toString()).printRefactorResult()); + Promise.reject(RefactorResult.Unsupported(identifier.toString()).printRenameResult()); case ScopedLocal(scopeStart, scopeEnd, type): context.verboseLog('rename scoped local "${identifier.name}" (${type.scopeTypeToString()}) to "${context.what.toName}"'); RenameScopedLocal.refactorScopedLocal(context, file, identifier, scopeStart, scopeEnd); diff --git a/src/refactor/refactor/ExtractInterface.hx b/src/refactor/refactor/ExtractInterface.hx index 249605f..544e79e 100644 --- a/src/refactor/refactor/ExtractInterface.hx +++ b/src/refactor/refactor/ExtractInterface.hx @@ -20,7 +20,7 @@ class ExtractInterface { public static function doRefactor(context:RefactorContext):Promise { final extractData = makeExtractInterfaceData(context); if (extractData == null) { - return Promise.resolve(RefactorResult.NotFound); + return Promise.reject("failed to collect extract interface data"); } final changelist:Changelist = new Changelist(context); diff --git a/src/refactor/refactor/ExtractMethod.hx b/src/refactor/refactor/ExtractMethod.hx index 034d965..ac404bf 100644 --- a/src/refactor/refactor/ExtractMethod.hx +++ b/src/refactor/refactor/ExtractMethod.hx @@ -26,14 +26,14 @@ class ExtractMethod { public static function doRefactor(context:RefactorContext):Promise { final extractData = makeExtractMethodData(context); if (extractData == null) { - return Promise.resolve(RefactorResult.Unsupported("failed to collect extract method data")); + return Promise.reject("failed to collect extract method data"); } final changelist:Changelist = new Changelist(context); // identifier of top-level containing function final functionIdentifier = getFunctionIdentifier(extractData, context); if (functionIdentifier == null) { - return Promise.resolve(RefactorResult.Unsupported("failed to find identifier of containing function")); + return Promise.reject("failed to find identifier of containing function"); } // find all parameters for extracted method @@ -45,7 +45,7 @@ class ExtractMethod { // determine type of selected code final codeGen:Null = findCodeGen(extractData, context, functionIdentifier, neededIdentifiers, localFunctionParameters); if (codeGen == null) { - return Promise.resolve(RefactorResult.Unsupported("could not extract method from selected code - no codegen")); + return Promise.reject("could not extract method from selected code - no codegen"); } var parameterList:String = ""; @@ -125,10 +125,7 @@ class ExtractMethod { // find corresponding tokens in tokentree, selection start/end in whitespace final tokensStart:TokensAtPos = RefactorHelper.findTokensAtPos(root, context.what.posStart); final tokensEnd:TokensAtPos = RefactorHelper.findTokensAtPos(root, context.what.posEnd); - if (tokensStart.after == null) { - return null; - } - if (tokensEnd.before == null) { + if (tokensStart.after == null || tokensEnd.before == null) { return null; } @@ -184,7 +181,6 @@ class ExtractMethod { // extracting only works if parent of start token is also grand…parent of end token if (!shareSameParent(tokenStart, tokenEnd)) { - trace("xxx"); return null; } diff --git a/src/refactor/refactor/ExtractType.hx b/src/refactor/refactor/ExtractType.hx index 6227d21..7cebd2b 100644 --- a/src/refactor/refactor/ExtractType.hx +++ b/src/refactor/refactor/ExtractType.hx @@ -24,7 +24,7 @@ class ExtractType { public static function doRefactor(context:RefactorContext):Promise { final extractData = makeExtractTypeData(context); if (extractData == null) { - return Promise.resolve(RefactorResult.NotFound); + return Promise.reject("failed to collect extract type data"); } // copy header + imports final fileHeader = makeHeader(extractData, context); diff --git a/test/refactor/refactor/RefactorClassTest.hx b/test/refactor/refactor/RefactorClassTest.hx index 3edd6a8..82f4028 100644 --- a/test/refactor/refactor/RefactorClassTest.hx +++ b/test/refactor/refactor/RefactorClassTest.hx @@ -5,6 +5,18 @@ class RefactorClassTest extends RefactorTestBase { setupTestSources(["testcases/classes"]); } + function testFailExtractTypChildClass(async:Async) { + failCanRefactor(RefactorExtractType, {fileName: "testcases/classes/ChildClass.hx", posStart: 30, posEnd: 30}, "unsupported"); + failRefactor(RefactorExtractType, {fileName: "testcases/classes/ChildClass.hx", posStart: 30, posEnd: 30}, "failed to collect extract type data", + async); + } + + function testFailExtractInterfaceNoClass(async:Async) { + failCanRefactor(RefactorExtractInterface, {fileName: "testcases/classes/ChildClass.hx", posStart: 875, posEnd: 875}, "unsupported"); + failRefactor(RefactorExtractInterface, {fileName: "testcases/classes/ChildClass.hx", posStart: 875, posEnd: 875}, + "failed to collect extract interface data", async); + } + function testExtractTypeListOfChilds(async:Async) { var edits:Array = [ makeRemoveTestEdit("testcases/classes/ChildClass.hx", 860, 901), @@ -34,6 +46,25 @@ class RefactorClassTest extends RefactorTestBase { checkRefactor(RefactorExtractType, {fileName: "testcases/classes/Printer.hx", posStart: 1273, posEnd: 1283}, edits, async); } + function testExtractTypeContextWithDocComment(async:Async) { + var edits:Array = [ + makeCreateTestEdit("testcases/classes/Context.hx"), + makeInsertTestEdit("testcases/classes/Context.hx", + "package classes;\n\n" + + "using classes.ChildHelper;\n" + + "using classes.pack.SecondChildHelper;\n\n" + + "/**\n" + + " * Context class\n" + + " */\n" + + "class Context {\n" + + " public static var printFunc:PrintFunc;\n" + + "}", + 0, true), + makeRemoveTestEdit("testcases/classes/StaticUsing.hx", 484, 566), + ]; + checkRefactor(RefactorExtractType, {fileName: "testcases/classes/StaticUsing.hx", posStart: 518, posEnd: 518}, edits, async); + } + function testExtractInterfaceBaseClass(async:Async) { var edits:Array = [ makeInsertTestEdit("testcases/classes/BaseClass.hx", " implements IBaseClass", 33), diff --git a/test/refactor/refactor/RefactorExtractMethodTest.hx b/test/refactor/refactor/RefactorExtractMethodTest.hx index 5e39f53..6017404 100644 --- a/test/refactor/refactor/RefactorExtractMethodTest.hx +++ b/test/refactor/refactor/RefactorExtractMethodTest.hx @@ -5,6 +5,39 @@ class RefactorExtractMethodTest extends RefactorTestBase { setupTestSources(["testcases/methods"]); } + function testFailCollectDataEmptyFile(async:Async) { + failCanRefactor(RefactorExtractMethod, {fileName: "testcases/methods/Empty.hx", posStart: 80, posEnd: 131}, "unsupported"); + failRefactor(RefactorExtractMethod, {fileName: "testcases/methods/Empty.hx", posStart: 80, posEnd: 131}, "failed to collect extract method data", + async); + } + + function testFailCollectDataEmptyRange(async:Async) { + failCanRefactor(RefactorExtractMethod, {fileName: "testcases/methods/Main.hx", posStart: 0, posEnd: 0}, "unsupported"); + failRefactor(RefactorExtractMethod, {fileName: "testcases/methods/Main.hx", posStart: 0, posEnd: 0}, "failed to collect extract method data", async); + } + + function testFailCollectData(async:Async) { + failCanRefactor(RefactorExtractMethod, {fileName: "testcases/methods/Main.hx", posStart: 80, posEnd: 131}, "unsupported"); + failRefactor(RefactorExtractMethod, {fileName: "testcases/methods/Main.hx", posStart: 80, posEnd: 131}, "failed to collect extract method data", async); + } + + function testFailCollectDataReverse(async:Async) { + failCanRefactor(RefactorExtractMethod, {fileName: "testcases/methods/Main.hx", posStart: 131, posEnd: 80}, "unsupported"); + failRefactor(RefactorExtractMethod, {fileName: "testcases/methods/Main.hx", posStart: 131, posEnd: 80}, "failed to collect extract method data", async); + } + + function testFailNoParentFunction(async:Async) { + failCanRefactor(RefactorExtractMethod, {fileName: "testcases/methods/Main.hx", posStart: 1962, posEnd: 1999}, "unsupported"); + failRefactor(RefactorExtractMethod, {fileName: "testcases/methods/Main.hx", posStart: 1962, posEnd: 1999}, "failed to collect extract method data", + async); + } + + function testFailAssignmentInLocalFunction(async:Async) { + failCanRefactor(RefactorExtractMethod, {fileName: "testcases/methods/LambdaExample.hx", posStart: 675, posEnd: 680}, "unsupported"); + failRefactor(RefactorExtractMethod, {fileName: "testcases/methods/LambdaExample.hx", posStart: 675, posEnd: 680}, + "failed to collect extract method data", async); + } + function testSimpleNoReturns(async:Async) { var edits:Array = [ makeReplaceTestEdit("testcases/methods/Main.hx", "noReturnsExtract();\n", 94, 131, true), @@ -765,4 +798,14 @@ class RefactorExtractMethodTest extends RefactorTestBase { addTypeHint("testcases/methods/LambdaExample.hx", 399, LibType("Int", "Int", [])); checkRefactor(RefactorExtractMethod, {fileName: "testcases/methods/LambdaExample.hx", posStart: 392, posEnd: 453}, edits, async); } + + function testLambdaExampleSimple(async:Async) { + var edits:Array = [ + makeReplaceTestEdit("testcases/methods/LambdaExample.hx", "processSimpleExtract", 669, 679, true), + makeInsertTestEdit("testcases/methods/LambdaExample.hx", "function processSimpleExtract(n:Int):Int {\n" + "n * n\n" + "}\n", 685, true), + ]; + addTypeHint("testcases/methods/LambdaExample.hx", 669, LibType("Int", "Int", [])); + addTypeHint("testcases/methods/LambdaExample.hx", 672, LibType("Int", "Int", [])); + checkRefactor(RefactorExtractMethod, {fileName: "testcases/methods/LambdaExample.hx", posStart: 669, posEnd: 679}, edits, async); + } } diff --git a/test/refactor/refactor/RefactorTestBase.hx b/test/refactor/refactor/RefactorTestBase.hx index df11f4a..d049a87 100644 --- a/test/refactor/refactor/RefactorTestBase.hx +++ b/test/refactor/refactor/RefactorTestBase.hx @@ -36,7 +36,7 @@ class RefactorTestBase extends TestBase { } } - function failRename(refactorType:RefactorType, what:RefactorWhat, expected:String, async:Async, ?pos:PosInfos) { + function failRefactor(refactorType:RefactorType, what:RefactorWhat, expected:String, async:Async, ?pos:PosInfos) { try { doRefactor(refactorType, what, [], pos).then(function(success:RefactorResult) { Assert.equals(expected, PrintHelper.printRefactorResult(success), pos); diff --git a/test/refactor/rename/RenameTestBase.hx b/test/refactor/rename/RenameTestBase.hx index d7c8771..b51ef44 100644 --- a/test/refactor/rename/RenameTestBase.hx +++ b/test/refactor/rename/RenameTestBase.hx @@ -24,7 +24,7 @@ class RenameTestBase extends TestBase { function failCanRename(what:RenameWhat, expected:String, ?async:Async, withTyper:Bool = false, ?pos:PosInfos) { try { doCanRename(what, [], withTyper, pos).then(function(success:RefactorResult) { - Assert.equals(expected, PrintHelper.printRefactorResult(success), pos); + Assert.equals(expected, PrintHelper.printRenameResult(success), pos); }).catchError(function(failure) { Assert.equals(expected, '$failure', pos); }).finally(function() { @@ -40,7 +40,7 @@ class RenameTestBase extends TestBase { function failRename(what:RenameWhat, expected:String, async:Async, withTyper:Bool = false, ?pos:PosInfos) { try { doRename(what, [], withTyper, pos).then(function(success:RefactorResult) { - Assert.equals(expected, PrintHelper.printRefactorResult(success), pos); + Assert.equals(expected, PrintHelper.printRenameResult(success), pos); }).catchError(function(failure) { Assert.equals(expected, '$failure', pos); }).finally(function() { diff --git a/testcases/classes/StaticUsing.hx b/testcases/classes/StaticUsing.hx index a48f813..166470e 100644 --- a/testcases/classes/StaticUsing.hx +++ b/testcases/classes/StaticUsing.hx @@ -26,6 +26,9 @@ class StaticUsing { } } +/** + * Context class + */ class Context { public static var printFunc:PrintFunc; } diff --git a/testcases/methods/LambdaExample.hx b/testcases/methods/LambdaExample.hx index 3a9525a..759fb7a 100644 --- a/testcases/methods/LambdaExample.hx +++ b/testcases/methods/LambdaExample.hx @@ -29,6 +29,11 @@ class LambdaExample { var numbers = [1, 2, 3]; var result = numbers.map(n -> cb(n, multiplier)); } + + function processSimple() { + var numbers = [1, 2, 3]; + var result = numbers.map(n -> n * n); + } } typedef ProcessCallback = (n:Int, m:Int) -> Int; From 4f8d8357ef354f0fcfe314f0202c9a0e9dc5b425 Mon Sep 17 00:00:00 2001 From: AlexHaxe Date: Thu, 28 Nov 2024 20:45:22 +0100 Subject: [PATCH 30/59] added missing file --- testcases/methods/Empty.hx | 0 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 testcases/methods/Empty.hx diff --git a/testcases/methods/Empty.hx b/testcases/methods/Empty.hx new file mode 100644 index 0000000..e69de29 From ac4b82cb9a6a217f700f6f59d0faed8637a5f710 Mon Sep 17 00:00:00 2001 From: AlexHaxe Date: Fri, 29 Nov 2024 01:04:13 +0100 Subject: [PATCH 31/59] fixed importInsertPos for files with comment headers --- src/refactor/discover/UsageCollector.hx | 2 ++ test/refactor/refactor/RefactorClassTest.hx | 18 ++++++++++++++++++ testcases/classes/DocModule.hx | 11 +++++++++++ testcases/classes/ForceRenameCrash.hx | 2 ++ testcases/classes/pack/UseDocModule.hx | 9 +++++++++ 5 files changed, 42 insertions(+) create mode 100644 testcases/classes/DocModule.hx create mode 100644 testcases/classes/pack/UseDocModule.hx diff --git a/src/refactor/discover/UsageCollector.hx b/src/refactor/discover/UsageCollector.hx index 3f69bc2..4d36f0f 100644 --- a/src/refactor/discover/UsageCollector.hx +++ b/src/refactor/discover/UsageCollector.hx @@ -100,6 +100,8 @@ class UsageCollector { pos = child.getPos().max + 1; case Kwd(KwdImport) | Kwd(KwdUsing): return child.pos.min; + case Comment(_) | CommentLine(_): + pos = child.pos.max + 1; default: return child.pos.min; } diff --git a/test/refactor/refactor/RefactorClassTest.hx b/test/refactor/refactor/RefactorClassTest.hx index 82f4028..61ae878 100644 --- a/test/refactor/refactor/RefactorClassTest.hx +++ b/test/refactor/refactor/RefactorClassTest.hx @@ -65,6 +65,24 @@ class RefactorClassTest extends RefactorTestBase { checkRefactor(RefactorExtractType, {fileName: "testcases/classes/StaticUsing.hx", posStart: 518, posEnd: 518}, edits, async); } + function testExtractTypeNotDocModule(async:Async) { + var edits:Array = [ + makeRemoveTestEdit("testcases/classes/DocModule.hx", 62, 110), + makeReplaceTestEdit("testcases/classes/ForceRenameCrash.hx", "classes.NotDocModule", 86, 116), + makeCreateTestEdit("testcases/classes/NotDocModule.hx"), + makeInsertTestEdit("testcases/classes/NotDocModule.hx", + "/**\n" + + " * file header\n" + + " */\n\n" + + "package classes;\n\n" + + "class NotDocModule {\n" + + " public function new() {}\n" + + "}", 0, true), + makeInsertTestEdit("testcases/classes/pack/UseDocModule.hx", "import classes.NotDocModule;\n", 23), + ]; + checkRefactor(RefactorExtractType, {fileName: "testcases/classes/DocModule.hx", posStart: 73, posEnd: 73}, edits, async); + } + function testExtractInterfaceBaseClass(async:Async) { var edits:Array = [ makeInsertTestEdit("testcases/classes/BaseClass.hx", " implements IBaseClass", 33), diff --git a/testcases/classes/DocModule.hx b/testcases/classes/DocModule.hx new file mode 100644 index 0000000..2e2c70b --- /dev/null +++ b/testcases/classes/DocModule.hx @@ -0,0 +1,11 @@ +/** + * file header + */ + +package classes; + +class DocModule {} + +class NotDocModule { + public function new() {} +} diff --git a/testcases/classes/ForceRenameCrash.hx b/testcases/classes/ForceRenameCrash.hx index 1c4a0cd..73c2909 100644 --- a/testcases/classes/ForceRenameCrash.hx +++ b/testcases/classes/ForceRenameCrash.hx @@ -2,6 +2,7 @@ package testcases.classes; import haxe.macro.Context; import haxe.macro.Expr; +import classes.DocModule.NotDocModule; abstract TestCrash(String) to String { public static inline function fail(data:Dynamic):TestCrash { @@ -23,5 +24,6 @@ class CrashInForLoop { inline final function updatePosts() { var posts = [for (vo in _vo.posts) (new A(vo))]; + new NotDocModule(); } } diff --git a/testcases/classes/pack/UseDocModule.hx b/testcases/classes/pack/UseDocModule.hx new file mode 100644 index 0000000..25f6b61 --- /dev/null +++ b/testcases/classes/pack/UseDocModule.hx @@ -0,0 +1,9 @@ +package classes.pack; + +import classes.DocModule; + +class UseDocModule { + function new() { + new NotDocModule(); + } +} From e627131c70aeca2be39ea62fc407ca613470d682 Mon Sep 17 00:00:00 2001 From: AlexHaxe Date: Sat, 30 Nov 2024 23:00:31 +0100 Subject: [PATCH 32/59] added indentation options for snippet fomratting added testcaes --- build.hxml | 1 + display.hxml | 2 + haxe_libraries/formatter.hxml | 3 + src/refactor/edits/Changelist.hx | 19 +- src/refactor/edits/FileEdit.hx | 4 +- src/refactor/edits/FormatType.hx | 6 + src/refactor/refactor/ExtractInterface.hx | 8 +- src/refactor/refactor/ExtractMethod.hx | 10 +- src/refactor/refactor/ExtractType.hx | 7 +- src/refactor/refactor/RefactorHelper.hx | 38 +++ .../extractmethod/ExtractMethodData.hx | 2 + src/refactor/rename/RenameAnonStructField.hx | 4 +- src/refactor/rename/RenameEnumField.hx | 2 +- src/refactor/rename/RenameField.hx | 2 +- src/refactor/rename/RenameHelper.hx | 12 +- src/refactor/rename/RenameImportAlias.hx | 4 +- .../rename/RenameModuleLevelStatic.hx | 8 +- src/refactor/rename/RenamePackage.hx | 12 +- src/refactor/rename/RenameScopedLocal.hx | 8 +- src/refactor/rename/RenameTypeName.hx | 6 +- test.hxml | 2 +- test/refactor/TestBase.hx | 26 +- test/refactor/refactor/RefactorClassTest.hx | 10 +- .../refactor/RefactorExtractMethodTest.hx | 274 ++++++++++++------ test/refactor/refactor/RefactorTypedefTest.hx | 2 +- test/refactor/rename/RenameTypedefTest.hx | 8 + testCoverage.hxml | 1 + testcases/methods/TestEditDoc.hx | 43 +++ testcases/typedefs/TestFormatter.hx | 31 ++ .../codedata/TestFormatterInputData.hx | 17 ++ 30 files changed, 429 insertions(+), 143 deletions(-) create mode 100644 haxe_libraries/formatter.hxml create mode 100644 src/refactor/edits/FormatType.hx create mode 100644 testcases/methods/TestEditDoc.hx create mode 100644 testcases/typedefs/TestFormatter.hx create mode 100644 testcases/typedefs/codedata/TestFormatterInputData.hx diff --git a/build.hxml b/build.hxml index 2451fc7..9996299 100644 --- a/build.hxml +++ b/build.hxml @@ -4,6 +4,7 @@ -lib tokentree -lib hxnodejs -lib hxargs +-lib formatter -D js-es=6 -js bin/rename.js diff --git a/display.hxml b/display.hxml index 850311d..3fe3bf8 100644 --- a/display.hxml +++ b/display.hxml @@ -1,10 +1,12 @@ -cp src -cp test +-cp testcases -lib haxeparser -lib tokentree -lib hxnodejs -lib hxargs +-lib formatter -lib utest -lib test-adapter diff --git a/haxe_libraries/formatter.hxml b/haxe_libraries/formatter.hxml new file mode 100644 index 0000000..00577ad --- /dev/null +++ b/haxe_libraries/formatter.hxml @@ -0,0 +1,3 @@ +# @install: lix --silent download "gh://github.com/HaxeCheckstyle/haxe-formatter#cad782a6571cea82c324fc97a83fb138b740b1bd" into formatter/1.17.1/github/cad782a6571cea82c324fc97a83fb138b740b1bd +-cp ${HAXE_LIBCACHE}/formatter/1.17.1/github/cad782a6571cea82c324fc97a83fb138b740b1bd/src +-D formatter=1.17.1 \ No newline at end of file diff --git a/src/refactor/edits/Changelist.hx b/src/refactor/edits/Changelist.hx index 849b539..6098c41 100644 --- a/src/refactor/edits/Changelist.hx +++ b/src/refactor/edits/Changelist.hx @@ -63,11 +63,11 @@ class Changelist { Sys.println('* delete file "$fileName"'); case Move(newFileName): Sys.println('* rename to file "$newFileName"'); - case InsertText(text, pos, format): - Sys.println('* insert text "$text" @${pos.start}-${pos.end}${format ? " with format" : ""}'); + case InsertText(text, pos, f): + Sys.println('* insert text "$text" @${pos.start}-${pos.end}${formatTypeToString(f)}'); Sys.println('+++ $text'); - case ReplaceText(text, pos, format): - Sys.println('* replace text with "$text" @${pos.start}-${pos.end}${format ? " with format" : ""}'); + case ReplaceText(text, pos, f): + Sys.println('* replace text with "$text" @${pos.start}-${pos.end}${formatTypeToString(f)}'); printDiffLines(pos, text); case RemoveText(pos): Sys.println('* remove text @${pos.start}-${pos.end}'); @@ -78,6 +78,17 @@ class Changelist { return (hasChanges ? DryRun : NoChange); } + function formatTypeToString(format:FormatType):String { + return switch (format) { + case NoFormat: + ""; + case Format(0): + " with format"; + case Format(indentOffset): + ' with format +indent=$indentOffset'; + } + } + function sortFileEdits(a:FileEdit, b:FileEdit):Int { var offsetA:Int = switch (a) { case CreateFile(_): 0; diff --git a/src/refactor/edits/FileEdit.hx b/src/refactor/edits/FileEdit.hx index 1bd459d..764edf1 100644 --- a/src/refactor/edits/FileEdit.hx +++ b/src/refactor/edits/FileEdit.hx @@ -6,7 +6,7 @@ enum FileEdit { CreateFile(newFileName:String); DeleteFile(fileName:String); Move(newFileName:String); - ReplaceText(text:String, pos:IdentifierPos, format:Bool); - InsertText(text:String, pos:IdentifierPos, format:Bool); + ReplaceText(text:String, pos:IdentifierPos, format:FormatType); + InsertText(text:String, pos:IdentifierPos, format:FormatType); RemoveText(pos:IdentifierPos); } diff --git a/src/refactor/edits/FormatType.hx b/src/refactor/edits/FormatType.hx new file mode 100644 index 0000000..407cffd --- /dev/null +++ b/src/refactor/edits/FormatType.hx @@ -0,0 +1,6 @@ +package refactor.edits; + +enum FormatType { + NoFormat; + Format(indentOffset:Int); +} diff --git a/src/refactor/refactor/ExtractInterface.hx b/src/refactor/refactor/ExtractInterface.hx index 544e79e..446dbc6 100644 --- a/src/refactor/refactor/ExtractInterface.hx +++ b/src/refactor/refactor/ExtractInterface.hx @@ -37,13 +37,13 @@ class ExtractInterface { final fieldDefinition:String = makeFields(extractData, context, fields); final interfaceText:String = 'interface ${extractData.newTypeName} {\n' + fieldDefinition + "}"; - changelist.addChange(extractData.newFileName, InsertText(fileHeader + interfaceText, {fileName: extractData.newFileName, start: 0, end: 0}, true), - null); + changelist.addChange(extractData.newFileName, + InsertText(fileHeader + interfaceText, {fileName: extractData.newFileName, start: 0, end: 0}, Format(0)), null); final implementsText:String = ' implements ${extractData.newTypeName}'; final pos:Position = findImplementsPos(extractData); - changelist.addChange(extractData.srcFile.name, InsertText(implementsText, {fileName: extractData.srcFile.name, start: pos.max, end: pos.max}, false), - null); + changelist.addChange(extractData.srcFile.name, + InsertText(implementsText, {fileName: extractData.srcFile.name, start: pos.max, end: pos.max}, NoFormat), null); return Promise.resolve(changelist.execute()); } diff --git a/src/refactor/refactor/ExtractMethod.hx b/src/refactor/refactor/ExtractMethod.hx index ac404bf..b98165a 100644 --- a/src/refactor/refactor/ExtractMethod.hx +++ b/src/refactor/refactor/ExtractMethod.hx @@ -69,7 +69,8 @@ class ExtractMethod { // replace selected code with call to newly extracted method final extractedCall:String = codeGen.makeCallSite(); changelist.addChange(context.what.fileName, - ReplaceText(extractedCall, {fileName: context.what.fileName, start: extractData.startToken.pos.min, end: extractData.endToken.pos.max}, true), + ReplaceText(extractedCall, {fileName: context.what.fileName, start: extractData.startToken.pos.min, end: extractData.endToken.pos.max}, + Format(extractData.snippetIndent)), null); // insert new method with function signature and body after current function @@ -80,7 +81,7 @@ class ExtractMethod { changelist.addChange(context.what.fileName, InsertText(functionDefinition + body, {fileName: context.what.fileName, start: extractData.newMethodOffset, end: extractData.newMethodOffset}, - true), + Format(extractData.functionIndent)), null); return changelist.execute(); @@ -211,6 +212,9 @@ class ExtractMethod { final name:String = nameToken.toString(); final newMethodName = '${name}Extract'; + final functionIndent:Int = RefactorHelper.calcIndentation(context, content, context.what.fileName, parentFunction.pos.min); + final snippetIndent:Int = RefactorHelper.calcIndentation(context, content, context.what.fileName, tokenStart.pos.min); + return { content: content, root: root, @@ -222,6 +226,8 @@ class ExtractMethod { isStatic: isStatic, isSingleExpr: isSingleExpr, functionType: functionType, + functionIndent: functionIndent, + snippetIndent: snippetIndent, }; } diff --git a/src/refactor/refactor/ExtractType.hx b/src/refactor/refactor/ExtractType.hx index 7cebd2b..2818a9c 100644 --- a/src/refactor/refactor/ExtractType.hx +++ b/src/refactor/refactor/ExtractType.hx @@ -48,7 +48,8 @@ class ExtractType { changelist.addChange(extractData.newFileName, CreateFile(extractData.newFileName), null); // copy file header, type and doc comment into new file - changelist.addChange(extractData.newFileName, InsertText(fileHeader + typeText, {fileName: extractData.newFileName, start: 0, end: 0}, true), null); + changelist.addChange(extractData.newFileName, InsertText(fileHeader + typeText, {fileName: extractData.newFileName, start: 0, end: 0}, Format(0)), + null); // find all places using type and update their imports findImportLocations(context, extractData, changelist); @@ -191,7 +192,7 @@ class ExtractType { final newFullName = oldPackageName + "." + extractData.name; var allUses:Array = context.nameMap.getIdentifiers(oldFullName); for (use in allUses) { - changelist.addChange(use.file.name, ReplaceText(newFullName, use.pos, false), use); + changelist.addChange(use.file.name, ReplaceText(newFullName, use.pos, NoFormat), use); } allUses = context.nameMap.getIdentifiers(extractData.name); var needsImport:Array = []; @@ -209,7 +210,7 @@ class ExtractType { final importNewModule = "import " + newFullName + ";\n"; for (file in needsImport) { var pos:IdentifierPos = {fileName: file.name, start: file.importInsertPos, end: file.importInsertPos}; - changelist.addChange(file.name, InsertText(importNewModule, pos, false), null); + changelist.addChange(file.name, InsertText(importNewModule, pos, NoFormat), null); } } } diff --git a/src/refactor/refactor/RefactorHelper.hx b/src/refactor/refactor/RefactorHelper.hx index 785a5e2..8181dd3 100644 --- a/src/refactor/refactor/RefactorHelper.hx +++ b/src/refactor/refactor/RefactorHelper.hx @@ -1,5 +1,6 @@ package refactor.refactor; +import formatter.Formatter; import refactor.CacheAndTyperContext.ByteToCharConverterFunc; import refactor.discover.File; @@ -135,6 +136,43 @@ class RefactorHelper { }); return headers.shift(); } + + public static function calcIndentation(context:CacheAndTyperContext, content:String, fileName:String, pos:Int):Int { + final config = Formatter.loadConfig(fileName); + var startPos = pos - 200; + if (startPos < 0) { + startPos = 0; + } + + var text = extractText(context.converter, content, startPos, pos); + var indexLF = text.lastIndexOf("\n"); + var indexCR = text.lastIndexOf("\r"); + if (indexCR < 0) { + indexCR = indexLF; + } + if (indexLF < 0) { + indexLF = indexCR; + } + if (indexLF < 0) { + return 0; + } + final index = if (indexLF > indexCR) indexLF else indexCR; + final line = text.substr(index); + final regex = ~/([\t ]+).*/; + if (!regex.match(line)) { + return 0; + } + var match = regex.matched(1); + var character = config.indentation.character; + if (character.toLowerCase() == "tab") { + character = "\t"; + } + var parts = match.split(character); + if (parts.length <= 0) { + return 0; + } + return parts.length - 1; + } } typedef TokensAtPos = { diff --git a/src/refactor/refactor/extractmethod/ExtractMethodData.hx b/src/refactor/refactor/extractmethod/ExtractMethodData.hx index 7a66b65..4920578 100644 --- a/src/refactor/refactor/extractmethod/ExtractMethodData.hx +++ b/src/refactor/refactor/extractmethod/ExtractMethodData.hx @@ -11,6 +11,8 @@ typedef ExtractMethodData = { var isStatic:Bool; var isSingleExpr:Bool; var functionType:LocalFunctionType; + var functionIndent:Int; + var snippetIndent:Int; } enum LocalFunctionType { diff --git a/src/refactor/rename/RenameAnonStructField.hx b/src/refactor/rename/RenameAnonStructField.hx index b934062..797da04 100644 --- a/src/refactor/rename/RenameAnonStructField.hx +++ b/src/refactor/rename/RenameAnonStructField.hx @@ -13,7 +13,7 @@ class RenameAnonStructField { fields:Array):Promise { var changelist:Changelist = new Changelist(context); - changelist.addChange(identifier.pos.fileName, ReplaceText(context.what.toName, identifier.pos, false), identifier); + changelist.addChange(identifier.pos.fileName, ReplaceText(context.what.toName, identifier.pos, NoFormat), identifier); return renameFieldsOfType(context, changelist, identifier.defineType, fields, identifier.name).then(function(result):RefactorResult { return changelist.execute(); @@ -60,7 +60,7 @@ class RenameAnonStructField { continue; case Global | SamePackage | Imported | ImportedWithAlias(_) | StarImported: } - changelist.addChange(use.pos.fileName, ReplaceText(context.what.toName, use.pos, false), use); + changelist.addChange(use.pos.fileName, ReplaceText(context.what.toName, use.pos, NoFormat), use); } promises.push(findAllExtending(context, changelist, type, fields, fromName)); diff --git a/src/refactor/rename/RenameEnumField.hx b/src/refactor/rename/RenameEnumField.hx index 788e759..816c919 100644 --- a/src/refactor/rename/RenameEnumField.hx +++ b/src/refactor/rename/RenameEnumField.hx @@ -9,7 +9,7 @@ import refactor.rename.RenameContext; class RenameEnumField { public static function refactorEnumField(context:RenameContext, file:File, identifier:Identifier):Promise { var changelist:Changelist = new Changelist(context); - changelist.addChange(identifier.pos.fileName, ReplaceText(context.what.toName, identifier.pos, false), identifier); + changelist.addChange(identifier.pos.fileName, ReplaceText(context.what.toName, identifier.pos, NoFormat), identifier); var packName:String = file.getPackage(); var mainModuleName:String = file.getMainModulName(); diff --git a/src/refactor/rename/RenameField.hx b/src/refactor/rename/RenameField.hx index a57da3b..ee072be 100644 --- a/src/refactor/rename/RenameField.hx +++ b/src/refactor/rename/RenameField.hx @@ -112,7 +112,7 @@ class RenameField { start: access.pos.start + prefix.length, end: access.pos.start + prefix.length + from.length }; - changelist.addChange(access.pos.fileName, ReplaceText(to, pos, false), access); + changelist.addChange(access.pos.fileName, ReplaceText(to, pos, NoFormat), access); } } diff --git a/src/refactor/rename/RenameHelper.hx b/src/refactor/rename/RenameHelper.hx index 4f93a2d..2ca00b8 100644 --- a/src/refactor/rename/RenameHelper.hx +++ b/src/refactor/rename/RenameHelper.hx @@ -11,14 +11,14 @@ import refactor.typing.TypingHelper; class RenameHelper { public static function replaceTextWithPrefix(use:Identifier, prefix:String, to:String, changelist:Changelist) { if (prefix.length <= 0) { - changelist.addChange(use.pos.fileName, ReplaceText(to, use.pos, false), use); + changelist.addChange(use.pos.fileName, ReplaceText(to, use.pos, NoFormat), use); } else { var pos:IdentifierPos = { fileName: use.pos.fileName, start: use.pos.start + prefix.length, end: use.pos.end }; - changelist.addChange(use.pos.fileName, ReplaceText(to, pos, false), use); + changelist.addChange(use.pos.fileName, ReplaceText(to, pos, NoFormat), use); } } @@ -59,12 +59,12 @@ class RenameHelper { case null: case ClasspathType(type, _): if (use.parent.name == type.name.name) { - changelist.addChange(use.pos.fileName, ReplaceText(context.what.toName, use.pos, false), use); + changelist.addChange(use.pos.fileName, ReplaceText(context.what.toName, use.pos, NoFormat), use); continue; } case LibType(name, _): if (use.parent.name == name) { - changelist.addChange(use.pos.fileName, ReplaceText(context.what.toName, use.pos, false), use); + changelist.addChange(use.pos.fileName, ReplaceText(context.what.toName, use.pos, NoFormat), use); continue; } case StructType(fields): @@ -123,7 +123,7 @@ class RenameHelper { end: use.pos.end }; pos.end = pos.start + fromName.length; - changelist.addChange(use.pos.fileName, ReplaceText(context.what.toName, pos, false), use); + changelist.addChange(use.pos.fileName, ReplaceText(context.what.toName, pos, NoFormat), use); } var search:SearchTypeOf = { name: name, @@ -176,7 +176,7 @@ class RenameHelper { end: use.pos.end }; pos.end = pos.start + fromName.length; - changelist.addChange(use.pos.fileName, ReplaceText(context.what.toName, pos, false), use); + changelist.addChange(use.pos.fileName, ReplaceText(context.what.toName, pos, NoFormat), use); } case LibType(_, _) | UnknownType(_): trace("TODO " + typeHint.typeHintToString()); diff --git a/src/refactor/rename/RenameImportAlias.hx b/src/refactor/rename/RenameImportAlias.hx index e3c3f79..5bf933c 100644 --- a/src/refactor/rename/RenameImportAlias.hx +++ b/src/refactor/rename/RenameImportAlias.hx @@ -14,12 +14,12 @@ class RenameImportAlias { var changelist:Changelist = new Changelist(context); for (use in allUses) { if (use.file.name == file.name) { - changelist.addChange(use.pos.fileName, ReplaceText(context.what.toName, use.pos,false), use); + changelist.addChange(use.pos.fileName, ReplaceText(context.what.toName, use.pos, NoFormat), use); continue; } if (isImportHx) { if (use.file.importHxFile.name == file.name) { - changelist.addChange(use.pos.fileName, ReplaceText(context.what.toName, use.pos, false), use); + changelist.addChange(use.pos.fileName, ReplaceText(context.what.toName, use.pos, NoFormat), use); } } } diff --git a/src/refactor/rename/RenameModuleLevelStatic.hx b/src/refactor/rename/RenameModuleLevelStatic.hx index 3faa63f..6a6832b 100644 --- a/src/refactor/rename/RenameModuleLevelStatic.hx +++ b/src/refactor/rename/RenameModuleLevelStatic.hx @@ -32,7 +32,7 @@ class RenameModuleLevelStatic { if ((use.pos.fileName != file.name) && (!filesWithStaticImport.contains(use.pos.fileName))) { continue; } - changelist.addChange(use.pos.fileName, ReplaceText(context.what.toName, use.pos, false), use); + changelist.addChange(use.pos.fileName, ReplaceText(context.what.toName, use.pos, NoFormat), use); } // all uses in files importing with package.mainmodul @@ -45,7 +45,7 @@ class RenameModuleLevelStatic { for (u in uses) { switch (u.type) { case Call(false) | Access: - changelist.addChange(u.pos.fileName, ReplaceText(context.what.toName, u.pos, false), u); + changelist.addChange(u.pos.fileName, ReplaceText(context.what.toName, u.pos, NoFormat), u); case ScopedLocal(_): default: } @@ -65,7 +65,7 @@ class RenameModuleLevelStatic { if (use.type.match(ImportModul)) { filesWithStaticImport.push(use.pos.fileName); } - changelist.addChange(use.pos.fileName, ReplaceText(replaceName, use.pos, false), use); + changelist.addChange(use.pos.fileName, ReplaceText(replaceName, use.pos, NoFormat), use); } var searchNameDot:String = '$searchName.'; var replaceNameDot:String = '$replaceName.'; @@ -76,7 +76,7 @@ class RenameModuleLevelStatic { start: use.pos.start, end: use.pos.start + searchNameDot.length } - changelist.addChange(use.pos.fileName, ReplaceText(replaceNameDot, pos, false), use); + changelist.addChange(use.pos.fileName, ReplaceText(replaceNameDot, pos, NoFormat), use); } } } diff --git a/src/refactor/rename/RenamePackage.hx b/src/refactor/rename/RenamePackage.hx index 09b75f2..bf8d472 100644 --- a/src/refactor/rename/RenamePackage.hx +++ b/src/refactor/rename/RenamePackage.hx @@ -17,16 +17,16 @@ class RenamePackage { var packageName:String = file.getPackage(); if (packageName.length > 0) { packageNamePrefix = file.packageIdentifier.name + "."; - changelist.addChange(file.name, ReplaceText(context.what.toName, file.packageIdentifier.pos, false), identifier); + changelist.addChange(file.name, ReplaceText(context.what.toName, file.packageIdentifier.pos, NoFormat), identifier); } else { - changelist.addChange(file.name, InsertText('package ${context.what.toName};\n', {fileName: file.name, start: 0, end: 0}, false), identifier); + changelist.addChange(file.name, InsertText('package ${context.what.toName};\n', {fileName: file.name, start: 0, end: 0}, NoFormat), identifier); } var newMainModulName:String = context.what.toName + "." + mainTypeName; var mainModule:String = packageNamePrefix + mainTypeName; var allUses:Array = context.nameMap.getIdentifiers(mainModule); for (use in allUses) { - changelist.addChange(use.pos.fileName, ReplaceText(newMainModulName, use.pos, false), use); + changelist.addChange(use.pos.fileName, ReplaceText(newMainModulName, use.pos, NoFormat), use); } for (type in file.typeList) { if (mainTypeName == type.name.name) { @@ -38,14 +38,14 @@ class RenamePackage { var newFullModulName:String = context.what.toName + "." + typeName; allUses = context.nameMap.getIdentifiers(fullModulName); for (use in allUses) { - changelist.addChange(use.pos.fileName, ReplaceText(newFullModulName, use.pos, false), use); + changelist.addChange(use.pos.fileName, ReplaceText(newFullModulName, use.pos, NoFormat), use); } fullModulName = packageNamePrefix + mainTypeName + "." + typeName; newFullModulName = context.what.toName + "." + mainTypeName + "." + typeName; allUses = context.nameMap.getIdentifiers(fullModulName); for (use in allUses) { - changelist.addChange(use.pos.fileName, ReplaceText(newFullModulName, use.pos, false), use); + changelist.addChange(use.pos.fileName, ReplaceText(newFullModulName, use.pos, NoFormat), use); } } var uniqueFiles:Array = []; @@ -66,7 +66,7 @@ class RenamePackage { case None: case Global | SamePackage | StarImported: var importPos:IdentifierPos = {fileName: use.pos.fileName, start: use.file.importInsertPos, end: use.file.importInsertPos} - changelist.addChange(use.pos.fileName, InsertText('import $newMainModulName;\n', importPos, false), use); + changelist.addChange(use.pos.fileName, InsertText('import $newMainModulName;\n', importPos, NoFormat), use); uniqueFiles.push(use.pos.fileName); case Imported: case ImportedWithAlias(_): diff --git a/src/refactor/rename/RenameScopedLocal.hx b/src/refactor/rename/RenameScopedLocal.hx index aff938e..fa833bd 100644 --- a/src/refactor/rename/RenameScopedLocal.hx +++ b/src/refactor/rename/RenameScopedLocal.hx @@ -12,7 +12,7 @@ class RenameScopedLocal { var changelist:Changelist = new Changelist(context); var identifierDot:String = identifier.name + "."; var toNameDot:String = context.what.toName + "."; - changelist.addChange(identifier.pos.fileName, ReplaceText(context.what.toName, identifier.pos, false), identifier); + changelist.addChange(identifier.pos.fileName, ReplaceText(context.what.toName, identifier.pos, NoFormat), identifier); var allUses:Array = identifier.defineType.findAllIdentifiers(function(ident:Identifier) { if (ident.pos.start < scopeStart) { @@ -53,7 +53,7 @@ class RenameScopedLocal { start: use.pos.start, end: use.pos.start }; - changelist.addChange(use.pos.fileName, InsertText("this.", pos, false), use); + changelist.addChange(use.pos.fileName, InsertText("this.", pos, NoFormat), use); case ScopedLocal(_, _): return Promise.reject('local var "${context.what.toName}" exists'); default: @@ -91,7 +91,7 @@ class RenameScopedLocal { } if (use.name == identifier.name) { // exact match - changelist.addChange(use.pos.fileName, ReplaceText(context.what.toName, use.pos, false), use); + changelist.addChange(use.pos.fileName, ReplaceText(context.what.toName, use.pos, NoFormat), use); } else { // starts with identifier + "." -> replace only identifier part var pos:IdentifierPos = { @@ -99,7 +99,7 @@ class RenameScopedLocal { start: use.pos.start, end: use.pos.start + identifier.pos.end - identifier.pos.start }; - changelist.addChange(use.pos.fileName, ReplaceText(context.what.toName, pos, false), use); + changelist.addChange(use.pos.fileName, ReplaceText(context.what.toName, pos, NoFormat), use); } } return Promise.resolve(changelist.execute()); diff --git a/src/refactor/rename/RenameTypeName.hx b/src/refactor/rename/RenameTypeName.hx index 0efe721..ca86fe1 100644 --- a/src/refactor/rename/RenameTypeName.hx +++ b/src/refactor/rename/RenameTypeName.hx @@ -22,7 +22,7 @@ class RenameTypeName { changelist.addChange(file.name, Move(newFileName), null); } // replace self - changelist.addChange(identifier.pos.fileName, ReplaceText(context.what.toName, identifier.pos, false), identifier); + changelist.addChange(identifier.pos.fileName, ReplaceText(context.what.toName, identifier.pos, NoFormat), identifier); var allUses:Array; // find all fully qualified modul names of type @@ -73,7 +73,7 @@ class RenameTypeName { return; } if (use.name == identifier.name) { - changelist.addChange(use.pos.fileName, ReplaceText(context.what.toName, use.pos, false), use); + changelist.addChange(use.pos.fileName, ReplaceText(context.what.toName, use.pos, NoFormat), use); return; } if (use.name.startsWith('${identifier.name}.')) { @@ -82,7 +82,7 @@ class RenameTypeName { start: use.pos.start, end: use.pos.start + identifier.name.length } - changelist.addChange(use.pos.fileName, ReplaceText(context.what.toName, newPos, false), use); + changelist.addChange(use.pos.fileName, ReplaceText(context.what.toName, newPos, NoFormat), use); } })); } diff --git a/test.hxml b/test.hxml index d5614d1..4cadc3e 100644 --- a/test.hxml +++ b/test.hxml @@ -5,11 +5,11 @@ -lib haxeparser -lib tokentree -lib hxargs +-lib formatter -lib utest -lib hxnodejs -lib test-adapter - --main TestMain --js out/tests.js diff --git a/test/refactor/TestBase.hx b/test/refactor/TestBase.hx index 2876aa3..dfa0261 100644 --- a/test/refactor/TestBase.hx +++ b/test/refactor/TestBase.hx @@ -18,6 +18,7 @@ import refactor.discover.TypeList; import refactor.discover.UsageCollector; import refactor.discover.UsageContext; import refactor.edits.FileEdit; +import refactor.edits.FormatType; import refactor.typing.TypeHintType; class TestBase implements ITest { @@ -67,15 +68,28 @@ class TestBase implements ITest { 'Delete $fileName'; case Move(newFileName): 'Move $newFileName'; - case ReplaceText(text, pos, format): - 'ReplaceText "$text" ${pos.fileName}@${pos.start}-${pos.end}${format ? " with format" : ""}'; - case InsertText(text, pos, format): - 'InsertText "$text" ${pos.fileName}@${pos.start}-${pos.end}${format ? " with format" : ""}'; + case ReplaceText(text, pos, f): + final formatText = formatTypeToString(f); + 'ReplaceText "$text" ${pos.fileName}@${pos.start}-${pos.end}$formatText'; + case InsertText(text, pos, f): + final formatText = formatTypeToString(f); + 'InsertText "$text" ${pos.fileName}@${pos.start}-${pos.end}$formatText'; case RemoveText(pos): 'RemoveText ${pos.fileName}@${pos.start}-${pos.end}'; } } + function formatTypeToString(format:FormatType):String { + return switch (format) { + case NoFormat: + ""; + case Format(0): + " with format"; + case Format(indentOffset): + ' with format +indent=$indentOffset'; + } + } + function makeCreateTestEdit(newFileName, ?pos:PosInfos):TestEdit { return { fileName: newFileName, @@ -100,7 +114,7 @@ class TestBase implements ITest { } } - function makeReplaceTestEdit(fileName:String, text:String, start:Int, end:Int, format:Bool = false, ?pos:PosInfos):TestEdit { + function makeReplaceTestEdit(fileName:String, text:String, start:Int, end:Int, format:FormatType = NoFormat, ?pos:PosInfos):TestEdit { return { fileName: fileName, edit: ReplaceText(text, {fileName: fileName, start: start, end: end}, format), @@ -108,7 +122,7 @@ class TestBase implements ITest { } } - function makeInsertTestEdit(fileName:String, text:String, insertPos:Int, format:Bool = false, ?pos:PosInfos):TestEdit { + function makeInsertTestEdit(fileName:String, text:String, insertPos:Int, format:FormatType = NoFormat, ?pos:PosInfos):TestEdit { return { fileName: fileName, edit: InsertText(text, {fileName: fileName, start: insertPos, end: insertPos}, format), diff --git a/test/refactor/refactor/RefactorClassTest.hx b/test/refactor/refactor/RefactorClassTest.hx index 61ae878..b7cfeb9 100644 --- a/test/refactor/refactor/RefactorClassTest.hx +++ b/test/refactor/refactor/RefactorClassTest.hx @@ -21,7 +21,7 @@ class RefactorClassTest extends RefactorTestBase { var edits:Array = [ makeRemoveTestEdit("testcases/classes/ChildClass.hx", 860, 901), makeCreateTestEdit("testcases/classes/ListOfChilds.hx"), - makeInsertTestEdit("testcases/classes/ListOfChilds.hx", "package classes;\n\ntypedef ListOfChilds = Array;", 0, true), + makeInsertTestEdit("testcases/classes/ListOfChilds.hx", "package classes;\n\ntypedef ListOfChilds = Array;", 0, Format(0)), makeInsertTestEdit("testcases/classes/pack/UseChild.hx", "import classes.ListOfChilds;\n", 23), ]; checkRefactor(RefactorExtractType, {fileName: "testcases/classes/ChildClass.hx", posStart: 873, posEnd: 873}, edits, async); @@ -40,7 +40,7 @@ class RefactorClassTest extends RefactorTestBase { + " return Promise.resolve(text);\n" + " }\n" + "}", - 0, true), + 0, Format(0)), makeInsertTestEdit("testcases/classes/pack/UsePrinter.hx", "import classes.TextLoader;\n", 23), ]; checkRefactor(RefactorExtractType, {fileName: "testcases/classes/Printer.hx", posStart: 1273, posEnd: 1283}, edits, async); @@ -59,7 +59,7 @@ class RefactorClassTest extends RefactorTestBase { + "class Context {\n" + " public static var printFunc:PrintFunc;\n" + "}", - 0, true), + 0, Format(0)), makeRemoveTestEdit("testcases/classes/StaticUsing.hx", 484, 566), ]; checkRefactor(RefactorExtractType, {fileName: "testcases/classes/StaticUsing.hx", posStart: 518, posEnd: 518}, edits, async); @@ -77,7 +77,7 @@ class RefactorClassTest extends RefactorTestBase { + "package classes;\n\n" + "class NotDocModule {\n" + " public function new() {}\n" - + "}", 0, true), + + "}", 0, Format(0)), makeInsertTestEdit("testcases/classes/pack/UseDocModule.hx", "import classes.NotDocModule;\n", 23), ]; checkRefactor(RefactorExtractType, {fileName: "testcases/classes/DocModule.hx", posStart: 73, posEnd: 73}, edits, async); @@ -96,7 +96,7 @@ class RefactorClassTest extends RefactorTestBase { + " function doSomething5(d:Array):Void;\n" + " function doSomething6(d:Array):Void;\n" + "}", - 0, true), + 0, Format(0)), ]; checkRefactor(RefactorExtractInterface, {fileName: "testcases/classes/BaseClass.hx", posStart: 27, posEnd: 27}, edits, async); } diff --git a/test/refactor/refactor/RefactorExtractMethodTest.hx b/test/refactor/refactor/RefactorExtractMethodTest.hx index 6017404..2e0639b 100644 --- a/test/refactor/refactor/RefactorExtractMethodTest.hx +++ b/test/refactor/refactor/RefactorExtractMethodTest.hx @@ -40,31 +40,31 @@ class RefactorExtractMethodTest extends RefactorTestBase { function testSimpleNoReturns(async:Async) { var edits:Array = [ - makeReplaceTestEdit("testcases/methods/Main.hx", "noReturnsExtract();\n", 94, 131, true), + makeReplaceTestEdit("testcases/methods/Main.hx", "noReturnsExtract();\n", 94, 131, Format(2)), makeInsertTestEdit("testcases/methods/Main.hx", "function noReturnsExtract():Void {\n" + "trace(\"hello 2\");\n" + " trace(\"hello 3\");\n" - + "}\n", 155, true), + + "}\n", 155, Format(1)), ]; checkRefactor(RefactorExtractMethod, {fileName: "testcases/methods/Main.hx", posStart: 93, posEnd: 131}, edits, async); } function testSimpleNoReturnsStatic(async:Async) { var edits:Array = [ - makeReplaceTestEdit("testcases/methods/Main.hx", "noReturnsStaticExtract();\n", 222, 259, true), + makeReplaceTestEdit("testcases/methods/Main.hx", "noReturnsStaticExtract();\n", 222, 259, Format(2)), makeInsertTestEdit("testcases/methods/Main.hx", "static function noReturnsStaticExtract():Void {\n" + "trace(\"hello 2\");\n" + " trace(\"hello 3\");\n" - + "}\n", 283, true), + + "}\n", 283, Format(1)), ]; checkRefactor(RefactorExtractMethod, {fileName: "testcases/methods/Main.hx", posStart: 221, posEnd: 259}, edits, async); } function testEmptyReturns(async:Async) { var edits:Array = [ - makeReplaceTestEdit("testcases/methods/Main.hx", "if (!emptyReturnsExtract(cond1, cond2)) {\n" + "return;\n" + "}\n", 342, 439, true), + makeReplaceTestEdit("testcases/methods/Main.hx", "if (!emptyReturnsExtract(cond1, cond2)) {\n" + "return;\n" + "}\n", 342, 439, Format(2)), makeInsertTestEdit("testcases/methods/Main.hx", "function emptyReturnsExtract(cond1:Bool, cond2:Bool):Bool {\n" + "if (cond1) {\n" @@ -77,14 +77,14 @@ class RefactorExtractMethodTest extends RefactorTestBase { + " trace(\"hello 2\");\n" + "return true;\n" + "}\n", - 483, true), + 483, Format(1)), ]; checkRefactor(RefactorExtractMethod, {fileName: "testcases/methods/Main.hx", posStart: 341, posEnd: 439}, edits, async); } function testCalculateTotal(async:Async) { var edits:Array = [ - makeReplaceTestEdit("testcases/methods/Main.hx", "total = calculateTotalExtract(items, total);\n", 569, 720, true), + makeReplaceTestEdit("testcases/methods/Main.hx", "total = calculateTotalExtract(items, total);\n", 569, 720, Format(2)), makeInsertTestEdit("testcases/methods/Main.hx", "function calculateTotalExtract(items:Array, total:Float):Float {\n" + "// Selected code block to extract\n" @@ -95,7 +95,7 @@ class RefactorExtractMethodTest extends RefactorTestBase { + " }\n" + "return total;\n" + "}\n", - 741, true), + 741, Format(1)), ]; checkRefactor(RefactorExtractMethod, {fileName: "testcases/methods/Main.hx", posStart: 568, posEnd: 720}, edits, async); } @@ -109,7 +109,7 @@ class RefactorExtractMethodTest extends RefactorTestBase { + "price = data.price;\n" + "quantity = data.quantity;\n" + "}\n", - 630, 686, true), + 630, 686, Format(3)), makeInsertTestEdit("testcases/methods/Main.hx", "function calculateTotalExtract(item:Item):{price:Float, quantity:Float} {\n" + "var price = item.price;\n" @@ -119,7 +119,7 @@ class RefactorExtractMethodTest extends RefactorTestBase { + "quantity: quantity,\n" + "};\n" + "}\n", - 741, true), + 741, Format(1)), ]; addTypeHint("testcases/methods/Main.hx", 613, LibType("Item", "Item", [])); addTypeHint("testcases/methods/Main.hx", 638, LibType("Float", "Float", [])); @@ -129,7 +129,7 @@ class RefactorExtractMethodTest extends RefactorTestBase { function testCalculateTotalWithLastReturn(async:Async) { var edits:Array = [ - makeReplaceTestEdit("testcases/methods/Main.hx", "return calculateTotalExtract(items, total);\n", 569, 737, true), + makeReplaceTestEdit("testcases/methods/Main.hx", "return calculateTotalExtract(items, total);\n", 569, 737, Format(2)), makeInsertTestEdit("testcases/methods/Main.hx", "function calculateTotalExtract(items:Array, total:Float):Float {\n" + "// Selected code block to extract\n" @@ -141,7 +141,7 @@ class RefactorExtractMethodTest extends RefactorTestBase { + "\n" + " return total;\n" + "}\n", - 741, true), + 741, Format(1)), ]; addTypeHint("testcases/methods/Main.hx", 514, LibType("Float", "Float", [])); addTypeHint("testcases/methods/Main.hx", 735, LibType("Float", "Float", [])); @@ -150,7 +150,7 @@ class RefactorExtractMethodTest extends RefactorTestBase { function testProcessUserWithLastReturn(async:Async) { var edits:Array = [ - makeReplaceTestEdit("testcases/methods/Main.hx", "return calculateTotal2Extract(items, total);\n", 1081, 1295, true), + makeReplaceTestEdit("testcases/methods/Main.hx", "return calculateTotal2Extract(items, total);\n", 1081, 1295, Format(2)), makeInsertTestEdit("testcases/methods/Main.hx", "function calculateTotal2Extract(items:Array, total:Float) {\n" + "// Selected code block to extract\n" @@ -164,7 +164,7 @@ class RefactorExtractMethodTest extends RefactorTestBase { + " }\n\n" + " return total;\n" + "}\n", - 1299, true), + 1299, Format(1)), ]; checkRefactor(RefactorExtractMethod, {fileName: "testcases/methods/Main.hx", posStart: 1080, posEnd: 1295}, edits, async); } @@ -181,7 +181,7 @@ class RefactorExtractMethodTest extends RefactorTestBase { + "total = result.data;\n" + "}\n" + "}\n", - 1081, 1278, true), + 1081, 1278, Format(2)), makeInsertTestEdit("testcases/methods/Main.hx", "function calculateTotal2Extract(items:Array, total:Float):{ret:haxe.ds.Option, ?data:Float} {\n" + "// Selected code block to extract\n" @@ -195,7 +195,7 @@ class RefactorExtractMethodTest extends RefactorTestBase { + " }\n" + "return {ret: None, data: total};\n" + "}\n", - 1299, true), + 1299, Format(1)), ]; addTypeHint("testcases/methods/Main.hx", 1026, FunctionType([LibType("Array", "Array", [LibType("Item", "Item", [])])], LibType("Float", "Float", []))); checkRefactor(RefactorExtractMethod, {fileName: "testcases/methods/Main.hx", posStart: 1080, posEnd: 1278}, edits, async); @@ -203,7 +203,7 @@ class RefactorExtractMethodTest extends RefactorTestBase { function testCalcConditionalLevel(async:Async) { var edits:Array = [ - makeReplaceTestEdit("testcases/methods/Main.hx", "count = calcConditionalLevelExtract(token, count);\n", 1388, 1543, true), + makeReplaceTestEdit("testcases/methods/Main.hx", "count = calcConditionalLevelExtract(token, count);\n", 1388, 1543, Format(2)), makeInsertTestEdit("testcases/methods/Main.hx", "function calcConditionalLevelExtract(token:tokentree.TokenTree, count:Int):Int {\n" + "while ((token != null) && (token.tok != Root)) {\n" @@ -216,14 +216,14 @@ class RefactorExtractMethodTest extends RefactorTestBase { + " }\n" + "return count;\n" + "}\n", - 1600, true), + 1600, Format(1)), ]; checkRefactor(RefactorExtractMethod, {fileName: "testcases/methods/Main.hx", posStart: 1387, posEnd: 1543}, edits, async); } function testCalcConditionalLevelWithVar(async:Async) { var edits:Array = [ - makeReplaceTestEdit("testcases/methods/Main.hx", "var count = calcConditionalLevelExtract(token);\n", 1366, 1543, true), + makeReplaceTestEdit("testcases/methods/Main.hx", "var count = calcConditionalLevelExtract(token);\n", 1366, 1543, Format(2)), makeInsertTestEdit("testcases/methods/Main.hx", "function calcConditionalLevelExtract(token:tokentree.TokenTree):Int {\n" + "var count:Int = -1;\n" @@ -237,14 +237,14 @@ class RefactorExtractMethodTest extends RefactorTestBase { + " }\n" + "return count;\n" + "}\n", - 1600, true), + 1600, Format(1)), ]; checkRefactor(RefactorExtractMethod, {fileName: "testcases/methods/Main.hx", posStart: 1365, posEnd: 1543}, edits, async); } function testAllEmptyReturns(async:Async) { var edits:Array = [ - makeReplaceTestEdit("testcases/methods/Main.hx", "allEmptyReturnsExtract();\n", 1722, 1809, true), + makeReplaceTestEdit("testcases/methods/Main.hx", "allEmptyReturnsExtract();\n", 1722, 1809, Format(2)), makeInsertTestEdit("testcases/methods/Main.hx", "function allEmptyReturnsExtract():Void {\n" + "trace(\"hello 1\");\n" @@ -253,17 +253,17 @@ class RefactorExtractMethodTest extends RefactorTestBase { + " trace(\"hello 4\");\n" + " return;\n" + "}\n", - 1813, true), + 1813, Format(1)), ]; checkRefactor(RefactorExtractMethod, {fileName: "testcases/methods/Main.hx", posStart: 1721, posEnd: 1809}, edits, async); } function testStringInterpolation(async:Async) { var edits:Array = [ - makeReplaceTestEdit("testcases/methods/Main.hx", "return interpolationExtract(data);\n", 1884, 1937, true), + makeReplaceTestEdit("testcases/methods/Main.hx", "return interpolationExtract(data);\n", 1884, 1937, Format(2)), makeInsertTestEdit("testcases/methods/Main.hx", "static function interpolationExtract(data:Dynamic):String {\n" + "return cast '${data.a}_${data.b}_${data.c}_${false}';\n" + "}\n", 1941, - true), + Format(1)), ]; addTypeHint("testcases/methods/Main.hx", 1857, LibType("String", "String", [])); checkRefactor(RefactorExtractMethod, {fileName: "testcases/methods/Main.hx", posStart: 1887, posEnd: 1937}, edits, async); @@ -271,7 +271,7 @@ class RefactorExtractMethodTest extends RefactorTestBase { function testDemoSimple(async:Async) { var edits:Array = [ - makeReplaceTestEdit("testcases/methods/Demo.hx", "doSomethingExtract();\n", 161, 252, true), + makeReplaceTestEdit("testcases/methods/Demo.hx", "doSomethingExtract();\n", 161, 252, Format(2)), makeInsertTestEdit("testcases/methods/Demo.hx", "function doSomethingExtract():Void {\n" + "doNothing();\n" @@ -280,14 +280,14 @@ class RefactorExtractMethodTest extends RefactorTestBase { + " trace(\"yep, still here\");\n" + " doNothing();\n" + "}\n", - 467, true), + 467, Format(1)), ]; checkRefactor(RefactorExtractMethod, {fileName: "testcases/methods/Demo.hx", posStart: 160, posEnd: 252}, edits, async); } function testDemoSwitch(async:Async) { var edits:Array = [ - makeReplaceTestEdit("testcases/methods/Demo.hx", "doSomethingExtract(cond, val, text);\n", 262, 463, true), + makeReplaceTestEdit("testcases/methods/Demo.hx", "doSomethingExtract(cond, val, text);\n", 262, 463, Format(2)), makeInsertTestEdit("testcases/methods/Demo.hx", "function doSomethingExtract(cond:Bool, val:Int, text:Null):Float {\n" + "return switch [cond, val] {\n" @@ -301,7 +301,7 @@ class RefactorExtractMethodTest extends RefactorTestBase { + " std.Math.NEGATIVE_INFINITY;\n" + " }\n" + "}\n", - 467, true), + 467, Format(1)), ]; addTypeHint("testcases/methods/Demo.hx", 61, LibType("Float", "Float", [])); checkRefactor(RefactorExtractMethod, {fileName: "testcases/methods/Demo.hx", posStart: 262, posEnd: 463}, edits, async); @@ -309,7 +309,7 @@ class RefactorExtractMethodTest extends RefactorTestBase { function testDemoReturnSwitch(async:Async) { var edits:Array = [ - makeReplaceTestEdit("testcases/methods/Demo.hx", "return doSomethingExtract(cond, val, text);\n", 255, 463, true), + makeReplaceTestEdit("testcases/methods/Demo.hx", "return doSomethingExtract(cond, val, text);\n", 255, 463, Format(2)), makeInsertTestEdit("testcases/methods/Demo.hx", "function doSomethingExtract(cond:Bool, val:Int, text:Null):Float {\n" + "return switch [cond, val] {\n" @@ -323,7 +323,7 @@ class RefactorExtractMethodTest extends RefactorTestBase { + " std.Math.NEGATIVE_INFINITY;\n" + " }\n" + "}\n", - 467, true), + 467, Format(1)), ]; addTypeHint("testcases/methods/Demo.hx", 61, LibType("Float", "Float", [])); checkRefactor(RefactorExtractMethod, {fileName: "testcases/methods/Demo.hx", posStart: 254, posEnd: 463}, edits, async); @@ -331,7 +331,7 @@ class RefactorExtractMethodTest extends RefactorTestBase { function testDemoCodeAndSwitch(async:Async) { var edits:Array = [ - makeReplaceTestEdit("testcases/methods/Demo.hx", "return doSomethingExtract(cond, val, text);\n", 161, 463, true), + makeReplaceTestEdit("testcases/methods/Demo.hx", "return doSomethingExtract(cond, val, text);\n", 161, 463, Format(2)), makeInsertTestEdit("testcases/methods/Demo.hx", "function doSomethingExtract(cond:Bool, val:Int, text:Null):Float {\n" + "doNothing();\n" @@ -350,7 +350,7 @@ class RefactorExtractMethodTest extends RefactorTestBase { + " std.Math.NEGATIVE_INFINITY;\n" + " }\n" + "}\n", - 467, true), + 467, Format(1)), ]; addTypeHint("testcases/methods/Demo.hx", 61, LibType("Float", "Float", [])); checkRefactor(RefactorExtractMethod, {fileName: "testcases/methods/Demo.hx", posStart: 160, posEnd: 463}, edits, async); @@ -363,7 +363,7 @@ class RefactorExtractMethodTest extends RefactorTestBase { + "case Some(data):\n" + "return data;\n" + "case None:\n" - + "}\n", 112, 252, true), + + "}\n", 112, 252, Format(2)), makeInsertTestEdit("testcases/methods/Demo.hx", "function doSomethingExtract(cond:Bool, text:Null):haxe.ds.Option {\n" + "if (cond && text == null) {\n" @@ -376,7 +376,7 @@ class RefactorExtractMethodTest extends RefactorTestBase { + " doNothing();\n" + "return None;\n" + "}\n", - 467, true), + 467, Format(1)), ]; addTypeHint("testcases/methods/Demo.hx", 61, LibType("Float", "Float", [])); checkRefactor(RefactorExtractMethod, {fileName: "testcases/methods/Demo.hx", posStart: 111, posEnd: 252}, edits, async); @@ -384,10 +384,9 @@ class RefactorExtractMethodTest extends RefactorTestBase { function testCalculateMath(async:Async) { var edits:Array = [ - makeReplaceTestEdit("testcases/methods/Math.hx", "calculateExtract(a, b);\n", 106, 122, true), - makeInsertTestEdit("testcases/methods/Math.hx", "function calculateExtract(a:Int, b:Int):Int {\n" - + "return a * b + (a - b);\n" - + "}\n", 143, true), + makeReplaceTestEdit("testcases/methods/Math.hx", "calculateExtract(a, b);\n", 106, 122, Format(2)), + makeInsertTestEdit("testcases/methods/Math.hx", "function calculateExtract(a:Int, b:Int):Int {\n" + "return a * b + (a - b);\n" + "}\n", 143, + Format(1)), ]; addTypeHint("testcases/methods/Math.hx", 71, LibType("Int", "Int", [])); addTypeHint("testcases/methods/Math.hx", 84, LibType("Int", "Int", [])); @@ -397,12 +396,12 @@ class RefactorExtractMethodTest extends RefactorTestBase { function testCalculateMathWithVar(async:Async) { var edits:Array = [ - makeReplaceTestEdit("testcases/methods/Math.hx", "var result = calculateExtract(a, b);\n", 93, 122, true), + makeReplaceTestEdit("testcases/methods/Math.hx", "var result = calculateExtract(a, b);\n", 93, 122, Format(2)), makeInsertTestEdit("testcases/methods/Math.hx", "function calculateExtract(a:Int, b:Int):Int {\n" + "var result = a * b + (a - b);\n" + "return result;\n" - + "}\n", 143, true), + + "}\n", 143, Format(1)), ]; addTypeHint("testcases/methods/Math.hx", 71, LibType("Int", "Int", [])); addTypeHint("testcases/methods/Math.hx", 84, LibType("Int", "Int", [])); @@ -412,7 +411,7 @@ class RefactorExtractMethodTest extends RefactorTestBase { function testNameProcessor(async:Async) { var edits:Array = [ - makeReplaceTestEdit("testcases/methods/NameProcessor.hx", "var upperNames = processExtract(names);\n", 113, 201, true), + makeReplaceTestEdit("testcases/methods/NameProcessor.hx", "var upperNames = processExtract(names);\n", 113, 201, Format(2)), makeInsertTestEdit("testcases/methods/NameProcessor.hx", "function processExtract(names:Array):Array {\n" + "var upperNames = [];\n" @@ -421,7 +420,7 @@ class RefactorExtractMethodTest extends RefactorTestBase { + " }\n" + "return upperNames;\n" + "}\n", - 226, true), + 226, Format(1)), ]; addTypeHint("testcases/methods/NameProcessor.hx", 82, LibType("Array", "Array", [LibType("String", "String", [])])); addTypeHint("testcases/methods/NameProcessor.hx", 126, LibType("Array", "Array", [UnknownType("?")])); @@ -430,7 +429,7 @@ class RefactorExtractMethodTest extends RefactorTestBase { function testArrayHandler(async:Async) { var edits:Array = [ - makeReplaceTestEdit("testcases/methods/ArrayHandler.hx", "var sum = handleExtract(numbers);\n", 105, 157, true), + makeReplaceTestEdit("testcases/methods/ArrayHandler.hx", "var sum = handleExtract(numbers);\n", 105, 157, Format(2)), makeInsertTestEdit("testcases/methods/ArrayHandler.hx", "function handleExtract(numbers:Array):Int {\n" + "var sum = 0;\n" @@ -439,7 +438,7 @@ class RefactorExtractMethodTest extends RefactorTestBase { + " }\n" + "return sum;\n" + "}\n", - 175, true), + 175, Format(1)), ]; addTypeHint("testcases/methods/ArrayHandler.hx", 82, LibType("Array", "Array", [LibType("Int", "Int", [])])); addTypeHint("testcases/methods/ArrayHandler.hx", 111, LibType("Int", "Int", [])); @@ -448,7 +447,7 @@ class RefactorExtractMethodTest extends RefactorTestBase { function testAgeChecker(async:Async) { var edits:Array = [ - makeReplaceTestEdit("testcases/methods/AgeChecker.hx", "message = checkExtract(age, message);\n", 105, 179, true), + makeReplaceTestEdit("testcases/methods/AgeChecker.hx", "message = checkExtract(age, message);\n", 105, 179, Format(2)), makeInsertTestEdit("testcases/methods/AgeChecker.hx", "function checkExtract(age:Int, message:String):String {\n" + "if (age < 18) {\n" @@ -458,7 +457,7 @@ class RefactorExtractMethodTest extends RefactorTestBase { + " }\n" + "return message;\n" + "}\n", - 201, true), + 201, Format(1)), ]; addTypeHint("testcases/methods/AgeChecker.hx", 75, LibType("Int", "Int", [])); addTypeHint("testcases/methods/AgeChecker.hx", 95, LibType("String", "String", [])); @@ -467,9 +466,9 @@ class RefactorExtractMethodTest extends RefactorTestBase { function testPersonHandler(async:Async) { var edits:Array = [ - makeReplaceTestEdit("testcases/methods/PersonHandler.hx", "handleExtract(person);\n", 113, 161, true), + makeReplaceTestEdit("testcases/methods/PersonHandler.hx", "handleExtract(person);\n", 113, 161, Format(2)), makeInsertTestEdit("testcases/methods/PersonHandler.hx", - "function handleExtract(person:Any) {\n" + "return \"Name: \" + person.name + \", Age: \" + person.age;\n" + "}\n", 180, true), + "function handleExtract(person:Any) {\n" + "return \"Name: \" + person.name + \", Age: \" + person.age;\n" + "}\n", 180, Format(1)), ]; addTypeHint("testcases/methods/PersonHandler.hx", 75, LibType("Int", "Int", [])); addTypeHint("testcases/methods/PersonHandler.hx", 95, LibType("String", "String", [])); @@ -478,13 +477,13 @@ class RefactorExtractMethodTest extends RefactorTestBase { function testPersonHandlerWithVar(async:Async) { var edits:Array = [ - makeReplaceTestEdit("testcases/methods/PersonHandler.hx", "var info = handleExtract(person);\n", 102, 161, true), + makeReplaceTestEdit("testcases/methods/PersonHandler.hx", "var info = handleExtract(person);\n", 102, 161, Format(2)), makeInsertTestEdit("testcases/methods/PersonHandler.hx", "function handleExtract(person:Any) {\n" + "var info = \"Name: \" + person.name + \", Age: \" + person.age;\n" + "return info;\n" + "}\n", - 180, true), + 180, Format(1)), ]; addTypeHint("testcases/methods/PersonHandler.hx", 75, LibType("Int", "Int", [])); addTypeHint("testcases/methods/PersonHandler.hx", 95, LibType("String", "String", [])); @@ -493,7 +492,7 @@ class RefactorExtractMethodTest extends RefactorTestBase { function testContainer(async:Async) { var edits:Array = [ - makeReplaceTestEdit("testcases/methods/Container.hx", "var result = processExtract(items, converter);\n", 108, 239, true), + makeReplaceTestEdit("testcases/methods/Container.hx", "var result = processExtract(items, converter);\n", 108, 239, Format(2)), makeInsertTestEdit("testcases/methods/Container.hx", "function processExtract(items:Array, converter:T -> String):Array {\n" + "var result = new Array();\n" @@ -503,7 +502,7 @@ class RefactorExtractMethodTest extends RefactorTestBase { + " }\n" + "return result;\n" + "}\n", - 260, true), + 260, Format(1)), ]; addTypeHint("testcases/methods/Container.hx", 91, FunctionType([LibType("T", "T", [])], LibType("String", "String", []))); addTypeHint("testcases/methods/Container.hx", 117, LibType("Array", "Array", [LibType("String", "String", [])])); @@ -512,7 +511,7 @@ class RefactorExtractMethodTest extends RefactorTestBase { function testMacroTools(async:Async) { var edits:Array = [ - makeReplaceTestEdit("testcases/methods/MacroTools.hx", "return buildExtract(fields);\n", 195, 353, true), + makeReplaceTestEdit("testcases/methods/MacroTools.hx", "return buildExtract(fields);\n", 195, 353, Format(2)), makeInsertTestEdit("testcases/methods/MacroTools.hx", "static function buildExtract(fields:Array):Array {\n" + "for (field in fields) {\n" @@ -525,7 +524,7 @@ class RefactorExtractMethodTest extends RefactorTestBase { + " }\n" + " return fields;\n" + "}\n", - 357, true), + 357, Format(1)), ]; addTypeHint("testcases/methods/MacroTools.hx", 133, LibType("Array", "Array", [LibType("Field", "Field", [])])); addTypeHint("testcases/methods/MacroTools.hx", 163, LibType("Array", "Array", [LibType("Field", "Field", [])])); @@ -535,7 +534,7 @@ class RefactorExtractMethodTest extends RefactorTestBase { function testMatcherOnlySwitch(async:Async) { var edits:Array = [ - makeReplaceTestEdit("testcases/methods/Matcher.hx", "processExtract(value);\n", 84, 284, true), + makeReplaceTestEdit("testcases/methods/Matcher.hx", "processExtract(value);\n", 84, 284, Format(2)), makeInsertTestEdit("testcases/methods/Matcher.hx", "function processExtract(value:Any) {\n" + "return switch value {\n" @@ -545,14 +544,14 @@ class RefactorExtractMethodTest extends RefactorTestBase { + " case _: 'Unknown';\n" + " }\n" + "}\n", - 288, true), + 288, Format(1)), ]; checkRefactor(RefactorExtractMethod, {fileName: "testcases/methods/Matcher.hx", posStart: 84, posEnd: 284}, edits, async); } function testMatcherWithReturn(async:Async) { var edits:Array = [ - makeReplaceTestEdit("testcases/methods/Matcher.hx", "return processExtract(value);\n", 77, 284, true), + makeReplaceTestEdit("testcases/methods/Matcher.hx", "return processExtract(value);\n", 77, 284, Format(2)), makeInsertTestEdit("testcases/methods/Matcher.hx", "function processExtract(value:Any) {\n" + "return switch value {\n" @@ -562,14 +561,14 @@ class RefactorExtractMethodTest extends RefactorTestBase { + " case _: 'Unknown';\n" + " }\n" + "}\n", - 288, true), + 288, Format(1)), ]; checkRefactor(RefactorExtractMethod, {fileName: "testcases/methods/Matcher.hx", posStart: 76, posEnd: 284}, edits, async); } function testTypeProcessor(async:Async) { var edits:Array = [ - makeReplaceTestEdit("testcases/methods/TypeProcessor.hx", "processExtract(item, compare);\n", 114, 221, true), + makeReplaceTestEdit("testcases/methods/TypeProcessor.hx", "processExtract(item, compare);\n", 114, 221, Format(2)), makeInsertTestEdit("testcases/methods/TypeProcessor.hx", "function processExtract(item:T, compare:U):Void {\n" + "var result = item.process();\n" @@ -577,21 +576,21 @@ class RefactorExtractMethodTest extends RefactorTestBase { + " item.update(compare.getValue());\n" + " }\n" + "}\n", - 225, true), + 225, Format(1)), ]; checkRefactor(RefactorExtractMethod, {fileName: "testcases/methods/TypeProcessor.hx", posStart: 113, posEnd: 221}, edits, async); } function testFunctionProcessor(async:Async) { var edits:Array = [ - makeReplaceTestEdit("testcases/methods/FunctionProcessor.hx", "processExtract(callback, value, text);\n", 143, 211, true), + makeReplaceTestEdit("testcases/methods/FunctionProcessor.hx", "processExtract(callback, value, text);\n", 143, 211, Format(2)), makeInsertTestEdit("testcases/methods/FunctionProcessor.hx", "function processExtract(callback:(Int, String) -> Bool, value:Int, text:String):Void {\n" + "if (callback(value, text)) {\n" + " trace('Success: $value, $text');\n" + " }\n" + "}\n", - 215, true), + 215, Format(1)), ]; addTypeHint("testcases/methods/FunctionProcessor.hx", 79, FunctionType([LibType("Int", "Int", []), LibType("String", "String", [])], LibType("Bool", "Bool", []))); @@ -602,7 +601,7 @@ class RefactorExtractMethodTest extends RefactorTestBase { function testArrayTools(async:Async) { var edits:Array = [ - makeReplaceTestEdit("testcases/methods/ArrayTools.hx", "return processItemsExtract(arr, fn);\n", 117, 231, true), + makeReplaceTestEdit("testcases/methods/ArrayTools.hx", "return processItemsExtract(arr, fn);\n", 117, 231, Format(2)), makeInsertTestEdit("testcases/methods/ArrayTools.hx", "static function processItemsExtract(arr:Array, fn:T -> Bool):Array {\n" + "var results = new Array();\n" @@ -612,7 +611,7 @@ class RefactorExtractMethodTest extends RefactorTestBase { + " }\n" + " return results;\n" + "}\n", - 235, true), + 235, Format(1)), ]; addTypeHint("testcases/methods/ArrayTools.hx", 82, LibType("Array", "Array", [LibType("T", "T", [])])); addTypeHint("testcases/methods/ArrayTools.hx", 102, FunctionType([LibType("T", "T", [])], LibType("Bool", "Bool", []))); @@ -621,25 +620,26 @@ class RefactorExtractMethodTest extends RefactorTestBase { function testExceptionHandlerTry(async:Async) { var edits:Array = [ - makeReplaceTestEdit("testcases/methods/ExceptionHandler.hx", "processExtract();\n", 86, 152, true), + makeReplaceTestEdit("testcases/methods/ExceptionHandler.hx", "processExtract();\n", 86, 152, Format(3)), makeInsertTestEdit("testcases/methods/ExceptionHandler.hx", "function processExtract():Void {\n" + "var data = getData();\n" + " validateData(data);\n" + " processData(data);\n" - + "}\n", 795, true), + + "}\n", 795, + Format(1)), ]; checkRefactor(RefactorExtractMethod, {fileName: "testcases/methods/ExceptionHandler.hx", posStart: 85, posEnd: 152}, edits, async); } function testExceptionHandlerCatch(async:Async) { var edits:Array = [ - makeReplaceTestEdit("testcases/methods/ExceptionHandler.hx", "processExtract();\n", 193, 250, true), + makeReplaceTestEdit("testcases/methods/ExceptionHandler.hx", "processExtract();\n", 193, 250, Format(3)), makeInsertTestEdit("testcases/methods/ExceptionHandler.hx", "function processExtract():Void {\n" + "logError(e);\n" + " throw new ProcessingException(e.message);\n" - + "}\n", 795, true), + + "}\n", 795, Format(1)), ]; addTypeHint("testcases/methods/ExceptionHandler.hx", 69, LibType("Void", "Void", [])); checkRefactor(RefactorExtractMethod, {fileName: "testcases/methods/ExceptionHandler.hx", posStart: 192, posEnd: 250}, edits, async); @@ -647,7 +647,7 @@ class RefactorExtractMethodTest extends RefactorTestBase { function testExceptionHandlerTryWithThrow(async:Async) { var edits:Array = [ - makeReplaceTestEdit("testcases/methods/ExceptionHandler.hx", "processExtract();\n", 266, 404, true), + makeReplaceTestEdit("testcases/methods/ExceptionHandler.hx", "processExtract();\n", 266, 404, Format(3)), makeInsertTestEdit("testcases/methods/ExceptionHandler.hx", "function processExtract():Void {\n" + "var data = getData();\n" @@ -656,14 +656,14 @@ class RefactorExtractMethodTest extends RefactorTestBase { + " }\n" + " throw new InvalidDataException(\"value should not be smaller than 11\");\n" + "}\n", - 795, true), + 795, Format(1)), ]; checkRefactor(RefactorExtractMethod, {fileName: "testcases/methods/ExceptionHandler.hx", posStart: 265, posEnd: 404}, edits, async); } function testExceptionHandlerTryWithThrowNotLast(async:Async) { var edits:Array = [ - makeReplaceTestEdit("testcases/methods/ExceptionHandler.hx", "processExtract();\n", 518, 689, true), + makeReplaceTestEdit("testcases/methods/ExceptionHandler.hx", "processExtract();\n", 518, 689, Format(3)), makeInsertTestEdit("testcases/methods/ExceptionHandler.hx", "function processExtract():Void {\n" + "var data = getData();\n" @@ -673,7 +673,7 @@ class RefactorExtractMethodTest extends RefactorTestBase { + " validateData(data);\n" + " processData(data);\n" + "}\n", - 795, true), + 795, Format(1)), ]; addTypeHint("testcases/methods/ExceptionHandler.hx", 69, LibType("Void", "Void", [])); checkRefactor(RefactorExtractMethod, {fileName: "testcases/methods/ExceptionHandler.hx", posStart: 517, posEnd: 689}, edits, async); @@ -681,7 +681,7 @@ class RefactorExtractMethodTest extends RefactorTestBase { function testMetadataProcessor(async:Async) { var edits:Array = [ - makeReplaceTestEdit("testcases/methods/MetadataProcessor.hx", "processExtract(meta, results);\n", 220, 575, true), + makeReplaceTestEdit("testcases/methods/MetadataProcessor.hx", "processExtract(meta, results);\n", 220, 575, Format(2)), makeInsertTestEdit("testcases/methods/MetadataProcessor.hx", "function processExtract(meta:Dynamic>>, results:Map>):Void {\n" + "for (field in Reflect.fields(meta)) {\n" @@ -695,7 +695,7 @@ class RefactorExtractMethodTest extends RefactorTestBase { + " }\n" + " }\n" + "}\n", - 676, true), + 676, Format(1)), ]; addTypeHint("testcases/methods/MetadataProcessor.hx", 132, LibType("Dynamic", "Dynamic", [ LibType("Dynamic", "Dynamic", [LibType("Array", "Array", [LibType("Dynamic", "Dynamic", [])])]) @@ -709,7 +709,7 @@ class RefactorExtractMethodTest extends RefactorTestBase { function testMetadataProcessorWithResults(async:Async) { var edits:Array = [ - makeReplaceTestEdit("testcases/methods/MetadataProcessor.hx", "var results = processExtract(meta);\n", 169, 575, true), + makeReplaceTestEdit("testcases/methods/MetadataProcessor.hx", "var results = processExtract(meta);\n", 169, 575, Format(2)), makeInsertTestEdit("testcases/methods/MetadataProcessor.hx", "function processExtract(meta:Dynamic>>):Map> {\n" + "var results = new Map>();\n\n" @@ -725,7 +725,7 @@ class RefactorExtractMethodTest extends RefactorTestBase { + " }\n" + "return results;\n" + "}\n", - 676, true), + 676, Format(1)), ]; addTypeHint("testcases/methods/MetadataProcessor.hx", 132, LibType("Dynamic", "Dynamic", [ LibType("Dynamic", "Dynamic", [LibType("Array", "Array", [LibType("Dynamic", "Dynamic", [])])]) @@ -739,14 +739,14 @@ class RefactorExtractMethodTest extends RefactorTestBase { function testMetadataProcessorPrint(async:Async) { var edits:Array = [ - makeReplaceTestEdit("testcases/methods/MetadataProcessor.hx", "processExtract(results);\n", 579, 672, true), + makeReplaceTestEdit("testcases/methods/MetadataProcessor.hx", "processExtract(results);\n", 579, 672, Format(2)), makeInsertTestEdit("testcases/methods/MetadataProcessor.hx", "function processExtract(results:Map>):Void {\n" + "for (field => values in results) {\n" + " trace('Field: $field, Meta: ${values.join(\", \")}');\n" + " }\n" + "}\n", - 676, true), + 676, Format(1)), ]; addTypeHint("testcases/methods/MetadataProcessor.hx", 179, LibType("Map", "Map", [ LibType("String", "String", []), @@ -757,12 +757,12 @@ class RefactorExtractMethodTest extends RefactorTestBase { function testLambdaExample(async:Async) { var edits:Array = [ - makeReplaceTestEdit("testcases/methods/LambdaExample.hx", "processExtract", 156, 230, true), + makeReplaceTestEdit("testcases/methods/LambdaExample.hx", "processExtract", 156, 230, Format(2)), makeInsertTestEdit("testcases/methods/LambdaExample.hx", "function processExtract(n:Int):Int {\n" + "var temp = n * multiplier;\n" + " return temp + this.multiplier;\n" - + "}\n", 236, true), + + "}\n", 236, Format(1)), ]; addTypeHint("testcases/methods/LambdaExample.hx", 156, LibType("Int", "Int", [])); addTypeHint("testcases/methods/LambdaExample.hx", 159, LibType("Int", "Int", [])); @@ -771,12 +771,12 @@ class RefactorExtractMethodTest extends RefactorTestBase { function testLambdaExampleCallback(async:Async) { var edits:Array = [ - makeReplaceTestEdit("testcases/methods/LambdaExample.hx", "processCallbackExtract", 281, 337, true), + makeReplaceTestEdit("testcases/methods/LambdaExample.hx", "processCallbackExtract", 281, 337, Format(2)), makeInsertTestEdit("testcases/methods/LambdaExample.hx", "function processCallbackExtract(n:Int, m:Int):Int {\n" + "var temp = n * m;\n" + " return temp + m;\n" - + "}\n", 343, true), + + "}\n", 343, Format(1)), ]; addTypeHint("testcases/methods/LambdaExample.hx", 282, LibType("Int", "Int", [])); addTypeHint("testcases/methods/LambdaExample.hx", 285, LibType("Int", "Int", [])); @@ -786,12 +786,12 @@ class RefactorExtractMethodTest extends RefactorTestBase { function testLambdaExampleCallbackFunc(async:Async) { var edits:Array = [ - makeReplaceTestEdit("testcases/methods/LambdaExample.hx", "processCallbackFuncExtract", 392, 453, true), + makeReplaceTestEdit("testcases/methods/LambdaExample.hx", "processCallbackFuncExtract", 392, 453, Format(2)), makeInsertTestEdit("testcases/methods/LambdaExample.hx", "function processCallbackFuncExtract(n:Int, m:Int):Int {\n" + "var temp = n * m;\n" + " return temp + m;\n" - + "}\n", 459, true), + + "}\n", 459, Format(1)), ]; addTypeHint("testcases/methods/LambdaExample.hx", 401, LibType("Int", "Int", [])); addTypeHint("testcases/methods/LambdaExample.hx", 404, LibType("Int", "Int", [])); @@ -801,11 +801,113 @@ class RefactorExtractMethodTest extends RefactorTestBase { function testLambdaExampleSimple(async:Async) { var edits:Array = [ - makeReplaceTestEdit("testcases/methods/LambdaExample.hx", "processSimpleExtract", 669, 679, true), - makeInsertTestEdit("testcases/methods/LambdaExample.hx", "function processSimpleExtract(n:Int):Int {\n" + "n * n\n" + "}\n", 685, true), + makeReplaceTestEdit("testcases/methods/LambdaExample.hx", "processSimpleExtract", 669, 679, Format(2)), + makeInsertTestEdit("testcases/methods/LambdaExample.hx", "function processSimpleExtract(n:Int):Int {\n" + "n * n\n" + "}\n", 685, Format(1)), ]; addTypeHint("testcases/methods/LambdaExample.hx", 669, LibType("Int", "Int", [])); addTypeHint("testcases/methods/LambdaExample.hx", 672, LibType("Int", "Int", [])); checkRefactor(RefactorExtractMethod, {fileName: "testcases/methods/LambdaExample.hx", posStart: 669, posEnd: 679}, edits, async); } + + function testEditDocInner(async:Async) { + var edits:Array = [ + makeReplaceTestEdit("testcases/methods/TestEditDoc.hx", "text = addChangeExtract(range, text);\n", 388, 485, Format(4)), + makeInsertTestEdit("testcases/methods/TestEditDoc.hx", + "function addChangeExtract(range:TestRange, text:String):String {\n" + + "if (range.start.character != 0) {\n" + + " range.start.character = 0;\n" + + " text = text.ltrim();\n" + + " }\n" + + "return text;\n" + + "}\n", + 538, Format(1)), + ]; + addTypeHint("testcases/methods/TestEditDoc.hx", 170, LibType("TestRange", "TestRange", [])); + addTypeHint("testcases/methods/TestEditDoc.hx", 235, LibType("String", "String", [])); + checkRefactor(RefactorExtractMethod, {fileName: "testcases/methods/TestEditDoc.hx", posStart: 387, posEnd: 485}, edits, async); + } + + function testEditDocInnerWithEdit(async:Async) { + var edits:Array = [ + makeReplaceTestEdit("testcases/methods/TestEditDoc.hx", "text = addChangeExtract(text, filePath, range);\n", 346, 485, Format(4)), + makeInsertTestEdit("testcases/methods/TestEditDoc.hx", + "function addChangeExtract(text:String, filePath:String, range:TestRange):String {\n" + + "text = formatSnippet(filePath, text);\n" + + " if (range.start.character != 0) {\n" + + " range.start.character = 0;\n" + + " text = text.ltrim();\n" + + " }\n" + + "return text;\n" + + "}\n", + 538, Format(1)), + ]; + addTypeHint("testcases/methods/TestEditDoc.hx", 170, LibType("TestRange", "TestRange", [])); + addTypeHint("testcases/methods/TestEditDoc.hx", 235, LibType("String", "String", [])); + addTypeHint("testcases/methods/TestEditDoc.hx", 262, LibType("String", "String", [])); + checkRefactor(RefactorExtractMethod, {fileName: "testcases/methods/TestEditDoc.hx", posStart: 345, posEnd: 485}, edits, async); + } + + function testEditDocInnerWithSwitch(async:Async) { + var edits:Array = [ + makeReplaceTestEdit("testcases/methods/TestEditDoc.hx", "var text = addChangeExtract(range);\n", 193, 489, Format(2)), + makeInsertTestEdit("testcases/methods/TestEditDoc.hx", + "function addChangeExtract(range:TestRange):String {\n" + + "final f:FormatType = Format(10);\n" + + " var text = \"text\";\n" + + " final filePath = \"import.hx\";\n" + + " switch (f) {\n" + + " case NoFormat:\n" + + " case Format(indentOffset):\n" + + " text = formatSnippet(filePath, text);\n" + + " if (range.start.character != 0) {\n" + + " range.start.character = 0;\n" + + " text = text.ltrim();\n" + + " }\n" + + " }\n" + + "return text;\n" + + "}\n", + 538, Format(1)), + ]; + addTypeHint("testcases/methods/TestEditDoc.hx", 170, LibType("TestRange", "TestRange", [])); + addTypeHint("testcases/methods/TestEditDoc.hx", 235, LibType("String", "String", [])); + checkRefactor(RefactorExtractMethod, {fileName: "testcases/methods/TestEditDoc.hx", posStart: 192, posEnd: 489}, edits, async); + } + + function testEditDocInnerWithRange(async:Async) { + var edits:Array = [ + makeReplaceTestEdit("testcases/methods/TestEditDoc.hx", + "var range;\n" + + "var text;\n" + + "{\n" + + "final data = addChangeExtract();\n" + + "range = data.range;\n" + + "text = data.text;\n" + + "}\n", 160, + 489, Format(2)), + makeInsertTestEdit("testcases/methods/TestEditDoc.hx", + "function addChangeExtract():{range:TestRange, text:String} {\n" + + "final range = posToRange(100);\n" + + " final f:FormatType = Format(10);\n" + + " var text = \"text\";\n" + + " final filePath = \"import.hx\";\n" + + " switch (f) {\n" + + " case NoFormat:\n" + + " case Format(indentOffset):\n" + + " text = formatSnippet(filePath, text);\n" + + " if (range.start.character != 0) {\n" + + " range.start.character = 0;\n" + + " text = text.ltrim();\n" + + " }\n" + + " }\n" + + "return {\n" + + "range: range,\n" + + "text: text,\n" + + "};\n" + + "}\n", + 538, Format(1)), + ]; + addTypeHint("testcases/methods/TestEditDoc.hx", 170, LibType("TestRange", "TestRange", [])); + addTypeHint("testcases/methods/TestEditDoc.hx", 235, LibType("String", "String", [])); + checkRefactor(RefactorExtractMethod, {fileName: "testcases/methods/TestEditDoc.hx", posStart: 159, posEnd: 489}, edits, async); + } } diff --git a/test/refactor/refactor/RefactorTypedefTest.hx b/test/refactor/refactor/RefactorTypedefTest.hx index 8ea1b53..c772711 100644 --- a/test/refactor/refactor/RefactorTypedefTest.hx +++ b/test/refactor/refactor/RefactorTypedefTest.hx @@ -29,7 +29,7 @@ class RefactorTypedefTest extends RefactorTestBase { + " var maxCompletionItems:Int;\n" + " var renameSourceFolders:Array;\n" + "}", - 0, true), + 0, Format(0)), ]; checkRefactor(RefactorExtractType, {fileName: "testcases/typedefs/Types.hx", posStart: 260, posEnd: 260}, edits, async); } diff --git a/test/refactor/rename/RenameTypedefTest.hx b/test/refactor/rename/RenameTypedefTest.hx index 33a5094..010acb4 100644 --- a/test/refactor/rename/RenameTypedefTest.hx +++ b/test/refactor/rename/RenameTypedefTest.hx @@ -88,4 +88,12 @@ class RenameTypedefTest extends RenameTestBase { ]; checkRename({fileName: "testcases/typedefs/Types.hx", toName: "sourceFolders", pos: 784}, edits, async); } + + public function testRenameIndentOffset(async:Async) { + var edits:Array = [ + makeReplaceTestEdit("testcases/typedefs/TestFormatter.hx", "indentationOffset", 496, 508), + makeReplaceTestEdit("testcases/typedefs/codedata/TestFormatterInputData.hx", "indentationOffset", 456, 468), + ]; + checkRename({fileName: "testcases/typedefs/codedata/TestFormatterInputData.hx", toName: "indentationOffset", pos: 462}, edits, async, true); + } } diff --git a/testCoverage.hxml b/testCoverage.hxml index 67dbcb5..f60de85 100644 --- a/testCoverage.hxml +++ b/testCoverage.hxml @@ -5,6 +5,7 @@ -lib haxeparser -lib tokentree -lib hxargs +-lib formatter -lib utest -lib hxnodejs -lib instrument diff --git a/testcases/methods/TestEditDoc.hx b/testcases/methods/TestEditDoc.hx new file mode 100644 index 0000000..d552551 --- /dev/null +++ b/testcases/methods/TestEditDoc.hx @@ -0,0 +1,43 @@ +package methods; + +import refactor.edits.FormatType; + +class TestEditDoc { + var edits:Array<{range:TestRange, newText:String}>; + + public function addChange() { + final range = posToRange(100); + final f:FormatType = Format(10); + var text = "text"; + final filePath = "import.hx"; + switch (f) { + case NoFormat: + case Format(indentOffset): + text = formatSnippet(filePath, text); + if (range.start.character != 0) { + range.start.character = 0; + text = text.ltrim(); + } + } + edits.push({range: range, newText: text}); + } + + public function posToRange(pos:Int):TestRange { + var posNull:TextPosition = {line: 0, character: 0}; + return {start: posNull, end: posNull}; + } + + function formatSnippet(filePath:String, text:String):String { + return text; + } +} + +typedef TestRange = { + var start:TextPosition; + var end:TextPosition; +} + +typedef TextPosition = { + var line:Int; + var character:Int; +} diff --git a/testcases/typedefs/TestFormatter.hx b/testcases/typedefs/TestFormatter.hx new file mode 100644 index 0000000..3b56a59 --- /dev/null +++ b/testcases/typedefs/TestFormatter.hx @@ -0,0 +1,31 @@ +package typedefs; + +import formatter.config.Config; +import typedefs.codedata.TestFormatterInputData; + +class TestFormatter { + public static function format(input:TestFormatterInput, ?config:Config, ?indentOffset:Int):Bool { + if (config == null) { + config = new Config(); + } + var inputData:TestFormatterInputData; + switch (input) { + case Code(code, origin): + inputData = { + fileName: origin, + content: code, + lineSeparator: null, + entryPoint: null, + range: null, + indentOffset: indentOffset + }; + return true; + default: + return false; + } + } +} + +enum TestFormatterInput { + Code(code:String, origin:String); +} diff --git a/testcases/typedefs/codedata/TestFormatterInputData.hx b/testcases/typedefs/codedata/TestFormatterInputData.hx new file mode 100644 index 0000000..fad4474 --- /dev/null +++ b/testcases/typedefs/codedata/TestFormatterInputData.hx @@ -0,0 +1,17 @@ +package typedefs.codedata; + +import haxeparser.Data.Token; +import hxparse.Position; +import tokentree.TokenTree; +import tokentree.TokenTreeBuilder.TokenTreeEntryPoint; + +typedef TestFormatterInputData = { + var fileName:String; + var content:String; + @:optional var tokenList:Array; + @:optional var tokenTree:TokenTree; + @:optional var entryPoint:TokenTreeEntryPoint; + @:optional var lineSeparator:String; + @:optional var range:Position; + @:optional var indentOffset:Int; +} From 140dbbe5aed05b779d85c3be4ef6beb2720e66cf Mon Sep 17 00:00:00 2001 From: AlexHaxe Date: Sun, 1 Dec 2024 01:22:54 +0100 Subject: [PATCH 33/59] fixed switch cases with conditional compilation --- src/refactor/discover/UsageCollector.hx | 15 +++++++++++++++ test/refactor/rename/RenameTypedefTest.hx | 2 +- testcases/typedefs/TestFormatter.hx | 2 ++ 3 files changed, 18 insertions(+), 1 deletion(-) diff --git a/src/refactor/discover/UsageCollector.hx b/src/refactor/discover/UsageCollector.hx index 4d36f0f..0f3d4cb 100644 --- a/src/refactor/discover/UsageCollector.hx +++ b/src/refactor/discover/UsageCollector.hx @@ -734,6 +734,21 @@ class UsageCollector { if (child.hasChildren()) { readBlock(context, switchIdent, child.getFirstChild()); } + case Sharp(_): + readExpression(context, identifier, child.getFirstChild()); + for (index in 1...child.children.length - 1) { + switch (child.children[index].tok) { + case Sharp(_): + case Kwd(KwdCase): + trace('$child'); + readCase(context, switchIdent, child.children[index]); + case Kwd(KwdDefault): + if (child.hasChildren()) { + readBlock(context, switchIdent, child.children[index].getFirstChild()); + } + default: + } + } default: break; } diff --git a/test/refactor/rename/RenameTypedefTest.hx b/test/refactor/rename/RenameTypedefTest.hx index 010acb4..7af9ba6 100644 --- a/test/refactor/rename/RenameTypedefTest.hx +++ b/test/refactor/rename/RenameTypedefTest.hx @@ -91,7 +91,7 @@ class RenameTypedefTest extends RenameTestBase { public function testRenameIndentOffset(async:Async) { var edits:Array = [ - makeReplaceTestEdit("testcases/typedefs/TestFormatter.hx", "indentationOffset", 496, 508), + makeReplaceTestEdit("testcases/typedefs/TestFormatter.hx", "indentationOffset", 507, 519), makeReplaceTestEdit("testcases/typedefs/codedata/TestFormatterInputData.hx", "indentationOffset", 456, 468), ]; checkRename({fileName: "testcases/typedefs/codedata/TestFormatterInputData.hx", toName: "indentationOffset", pos: 462}, edits, async, true); diff --git a/testcases/typedefs/TestFormatter.hx b/testcases/typedefs/TestFormatter.hx index 3b56a59..656c176 100644 --- a/testcases/typedefs/TestFormatter.hx +++ b/testcases/typedefs/TestFormatter.hx @@ -10,6 +10,7 @@ class TestFormatter { } var inputData:TestFormatterInputData; switch (input) { + #if sys case Code(code, origin): inputData = { fileName: origin, @@ -20,6 +21,7 @@ class TestFormatter { indentOffset: indentOffset }; return true; + #end default: return false; } From 43e030a4fa39c2f2e50f0664db582daf97bf817e Mon Sep 17 00:00:00 2001 From: AlexHaxe Date: Sun, 1 Dec 2024 01:33:59 +0100 Subject: [PATCH 34/59] cleanup --- src/refactor/discover/UsageCollector.hx | 1 - 1 file changed, 1 deletion(-) diff --git a/src/refactor/discover/UsageCollector.hx b/src/refactor/discover/UsageCollector.hx index 0f3d4cb..48cb5cf 100644 --- a/src/refactor/discover/UsageCollector.hx +++ b/src/refactor/discover/UsageCollector.hx @@ -740,7 +740,6 @@ class UsageCollector { switch (child.children[index].tok) { case Sharp(_): case Kwd(KwdCase): - trace('$child'); readCase(context, switchIdent, child.children[index]); case Kwd(KwdDefault): if (child.hasChildren()) { From 22a77d9b9efb8d8da7ad94f30b08f22727e4d5c5 Mon Sep 17 00:00:00 2001 From: AlexHaxe Date: Mon, 2 Dec 2024 00:23:07 +0100 Subject: [PATCH 35/59] fixed enum rename eating Dot field separator fixed abstract enum type resolution added testcases --- src/refactor/Rename.hx | 4 +-- src/refactor/rename/RenameEnumField.hx | 4 +-- src/refactor/rename/RenameField.hx | 28 +++++++++++++-- src/refactor/rename/RenameTypeName.hx | 1 - src/refactor/typing/TypingHelper.hx | 13 ++++++- test/refactor/rename/RenameEnumTest.hx | 23 ++++++++++++ testcases/enums/Main.hx | 49 ++++++++++++++++++++++++++ testcases/enums/SmokeDetector.hx | 13 +++++++ 8 files changed, 126 insertions(+), 9 deletions(-) create mode 100644 testcases/enums/SmokeDetector.hx diff --git a/src/refactor/Rename.hx b/src/refactor/Rename.hx index 058dbba..8dc24e7 100644 --- a/src/refactor/Rename.hx +++ b/src/refactor/Rename.hx @@ -91,10 +91,10 @@ class Rename { context.verboseLog('rename property "${identifier.name}" to "${context.what.toName}"'); RenameField.refactorField(context, file, identifier, false); case FieldVar(isStatic): - context.verboseLog('rename field "${identifier.name}" to "${context.what.toName}"'); + context.verboseLog('rename ${isStatic ? "static" : ""} field "${identifier.name}" to "${context.what.toName}"'); RenameField.refactorField(context, file, identifier, isStatic); case Method(isStatic): - context.verboseLog('rename class method "${identifier.name}" to "${context.what.toName}"'); + context.verboseLog('rename ${isStatic ? "static" : ""} class method "${identifier.name}" to "${context.what.toName}"'); RenameField.refactorField(context, file, identifier, isStatic); case TypedParameter: Promise.reject(RefactorResult.Unsupported(identifier.toString()).printRenameResult()); diff --git a/src/refactor/rename/RenameEnumField.hx b/src/refactor/rename/RenameEnumField.hx index 816c919..5b30408 100644 --- a/src/refactor/rename/RenameEnumField.hx +++ b/src/refactor/rename/RenameEnumField.hx @@ -26,12 +26,12 @@ class RenameEnumField { } case Global | SamePackage | Imported | StarImported: } - RenameHelper.replaceTextWithPrefix(use, typeName, context.what.toName, changelist); + RenameHelper.replaceTextWithPrefix(use, typeName + ".", context.what.toName, changelist); } allUses = context.nameMap.getIdentifiers('$fullModuleTypeName.${identifier.name}'); for (use in allUses) { - RenameHelper.replaceTextWithPrefix(use, fullModuleTypeName, context.what.toName, changelist); + RenameHelper.replaceTextWithPrefix(use, fullModuleTypeName + ".", context.what.toName, changelist); } allUses = context.nameMap.matchIdentifierPart(identifier.name, true); diff --git a/src/refactor/rename/RenameField.hx b/src/refactor/rename/RenameField.hx index ee072be..f8843af 100644 --- a/src/refactor/rename/RenameField.hx +++ b/src/refactor/rename/RenameField.hx @@ -38,7 +38,7 @@ class RenameField { } if (isStatic) { - replaceStaticUse(context, changelist, type, identifier.name); + changes.push(replaceStaticUse(context, changelist, type, identifier.name)); switch (identifier.type) { case Method(true): changes.push(RenameHelper.replaceStaticExtension(context, changelist, identifier)); @@ -116,7 +116,7 @@ class RenameField { } } - static function replaceStaticUse(context:RenameContext, changelist:Changelist, type:Type, fromName:String) { + static function replaceStaticUse(context:RenameContext, changelist:Changelist, type:Type, fromName:String):Promise { var packName:String = type.file.getPackage(); var allUses:Array = context.nameMap.getIdentifiers('${type.name.name}.$fromName'); for (use in allUses) { @@ -131,9 +131,31 @@ class RenameField { } var fullModuleName:String = type.fullModuleName; - var allUses:Array = context.nameMap.getIdentifiers('$fullModuleName.$fromName'); + allUses = context.nameMap.getIdentifiers('$fullModuleName.$fromName'); for (use in allUses) { RenameHelper.replaceTextWithPrefix(use, '$fullModuleName.', context.what.toName, changelist); } + var changes:Array> = []; + switch (type.name.type) { + case Abstract: + allUses = context.nameMap.matchIdentifierPart(fromName, true); + for (use in allUses) { + switch (use.type) { + case CaseLabel(switchIdentifier): + changes.push(TypingHelper.matchesType(context, { + name: switchIdentifier.name, + pos: switchIdentifier.pos.start, + defineType: switchIdentifier.defineType + }, ClasspathType(type, [])).then(function(matched:Bool) { + if (matched) { + RenameHelper.replaceTextWithPrefix(use, "", context.what.toName, changelist); + } + })); + default: + } + } + default: + } + return Promise.all(changes).then(null); } } diff --git a/src/refactor/rename/RenameTypeName.hx b/src/refactor/rename/RenameTypeName.hx index ca86fe1..ad9b03f 100644 --- a/src/refactor/rename/RenameTypeName.hx +++ b/src/refactor/rename/RenameTypeName.hx @@ -39,7 +39,6 @@ class RenameTypeName { } } allUses = context.nameMap.matchIdentifierPart(identifier.name, true); - var type = context.typeList.findTypeName("Index"); var changes:Array> = []; for (use in allUses) { diff --git a/src/refactor/typing/TypingHelper.hx b/src/refactor/typing/TypingHelper.hx index 5c74bc8..cfbdfa0 100644 --- a/src/refactor/typing/TypingHelper.hx +++ b/src/refactor/typing/TypingHelper.hx @@ -221,7 +221,18 @@ class TypingHelper { candidate = fieldCandidate; } if (candidate == null) { - return Promise.resolve(null); + var typeCandidates = context.typeList.findTypeName(name); + for (candidateType in typeCandidates) { + switch (containerType.file.importsModule(candidateType.file.getPackage(), candidateType.file.getMainModulName(), candidateType.name.name)) { + case None: + case ImportedWithAlias(_): + case Global | SamePackage | Imported | StarImported: + return Promise.resolve(ClasspathType(candidateType, [])); + } + } + if (candidate == null) { + return Promise.resolve(null); + } } var typeHint:Null = candidate.getTypeHint(); switch (candidate.type) { diff --git a/test/refactor/rename/RenameEnumTest.hx b/test/refactor/rename/RenameEnumTest.hx index 509fdd5..bd6213d 100644 --- a/test/refactor/rename/RenameEnumTest.hx +++ b/test/refactor/rename/RenameEnumTest.hx @@ -13,6 +13,14 @@ class RenameEnumTest extends RenameTestBase { makeReplaceTestEdit("testcases/enums/Main.hx", "IdentType", 90, 104), makeReplaceTestEdit("testcases/enums/Main.hx", "IdentType", 480, 494), makeReplaceTestEdit("testcases/enums/Main.hx", "IdentType", 2012, 2026), + makeReplaceTestEdit("testcases/enums/Main.hx", "IdentType", 2202, 2216), + makeReplaceTestEdit("testcases/enums/Main.hx", "IdentType", 2239, 2253), + makeReplaceTestEdit("testcases/enums/Main.hx", "IdentType", 2269, 2283), + makeReplaceTestEdit("testcases/enums/Main.hx", "IdentType", 2301, 2315), + makeReplaceTestEdit("testcases/enums/Main.hx", "IdentType", 2380, 2394), + makeReplaceTestEdit("testcases/enums/Main.hx", "IdentType", 2428, 2442), + makeReplaceTestEdit("testcases/enums/Main.hx", "IdentType", 2481, 2495), + makeReplaceTestEdit("testcases/enums/Main.hx", "IdentType", 2531, 2545), ]; checkRename({fileName: "testcases/enums/IdentifierType.hx", toName: "IdentType", pos: 30}, edits, async); } @@ -25,6 +33,8 @@ class RenameEnumTest extends RenameTestBase { makeReplaceTestEdit("testcases/enums/Main.hx", "LocalScopeVar", 1427, 1438), makeReplaceTestEdit("testcases/enums/Main.hx", "LocalScopeVar", 1734, 1745), makeReplaceTestEdit("testcases/enums/Main.hx", "LocalScopeVar", 1869, 1880), + makeReplaceTestEdit("testcases/enums/Main.hx", "LocalScopeVar", 2316, 2327), + makeReplaceTestEdit("testcases/enums/Main.hx", "LocalScopeVar", 2496, 2507), ]; checkRename({fileName: "testcases/enums/IdentifierType.hx", toName: "LocalScopeVar", pos: 77}, edits, async); } @@ -37,6 +47,7 @@ class RenameEnumTest extends RenameTestBase { makeReplaceTestEdit("testcases/enums/Main.hx", "FunctionCall", 1101, 1105), makeReplaceTestEdit("testcases/enums/Main.hx", "FunctionCall", 1395, 1399), makeReplaceTestEdit("testcases/enums/Main.hx", "FunctionCall", 1702, 1706), + makeReplaceTestEdit("testcases/enums/Main.hx", "FunctionCall", 2254, 2258), ]; checkRename({fileName: "testcases/enums/IdentifierType.hx", toName: "FunctionCall", pos: 55}, edits, async); } @@ -49,7 +60,19 @@ class RenameEnumTest extends RenameTestBase { makeReplaceTestEdit("testcases/enums/Main.hx", "GlobalScopeVar", 1491, 1503), makeReplaceTestEdit("testcases/enums/Main.hx", "GlobalScopeVar", 1798, 1810), makeReplaceTestEdit("testcases/enums/Main.hx", "GlobalScopeVar", 1904, 1916), + makeReplaceTestEdit("testcases/enums/Main.hx", "GlobalScopeVar", 2395, 2407), + makeReplaceTestEdit("testcases/enums/Main.hx", "GlobalScopeVar", 2546, 2558), ]; checkRename({fileName: "testcases/enums/IdentifierType.hx", toName: "GlobalScopeVar", pos: 103}, edits, async); } + + public function testRenameBedroom1(async:Async) { + var edits:Array = [ + makeReplaceTestEdit("testcases/enums/Main.hx", "MasterBedroom", 2740, 2748), + makeReplaceTestEdit("testcases/enums/Main.hx", "MasterBedroom", 3137, 3145), + makeReplaceTestEdit("testcases/enums/Main.hx", "MasterBedroom", 3408, 3416), + makeReplaceTestEdit("testcases/enums/SmokeDetector.hx", "MasterBedroom", 127, 135), + ]; + checkRename({fileName: "testcases/enums/SmokeDetector.hx", toName: "MasterBedroom", pos: 132}, edits, async); + } } diff --git a/testcases/enums/Main.hx b/testcases/enums/Main.hx index 7a81196..6cc0972 100644 --- a/testcases/enums/Main.hx +++ b/testcases/enums/Main.hx @@ -97,4 +97,53 @@ class Main { function printType(prefix:String, type:IdentifierType) { Sys.println(prefix + type); } + + function listParentChildsFullName(identifier:Identifier) { + for (item in identifier.parent.children) { + switch (item.type) { + case IdentifierType.PackageName: + case IdentifierType.Call: + case IdentifierType.Access: + case IdentifierType.ScopedLocal(scopeEnd): + new ScopedLocal(scopeEnd); + case IdentifierType.ScopedGlobal(scopeEnd): + case IdentifierType.StringConst: + } + } + printType("", IdentifierType.ScopedLocal(100)); + printType("", IdentifierType.ScopedGlobal(100)); + printType("", new ScopedLocal(100).type); + } + + function alarm(detector:SmokeDetector) { + switch (detector) { + case Staircase: + trace("alarm in staircase!!"); + case Bedroom1: + trace("alarm in bedroom 1!!"); + case Hallway1: + trace("alarm in hallway 1!!"); + case Office: + trace("alarm in office!!"); + case LivingRoom: + trace("alarm in living room!!"); + } + trace(detector.available()); + } + + function alarm2(detector:SmokeDetector) { + switch (detector) { + case SmokeDetector.Staircase: + trace("alarm in staircase!!"); + case SmokeDetector.Bedroom1: + trace("alarm in bedroom 1!!"); + case SmokeDetector.Hallway1: + trace("alarm in hallway 1!!"); + case SmokeDetector.Office: + trace("alarm in office!!"); + case SmokeDetector.LivingRoom: + trace("alarm in living room!!"); + } + trace(SmokeDetector.Bedroom1.available()); + } } diff --git a/testcases/enums/SmokeDetector.hx b/testcases/enums/SmokeDetector.hx new file mode 100644 index 0000000..9339b58 --- /dev/null +++ b/testcases/enums/SmokeDetector.hx @@ -0,0 +1,13 @@ +package enums; + +enum abstract SmokeDetector(String) from String to String { + var Staircase = "zigbee.0.0000000000000000"; + var Bedroom1 = "zigbee.1.0000000000000000"; + var Hallway1 = "zigbee.2.0000000000000000"; + var Office = "zigbee.3.0000000000000000"; + var LivingRoom = "zigbee.4.0000000000000000"; + + public inline function available():String { + return '${this}.available'; + } +} From c132b04bd9161db431a7c435467f8e889403b0d0 Mon Sep 17 00:00:00 2001 From: AlexHaxe Date: Mon, 2 Dec 2024 16:46:38 +0100 Subject: [PATCH 36/59] fixed extraction of named local functions --- src/refactor/refactor/ExtractMethod.hx | 12 +++++++++--- src/refactor/refactor/ExtractType.hx | 2 +- .../refactor/extractmethod/CodeGenLocalFunction.hx | 6 +++--- .../refactor/extractmethod/ExtractMethodData.hx | 2 +- test/refactor/refactor/RefactorExtractMethodTest.hx | 13 +++++++++++++ testcases/methods/LambdaExample.hx | 8 ++++++++ 6 files changed, 35 insertions(+), 8 deletions(-) diff --git a/src/refactor/refactor/ExtractMethod.hx b/src/refactor/refactor/ExtractMethod.hx index b98165a..e7b5d8d 100644 --- a/src/refactor/refactor/ExtractMethod.hx +++ b/src/refactor/refactor/ExtractMethod.hx @@ -149,8 +149,8 @@ class ExtractMethod { return null; } switch (child.tok) { - case Const(_): - functionType = Named; + case Const(CIdent(s)): + functionType = Named(s); case POpen: functionType = Unnamed; default: @@ -210,7 +210,13 @@ class ExtractMethod { return null; } final name:String = nameToken.toString(); - final newMethodName = '${name}Extract'; + var newMethodName = '${name}Extract'; + switch (functionType) { + case NoFunction: + case Named(name): + newMethodName = name; + case Unnamed: + } final functionIndent:Int = RefactorHelper.calcIndentation(context, content, context.what.fileName, parentFunction.pos.min); final snippetIndent:Int = RefactorHelper.calcIndentation(context, content, context.what.fileName, tokenStart.pos.min); diff --git a/src/refactor/refactor/ExtractType.hx b/src/refactor/refactor/ExtractType.hx index 2818a9c..859c4bf 100644 --- a/src/refactor/refactor/ExtractType.hx +++ b/src/refactor/refactor/ExtractType.hx @@ -18,7 +18,7 @@ class ExtractType { if (extractData == null) { return Unsupported; } - return Supported('Extract "${extractData.name}" to ${Path.withoutDirectory(extractData.newFileName)}'); + return Supported('Extract Type "${extractData.name}" to ${Path.withoutDirectory(extractData.newFileName)}'); } public static function doRefactor(context:RefactorContext):Promise { diff --git a/src/refactor/refactor/extractmethod/CodeGenLocalFunction.hx b/src/refactor/refactor/extractmethod/CodeGenLocalFunction.hx index 4c63400..cd51898 100644 --- a/src/refactor/refactor/extractmethod/CodeGenLocalFunction.hx +++ b/src/refactor/refactor/extractmethod/CodeGenLocalFunction.hx @@ -17,7 +17,7 @@ class CodeGenLocalFunction extends CodeGenBase { return switch (extractData.functionType) { case NoFunction: ""; - case Named: + case Named(_): ""; case Unnamed if (neededIdentifiers.length == 0): extractData.newMethodName; @@ -45,7 +45,7 @@ class CodeGenLocalFunction extends CodeGenBase { var func:Null = switch (extractData.functionType) { case NoFunction: null; - case Named: + case Named(_): extractData.startToken.access().firstChild().token; case Unnamed: switch (extractData.startToken.tok) { @@ -74,7 +74,7 @@ class CodeGenLocalFunction extends CodeGenBase { var body:Null = switch (extractData.functionType) { case NoFunction: null; - case Named: + case Named(_): extractData.startToken.access().firstChild().firstOf(BrOpen).token; case Unnamed: switch (extractData.startToken.tok) { diff --git a/src/refactor/refactor/extractmethod/ExtractMethodData.hx b/src/refactor/refactor/extractmethod/ExtractMethodData.hx index 4920578..2af265b 100644 --- a/src/refactor/refactor/extractmethod/ExtractMethodData.hx +++ b/src/refactor/refactor/extractmethod/ExtractMethodData.hx @@ -17,6 +17,6 @@ typedef ExtractMethodData = { enum LocalFunctionType { NoFunction; - Named; + Named(name:String); Unnamed; } diff --git a/test/refactor/refactor/RefactorExtractMethodTest.hx b/test/refactor/refactor/RefactorExtractMethodTest.hx index 2e0639b..9523525 100644 --- a/test/refactor/refactor/RefactorExtractMethodTest.hx +++ b/test/refactor/refactor/RefactorExtractMethodTest.hx @@ -809,6 +809,19 @@ class RefactorExtractMethodTest extends RefactorTestBase { checkRefactor(RefactorExtractMethod, {fileName: "testcases/methods/LambdaExample.hx", posStart: 669, posEnd: 679}, edits, async); } + function testLambdaExampleNamedCallbackFunc(async:Async) { + var edits:Array = [ + makeReplaceTestEdit("testcases/methods/LambdaExample.hx", "", 727, 806, Format(2)), + makeInsertTestEdit("testcases/methods/LambdaExample.hx", + "function processCB(n:Int, m:Int):Int {\n" + + "var temp = n * m;\n" + + " return temp + m;\n" + + "}\n", 836, Format(1)), + ]; + addTypeHint("testcases/methods/LambdaExample.hx", 744, LibType("Int", "Int", [])); + checkRefactor(RefactorExtractMethod, {fileName: "testcases/methods/LambdaExample.hx", posStart: 726, posEnd: 806}, edits, async); + } + function testEditDocInner(async:Async) { var edits:Array = [ makeReplaceTestEdit("testcases/methods/TestEditDoc.hx", "text = addChangeExtract(range, text);\n", 388, 485, Format(4)), diff --git a/testcases/methods/LambdaExample.hx b/testcases/methods/LambdaExample.hx index 759fb7a..550710d 100644 --- a/testcases/methods/LambdaExample.hx +++ b/testcases/methods/LambdaExample.hx @@ -34,6 +34,14 @@ class LambdaExample { var numbers = [1, 2, 3]; var result = numbers.map(n -> n * n); } + + function processNamedCallbackFunc() { + function processCB(n:Int, m:Int) { + var temp = n * m; + return temp + m; + } + processItem(processCB); + } } typedef ProcessCallback = (n:Int, m:Int) -> Int; From 419b4268ba3e430f905846004ae34a0107cbdfeb Mon Sep 17 00:00:00 2001 From: AlexHaxe Date: Mon, 2 Dec 2024 18:45:14 +0100 Subject: [PATCH 37/59] added type resolution for return type hints to extract interface --- src/refactor/refactor/ExtractInterface.hx | 131 ++++++++++++-------- test/refactor/refactor/RefactorClassTest.hx | 18 ++- testcases/classes/BaseClass.hx | 1 + 3 files changed, 95 insertions(+), 55 deletions(-) diff --git a/src/refactor/refactor/ExtractInterface.hx b/src/refactor/refactor/ExtractInterface.hx index 446dbc6..95f9698 100644 --- a/src/refactor/refactor/ExtractInterface.hx +++ b/src/refactor/refactor/ExtractInterface.hx @@ -34,18 +34,19 @@ class ExtractInterface { // copy header + imports final fileHeader:String = makeHeader(extractData, context, findImportCandidates(extractData, context, fields)); // create interface + fields - final fieldDefinition:String = makeFields(extractData, context, fields); - final interfaceText:String = 'interface ${extractData.newTypeName} {\n' + fieldDefinition + "}"; + return makeFields(extractData, context, fields).then(function(fieldDefinitions:String) { + final interfaceText:String = 'interface ${extractData.newTypeName} {\n' + fieldDefinitions + "}"; - changelist.addChange(extractData.newFileName, - InsertText(fileHeader + interfaceText, {fileName: extractData.newFileName, start: 0, end: 0}, Format(0)), null); + changelist.addChange(extractData.newFileName, + InsertText(fileHeader + interfaceText, {fileName: extractData.newFileName, start: 0, end: 0}, Format(0)), null); - final implementsText:String = ' implements ${extractData.newTypeName}'; - final pos:Position = findImplementsPos(extractData); - changelist.addChange(extractData.srcFile.name, - InsertText(implementsText, {fileName: extractData.srcFile.name, start: pos.max, end: pos.max}, NoFormat), null); + final implementsText:String = ' implements ${extractData.newTypeName}'; + final pos:Position = findImplementsPos(extractData); + changelist.addChange(extractData.srcFile.name, + InsertText(implementsText, {fileName: extractData.srcFile.name, start: pos.max, end: pos.max}, NoFormat), null); - return Promise.resolve(changelist.execute()); + return Promise.resolve(changelist.execute()); + }); } static function makeExtractInterfaceData(context:CanRefactorContext):Null { @@ -284,55 +285,87 @@ class ExtractInterface { return names; } - static function makeFields(extractData:ExtractInterfaceData, context:RefactorContext, fields:Array):String { - final buf:StringBuf = new StringBuf(); + static function makeFields(extractData:ExtractInterfaceData, context:RefactorContext, fields:Array):Promise { + var changes:Array> = []; + for (field in fields) { + changes.push(makeField(context, extractData, field)); + } + + return Promise.all(changes).then(function(fields) { + return Promise.resolve(fields.join("")); + }); + } + + static function makeField(context:RefactorContext, extractData:ExtractInterfaceData, field:FieldData):Promise { final defaultHint:String = ":Void"; + final buf:StringBuf = new StringBuf(); - for (field in fields) { - buf.add("\t"); - var text:String = RefactorHelper.extractText(context.converter, extractData.content, field.pos.min, field.pos.max); - var index = text.lastIndexOf("function "); - if (index < 0) { - index = text.lastIndexOf("var "); + var text:String = RefactorHelper.extractText(context.converter, extractData.content, field.pos.min, field.pos.max); + var index = text.lastIndexOf("function "); + if (index < 0) { + index = text.lastIndexOf("var "); + } + if (index < 0) { + index = text.lastIndexOf("final "); + } + if (index > 0) { + var commentIndex:Int = text.lastIndexOf("*/", index); + var comment = ""; + if (commentIndex < 0) { + commentIndex = 0; } - if (index < 0) { - index = text.lastIndexOf("final "); + if (commentIndex > 0) { + comment = text.substr(0, commentIndex); } - if (index > 0) { - var commentIndex:Int = text.lastIndexOf("*/", index); - var comment = ""; - if (commentIndex < 0) { - commentIndex = 0; - } - if (commentIndex > 0) { - comment = text.substr(0, commentIndex); + var modifier = text.substring(commentIndex, index); + final funcSignature = text.substr(index); + modifier = modifier.replace("public", ""); + modifier = modifier.replace("inline", ""); + modifier = modifier.replace("override", ""); + modifier = modifier.replace("abstract", ""); + text = comment + modifier + funcSignature; + if (!funcSignature.endsWith(";")) { + text += ";"; + } + } + buf.add(text); + if (field.isSharp) { + buf.add("\n"); + return Promise.resolve(buf.toString()); + } + if (!field.hasHint) { + return typeHint(context, field.pos).then(function(typeHint):Promise { + if (typeHint == null) { + return Promise.resolve(""); } - var modifier = text.substring(commentIndex, index); - final funcSignature = text.substr(index); - modifier = modifier.replace("public", ""); - modifier = modifier.replace("inline", ""); - modifier = modifier.replace("override", ""); - modifier = modifier.replace("abstract", ""); - text = comment + modifier + funcSignature; - if (!funcSignature.endsWith(";")) { - text += ";"; + buf.add(":"); + buf.add(typeHint.printTypeHint()); + if (!text.endsWith(";")) { + buf.add(";"); } - } - buf.add(text); - if (field.isSharp) { buf.add("\n"); - continue; - } - if (!field.hasHint) { - buf.add(defaultHint); - } - if (!text.endsWith(";")) { - buf.add(";"); - } - buf.add("\n"); + return Promise.resolve(buf.toString()); + }); + } + if (!text.endsWith(";")) { + buf.add(";"); } + buf.add("\n"); + return Promise.resolve(buf.toString()); + } - return buf.toString(); + static function typeHint(context:RefactorContext, pos:Position):Promise { + if (pos == null) { + return Promise.reject("failed to find return type of selected code"); + } + return TypingHelper.findTypeWithTyper(context, pos.file, pos.min).then(function(typeHint) { + return switch (typeHint) { + case null | ClasspathType(_) | LibType(_) | StructType(_) | UnknownType(_): + Promise.resolve(typeHint); + case FunctionType(args, retVal): + Promise.resolve(retVal); + } + }); } } diff --git a/test/refactor/refactor/RefactorClassTest.hx b/test/refactor/refactor/RefactorClassTest.hx index b7cfeb9..bb29e06 100644 --- a/test/refactor/refactor/RefactorClassTest.hx +++ b/test/refactor/refactor/RefactorClassTest.hx @@ -77,7 +77,8 @@ class RefactorClassTest extends RefactorTestBase { + "package classes;\n\n" + "class NotDocModule {\n" + " public function new() {}\n" - + "}", 0, Format(0)), + + "}", 0, + Format(0)), makeInsertTestEdit("testcases/classes/pack/UseDocModule.hx", "import classes.NotDocModule;\n", 23), ]; checkRefactor(RefactorExtractType, {fileName: "testcases/classes/DocModule.hx", posStart: 73, posEnd: 73}, edits, async); @@ -90,14 +91,19 @@ class RefactorClassTest extends RefactorTestBase { makeInsertTestEdit("testcases/classes/IBaseClass.hx", "package classes;\n\n" + "interface IBaseClass {\n" - + " function doSomething(data:Array):Void;\n" - + " function doSomething3(d:Array):Void;\n" - + " function doSomething4(d:Array):Void;\n" - + " function doSomething5(d:Array):Void;\n" - + " function doSomething6(d:Array):Void;\n" + + "function doSomething(data:Array):Void;\n" + + "function doSomething3(d:Array):Void;\n" + + "function doSomething4(d:Array):Void;\n" + + "function doSomething5(d:Array):Void;\n" + + "function doSomething6(d:Array):Bool;\n" + "}", 0, Format(0)), ]; + addTypeHint("testcases/classes/BaseClass.hx", 112, LibType("Void", "Void", [])); + addTypeHint("testcases/classes/BaseClass.hx", 205, LibType("Void", "Void", [])); + addTypeHint("testcases/classes/BaseClass.hx", 276, LibType("Void", "Void", [])); + addTypeHint("testcases/classes/BaseClass.hx", 364, LibType("Void", "Void", [])); + addTypeHint("testcases/classes/BaseClass.hx", 448, LibType("Bool", "Bool", [])); checkRefactor(RefactorExtractInterface, {fileName: "testcases/classes/BaseClass.hx", posStart: 27, posEnd: 27}, edits, async); } } diff --git a/testcases/classes/BaseClass.hx b/testcases/classes/BaseClass.hx index 94be577..d47de4f 100644 --- a/testcases/classes/BaseClass.hx +++ b/testcases/classes/BaseClass.hx @@ -30,5 +30,6 @@ class BaseClass { case Case1(data): default: } + return true; } } From 9d7b478c27113bc45474a094661a8aee4c080e73 Mon Sep 17 00:00:00 2001 From: AlexHaxe Date: Mon, 2 Dec 2024 18:51:04 +0100 Subject: [PATCH 38/59] fixed testcases --- src/refactor/refactor/ExtractInterface.hx | 9 +++++++-- test/refactor/refactor/RefactorClassTest.hx | 10 +++++----- 2 files changed, 12 insertions(+), 7 deletions(-) diff --git a/src/refactor/refactor/ExtractInterface.hx b/src/refactor/refactor/ExtractInterface.hx index 95f9698..f3692cd 100644 --- a/src/refactor/refactor/ExtractInterface.hx +++ b/src/refactor/refactor/ExtractInterface.hx @@ -198,6 +198,7 @@ class ExtractInterface { return; } var data:FieldData = { + nameToken: nameToken, pos: pos, hasHint: hasHint, isSharp: false @@ -223,6 +224,7 @@ class ExtractInterface { var pos:Position = {file: child.pos.file, min: child.pos.min, max: child.pos.max}; expandPos(pos, child.getFirstChild().getPos()); fields.push({ + nameToken: child, pos: pos, hasHint: true, isSharp: true @@ -231,6 +233,7 @@ class ExtractInterface { case Sharp("else"): var pos:Position = {file: child.pos.file, min: child.pos.min, max: child.pos.max}; fields.push({ + nameToken: child, pos: pos, hasHint: true, isSharp: true @@ -239,6 +242,7 @@ class ExtractInterface { case Sharp("end"): var pos:Position = {file: child.pos.file, min: child.pos.min, max: child.pos.max}; fields.push({ + nameToken: child, pos: pos, hasHint: true, isSharp: true @@ -334,7 +338,7 @@ class ExtractInterface { return Promise.resolve(buf.toString()); } if (!field.hasHint) { - return typeHint(context, field.pos).then(function(typeHint):Promise { + return typeHint(context, field.nameToken.pos).then(function(typeHint):Promise { if (typeHint == null) { return Promise.resolve(""); } @@ -358,7 +362,7 @@ class ExtractInterface { if (pos == null) { return Promise.reject("failed to find return type of selected code"); } - return TypingHelper.findTypeWithTyper(context, pos.file, pos.min).then(function(typeHint) { + return TypingHelper.findTypeWithTyper(context, context.what.fileName, pos.max - 1).then(function(typeHint) { return switch (typeHint) { case null | ClasspathType(_) | LibType(_) | StructType(_) | UnknownType(_): Promise.resolve(typeHint); @@ -381,6 +385,7 @@ typedef ExtractInterfaceData = { } typedef FieldData = { + var nameToken:TokenTree; var pos:Position; var hasHint:Bool; var isSharp:Bool; diff --git a/test/refactor/refactor/RefactorClassTest.hx b/test/refactor/refactor/RefactorClassTest.hx index bb29e06..5006a9b 100644 --- a/test/refactor/refactor/RefactorClassTest.hx +++ b/test/refactor/refactor/RefactorClassTest.hx @@ -99,11 +99,11 @@ class RefactorClassTest extends RefactorTestBase { + "}", 0, Format(0)), ]; - addTypeHint("testcases/classes/BaseClass.hx", 112, LibType("Void", "Void", [])); - addTypeHint("testcases/classes/BaseClass.hx", 205, LibType("Void", "Void", [])); - addTypeHint("testcases/classes/BaseClass.hx", 276, LibType("Void", "Void", [])); - addTypeHint("testcases/classes/BaseClass.hx", 364, LibType("Void", "Void", [])); - addTypeHint("testcases/classes/BaseClass.hx", 448, LibType("Bool", "Bool", [])); + addTypeHint("testcases/classes/BaseClass.hx", 131, LibType("Void", "Void", [])); + addTypeHint("testcases/classes/BaseClass.hx", 225, LibType("Void", "Void", [])); + addTypeHint("testcases/classes/BaseClass.hx", 296, LibType("Void", "Void", [])); + addTypeHint("testcases/classes/BaseClass.hx", 384, LibType("Void", "Void", [])); + addTypeHint("testcases/classes/BaseClass.hx", 468, LibType("Bool", "Bool", [])); checkRefactor(RefactorExtractInterface, {fileName: "testcases/classes/BaseClass.hx", posStart: 27, posEnd: 27}, edits, async); } } From df49ba7fb2fa997752db010f91311eec016c5b11 Mon Sep 17 00:00:00 2001 From: AlexHaxe Date: Mon, 2 Dec 2024 19:17:15 +0100 Subject: [PATCH 39/59] fixed extrating from constructor fixed extract type from root package --- src/refactor/discover/UsageCollector.hx | 4 ++-- src/refactor/refactor/ExtractInterface.hx | 2 +- src/refactor/refactor/ExtractType.hx | 6 +++--- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/src/refactor/discover/UsageCollector.hx b/src/refactor/discover/UsageCollector.hx index 48cb5cf..149cd66 100644 --- a/src/refactor/discover/UsageCollector.hx +++ b/src/refactor/discover/UsageCollector.hx @@ -926,7 +926,7 @@ class UsageCollector { return null; } switch (nameToken.tok) { - case Kwd(KwdThis) | Kwd(KwdNull) | Const(CIdent(_)): + case Kwd(KwdNew) | Kwd(KwdThis) | Kwd(KwdNull) | Const(CIdent(_)): default: return null; } @@ -946,7 +946,7 @@ class UsageCollector { } for (child in parentPart.children) { switch (child.tok) { - case Kwd(KwdThis) | Kwd(KwdNull) | Const(_): + case Kwd(KwdNew) | Kwd(KwdThis) | Kwd(KwdNull) | Const(_): pack.push(child.toString()); case Dot: case POpen: diff --git a/src/refactor/refactor/ExtractInterface.hx b/src/refactor/refactor/ExtractInterface.hx index f3692cd..ef498b1 100644 --- a/src/refactor/refactor/ExtractInterface.hx +++ b/src/refactor/refactor/ExtractInterface.hx @@ -177,7 +177,7 @@ class ExtractInterface { for (child in nameToken.children) { switch (child.tok) { - case Kwd(KwdPrivate) | Kwd(KwdStatic): + case Kwd(KwdPrivate) | Kwd(KwdStatic) | Kwd(KwdMacro): return; case Kwd(KwdPublic): isPublic = true; diff --git a/src/refactor/refactor/ExtractType.hx b/src/refactor/refactor/ExtractType.hx index 859c4bf..8382b17 100644 --- a/src/refactor/refactor/ExtractType.hx +++ b/src/refactor/refactor/ExtractType.hx @@ -187,9 +187,9 @@ class ExtractType { static function findImportLocations(context:CanRefactorContext, extractData:ExtractTypeData, changelist:Changelist) { final oldFullName = extractData.oldType.fullModuleName; - final oldPackageName = extractData.oldFile.packageIdentifier.name; - final oldModulName = oldPackageName + "." + extractData.oldFile.getMainModulName(); - final newFullName = oldPackageName + "." + extractData.name; + final oldPackageName = extractData.oldFile.packageIdentifier?.name + "." ?? ""; + final oldModulName = oldPackageName + extractData.oldFile.getMainModulName(); + final newFullName = oldPackageName + extractData.name; var allUses:Array = context.nameMap.getIdentifiers(oldFullName); for (use in allUses) { changelist.addChange(use.file.name, ReplaceText(newFullName, use.pos, NoFormat), use); From 3001ac0d3519e197dd130edd4eb65b606df4f842 Mon Sep 17 00:00:00 2001 From: AlexHaxe Date: Tue, 3 Dec 2024 00:15:20 +0100 Subject: [PATCH 40/59] added Extract Constructor Params refactor module --- src/refactor/Refactoring.hx | 5 + .../refactor/ExtractConstructorParams.hx | 234 ++++++++++++++++++ src/refactor/refactor/RefactorType.hx | 1 + 3 files changed, 240 insertions(+) create mode 100644 src/refactor/refactor/ExtractConstructorParams.hx diff --git a/src/refactor/Refactoring.hx b/src/refactor/Refactoring.hx index 05891e4..7c3cc09 100644 --- a/src/refactor/Refactoring.hx +++ b/src/refactor/Refactoring.hx @@ -2,6 +2,7 @@ package refactor; import refactor.refactor.CanRefactorContext; import refactor.refactor.CanRefactorResult; +import refactor.refactor.ExtractConstructorParams; import refactor.refactor.ExtractInterface; import refactor.refactor.ExtractMethod; import refactor.refactor.ExtractType; @@ -17,6 +18,8 @@ class Refactoring { return ExtractMethod.canRefactor(context); case RefactorExtractType: return ExtractType.canRefactor(context); + case RefactorExtractConstructorParams: + return ExtractConstructorParams.canRefactor(context); } return null; } @@ -29,6 +32,8 @@ class Refactoring { return ExtractMethod.doRefactor(context); case RefactorExtractType: return ExtractType.doRefactor(context); + case RefactorExtractConstructorParams: + return ExtractConstructorParams.doRefactor(context); } return Promise.reject("no refactor type selected"); } diff --git a/src/refactor/refactor/ExtractConstructorParams.hx b/src/refactor/refactor/ExtractConstructorParams.hx new file mode 100644 index 0000000..569d00f --- /dev/null +++ b/src/refactor/refactor/ExtractConstructorParams.hx @@ -0,0 +1,234 @@ +package refactor.refactor; + +import refactor.discover.File; +import refactor.discover.Identifier; +import refactor.edits.Changelist; +import refactor.refactor.RefactorHelper.TokensAtPos; + +class ExtractConstructorParams { + public static function canRefactor(context:CanRefactorContext):CanRefactorResult { + final extractData = makeExtractConstructorParamsData(context); + if (extractData == null) { + return Unsupported; + } + return Supported('Extract Constructor Params'); + } + + public static function doRefactor(context:RefactorContext):Promise { + final extractData = makeExtractConstructorParamsData(context); + if (extractData == null) { + return Promise.reject("failed to collect extract method data"); + } + final changelist:Changelist = new Changelist(context); + + final newFields:StringBuf = new StringBuf(); + final newAssigns:StringBuf = new StringBuf(); + for (param in extractData.parameters) { + newFields.add(makeField(context, extractData, param)); + newAssigns.add(makeAssignment(extractData, param)); + } + + final fieldInsertPos = findFieldInsertPos(context, extractData); + final assignmentInsertPos = findAssignmentInsertPos(context, extractData); + if (fieldInsertPos <= 0 || assignmentInsertPos <= 0) { + return Promise.reject("Extract Constructor Parameter cannot find positions for new fields"); + } + + changelist.addChange(context.what.fileName, + InsertText(newFields.toString(), {fileName: context.what.fileName, start: fieldInsertPos, end: fieldInsertPos}, Format(1)), null); + + changelist.addChange(context.what.fileName, + InsertText(newAssigns.toString(), {fileName: context.what.fileName, start: assignmentInsertPos, end: assignmentInsertPos}, Format(2)), null); + + return Promise.resolve(changelist.execute()); + } + + static function makeExtractConstructorParamsData(context:CanRefactorContext):Null { + final fileContent = context.fileReader(context.what.fileName); + var content:String; + var root:TokenTree; + switch (fileContent) { + case Text(_): + return null; + case Token(tokens, text): + content = text; + root = tokens; + } + if (root == null) { + return null; + } + if (content == null) { + return null; + } + + final file:Null = context.fileList.getFile(context.what.fileName); + if (file == null) { + return null; + } + + final tokensStart:TokensAtPos = RefactorHelper.findTokensAtPos(root, context.what.posStart); + + // find corresponding tokens in tokentree, selection start/end in whitespace + if (tokensStart.after == null) { + return null; + } + + final tokenNew:Null = tokensStart.after; + + if (tokenNew == null) { + return null; + } + if (!tokenNew.matches(Kwd(KwdNew))) { + return null; + } + final identifierNew = file.getIdentifier(tokenNew.pos.min); + // find constructor parameters and see if there's at least one with no corresponding field + var parameters = findParametersWithNoFields(context, identifierNew); + if (parameters.length <= 0) { + return null; + } + + final superTokens = tokenNew.filterCallback(function(token, index) { + return switch (token.tok) { + case Const(CIdent("super")): + return FoundSkipSubtree; + default: + GoDeeper; + } + }); + final superParams:Array = []; + var superToken:Null = null; + for (call in superTokens) { + superToken = call; + call.filterCallback(function(token, index) { + switch (token.tok) { + case Const(CIdent(s)): + for (param in parameters) { + if (param.name == s) { + superParams.push(s); + return GoDeeper; + } + } + default: + } + return GoDeeper; + }); + } + parameters = parameters.filter(i -> !superParams.contains(i.name)); + if (parameters.length <= 0) { + return null; + } + + return { + content: content, + root: root, + tokenNew: tokenNew, + identifierNew: identifierNew, + parameters: parameters, + superToken: superToken, + file: file, + }; + } + + static function findParametersWithNoFields(context:CanRefactorContext, identifierNew:Identifier):Array { + final params:Array = []; + var paramCandidates:Array = []; + + for (use in identifierNew.uses) { + switch (use.type) { + case ScopedLocal(_, _, Parameter(params)): + paramCandidates = params; + break; + default: + } + } + final type = identifierNew.defineType; + for (candidate in paramCandidates) { + var allUses = type.getIdentifiers(candidate.name); + var found = false; + for (use in allUses) { + switch (use.type) { + case FieldVar(_): + found = true; + break; + default: + } + } + if (found) { + continue; + } + params.push(candidate); + } + + return params; + } + + static function makeField(context:RefactorContext, extractData:ExtractConstructorParamsData, param:Identifier):String { + final tokensParam:TokensAtPos = RefactorHelper.findTokensAtPos(extractData.root, param.pos.start); + if (tokensParam.after == null) { + return ""; + } + final lastToken = TokenTreeCheckUtils.getLastToken(tokensParam.after); + final fullPos = tokensParam.after.getPos(); + if (lastToken.matches(Comma)) { + fullPos.max = lastToken.pos.min; + } + final name = RefactorHelper.extractText(context.converter, extractData.content, fullPos.min, fullPos.max); + return 'final $name;\n'; + } + + static function makeAssignment(extractData:ExtractConstructorParamsData, param:Identifier):String { + return 'this.${param.name} = ${param.name};\n'; + } + + static function findFieldInsertPos(context:RefactorContext, extractData:ExtractConstructorParamsData):Int { + var parent = extractData.tokenNew.parent; + if (parent == null) { + return findFieldInsertPosFromType(extractData); + } + if (parent.previousSibling != null) { + final fullPos = parent.previousSibling.getPos(); + return fullPos.max + 1; + } + parent = parent.parent; + if (parent == null) { + return findFieldInsertPosFromType(extractData); + } + return parent.pos.max + 1; + } + + static function findFieldInsertPosFromType(extractData:ExtractConstructorParamsData):Int { + final tokens:TokensAtPos = RefactorHelper.findTokensAtPos(extractData.root, extractData.identifierNew.defineType.name.pos.start); + final typeName = tokens.after; + if (typeName == null) { + return -1; + } + final brOpen = typeName.access().firstOf(BrOpen).token; + if (brOpen == null) { + return -1; + } + return brOpen.pos.max + 1; + } + + static function findAssignmentInsertPos(context:RefactorContext, extractData:ExtractConstructorParamsData):Int { + if (extractData.superToken != null) { + final fullPos = extractData.superToken.getPos(); + return fullPos.max + 1; + } + final brOpen = extractData.tokenNew.access().firstOf(BrOpen).token; + if (brOpen == null) { + return -1; + } + return brOpen.pos.max + 1; + } +} + +typedef ExtractConstructorParamsData = { + var content:String; + var root:TokenTree; + var tokenNew:TokenTree; + var identifierNew:Identifier; + var parameters:Array; + var superToken:Null; + var file:File; +} diff --git a/src/refactor/refactor/RefactorType.hx b/src/refactor/refactor/RefactorType.hx index 3834fb9..b43f41e 100644 --- a/src/refactor/refactor/RefactorType.hx +++ b/src/refactor/refactor/RefactorType.hx @@ -4,4 +4,5 @@ enum RefactorType { RefactorExtractInterface; RefactorExtractMethod; RefactorExtractType; + RefactorExtractConstructorParams; } From f39e49272a7db61fbcf0e784a3ef53f76c97cdc2 Mon Sep 17 00:00:00 2001 From: AlexHaxe Date: Tue, 3 Dec 2024 00:30:21 +0100 Subject: [PATCH 41/59] fixed constructors with no parameters --- src/refactor/refactor/ExtractConstructorParams.hx | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/refactor/refactor/ExtractConstructorParams.hx b/src/refactor/refactor/ExtractConstructorParams.hx index 569d00f..d4dac05 100644 --- a/src/refactor/refactor/ExtractConstructorParams.hx +++ b/src/refactor/refactor/ExtractConstructorParams.hx @@ -133,6 +133,9 @@ class ExtractConstructorParams { static function findParametersWithNoFields(context:CanRefactorContext, identifierNew:Identifier):Array { final params:Array = []; var paramCandidates:Array = []; + if (identifierNew.uses == null) { + return []; + } for (use in identifierNew.uses) { switch (use.type) { From 3f1786d6a9a0b873f9387af1d282f4dcec933666 Mon Sep 17 00:00:00 2001 From: AlexHaxe Date: Tue, 3 Dec 2024 01:05:23 +0100 Subject: [PATCH 42/59] added testcases fixed insert position for empty constructor body --- .../refactor/ExtractConstructorParams.hx | 2 +- test/TestMain.hx | 2 ++ .../RefactorExtractConstructorParams.hx | 35 +++++++++++++++++++ testcases/constructor/Point.hx | 22 ++++++++++++ 4 files changed, 60 insertions(+), 1 deletion(-) create mode 100644 test/refactor/refactor/RefactorExtractConstructorParams.hx create mode 100644 testcases/constructor/Point.hx diff --git a/src/refactor/refactor/ExtractConstructorParams.hx b/src/refactor/refactor/ExtractConstructorParams.hx index d4dac05..2e0da7c 100644 --- a/src/refactor/refactor/ExtractConstructorParams.hx +++ b/src/refactor/refactor/ExtractConstructorParams.hx @@ -222,7 +222,7 @@ class ExtractConstructorParams { if (brOpen == null) { return -1; } - return brOpen.pos.max + 1; + return brOpen.pos.max; } } diff --git a/test/TestMain.hx b/test/TestMain.hx index 1b454b7..904a516 100644 --- a/test/TestMain.hx +++ b/test/TestMain.hx @@ -1,4 +1,5 @@ import refactor.refactor.RefactorClassTest; +import refactor.refactor.RefactorExtractConstructorParams; import refactor.refactor.RefactorExtractMethodTest; import refactor.refactor.RefactorTypedefTest; import refactor.rename.RenameClassTest; @@ -26,6 +27,7 @@ class TestMain { new RefactorClassTest(), new RefactorTypedefTest(), new RefactorExtractMethodTest(), + new RefactorExtractConstructorParams(), ]; var runner:Runner = new Runner(); diff --git a/test/refactor/refactor/RefactorExtractConstructorParams.hx b/test/refactor/refactor/RefactorExtractConstructorParams.hx new file mode 100644 index 0000000..e378777 --- /dev/null +++ b/test/refactor/refactor/RefactorExtractConstructorParams.hx @@ -0,0 +1,35 @@ +package refactor.refactor; + +class RefactorExtractConstructorParams extends RefactorTestBase { + function setupClass() { + setupTestSources(["testcases/constructor"]); + } + + function testFailExtractParamsNotConstructor(async:Async) { + failCanRefactor(RefactorExtractConstructorParams, {fileName: "testcases/constructor/Point.hx", posStart: 148, posEnd: 148}, "unsupported"); + failRefactor(RefactorExtractConstructorParams, {fileName: "testcases/constructor/Point.hx", posStart: 148, posEnd: 148}, + "failed to collect extract method data", async); + } + + function testExtractParamsEmptyNew(async:Async) { + failCanRefactor(RefactorExtractConstructorParams, {fileName: "testcases/constructor/Point.hx", posStart: 247, posEnd: 247}, "unsupported"); + failRefactor(RefactorExtractConstructorParams, {fileName: "testcases/constructor/Point.hx", posStart: 247, posEnd: 247}, + "failed to collect extract method data", async); + } + + function testExtractParamsYAndText(async:Async) { + var edits:Array = [ + makeInsertTestEdit("testcases/constructor/Point.hx", "final z:Int;\nfinal text:String;\n", 39, Format(1)), + makeInsertTestEdit("testcases/constructor/Point.hx", "this.z = z;\nthis.text = text;\n", 109, Format(2)), + ]; + checkRefactor(RefactorExtractConstructorParams, {fileName: "testcases/constructor/Point.hx", posStart: 58, posEnd: 58}, edits, async); + } + + function testExtractParamsMain(async:Async) { + var edits:Array = [ + makeInsertTestEdit("testcases/constructor/Point.hx", "final x:Int;\n", 275, Format(1)), + makeInsertTestEdit("testcases/constructor/Point.hx", "this.x = x;\n", 304, Format(2)), + ]; + checkRefactor(RefactorExtractConstructorParams, {fileName: "testcases/constructor/Point.hx", posStart: 294, posEnd: 294}, edits, async); + } +} diff --git a/testcases/constructor/Point.hx b/testcases/constructor/Point.hx new file mode 100644 index 0000000..e4e0344 --- /dev/null +++ b/testcases/constructor/Point.hx @@ -0,0 +1,22 @@ +class Point extends Base { + var y:Int; + + public function new(x:Int, y:Int, z:Int, text:String) { + super(x); + this.y = y; + } + + public function toString() { + return "Point(" + x + "," + y + ")"; + } +} + +class Base { + var x:Int; + + public function new(x:Int) {} +} + +class Main { + public function new(x:Int) {} +} From 22da8d2b00729a7c01ef683e102bb9d17949690f Mon Sep 17 00:00:00 2001 From: AlexHaxe Date: Tue, 3 Dec 2024 12:23:10 +0100 Subject: [PATCH 43/59] removed trailing newline for callsite code generation --- .../extractmethod/CodeGenAsExpression.hx | 2 +- .../extractmethod/CodeGenEmptyReturn.hx | 8 +- .../refactor/extractmethod/CodeGenNoReturn.hx | 8 +- .../extractmethod/CodeGenOpenEnded.hx | 8 +- .../extractmethod/CodeGenReturnIsLast.hx | 4 +- .../refactor/RefactorExtractMethodTest.hx | 84 +++++++++---------- 6 files changed, 57 insertions(+), 57 deletions(-) diff --git a/src/refactor/refactor/extractmethod/CodeGenAsExpression.hx b/src/refactor/refactor/extractmethod/CodeGenAsExpression.hx index b48dae0..8be037e 100644 --- a/src/refactor/refactor/extractmethod/CodeGenAsExpression.hx +++ b/src/refactor/refactor/extractmethod/CodeGenAsExpression.hx @@ -13,7 +13,7 @@ class CodeGenAsExpression extends CodeGenBase { return switch (extractData.endToken.tok) { case Semicolon | BrClose: - '$call;\n'; + '$call;'; default: '$call'; } diff --git a/src/refactor/refactor/extractmethod/CodeGenEmptyReturn.hx b/src/refactor/refactor/extractmethod/CodeGenEmptyReturn.hx index 5d20b28..a311ce5 100644 --- a/src/refactor/refactor/extractmethod/CodeGenEmptyReturn.hx +++ b/src/refactor/refactor/extractmethod/CodeGenEmptyReturn.hx @@ -23,24 +23,24 @@ class CodeGenEmptyReturn extends CodeGenBase { return switch [assignments.length, vars.length] { case [0, 0]: - 'if (!$call) {\nreturn;\n}\n'; + 'if (!$call) {\nreturn;\n}'; case [1, 0]: 'switch ($call) {\n' + 'case None:\n' + 'return;\n' + 'case Some(data):\n' - + '${assignments[0].name} = data;\n}\n'; + + '${assignments[0].name} = data;\n}'; case [0, 1]: 'var ${vars[0].name};\n' + 'switch ($call) {\n' + 'case None:\n' + 'return;\n' + 'case Some(data):\n' - + '${vars[0].name} = data;\n}\n'; + + '${vars[0].name} = data;\n}'; case [_, _]: final dataVars = vars.map(v -> 'var ${v.name};').join("\n"); final assignData = assignments.concat(vars).map(a -> '${a.name} = data.${a.name};').join("\n"); - dataVars + 'switch ($call) {\n' + "case None:\n" + "return;\n" + "case Some(data):\n" + '${assignData}\n' + "}\n"; + dataVars + 'switch ($call) {\n' + "case None:\n" + "return;\n" + "case Some(data):\n" + '${assignData}\n' + "}"; } } diff --git a/src/refactor/refactor/extractmethod/CodeGenNoReturn.hx b/src/refactor/refactor/extractmethod/CodeGenNoReturn.hx index 157126e..6ba0c4c 100644 --- a/src/refactor/refactor/extractmethod/CodeGenNoReturn.hx +++ b/src/refactor/refactor/extractmethod/CodeGenNoReturn.hx @@ -20,15 +20,15 @@ class CodeGenNoReturn extends CodeGenBase { return switch [assignments.length, vars.length] { case [0, 0]: - '$call;\n'; + '$call;'; case [0, 1]: - 'var ${vars[0].name} = $call;\n'; + 'var ${vars[0].name} = $call;'; case [1, 0]: - '${assignments[0].name} = $call;\n'; + '${assignments[0].name} = $call;'; case [_, _]: final dataVars = vars.map(v -> 'var ${v.name};').join("\n"); final assignData = assignments.concat(vars).map(a -> '${a.name} = data.${a.name};').join("\n"); - '$dataVars\n{\nfinal data = $call;\n' + assignData + "\n}\n"; + '$dataVars\n{\nfinal data = $call;\n' + assignData + "\n}"; } } diff --git a/src/refactor/refactor/extractmethod/CodeGenOpenEnded.hx b/src/refactor/refactor/extractmethod/CodeGenOpenEnded.hx index 0213c9e..4e21d9d 100644 --- a/src/refactor/refactor/extractmethod/CodeGenOpenEnded.hx +++ b/src/refactor/refactor/extractmethod/CodeGenOpenEnded.hx @@ -22,7 +22,7 @@ class CodeGenOpenEnded extends CodeGenBase { return switch [assignments.length, vars.length] { case [0, 0]: - 'switch ($call) {\n' + "case Some(data):\n" + "return data;\n" + "case None:\n" + "}\n"; + 'switch ($call) {\n' + "case Some(data):\n" + "return data;\n" + "case None:\n" + "}"; case [1, 0]: '{\nfinal result = $call;\n' + "switch (result.ret) {\n" @@ -31,7 +31,7 @@ class CodeGenOpenEnded extends CodeGenBase { + "case None:\n" + '${assignments[0].name} = result.data;\n' + "}\n" - + "}\n"; + + "}"; case [0, 1]: 'var ${vars[0].name};\n' + '{\nfinal result = $call;\n' @@ -41,7 +41,7 @@ class CodeGenOpenEnded extends CodeGenBase { + "case None:\n" + '${vars[0].name} = result.data;\n' + "}\n" - + "}\n"; + + "}"; case [_, _]: final dataVars = vars.map(v -> 'var ${v.name};').join("\n"); final assignData = assignments.concat(vars).map(a -> '${a.name} = result.data.${a.name};').join("\n"); @@ -53,7 +53,7 @@ class CodeGenOpenEnded extends CodeGenBase { + "case None:\n" + '${assignData}\n' + "}\n" - + "}\n"; + + "}"; } } diff --git a/src/refactor/refactor/extractmethod/CodeGenReturnIsLast.hx b/src/refactor/refactor/extractmethod/CodeGenReturnIsLast.hx index bd4c82a..5e7802d 100644 --- a/src/refactor/refactor/extractmethod/CodeGenReturnIsLast.hx +++ b/src/refactor/refactor/extractmethod/CodeGenReturnIsLast.hx @@ -14,9 +14,9 @@ class CodeGenReturnIsLast extends CodeGenBase { final callParams:String = neededIdentifiers.map(i -> i.name).join(", "); final call = '${extractData.newMethodName}($callParams)'; if (returnEmpty) { - return '$call;\n'; + return '$call;'; } else { - return 'return $call;\n'; + return 'return $call;'; } } diff --git a/test/refactor/refactor/RefactorExtractMethodTest.hx b/test/refactor/refactor/RefactorExtractMethodTest.hx index 9523525..b790d0d 100644 --- a/test/refactor/refactor/RefactorExtractMethodTest.hx +++ b/test/refactor/refactor/RefactorExtractMethodTest.hx @@ -40,7 +40,7 @@ class RefactorExtractMethodTest extends RefactorTestBase { function testSimpleNoReturns(async:Async) { var edits:Array = [ - makeReplaceTestEdit("testcases/methods/Main.hx", "noReturnsExtract();\n", 94, 131, Format(2)), + makeReplaceTestEdit("testcases/methods/Main.hx", "noReturnsExtract();", 94, 131, Format(2)), makeInsertTestEdit("testcases/methods/Main.hx", "function noReturnsExtract():Void {\n" + "trace(\"hello 2\");\n" @@ -52,7 +52,7 @@ class RefactorExtractMethodTest extends RefactorTestBase { function testSimpleNoReturnsStatic(async:Async) { var edits:Array = [ - makeReplaceTestEdit("testcases/methods/Main.hx", "noReturnsStaticExtract();\n", 222, 259, Format(2)), + makeReplaceTestEdit("testcases/methods/Main.hx", "noReturnsStaticExtract();", 222, 259, Format(2)), makeInsertTestEdit("testcases/methods/Main.hx", "static function noReturnsStaticExtract():Void {\n" + "trace(\"hello 2\");\n" @@ -64,7 +64,7 @@ class RefactorExtractMethodTest extends RefactorTestBase { function testEmptyReturns(async:Async) { var edits:Array = [ - makeReplaceTestEdit("testcases/methods/Main.hx", "if (!emptyReturnsExtract(cond1, cond2)) {\n" + "return;\n" + "}\n", 342, 439, Format(2)), + makeReplaceTestEdit("testcases/methods/Main.hx", "if (!emptyReturnsExtract(cond1, cond2)) {\n" + "return;\n" + "}", 342, 439, Format(2)), makeInsertTestEdit("testcases/methods/Main.hx", "function emptyReturnsExtract(cond1:Bool, cond2:Bool):Bool {\n" + "if (cond1) {\n" @@ -84,7 +84,7 @@ class RefactorExtractMethodTest extends RefactorTestBase { function testCalculateTotal(async:Async) { var edits:Array = [ - makeReplaceTestEdit("testcases/methods/Main.hx", "total = calculateTotalExtract(items, total);\n", 569, 720, Format(2)), + makeReplaceTestEdit("testcases/methods/Main.hx", "total = calculateTotalExtract(items, total);", 569, 720, Format(2)), makeInsertTestEdit("testcases/methods/Main.hx", "function calculateTotalExtract(items:Array, total:Float):Float {\n" + "// Selected code block to extract\n" @@ -108,7 +108,7 @@ class RefactorExtractMethodTest extends RefactorTestBase { + "{\nfinal data = calculateTotalExtract(item);\n" + "price = data.price;\n" + "quantity = data.quantity;\n" - + "}\n", + + "}", 630, 686, Format(3)), makeInsertTestEdit("testcases/methods/Main.hx", "function calculateTotalExtract(item:Item):{price:Float, quantity:Float} {\n" @@ -129,7 +129,7 @@ class RefactorExtractMethodTest extends RefactorTestBase { function testCalculateTotalWithLastReturn(async:Async) { var edits:Array = [ - makeReplaceTestEdit("testcases/methods/Main.hx", "return calculateTotalExtract(items, total);\n", 569, 737, Format(2)), + makeReplaceTestEdit("testcases/methods/Main.hx", "return calculateTotalExtract(items, total);", 569, 737, Format(2)), makeInsertTestEdit("testcases/methods/Main.hx", "function calculateTotalExtract(items:Array, total:Float):Float {\n" + "// Selected code block to extract\n" @@ -150,7 +150,7 @@ class RefactorExtractMethodTest extends RefactorTestBase { function testProcessUserWithLastReturn(async:Async) { var edits:Array = [ - makeReplaceTestEdit("testcases/methods/Main.hx", "return calculateTotal2Extract(items, total);\n", 1081, 1295, Format(2)), + makeReplaceTestEdit("testcases/methods/Main.hx", "return calculateTotal2Extract(items, total);", 1081, 1295, Format(2)), makeInsertTestEdit("testcases/methods/Main.hx", "function calculateTotal2Extract(items:Array, total:Float) {\n" + "// Selected code block to extract\n" @@ -180,7 +180,7 @@ class RefactorExtractMethodTest extends RefactorTestBase { + "case None:\n" + "total = result.data;\n" + "}\n" - + "}\n", + + "}", 1081, 1278, Format(2)), makeInsertTestEdit("testcases/methods/Main.hx", "function calculateTotal2Extract(items:Array, total:Float):{ret:haxe.ds.Option, ?data:Float} {\n" @@ -203,7 +203,7 @@ class RefactorExtractMethodTest extends RefactorTestBase { function testCalcConditionalLevel(async:Async) { var edits:Array = [ - makeReplaceTestEdit("testcases/methods/Main.hx", "count = calcConditionalLevelExtract(token, count);\n", 1388, 1543, Format(2)), + makeReplaceTestEdit("testcases/methods/Main.hx", "count = calcConditionalLevelExtract(token, count);", 1388, 1543, Format(2)), makeInsertTestEdit("testcases/methods/Main.hx", "function calcConditionalLevelExtract(token:tokentree.TokenTree, count:Int):Int {\n" + "while ((token != null) && (token.tok != Root)) {\n" @@ -223,7 +223,7 @@ class RefactorExtractMethodTest extends RefactorTestBase { function testCalcConditionalLevelWithVar(async:Async) { var edits:Array = [ - makeReplaceTestEdit("testcases/methods/Main.hx", "var count = calcConditionalLevelExtract(token);\n", 1366, 1543, Format(2)), + makeReplaceTestEdit("testcases/methods/Main.hx", "var count = calcConditionalLevelExtract(token);", 1366, 1543, Format(2)), makeInsertTestEdit("testcases/methods/Main.hx", "function calcConditionalLevelExtract(token:tokentree.TokenTree):Int {\n" + "var count:Int = -1;\n" @@ -244,7 +244,7 @@ class RefactorExtractMethodTest extends RefactorTestBase { function testAllEmptyReturns(async:Async) { var edits:Array = [ - makeReplaceTestEdit("testcases/methods/Main.hx", "allEmptyReturnsExtract();\n", 1722, 1809, Format(2)), + makeReplaceTestEdit("testcases/methods/Main.hx", "allEmptyReturnsExtract();", 1722, 1809, Format(2)), makeInsertTestEdit("testcases/methods/Main.hx", "function allEmptyReturnsExtract():Void {\n" + "trace(\"hello 1\");\n" @@ -260,7 +260,7 @@ class RefactorExtractMethodTest extends RefactorTestBase { function testStringInterpolation(async:Async) { var edits:Array = [ - makeReplaceTestEdit("testcases/methods/Main.hx", "return interpolationExtract(data);\n", 1884, 1937, Format(2)), + makeReplaceTestEdit("testcases/methods/Main.hx", "return interpolationExtract(data);", 1884, 1937, Format(2)), makeInsertTestEdit("testcases/methods/Main.hx", "static function interpolationExtract(data:Dynamic):String {\n" + "return cast '${data.a}_${data.b}_${data.c}_${false}';\n" + "}\n", 1941, Format(1)), @@ -271,7 +271,7 @@ class RefactorExtractMethodTest extends RefactorTestBase { function testDemoSimple(async:Async) { var edits:Array = [ - makeReplaceTestEdit("testcases/methods/Demo.hx", "doSomethingExtract();\n", 161, 252, Format(2)), + makeReplaceTestEdit("testcases/methods/Demo.hx", "doSomethingExtract();", 161, 252, Format(2)), makeInsertTestEdit("testcases/methods/Demo.hx", "function doSomethingExtract():Void {\n" + "doNothing();\n" @@ -287,7 +287,7 @@ class RefactorExtractMethodTest extends RefactorTestBase { function testDemoSwitch(async:Async) { var edits:Array = [ - makeReplaceTestEdit("testcases/methods/Demo.hx", "doSomethingExtract(cond, val, text);\n", 262, 463, Format(2)), + makeReplaceTestEdit("testcases/methods/Demo.hx", "doSomethingExtract(cond, val, text);", 262, 463, Format(2)), makeInsertTestEdit("testcases/methods/Demo.hx", "function doSomethingExtract(cond:Bool, val:Int, text:Null):Float {\n" + "return switch [cond, val] {\n" @@ -309,7 +309,7 @@ class RefactorExtractMethodTest extends RefactorTestBase { function testDemoReturnSwitch(async:Async) { var edits:Array = [ - makeReplaceTestEdit("testcases/methods/Demo.hx", "return doSomethingExtract(cond, val, text);\n", 255, 463, Format(2)), + makeReplaceTestEdit("testcases/methods/Demo.hx", "return doSomethingExtract(cond, val, text);", 255, 463, Format(2)), makeInsertTestEdit("testcases/methods/Demo.hx", "function doSomethingExtract(cond:Bool, val:Int, text:Null):Float {\n" + "return switch [cond, val] {\n" @@ -331,7 +331,7 @@ class RefactorExtractMethodTest extends RefactorTestBase { function testDemoCodeAndSwitch(async:Async) { var edits:Array = [ - makeReplaceTestEdit("testcases/methods/Demo.hx", "return doSomethingExtract(cond, val, text);\n", 161, 463, Format(2)), + makeReplaceTestEdit("testcases/methods/Demo.hx", "return doSomethingExtract(cond, val, text);", 161, 463, Format(2)), makeInsertTestEdit("testcases/methods/Demo.hx", "function doSomethingExtract(cond:Bool, val:Int, text:Null):Float {\n" + "doNothing();\n" @@ -363,7 +363,7 @@ class RefactorExtractMethodTest extends RefactorTestBase { + "case Some(data):\n" + "return data;\n" + "case None:\n" - + "}\n", 112, 252, Format(2)), + + "}", 112, 252, Format(2)), makeInsertTestEdit("testcases/methods/Demo.hx", "function doSomethingExtract(cond:Bool, text:Null):haxe.ds.Option {\n" + "if (cond && text == null) {\n" @@ -384,7 +384,7 @@ class RefactorExtractMethodTest extends RefactorTestBase { function testCalculateMath(async:Async) { var edits:Array = [ - makeReplaceTestEdit("testcases/methods/Math.hx", "calculateExtract(a, b);\n", 106, 122, Format(2)), + makeReplaceTestEdit("testcases/methods/Math.hx", "calculateExtract(a, b);", 106, 122, Format(2)), makeInsertTestEdit("testcases/methods/Math.hx", "function calculateExtract(a:Int, b:Int):Int {\n" + "return a * b + (a - b);\n" + "}\n", 143, Format(1)), ]; @@ -396,7 +396,7 @@ class RefactorExtractMethodTest extends RefactorTestBase { function testCalculateMathWithVar(async:Async) { var edits:Array = [ - makeReplaceTestEdit("testcases/methods/Math.hx", "var result = calculateExtract(a, b);\n", 93, 122, Format(2)), + makeReplaceTestEdit("testcases/methods/Math.hx", "var result = calculateExtract(a, b);", 93, 122, Format(2)), makeInsertTestEdit("testcases/methods/Math.hx", "function calculateExtract(a:Int, b:Int):Int {\n" + "var result = a * b + (a - b);\n" @@ -411,7 +411,7 @@ class RefactorExtractMethodTest extends RefactorTestBase { function testNameProcessor(async:Async) { var edits:Array = [ - makeReplaceTestEdit("testcases/methods/NameProcessor.hx", "var upperNames = processExtract(names);\n", 113, 201, Format(2)), + makeReplaceTestEdit("testcases/methods/NameProcessor.hx", "var upperNames = processExtract(names);", 113, 201, Format(2)), makeInsertTestEdit("testcases/methods/NameProcessor.hx", "function processExtract(names:Array):Array {\n" + "var upperNames = [];\n" @@ -429,7 +429,7 @@ class RefactorExtractMethodTest extends RefactorTestBase { function testArrayHandler(async:Async) { var edits:Array = [ - makeReplaceTestEdit("testcases/methods/ArrayHandler.hx", "var sum = handleExtract(numbers);\n", 105, 157, Format(2)), + makeReplaceTestEdit("testcases/methods/ArrayHandler.hx", "var sum = handleExtract(numbers);", 105, 157, Format(2)), makeInsertTestEdit("testcases/methods/ArrayHandler.hx", "function handleExtract(numbers:Array):Int {\n" + "var sum = 0;\n" @@ -447,7 +447,7 @@ class RefactorExtractMethodTest extends RefactorTestBase { function testAgeChecker(async:Async) { var edits:Array = [ - makeReplaceTestEdit("testcases/methods/AgeChecker.hx", "message = checkExtract(age, message);\n", 105, 179, Format(2)), + makeReplaceTestEdit("testcases/methods/AgeChecker.hx", "message = checkExtract(age, message);", 105, 179, Format(2)), makeInsertTestEdit("testcases/methods/AgeChecker.hx", "function checkExtract(age:Int, message:String):String {\n" + "if (age < 18) {\n" @@ -466,7 +466,7 @@ class RefactorExtractMethodTest extends RefactorTestBase { function testPersonHandler(async:Async) { var edits:Array = [ - makeReplaceTestEdit("testcases/methods/PersonHandler.hx", "handleExtract(person);\n", 113, 161, Format(2)), + makeReplaceTestEdit("testcases/methods/PersonHandler.hx", "handleExtract(person);", 113, 161, Format(2)), makeInsertTestEdit("testcases/methods/PersonHandler.hx", "function handleExtract(person:Any) {\n" + "return \"Name: \" + person.name + \", Age: \" + person.age;\n" + "}\n", 180, Format(1)), ]; @@ -477,7 +477,7 @@ class RefactorExtractMethodTest extends RefactorTestBase { function testPersonHandlerWithVar(async:Async) { var edits:Array = [ - makeReplaceTestEdit("testcases/methods/PersonHandler.hx", "var info = handleExtract(person);\n", 102, 161, Format(2)), + makeReplaceTestEdit("testcases/methods/PersonHandler.hx", "var info = handleExtract(person);", 102, 161, Format(2)), makeInsertTestEdit("testcases/methods/PersonHandler.hx", "function handleExtract(person:Any) {\n" + "var info = \"Name: \" + person.name + \", Age: \" + person.age;\n" @@ -492,7 +492,7 @@ class RefactorExtractMethodTest extends RefactorTestBase { function testContainer(async:Async) { var edits:Array = [ - makeReplaceTestEdit("testcases/methods/Container.hx", "var result = processExtract(items, converter);\n", 108, 239, Format(2)), + makeReplaceTestEdit("testcases/methods/Container.hx", "var result = processExtract(items, converter);", 108, 239, Format(2)), makeInsertTestEdit("testcases/methods/Container.hx", "function processExtract(items:Array, converter:T -> String):Array {\n" + "var result = new Array();\n" @@ -511,7 +511,7 @@ class RefactorExtractMethodTest extends RefactorTestBase { function testMacroTools(async:Async) { var edits:Array = [ - makeReplaceTestEdit("testcases/methods/MacroTools.hx", "return buildExtract(fields);\n", 195, 353, Format(2)), + makeReplaceTestEdit("testcases/methods/MacroTools.hx", "return buildExtract(fields);", 195, 353, Format(2)), makeInsertTestEdit("testcases/methods/MacroTools.hx", "static function buildExtract(fields:Array):Array {\n" + "for (field in fields) {\n" @@ -534,7 +534,7 @@ class RefactorExtractMethodTest extends RefactorTestBase { function testMatcherOnlySwitch(async:Async) { var edits:Array = [ - makeReplaceTestEdit("testcases/methods/Matcher.hx", "processExtract(value);\n", 84, 284, Format(2)), + makeReplaceTestEdit("testcases/methods/Matcher.hx", "processExtract(value);", 84, 284, Format(2)), makeInsertTestEdit("testcases/methods/Matcher.hx", "function processExtract(value:Any) {\n" + "return switch value {\n" @@ -551,7 +551,7 @@ class RefactorExtractMethodTest extends RefactorTestBase { function testMatcherWithReturn(async:Async) { var edits:Array = [ - makeReplaceTestEdit("testcases/methods/Matcher.hx", "return processExtract(value);\n", 77, 284, Format(2)), + makeReplaceTestEdit("testcases/methods/Matcher.hx", "return processExtract(value);", 77, 284, Format(2)), makeInsertTestEdit("testcases/methods/Matcher.hx", "function processExtract(value:Any) {\n" + "return switch value {\n" @@ -568,7 +568,7 @@ class RefactorExtractMethodTest extends RefactorTestBase { function testTypeProcessor(async:Async) { var edits:Array = [ - makeReplaceTestEdit("testcases/methods/TypeProcessor.hx", "processExtract(item, compare);\n", 114, 221, Format(2)), + makeReplaceTestEdit("testcases/methods/TypeProcessor.hx", "processExtract(item, compare);", 114, 221, Format(2)), makeInsertTestEdit("testcases/methods/TypeProcessor.hx", "function processExtract(item:T, compare:U):Void {\n" + "var result = item.process();\n" @@ -583,7 +583,7 @@ class RefactorExtractMethodTest extends RefactorTestBase { function testFunctionProcessor(async:Async) { var edits:Array = [ - makeReplaceTestEdit("testcases/methods/FunctionProcessor.hx", "processExtract(callback, value, text);\n", 143, 211, Format(2)), + makeReplaceTestEdit("testcases/methods/FunctionProcessor.hx", "processExtract(callback, value, text);", 143, 211, Format(2)), makeInsertTestEdit("testcases/methods/FunctionProcessor.hx", "function processExtract(callback:(Int, String) -> Bool, value:Int, text:String):Void {\n" + "if (callback(value, text)) {\n" @@ -601,7 +601,7 @@ class RefactorExtractMethodTest extends RefactorTestBase { function testArrayTools(async:Async) { var edits:Array = [ - makeReplaceTestEdit("testcases/methods/ArrayTools.hx", "return processItemsExtract(arr, fn);\n", 117, 231, Format(2)), + makeReplaceTestEdit("testcases/methods/ArrayTools.hx", "return processItemsExtract(arr, fn);", 117, 231, Format(2)), makeInsertTestEdit("testcases/methods/ArrayTools.hx", "static function processItemsExtract(arr:Array, fn:T -> Bool):Array {\n" + "var results = new Array();\n" @@ -620,7 +620,7 @@ class RefactorExtractMethodTest extends RefactorTestBase { function testExceptionHandlerTry(async:Async) { var edits:Array = [ - makeReplaceTestEdit("testcases/methods/ExceptionHandler.hx", "processExtract();\n", 86, 152, Format(3)), + makeReplaceTestEdit("testcases/methods/ExceptionHandler.hx", "processExtract();", 86, 152, Format(3)), makeInsertTestEdit("testcases/methods/ExceptionHandler.hx", "function processExtract():Void {\n" + "var data = getData();\n" @@ -634,7 +634,7 @@ class RefactorExtractMethodTest extends RefactorTestBase { function testExceptionHandlerCatch(async:Async) { var edits:Array = [ - makeReplaceTestEdit("testcases/methods/ExceptionHandler.hx", "processExtract();\n", 193, 250, Format(3)), + makeReplaceTestEdit("testcases/methods/ExceptionHandler.hx", "processExtract();", 193, 250, Format(3)), makeInsertTestEdit("testcases/methods/ExceptionHandler.hx", "function processExtract():Void {\n" + "logError(e);\n" @@ -647,7 +647,7 @@ class RefactorExtractMethodTest extends RefactorTestBase { function testExceptionHandlerTryWithThrow(async:Async) { var edits:Array = [ - makeReplaceTestEdit("testcases/methods/ExceptionHandler.hx", "processExtract();\n", 266, 404, Format(3)), + makeReplaceTestEdit("testcases/methods/ExceptionHandler.hx", "processExtract();", 266, 404, Format(3)), makeInsertTestEdit("testcases/methods/ExceptionHandler.hx", "function processExtract():Void {\n" + "var data = getData();\n" @@ -663,7 +663,7 @@ class RefactorExtractMethodTest extends RefactorTestBase { function testExceptionHandlerTryWithThrowNotLast(async:Async) { var edits:Array = [ - makeReplaceTestEdit("testcases/methods/ExceptionHandler.hx", "processExtract();\n", 518, 689, Format(3)), + makeReplaceTestEdit("testcases/methods/ExceptionHandler.hx", "processExtract();", 518, 689, Format(3)), makeInsertTestEdit("testcases/methods/ExceptionHandler.hx", "function processExtract():Void {\n" + "var data = getData();\n" @@ -681,7 +681,7 @@ class RefactorExtractMethodTest extends RefactorTestBase { function testMetadataProcessor(async:Async) { var edits:Array = [ - makeReplaceTestEdit("testcases/methods/MetadataProcessor.hx", "processExtract(meta, results);\n", 220, 575, Format(2)), + makeReplaceTestEdit("testcases/methods/MetadataProcessor.hx", "processExtract(meta, results);", 220, 575, Format(2)), makeInsertTestEdit("testcases/methods/MetadataProcessor.hx", "function processExtract(meta:Dynamic>>, results:Map>):Void {\n" + "for (field in Reflect.fields(meta)) {\n" @@ -709,7 +709,7 @@ class RefactorExtractMethodTest extends RefactorTestBase { function testMetadataProcessorWithResults(async:Async) { var edits:Array = [ - makeReplaceTestEdit("testcases/methods/MetadataProcessor.hx", "var results = processExtract(meta);\n", 169, 575, Format(2)), + makeReplaceTestEdit("testcases/methods/MetadataProcessor.hx", "var results = processExtract(meta);", 169, 575, Format(2)), makeInsertTestEdit("testcases/methods/MetadataProcessor.hx", "function processExtract(meta:Dynamic>>):Map> {\n" + "var results = new Map>();\n\n" @@ -739,7 +739,7 @@ class RefactorExtractMethodTest extends RefactorTestBase { function testMetadataProcessorPrint(async:Async) { var edits:Array = [ - makeReplaceTestEdit("testcases/methods/MetadataProcessor.hx", "processExtract(results);\n", 579, 672, Format(2)), + makeReplaceTestEdit("testcases/methods/MetadataProcessor.hx", "processExtract(results);", 579, 672, Format(2)), makeInsertTestEdit("testcases/methods/MetadataProcessor.hx", "function processExtract(results:Map>):Void {\n" + "for (field => values in results) {\n" @@ -824,7 +824,7 @@ class RefactorExtractMethodTest extends RefactorTestBase { function testEditDocInner(async:Async) { var edits:Array = [ - makeReplaceTestEdit("testcases/methods/TestEditDoc.hx", "text = addChangeExtract(range, text);\n", 388, 485, Format(4)), + makeReplaceTestEdit("testcases/methods/TestEditDoc.hx", "text = addChangeExtract(range, text);", 388, 485, Format(4)), makeInsertTestEdit("testcases/methods/TestEditDoc.hx", "function addChangeExtract(range:TestRange, text:String):String {\n" + "if (range.start.character != 0) {\n" @@ -842,7 +842,7 @@ class RefactorExtractMethodTest extends RefactorTestBase { function testEditDocInnerWithEdit(async:Async) { var edits:Array = [ - makeReplaceTestEdit("testcases/methods/TestEditDoc.hx", "text = addChangeExtract(text, filePath, range);\n", 346, 485, Format(4)), + makeReplaceTestEdit("testcases/methods/TestEditDoc.hx", "text = addChangeExtract(text, filePath, range);", 346, 485, Format(4)), makeInsertTestEdit("testcases/methods/TestEditDoc.hx", "function addChangeExtract(text:String, filePath:String, range:TestRange):String {\n" + "text = formatSnippet(filePath, text);\n" @@ -862,7 +862,7 @@ class RefactorExtractMethodTest extends RefactorTestBase { function testEditDocInnerWithSwitch(async:Async) { var edits:Array = [ - makeReplaceTestEdit("testcases/methods/TestEditDoc.hx", "var text = addChangeExtract(range);\n", 193, 489, Format(2)), + makeReplaceTestEdit("testcases/methods/TestEditDoc.hx", "var text = addChangeExtract(range);", 193, 489, Format(2)), makeInsertTestEdit("testcases/methods/TestEditDoc.hx", "function addChangeExtract(range:TestRange):String {\n" + "final f:FormatType = Format(10);\n" @@ -895,7 +895,7 @@ class RefactorExtractMethodTest extends RefactorTestBase { + "final data = addChangeExtract();\n" + "range = data.range;\n" + "text = data.text;\n" - + "}\n", 160, + + "}", 160, 489, Format(2)), makeInsertTestEdit("testcases/methods/TestEditDoc.hx", "function addChangeExtract():{range:TestRange, text:String} {\n" From 9a939953ec1ef7789591903c7205f35f29111540 Mon Sep 17 00:00:00 2001 From: AlexHaxe Date: Tue, 3 Dec 2024 14:32:57 +0100 Subject: [PATCH 44/59] added Rewrite Vars to Finals and vice versa --- src/refactor/Refactoring.hx | 13 +- src/refactor/edits/Changelist.hx | 8 +- src/refactor/edits/FormatType.hx | 2 +- .../refactor/ExtractConstructorParams.hx | 24 ++- src/refactor/refactor/ExtractInterface.hx | 2 +- src/refactor/refactor/ExtractMethod.hx | 4 +- src/refactor/refactor/ExtractType.hx | 4 +- src/refactor/refactor/RefactorType.hx | 3 +- src/refactor/refactor/RewriteVarsToFinals.hx | 124 ++++++++++++ test/refactor/TestBase.hx | 8 +- test/refactor/refactor/RefactorClassTest.hx | 41 +++- .../RefactorExtractConstructorParams.hx | 46 +++-- .../refactor/RefactorExtractMethodTest.hx | 191 +++++++++--------- test/refactor/refactor/RefactorTypedefTest.hx | 2 +- 14 files changed, 337 insertions(+), 135 deletions(-) create mode 100644 src/refactor/refactor/RewriteVarsToFinals.hx diff --git a/src/refactor/Refactoring.hx b/src/refactor/Refactoring.hx index 7c3cc09..ca8ca34 100644 --- a/src/refactor/Refactoring.hx +++ b/src/refactor/Refactoring.hx @@ -8,6 +8,7 @@ import refactor.refactor.ExtractMethod; import refactor.refactor.ExtractType; import refactor.refactor.RefactorContext; import refactor.refactor.RefactorType; +import refactor.refactor.RewriteVarsToFinals; class Refactoring { public static function canRefactor(refactorType:RefactorType, context:CanRefactorContext):CanRefactorResult { @@ -18,8 +19,10 @@ class Refactoring { return ExtractMethod.canRefactor(context); case RefactorExtractType: return ExtractType.canRefactor(context); - case RefactorExtractConstructorParams: - return ExtractConstructorParams.canRefactor(context); + case RefactorExtractConstructorParams(asFinal): + return ExtractConstructorParams.canRefactor(context, asFinal); + case RefactorRewriteVarsToFinals(toFinals): + return RewriteVarsToFinals.canRefactor(context, toFinals); } return null; } @@ -32,8 +35,10 @@ class Refactoring { return ExtractMethod.doRefactor(context); case RefactorExtractType: return ExtractType.doRefactor(context); - case RefactorExtractConstructorParams: - return ExtractConstructorParams.doRefactor(context); + case RefactorExtractConstructorParams(asFinal): + return ExtractConstructorParams.doRefactor(context, asFinal); + case RefactorRewriteVarsToFinals(toFinals): + return RewriteVarsToFinals.doRefactor(context, toFinals); } return Promise.reject("no refactor type selected"); } diff --git a/src/refactor/edits/Changelist.hx b/src/refactor/edits/Changelist.hx index 6098c41..62af146 100644 --- a/src/refactor/edits/Changelist.hx +++ b/src/refactor/edits/Changelist.hx @@ -82,9 +82,13 @@ class Changelist { return switch (format) { case NoFormat: ""; - case Format(0): + case Format(0, true): + " with format and rtrim"; + case Format(0, _): " with format"; - case Format(indentOffset): + case Format(indentOffset, true): + ' with format +indent=$indentOffset and rtrim'; + case Format(indentOffset, _): ' with format +indent=$indentOffset'; } } diff --git a/src/refactor/edits/FormatType.hx b/src/refactor/edits/FormatType.hx index 407cffd..844103d 100644 --- a/src/refactor/edits/FormatType.hx +++ b/src/refactor/edits/FormatType.hx @@ -2,5 +2,5 @@ package refactor.edits; enum FormatType { NoFormat; - Format(indentOffset:Int); + Format(indentOffset:Int, trimRight:Bool); } diff --git a/src/refactor/refactor/ExtractConstructorParams.hx b/src/refactor/refactor/ExtractConstructorParams.hx index 2e0da7c..c2d61f9 100644 --- a/src/refactor/refactor/ExtractConstructorParams.hx +++ b/src/refactor/refactor/ExtractConstructorParams.hx @@ -6,15 +6,19 @@ import refactor.edits.Changelist; import refactor.refactor.RefactorHelper.TokensAtPos; class ExtractConstructorParams { - public static function canRefactor(context:CanRefactorContext):CanRefactorResult { + public static function canRefactor(context:CanRefactorContext, asFinal:Bool):CanRefactorResult { final extractData = makeExtractConstructorParamsData(context); if (extractData == null) { return Unsupported; } - return Supported('Extract Constructor Params'); + if (asFinal) { + return Supported('Extract Constructor Params as final'); + } else { + return Supported('Extract Constructor Params as var'); + } } - public static function doRefactor(context:RefactorContext):Promise { + public static function doRefactor(context:RefactorContext, asFinal:Bool):Promise { final extractData = makeExtractConstructorParamsData(context); if (extractData == null) { return Promise.reject("failed to collect extract method data"); @@ -24,7 +28,7 @@ class ExtractConstructorParams { final newFields:StringBuf = new StringBuf(); final newAssigns:StringBuf = new StringBuf(); for (param in extractData.parameters) { - newFields.add(makeField(context, extractData, param)); + newFields.add(makeField(context, extractData, param, asFinal)); newAssigns.add(makeAssignment(extractData, param)); } @@ -35,10 +39,10 @@ class ExtractConstructorParams { } changelist.addChange(context.what.fileName, - InsertText(newFields.toString(), {fileName: context.what.fileName, start: fieldInsertPos, end: fieldInsertPos}, Format(1)), null); + InsertText(newFields.toString(), {fileName: context.what.fileName, start: fieldInsertPos, end: fieldInsertPos}, Format(1, false)), null); changelist.addChange(context.what.fileName, - InsertText(newAssigns.toString(), {fileName: context.what.fileName, start: assignmentInsertPos, end: assignmentInsertPos}, Format(2)), null); + InsertText(newAssigns.toString(), {fileName: context.what.fileName, start: assignmentInsertPos, end: assignmentInsertPos}, Format(2, false)), null); return Promise.resolve(changelist.execute()); } @@ -166,7 +170,7 @@ class ExtractConstructorParams { return params; } - static function makeField(context:RefactorContext, extractData:ExtractConstructorParamsData, param:Identifier):String { + static function makeField(context:RefactorContext, extractData:ExtractConstructorParamsData, param:Identifier, asFinal:Bool):String { final tokensParam:TokensAtPos = RefactorHelper.findTokensAtPos(extractData.root, param.pos.start); if (tokensParam.after == null) { return ""; @@ -177,7 +181,11 @@ class ExtractConstructorParams { fullPos.max = lastToken.pos.min; } final name = RefactorHelper.extractText(context.converter, extractData.content, fullPos.min, fullPos.max); - return 'final $name;\n'; + if (asFinal) { + return 'final $name;\n'; + } else { + return 'var $name;\n'; + } } static function makeAssignment(extractData:ExtractConstructorParamsData, param:Identifier):String { diff --git a/src/refactor/refactor/ExtractInterface.hx b/src/refactor/refactor/ExtractInterface.hx index ef498b1..a018fcf 100644 --- a/src/refactor/refactor/ExtractInterface.hx +++ b/src/refactor/refactor/ExtractInterface.hx @@ -38,7 +38,7 @@ class ExtractInterface { final interfaceText:String = 'interface ${extractData.newTypeName} {\n' + fieldDefinitions + "}"; changelist.addChange(extractData.newFileName, - InsertText(fileHeader + interfaceText, {fileName: extractData.newFileName, start: 0, end: 0}, Format(0)), null); + InsertText(fileHeader + interfaceText, {fileName: extractData.newFileName, start: 0, end: 0}, Format(0, false)), null); final implementsText:String = ' implements ${extractData.newTypeName}'; final pos:Position = findImplementsPos(extractData); diff --git a/src/refactor/refactor/ExtractMethod.hx b/src/refactor/refactor/ExtractMethod.hx index e7b5d8d..bebbe47 100644 --- a/src/refactor/refactor/ExtractMethod.hx +++ b/src/refactor/refactor/ExtractMethod.hx @@ -70,7 +70,7 @@ class ExtractMethod { final extractedCall:String = codeGen.makeCallSite(); changelist.addChange(context.what.fileName, ReplaceText(extractedCall, {fileName: context.what.fileName, start: extractData.startToken.pos.min, end: extractData.endToken.pos.max}, - Format(extractData.snippetIndent)), + Format(extractData.snippetIndent, true)), null); // insert new method with function signature and body after current function @@ -81,7 +81,7 @@ class ExtractMethod { changelist.addChange(context.what.fileName, InsertText(functionDefinition + body, {fileName: context.what.fileName, start: extractData.newMethodOffset, end: extractData.newMethodOffset}, - Format(extractData.functionIndent)), + Format(extractData.functionIndent, false)), null); return changelist.execute(); diff --git a/src/refactor/refactor/ExtractType.hx b/src/refactor/refactor/ExtractType.hx index 8382b17..63c5bda 100644 --- a/src/refactor/refactor/ExtractType.hx +++ b/src/refactor/refactor/ExtractType.hx @@ -48,8 +48,8 @@ class ExtractType { changelist.addChange(extractData.newFileName, CreateFile(extractData.newFileName), null); // copy file header, type and doc comment into new file - changelist.addChange(extractData.newFileName, InsertText(fileHeader + typeText, {fileName: extractData.newFileName, start: 0, end: 0}, Format(0)), - null); + changelist.addChange(extractData.newFileName, + InsertText(fileHeader + typeText, {fileName: extractData.newFileName, start: 0, end: 0}, Format(0, false)), null); // find all places using type and update their imports findImportLocations(context, extractData, changelist); diff --git a/src/refactor/refactor/RefactorType.hx b/src/refactor/refactor/RefactorType.hx index b43f41e..1aba504 100644 --- a/src/refactor/refactor/RefactorType.hx +++ b/src/refactor/refactor/RefactorType.hx @@ -4,5 +4,6 @@ enum RefactorType { RefactorExtractInterface; RefactorExtractMethod; RefactorExtractType; - RefactorExtractConstructorParams; + RefactorExtractConstructorParams(asFinal:Bool); + RefactorRewriteVarsToFinals(toFinals:Bool); } diff --git a/src/refactor/refactor/RewriteVarsToFinals.hx b/src/refactor/refactor/RewriteVarsToFinals.hx new file mode 100644 index 0000000..1646a1e --- /dev/null +++ b/src/refactor/refactor/RewriteVarsToFinals.hx @@ -0,0 +1,124 @@ +package refactor.refactor; + +import refactor.edits.Changelist; +import refactor.refactor.RefactorHelper.TokensAtPos; + +class RewriteVarsToFinals { + public static function canRefactor(context:CanRefactorContext, toFinals:Bool):CanRefactorResult { + final extractData = makeRewriteVarsToFinalsData(context, toFinals); + if (extractData == null) { + return Unsupported; + } + if (toFinals) { + return Supported('Rewrite Vars to Finals'); + } else { + return Supported('Rewrite Finals to Vars'); + } + } + + public static function doRefactor(context:RefactorContext, toFinals:Bool):Promise { + final extractData = makeRewriteVarsToFinalsData(context, toFinals); + if (extractData == null) { + return Promise.reject("failed to collect rewrite vars/finals data"); + } + final changelist:Changelist = new Changelist(context); + + for (varToken in extractData.allVarTokens) { + if (toFinals) { + changelist.addChange(context.what.fileName, + ReplaceText("final", {fileName: context.what.fileName, start: varToken.pos.min, end: varToken.pos.max}, NoFormat), null); + } else { + changelist.addChange(context.what.fileName, + ReplaceText("var", {fileName: context.what.fileName, start: varToken.pos.min, end: varToken.pos.max}, NoFormat), null); + } + } + + return Promise.resolve(changelist.execute()); + } + + static function makeRewriteVarsToFinalsData(context:CanRefactorContext, toFinals:Bool):Null { + final fileContent = context.fileReader(context.what.fileName); + var content:String; + var root:TokenTree; + switch (fileContent) { + case Text(_): + return null; + case Token(tokens, text): + content = text; + root = tokens; + } + if (root == null) { + return null; + } + if (content == null) { + return null; + } + + // find corresponding tokens in tokentree, selection start/end in whitespace + final tokensStart:TokensAtPos = RefactorHelper.findTokensAtPos(root, context.what.posStart); + final tokensEnd:TokensAtPos = RefactorHelper.findTokensAtPos(root, context.what.posEnd); + if (tokensStart.after == null || tokensEnd.before == null) { + return null; + } + + final tokenStart:Null = tokensStart.after; + final tokenEnd:Null = tokensEnd.before; + + if (tokenStart == null || tokenEnd == null) { + return null; + } + if (tokenStart.index >= tokenEnd.index) { + return null; + } + final parent = tokenStart.parent; + if (parent == null) { + return null; + } + final allVarTokens:Array = parent.filterCallback(function(token, index) { + if (token.pos.max < context.what.posStart) { + return GoDeeper; + } + if (token.pos.min > context.what.posEnd) { + return SkipSubtree; + } + return switch (token.tok) { + case Kwd(KwdVar) if (toFinals): + FoundSkipSubtree; + case Kwd(KwdVar): + SkipSubtree; + case Kwd(KwdFinal): + var finalParent = token.parent; + if (finalParent == null) { + return SkipSubtree; + } + return switch (finalParent.tok) { + case Const(_): + GoDeeper; + default: + if (toFinals) { + return SkipSubtree; + } else { + return FoundSkipSubtree; + } + } + default: + GoDeeper; + } + }); + if (allVarTokens.length <= 0) { + return null; + } + + return { + content: content, + root: root, + allVarTokens: allVarTokens, + }; + } +} + +typedef RewriteVarsToFinalsData = { + var content:String; + var root:TokenTree; + var allVarTokens:Array; +} diff --git a/test/refactor/TestBase.hx b/test/refactor/TestBase.hx index dfa0261..06d2982 100644 --- a/test/refactor/TestBase.hx +++ b/test/refactor/TestBase.hx @@ -83,9 +83,13 @@ class TestBase implements ITest { return switch (format) { case NoFormat: ""; - case Format(0): + case Format(0, true): + " with format and rtrim"; + case Format(0, _): " with format"; - case Format(indentOffset): + case Format(indentOffset, true): + ' with format +indent=$indentOffset and rtrim'; + case Format(indentOffset, _): ' with format +indent=$indentOffset'; } } diff --git a/test/refactor/refactor/RefactorClassTest.hx b/test/refactor/refactor/RefactorClassTest.hx index 5006a9b..19500de 100644 --- a/test/refactor/refactor/RefactorClassTest.hx +++ b/test/refactor/refactor/RefactorClassTest.hx @@ -21,7 +21,7 @@ class RefactorClassTest extends RefactorTestBase { var edits:Array = [ makeRemoveTestEdit("testcases/classes/ChildClass.hx", 860, 901), makeCreateTestEdit("testcases/classes/ListOfChilds.hx"), - makeInsertTestEdit("testcases/classes/ListOfChilds.hx", "package classes;\n\ntypedef ListOfChilds = Array;", 0, Format(0)), + makeInsertTestEdit("testcases/classes/ListOfChilds.hx", "package classes;\n\ntypedef ListOfChilds = Array;", 0, Format(0, false)), makeInsertTestEdit("testcases/classes/pack/UseChild.hx", "import classes.ListOfChilds;\n", 23), ]; checkRefactor(RefactorExtractType, {fileName: "testcases/classes/ChildClass.hx", posStart: 873, posEnd: 873}, edits, async); @@ -40,7 +40,7 @@ class RefactorClassTest extends RefactorTestBase { + " return Promise.resolve(text);\n" + " }\n" + "}", - 0, Format(0)), + 0, Format(0, false)), makeInsertTestEdit("testcases/classes/pack/UsePrinter.hx", "import classes.TextLoader;\n", 23), ]; checkRefactor(RefactorExtractType, {fileName: "testcases/classes/Printer.hx", posStart: 1273, posEnd: 1283}, edits, async); @@ -59,7 +59,7 @@ class RefactorClassTest extends RefactorTestBase { + "class Context {\n" + " public static var printFunc:PrintFunc;\n" + "}", - 0, Format(0)), + 0, Format(0, false)), makeRemoveTestEdit("testcases/classes/StaticUsing.hx", 484, 566), ]; checkRefactor(RefactorExtractType, {fileName: "testcases/classes/StaticUsing.hx", posStart: 518, posEnd: 518}, edits, async); @@ -78,7 +78,7 @@ class RefactorClassTest extends RefactorTestBase { + "class NotDocModule {\n" + " public function new() {}\n" + "}", 0, - Format(0)), + Format(0, false)), makeInsertTestEdit("testcases/classes/pack/UseDocModule.hx", "import classes.NotDocModule;\n", 23), ]; checkRefactor(RefactorExtractType, {fileName: "testcases/classes/DocModule.hx", posStart: 73, posEnd: 73}, edits, async); @@ -97,7 +97,7 @@ class RefactorClassTest extends RefactorTestBase { + "function doSomething5(d:Array):Void;\n" + "function doSomething6(d:Array):Bool;\n" + "}", - 0, Format(0)), + 0, Format(0, false)), ]; addTypeHint("testcases/classes/BaseClass.hx", 131, LibType("Void", "Void", [])); addTypeHint("testcases/classes/BaseClass.hx", 225, LibType("Void", "Void", [])); @@ -106,4 +106,35 @@ class RefactorClassTest extends RefactorTestBase { addTypeHint("testcases/classes/BaseClass.hx", 468, LibType("Bool", "Bool", [])); checkRefactor(RefactorExtractInterface, {fileName: "testcases/classes/BaseClass.hx", posStart: 27, posEnd: 27}, edits, async); } + + function testRewriteFinalsToVarsPrinter(async:Async) { + var edits:Array = [ + makeReplaceTestEdit("testcases/classes/Printer.hx", "var", 147, 152, NoFormat), + makeReplaceTestEdit("testcases/classes/Printer.hx", "var", 225, 230, NoFormat), + ]; + checkRefactor(RefactorRewriteVarsToFinals(false), {fileName: "testcases/classes/Printer.hx", posStart: 129, posEnd: 1079}, edits, async); + } + + function testRewriteVarsToFinalsPrinter(async:Async) { + failCanRefactor(RefactorRewriteVarsToFinals(true), {fileName: "testcases/classes/Printer.hx", posStart: 129, posEnd: 1079}, "unsupported"); + failRefactor(RefactorRewriteVarsToFinals(true), {fileName: "testcases/classes/Printer.hx", posStart: 129, posEnd: 1079}, + "failed to collect rewrite vars/finals data", async); + } + + function testRewriteVarsToFinalsJsonClass(async:Async) { + var edits:Array = [ + makeReplaceTestEdit("testcases/classes/JsonClass.hx", "final", 37, 40, NoFormat), + makeReplaceTestEdit("testcases/classes/JsonClass.hx", "final", 50, 53, NoFormat), + makeReplaceTestEdit("testcases/classes/JsonClass.hx", "final", 68, 71, NoFormat), + makeReplaceTestEdit("testcases/classes/JsonClass.hx", "final", 84, 87, NoFormat), + makeReplaceTestEdit("testcases/classes/JsonClass.hx", "final", 301, 304, NoFormat), + ]; + checkRefactor(RefactorRewriteVarsToFinals(true), {fileName: "testcases/classes/JsonClass.hx", posStart: 18, posEnd: 658}, edits, async); + } + + function testRewriteFinalsToVarsJsonClass(async:Async) { + failCanRefactor(RefactorRewriteVarsToFinals(false), {fileName: "testcases/classes/JsonClass.hx", posStart: 18, posEnd: 658}, "unsupported"); + failRefactor(RefactorRewriteVarsToFinals(false), {fileName: "testcases/classes/JsonClass.hx", posStart: 18, posEnd: 658}, + "failed to collect rewrite vars/finals data", async); + } } diff --git a/test/refactor/refactor/RefactorExtractConstructorParams.hx b/test/refactor/refactor/RefactorExtractConstructorParams.hx index e378777..a86ef5f 100644 --- a/test/refactor/refactor/RefactorExtractConstructorParams.hx +++ b/test/refactor/refactor/RefactorExtractConstructorParams.hx @@ -6,30 +6,52 @@ class RefactorExtractConstructorParams extends RefactorTestBase { } function testFailExtractParamsNotConstructor(async:Async) { - failCanRefactor(RefactorExtractConstructorParams, {fileName: "testcases/constructor/Point.hx", posStart: 148, posEnd: 148}, "unsupported"); - failRefactor(RefactorExtractConstructorParams, {fileName: "testcases/constructor/Point.hx", posStart: 148, posEnd: 148}, + failCanRefactor(RefactorExtractConstructorParams(false), {fileName: "testcases/constructor/Point.hx", posStart: 148, posEnd: 148}, "unsupported"); + failRefactor(RefactorExtractConstructorParams(false), {fileName: "testcases/constructor/Point.hx", posStart: 148, posEnd: 148}, + "failed to collect extract method data", async); + failCanRefactor(RefactorExtractConstructorParams(true), {fileName: "testcases/constructor/Point.hx", posStart: 148, posEnd: 148}, "unsupported"); + failRefactor(RefactorExtractConstructorParams(true), {fileName: "testcases/constructor/Point.hx", posStart: 148, posEnd: 148}, "failed to collect extract method data", async); } function testExtractParamsEmptyNew(async:Async) { - failCanRefactor(RefactorExtractConstructorParams, {fileName: "testcases/constructor/Point.hx", posStart: 247, posEnd: 247}, "unsupported"); - failRefactor(RefactorExtractConstructorParams, {fileName: "testcases/constructor/Point.hx", posStart: 247, posEnd: 247}, + failCanRefactor(RefactorExtractConstructorParams(false), {fileName: "testcases/constructor/Point.hx", posStart: 247, posEnd: 247}, "unsupported"); + failRefactor(RefactorExtractConstructorParams(false), {fileName: "testcases/constructor/Point.hx", posStart: 247, posEnd: 247}, + "failed to collect extract method data", async); + failCanRefactor(RefactorExtractConstructorParams(true), {fileName: "testcases/constructor/Point.hx", posStart: 247, posEnd: 247}, "unsupported"); + failRefactor(RefactorExtractConstructorParams(true), {fileName: "testcases/constructor/Point.hx", posStart: 247, posEnd: 247}, "failed to collect extract method data", async); } - function testExtractParamsYAndText(async:Async) { + function testExtractParamsYAndTextAsVar(async:Async) { + var edits:Array = [ + makeInsertTestEdit("testcases/constructor/Point.hx", "var z:Int;\nvar text:String;\n", 39, Format(1, false)), + makeInsertTestEdit("testcases/constructor/Point.hx", "this.z = z;\nthis.text = text;\n", 109, Format(2, false)), + ]; + checkRefactor(RefactorExtractConstructorParams(false), {fileName: "testcases/constructor/Point.hx", posStart: 58, posEnd: 58}, edits, async); + } + + function testExtractParamsMainAsVar(async:Async) { + var edits:Array = [ + makeInsertTestEdit("testcases/constructor/Point.hx", "var x:Int;\n", 275, Format(1, false)), + makeInsertTestEdit("testcases/constructor/Point.hx", "this.x = x;\n", 304, Format(2, false)), + ]; + checkRefactor(RefactorExtractConstructorParams(false), {fileName: "testcases/constructor/Point.hx", posStart: 294, posEnd: 294}, edits, async); + } + + function testExtractParamsYAndTextAsFinal(async:Async) { var edits:Array = [ - makeInsertTestEdit("testcases/constructor/Point.hx", "final z:Int;\nfinal text:String;\n", 39, Format(1)), - makeInsertTestEdit("testcases/constructor/Point.hx", "this.z = z;\nthis.text = text;\n", 109, Format(2)), + makeInsertTestEdit("testcases/constructor/Point.hx", "final z:Int;\nfinal text:String;\n", 39, Format(1, false)), + makeInsertTestEdit("testcases/constructor/Point.hx", "this.z = z;\nthis.text = text;\n", 109, Format(2, false)), ]; - checkRefactor(RefactorExtractConstructorParams, {fileName: "testcases/constructor/Point.hx", posStart: 58, posEnd: 58}, edits, async); + checkRefactor(RefactorExtractConstructorParams(true), {fileName: "testcases/constructor/Point.hx", posStart: 58, posEnd: 58}, edits, async); } - function testExtractParamsMain(async:Async) { + function testExtractParamsMainAsFinal(async:Async) { var edits:Array = [ - makeInsertTestEdit("testcases/constructor/Point.hx", "final x:Int;\n", 275, Format(1)), - makeInsertTestEdit("testcases/constructor/Point.hx", "this.x = x;\n", 304, Format(2)), + makeInsertTestEdit("testcases/constructor/Point.hx", "final x:Int;\n", 275, Format(1, false)), + makeInsertTestEdit("testcases/constructor/Point.hx", "this.x = x;\n", 304, Format(2, false)), ]; - checkRefactor(RefactorExtractConstructorParams, {fileName: "testcases/constructor/Point.hx", posStart: 294, posEnd: 294}, edits, async); + checkRefactor(RefactorExtractConstructorParams(true), {fileName: "testcases/constructor/Point.hx", posStart: 294, posEnd: 294}, edits, async); } } diff --git a/test/refactor/refactor/RefactorExtractMethodTest.hx b/test/refactor/refactor/RefactorExtractMethodTest.hx index b790d0d..7a21d1f 100644 --- a/test/refactor/refactor/RefactorExtractMethodTest.hx +++ b/test/refactor/refactor/RefactorExtractMethodTest.hx @@ -40,31 +40,31 @@ class RefactorExtractMethodTest extends RefactorTestBase { function testSimpleNoReturns(async:Async) { var edits:Array = [ - makeReplaceTestEdit("testcases/methods/Main.hx", "noReturnsExtract();", 94, 131, Format(2)), + makeReplaceTestEdit("testcases/methods/Main.hx", "noReturnsExtract();", 94, 131, Format(2, true)), makeInsertTestEdit("testcases/methods/Main.hx", "function noReturnsExtract():Void {\n" + "trace(\"hello 2\");\n" + " trace(\"hello 3\");\n" - + "}\n", 155, Format(1)), + + "}\n", 155, Format(1, false)), ]; checkRefactor(RefactorExtractMethod, {fileName: "testcases/methods/Main.hx", posStart: 93, posEnd: 131}, edits, async); } function testSimpleNoReturnsStatic(async:Async) { var edits:Array = [ - makeReplaceTestEdit("testcases/methods/Main.hx", "noReturnsStaticExtract();", 222, 259, Format(2)), + makeReplaceTestEdit("testcases/methods/Main.hx", "noReturnsStaticExtract();", 222, 259, Format(2, true)), makeInsertTestEdit("testcases/methods/Main.hx", "static function noReturnsStaticExtract():Void {\n" + "trace(\"hello 2\");\n" + " trace(\"hello 3\");\n" - + "}\n", 283, Format(1)), + + "}\n", 283, Format(1, false)), ]; checkRefactor(RefactorExtractMethod, {fileName: "testcases/methods/Main.hx", posStart: 221, posEnd: 259}, edits, async); } function testEmptyReturns(async:Async) { var edits:Array = [ - makeReplaceTestEdit("testcases/methods/Main.hx", "if (!emptyReturnsExtract(cond1, cond2)) {\n" + "return;\n" + "}", 342, 439, Format(2)), + makeReplaceTestEdit("testcases/methods/Main.hx", "if (!emptyReturnsExtract(cond1, cond2)) {\n" + "return;\n" + "}", 342, 439, Format(2, true)), makeInsertTestEdit("testcases/methods/Main.hx", "function emptyReturnsExtract(cond1:Bool, cond2:Bool):Bool {\n" + "if (cond1) {\n" @@ -77,14 +77,14 @@ class RefactorExtractMethodTest extends RefactorTestBase { + " trace(\"hello 2\");\n" + "return true;\n" + "}\n", - 483, Format(1)), + 483, Format(1, false)), ]; checkRefactor(RefactorExtractMethod, {fileName: "testcases/methods/Main.hx", posStart: 341, posEnd: 439}, edits, async); } function testCalculateTotal(async:Async) { var edits:Array = [ - makeReplaceTestEdit("testcases/methods/Main.hx", "total = calculateTotalExtract(items, total);", 569, 720, Format(2)), + makeReplaceTestEdit("testcases/methods/Main.hx", "total = calculateTotalExtract(items, total);", 569, 720, Format(2, true)), makeInsertTestEdit("testcases/methods/Main.hx", "function calculateTotalExtract(items:Array, total:Float):Float {\n" + "// Selected code block to extract\n" @@ -95,7 +95,7 @@ class RefactorExtractMethodTest extends RefactorTestBase { + " }\n" + "return total;\n" + "}\n", - 741, Format(1)), + 741, Format(1, false)), ]; checkRefactor(RefactorExtractMethod, {fileName: "testcases/methods/Main.hx", posStart: 568, posEnd: 720}, edits, async); } @@ -109,7 +109,7 @@ class RefactorExtractMethodTest extends RefactorTestBase { + "price = data.price;\n" + "quantity = data.quantity;\n" + "}", - 630, 686, Format(3)), + 630, 686, Format(3, true)), makeInsertTestEdit("testcases/methods/Main.hx", "function calculateTotalExtract(item:Item):{price:Float, quantity:Float} {\n" + "var price = item.price;\n" @@ -119,7 +119,7 @@ class RefactorExtractMethodTest extends RefactorTestBase { + "quantity: quantity,\n" + "};\n" + "}\n", - 741, Format(1)), + 741, Format(1, false)), ]; addTypeHint("testcases/methods/Main.hx", 613, LibType("Item", "Item", [])); addTypeHint("testcases/methods/Main.hx", 638, LibType("Float", "Float", [])); @@ -129,7 +129,7 @@ class RefactorExtractMethodTest extends RefactorTestBase { function testCalculateTotalWithLastReturn(async:Async) { var edits:Array = [ - makeReplaceTestEdit("testcases/methods/Main.hx", "return calculateTotalExtract(items, total);", 569, 737, Format(2)), + makeReplaceTestEdit("testcases/methods/Main.hx", "return calculateTotalExtract(items, total);", 569, 737, Format(2, true)), makeInsertTestEdit("testcases/methods/Main.hx", "function calculateTotalExtract(items:Array, total:Float):Float {\n" + "// Selected code block to extract\n" @@ -141,7 +141,7 @@ class RefactorExtractMethodTest extends RefactorTestBase { + "\n" + " return total;\n" + "}\n", - 741, Format(1)), + 741, Format(1, false)), ]; addTypeHint("testcases/methods/Main.hx", 514, LibType("Float", "Float", [])); addTypeHint("testcases/methods/Main.hx", 735, LibType("Float", "Float", [])); @@ -150,7 +150,7 @@ class RefactorExtractMethodTest extends RefactorTestBase { function testProcessUserWithLastReturn(async:Async) { var edits:Array = [ - makeReplaceTestEdit("testcases/methods/Main.hx", "return calculateTotal2Extract(items, total);", 1081, 1295, Format(2)), + makeReplaceTestEdit("testcases/methods/Main.hx", "return calculateTotal2Extract(items, total);", 1081, 1295, Format(2, true)), makeInsertTestEdit("testcases/methods/Main.hx", "function calculateTotal2Extract(items:Array, total:Float) {\n" + "// Selected code block to extract\n" @@ -164,7 +164,7 @@ class RefactorExtractMethodTest extends RefactorTestBase { + " }\n\n" + " return total;\n" + "}\n", - 1299, Format(1)), + 1299, Format(1, false)), ]; checkRefactor(RefactorExtractMethod, {fileName: "testcases/methods/Main.hx", posStart: 1080, posEnd: 1295}, edits, async); } @@ -181,7 +181,7 @@ class RefactorExtractMethodTest extends RefactorTestBase { + "total = result.data;\n" + "}\n" + "}", - 1081, 1278, Format(2)), + 1081, 1278, Format(2, true)), makeInsertTestEdit("testcases/methods/Main.hx", "function calculateTotal2Extract(items:Array, total:Float):{ret:haxe.ds.Option, ?data:Float} {\n" + "// Selected code block to extract\n" @@ -195,7 +195,7 @@ class RefactorExtractMethodTest extends RefactorTestBase { + " }\n" + "return {ret: None, data: total};\n" + "}\n", - 1299, Format(1)), + 1299, Format(1, false)), ]; addTypeHint("testcases/methods/Main.hx", 1026, FunctionType([LibType("Array", "Array", [LibType("Item", "Item", [])])], LibType("Float", "Float", []))); checkRefactor(RefactorExtractMethod, {fileName: "testcases/methods/Main.hx", posStart: 1080, posEnd: 1278}, edits, async); @@ -203,7 +203,7 @@ class RefactorExtractMethodTest extends RefactorTestBase { function testCalcConditionalLevel(async:Async) { var edits:Array = [ - makeReplaceTestEdit("testcases/methods/Main.hx", "count = calcConditionalLevelExtract(token, count);", 1388, 1543, Format(2)), + makeReplaceTestEdit("testcases/methods/Main.hx", "count = calcConditionalLevelExtract(token, count);", 1388, 1543, Format(2, true)), makeInsertTestEdit("testcases/methods/Main.hx", "function calcConditionalLevelExtract(token:tokentree.TokenTree, count:Int):Int {\n" + "while ((token != null) && (token.tok != Root)) {\n" @@ -216,14 +216,14 @@ class RefactorExtractMethodTest extends RefactorTestBase { + " }\n" + "return count;\n" + "}\n", - 1600, Format(1)), + 1600, Format(1, false)), ]; checkRefactor(RefactorExtractMethod, {fileName: "testcases/methods/Main.hx", posStart: 1387, posEnd: 1543}, edits, async); } function testCalcConditionalLevelWithVar(async:Async) { var edits:Array = [ - makeReplaceTestEdit("testcases/methods/Main.hx", "var count = calcConditionalLevelExtract(token);", 1366, 1543, Format(2)), + makeReplaceTestEdit("testcases/methods/Main.hx", "var count = calcConditionalLevelExtract(token);", 1366, 1543, Format(2, true)), makeInsertTestEdit("testcases/methods/Main.hx", "function calcConditionalLevelExtract(token:tokentree.TokenTree):Int {\n" + "var count:Int = -1;\n" @@ -237,14 +237,14 @@ class RefactorExtractMethodTest extends RefactorTestBase { + " }\n" + "return count;\n" + "}\n", - 1600, Format(1)), + 1600, Format(1, false)), ]; checkRefactor(RefactorExtractMethod, {fileName: "testcases/methods/Main.hx", posStart: 1365, posEnd: 1543}, edits, async); } function testAllEmptyReturns(async:Async) { var edits:Array = [ - makeReplaceTestEdit("testcases/methods/Main.hx", "allEmptyReturnsExtract();", 1722, 1809, Format(2)), + makeReplaceTestEdit("testcases/methods/Main.hx", "allEmptyReturnsExtract();", 1722, 1809, Format(2, true)), makeInsertTestEdit("testcases/methods/Main.hx", "function allEmptyReturnsExtract():Void {\n" + "trace(\"hello 1\");\n" @@ -253,17 +253,17 @@ class RefactorExtractMethodTest extends RefactorTestBase { + " trace(\"hello 4\");\n" + " return;\n" + "}\n", - 1813, Format(1)), + 1813, Format(1, false)), ]; checkRefactor(RefactorExtractMethod, {fileName: "testcases/methods/Main.hx", posStart: 1721, posEnd: 1809}, edits, async); } function testStringInterpolation(async:Async) { var edits:Array = [ - makeReplaceTestEdit("testcases/methods/Main.hx", "return interpolationExtract(data);", 1884, 1937, Format(2)), + makeReplaceTestEdit("testcases/methods/Main.hx", "return interpolationExtract(data);", 1884, 1937, Format(2, true)), makeInsertTestEdit("testcases/methods/Main.hx", "static function interpolationExtract(data:Dynamic):String {\n" + "return cast '${data.a}_${data.b}_${data.c}_${false}';\n" + "}\n", 1941, - Format(1)), + Format(1, false)), ]; addTypeHint("testcases/methods/Main.hx", 1857, LibType("String", "String", [])); checkRefactor(RefactorExtractMethod, {fileName: "testcases/methods/Main.hx", posStart: 1887, posEnd: 1937}, edits, async); @@ -271,7 +271,7 @@ class RefactorExtractMethodTest extends RefactorTestBase { function testDemoSimple(async:Async) { var edits:Array = [ - makeReplaceTestEdit("testcases/methods/Demo.hx", "doSomethingExtract();", 161, 252, Format(2)), + makeReplaceTestEdit("testcases/methods/Demo.hx", "doSomethingExtract();", 161, 252, Format(2, true)), makeInsertTestEdit("testcases/methods/Demo.hx", "function doSomethingExtract():Void {\n" + "doNothing();\n" @@ -280,14 +280,14 @@ class RefactorExtractMethodTest extends RefactorTestBase { + " trace(\"yep, still here\");\n" + " doNothing();\n" + "}\n", - 467, Format(1)), + 467, Format(1, false)), ]; checkRefactor(RefactorExtractMethod, {fileName: "testcases/methods/Demo.hx", posStart: 160, posEnd: 252}, edits, async); } function testDemoSwitch(async:Async) { var edits:Array = [ - makeReplaceTestEdit("testcases/methods/Demo.hx", "doSomethingExtract(cond, val, text);", 262, 463, Format(2)), + makeReplaceTestEdit("testcases/methods/Demo.hx", "doSomethingExtract(cond, val, text);", 262, 463, Format(2, true)), makeInsertTestEdit("testcases/methods/Demo.hx", "function doSomethingExtract(cond:Bool, val:Int, text:Null):Float {\n" + "return switch [cond, val] {\n" @@ -301,7 +301,7 @@ class RefactorExtractMethodTest extends RefactorTestBase { + " std.Math.NEGATIVE_INFINITY;\n" + " }\n" + "}\n", - 467, Format(1)), + 467, Format(1, false)), ]; addTypeHint("testcases/methods/Demo.hx", 61, LibType("Float", "Float", [])); checkRefactor(RefactorExtractMethod, {fileName: "testcases/methods/Demo.hx", posStart: 262, posEnd: 463}, edits, async); @@ -309,7 +309,7 @@ class RefactorExtractMethodTest extends RefactorTestBase { function testDemoReturnSwitch(async:Async) { var edits:Array = [ - makeReplaceTestEdit("testcases/methods/Demo.hx", "return doSomethingExtract(cond, val, text);", 255, 463, Format(2)), + makeReplaceTestEdit("testcases/methods/Demo.hx", "return doSomethingExtract(cond, val, text);", 255, 463, Format(2, true)), makeInsertTestEdit("testcases/methods/Demo.hx", "function doSomethingExtract(cond:Bool, val:Int, text:Null):Float {\n" + "return switch [cond, val] {\n" @@ -323,7 +323,7 @@ class RefactorExtractMethodTest extends RefactorTestBase { + " std.Math.NEGATIVE_INFINITY;\n" + " }\n" + "}\n", - 467, Format(1)), + 467, Format(1, false)), ]; addTypeHint("testcases/methods/Demo.hx", 61, LibType("Float", "Float", [])); checkRefactor(RefactorExtractMethod, {fileName: "testcases/methods/Demo.hx", posStart: 254, posEnd: 463}, edits, async); @@ -331,7 +331,7 @@ class RefactorExtractMethodTest extends RefactorTestBase { function testDemoCodeAndSwitch(async:Async) { var edits:Array = [ - makeReplaceTestEdit("testcases/methods/Demo.hx", "return doSomethingExtract(cond, val, text);", 161, 463, Format(2)), + makeReplaceTestEdit("testcases/methods/Demo.hx", "return doSomethingExtract(cond, val, text);", 161, 463, Format(2, true)), makeInsertTestEdit("testcases/methods/Demo.hx", "function doSomethingExtract(cond:Bool, val:Int, text:Null):Float {\n" + "doNothing();\n" @@ -350,7 +350,7 @@ class RefactorExtractMethodTest extends RefactorTestBase { + " std.Math.NEGATIVE_INFINITY;\n" + " }\n" + "}\n", - 467, Format(1)), + 467, Format(1, false)), ]; addTypeHint("testcases/methods/Demo.hx", 61, LibType("Float", "Float", [])); checkRefactor(RefactorExtractMethod, {fileName: "testcases/methods/Demo.hx", posStart: 160, posEnd: 463}, edits, async); @@ -363,7 +363,7 @@ class RefactorExtractMethodTest extends RefactorTestBase { + "case Some(data):\n" + "return data;\n" + "case None:\n" - + "}", 112, 252, Format(2)), + + "}", 112, 252, Format(2, true)), makeInsertTestEdit("testcases/methods/Demo.hx", "function doSomethingExtract(cond:Bool, text:Null):haxe.ds.Option {\n" + "if (cond && text == null) {\n" @@ -376,7 +376,7 @@ class RefactorExtractMethodTest extends RefactorTestBase { + " doNothing();\n" + "return None;\n" + "}\n", - 467, Format(1)), + 467, Format(1, false)), ]; addTypeHint("testcases/methods/Demo.hx", 61, LibType("Float", "Float", [])); checkRefactor(RefactorExtractMethod, {fileName: "testcases/methods/Demo.hx", posStart: 111, posEnd: 252}, edits, async); @@ -384,9 +384,9 @@ class RefactorExtractMethodTest extends RefactorTestBase { function testCalculateMath(async:Async) { var edits:Array = [ - makeReplaceTestEdit("testcases/methods/Math.hx", "calculateExtract(a, b);", 106, 122, Format(2)), + makeReplaceTestEdit("testcases/methods/Math.hx", "calculateExtract(a, b);", 106, 122, Format(2, true)), makeInsertTestEdit("testcases/methods/Math.hx", "function calculateExtract(a:Int, b:Int):Int {\n" + "return a * b + (a - b);\n" + "}\n", 143, - Format(1)), + Format(1, false)), ]; addTypeHint("testcases/methods/Math.hx", 71, LibType("Int", "Int", [])); addTypeHint("testcases/methods/Math.hx", 84, LibType("Int", "Int", [])); @@ -396,12 +396,12 @@ class RefactorExtractMethodTest extends RefactorTestBase { function testCalculateMathWithVar(async:Async) { var edits:Array = [ - makeReplaceTestEdit("testcases/methods/Math.hx", "var result = calculateExtract(a, b);", 93, 122, Format(2)), + makeReplaceTestEdit("testcases/methods/Math.hx", "var result = calculateExtract(a, b);", 93, 122, Format(2, true)), makeInsertTestEdit("testcases/methods/Math.hx", "function calculateExtract(a:Int, b:Int):Int {\n" + "var result = a * b + (a - b);\n" + "return result;\n" - + "}\n", 143, Format(1)), + + "}\n", 143, Format(1, false)), ]; addTypeHint("testcases/methods/Math.hx", 71, LibType("Int", "Int", [])); addTypeHint("testcases/methods/Math.hx", 84, LibType("Int", "Int", [])); @@ -411,7 +411,7 @@ class RefactorExtractMethodTest extends RefactorTestBase { function testNameProcessor(async:Async) { var edits:Array = [ - makeReplaceTestEdit("testcases/methods/NameProcessor.hx", "var upperNames = processExtract(names);", 113, 201, Format(2)), + makeReplaceTestEdit("testcases/methods/NameProcessor.hx", "var upperNames = processExtract(names);", 113, 201, Format(2, true)), makeInsertTestEdit("testcases/methods/NameProcessor.hx", "function processExtract(names:Array):Array {\n" + "var upperNames = [];\n" @@ -420,7 +420,7 @@ class RefactorExtractMethodTest extends RefactorTestBase { + " }\n" + "return upperNames;\n" + "}\n", - 226, Format(1)), + 226, Format(1, false)), ]; addTypeHint("testcases/methods/NameProcessor.hx", 82, LibType("Array", "Array", [LibType("String", "String", [])])); addTypeHint("testcases/methods/NameProcessor.hx", 126, LibType("Array", "Array", [UnknownType("?")])); @@ -429,7 +429,7 @@ class RefactorExtractMethodTest extends RefactorTestBase { function testArrayHandler(async:Async) { var edits:Array = [ - makeReplaceTestEdit("testcases/methods/ArrayHandler.hx", "var sum = handleExtract(numbers);", 105, 157, Format(2)), + makeReplaceTestEdit("testcases/methods/ArrayHandler.hx", "var sum = handleExtract(numbers);", 105, 157, Format(2, true)), makeInsertTestEdit("testcases/methods/ArrayHandler.hx", "function handleExtract(numbers:Array):Int {\n" + "var sum = 0;\n" @@ -438,7 +438,7 @@ class RefactorExtractMethodTest extends RefactorTestBase { + " }\n" + "return sum;\n" + "}\n", - 175, Format(1)), + 175, Format(1, false)), ]; addTypeHint("testcases/methods/ArrayHandler.hx", 82, LibType("Array", "Array", [LibType("Int", "Int", [])])); addTypeHint("testcases/methods/ArrayHandler.hx", 111, LibType("Int", "Int", [])); @@ -447,7 +447,7 @@ class RefactorExtractMethodTest extends RefactorTestBase { function testAgeChecker(async:Async) { var edits:Array = [ - makeReplaceTestEdit("testcases/methods/AgeChecker.hx", "message = checkExtract(age, message);", 105, 179, Format(2)), + makeReplaceTestEdit("testcases/methods/AgeChecker.hx", "message = checkExtract(age, message);", 105, 179, Format(2, true)), makeInsertTestEdit("testcases/methods/AgeChecker.hx", "function checkExtract(age:Int, message:String):String {\n" + "if (age < 18) {\n" @@ -457,7 +457,7 @@ class RefactorExtractMethodTest extends RefactorTestBase { + " }\n" + "return message;\n" + "}\n", - 201, Format(1)), + 201, Format(1, false)), ]; addTypeHint("testcases/methods/AgeChecker.hx", 75, LibType("Int", "Int", [])); addTypeHint("testcases/methods/AgeChecker.hx", 95, LibType("String", "String", [])); @@ -466,9 +466,9 @@ class RefactorExtractMethodTest extends RefactorTestBase { function testPersonHandler(async:Async) { var edits:Array = [ - makeReplaceTestEdit("testcases/methods/PersonHandler.hx", "handleExtract(person);", 113, 161, Format(2)), + makeReplaceTestEdit("testcases/methods/PersonHandler.hx", "handleExtract(person);", 113, 161, Format(2, true)), makeInsertTestEdit("testcases/methods/PersonHandler.hx", - "function handleExtract(person:Any) {\n" + "return \"Name: \" + person.name + \", Age: \" + person.age;\n" + "}\n", 180, Format(1)), + "function handleExtract(person:Any) {\n" + "return \"Name: \" + person.name + \", Age: \" + person.age;\n" + "}\n", 180, Format(1, false)), ]; addTypeHint("testcases/methods/PersonHandler.hx", 75, LibType("Int", "Int", [])); addTypeHint("testcases/methods/PersonHandler.hx", 95, LibType("String", "String", [])); @@ -477,13 +477,13 @@ class RefactorExtractMethodTest extends RefactorTestBase { function testPersonHandlerWithVar(async:Async) { var edits:Array = [ - makeReplaceTestEdit("testcases/methods/PersonHandler.hx", "var info = handleExtract(person);", 102, 161, Format(2)), + makeReplaceTestEdit("testcases/methods/PersonHandler.hx", "var info = handleExtract(person);", 102, 161, Format(2, true)), makeInsertTestEdit("testcases/methods/PersonHandler.hx", "function handleExtract(person:Any) {\n" + "var info = \"Name: \" + person.name + \", Age: \" + person.age;\n" + "return info;\n" + "}\n", - 180, Format(1)), + 180, Format(1, false)), ]; addTypeHint("testcases/methods/PersonHandler.hx", 75, LibType("Int", "Int", [])); addTypeHint("testcases/methods/PersonHandler.hx", 95, LibType("String", "String", [])); @@ -492,7 +492,7 @@ class RefactorExtractMethodTest extends RefactorTestBase { function testContainer(async:Async) { var edits:Array = [ - makeReplaceTestEdit("testcases/methods/Container.hx", "var result = processExtract(items, converter);", 108, 239, Format(2)), + makeReplaceTestEdit("testcases/methods/Container.hx", "var result = processExtract(items, converter);", 108, 239, Format(2, true)), makeInsertTestEdit("testcases/methods/Container.hx", "function processExtract(items:Array, converter:T -> String):Array {\n" + "var result = new Array();\n" @@ -502,7 +502,7 @@ class RefactorExtractMethodTest extends RefactorTestBase { + " }\n" + "return result;\n" + "}\n", - 260, Format(1)), + 260, Format(1, false)), ]; addTypeHint("testcases/methods/Container.hx", 91, FunctionType([LibType("T", "T", [])], LibType("String", "String", []))); addTypeHint("testcases/methods/Container.hx", 117, LibType("Array", "Array", [LibType("String", "String", [])])); @@ -511,7 +511,7 @@ class RefactorExtractMethodTest extends RefactorTestBase { function testMacroTools(async:Async) { var edits:Array = [ - makeReplaceTestEdit("testcases/methods/MacroTools.hx", "return buildExtract(fields);", 195, 353, Format(2)), + makeReplaceTestEdit("testcases/methods/MacroTools.hx", "return buildExtract(fields);", 195, 353, Format(2, true)), makeInsertTestEdit("testcases/methods/MacroTools.hx", "static function buildExtract(fields:Array):Array {\n" + "for (field in fields) {\n" @@ -524,7 +524,7 @@ class RefactorExtractMethodTest extends RefactorTestBase { + " }\n" + " return fields;\n" + "}\n", - 357, Format(1)), + 357, Format(1, false)), ]; addTypeHint("testcases/methods/MacroTools.hx", 133, LibType("Array", "Array", [LibType("Field", "Field", [])])); addTypeHint("testcases/methods/MacroTools.hx", 163, LibType("Array", "Array", [LibType("Field", "Field", [])])); @@ -534,7 +534,7 @@ class RefactorExtractMethodTest extends RefactorTestBase { function testMatcherOnlySwitch(async:Async) { var edits:Array = [ - makeReplaceTestEdit("testcases/methods/Matcher.hx", "processExtract(value);", 84, 284, Format(2)), + makeReplaceTestEdit("testcases/methods/Matcher.hx", "processExtract(value);", 84, 284, Format(2, true)), makeInsertTestEdit("testcases/methods/Matcher.hx", "function processExtract(value:Any) {\n" + "return switch value {\n" @@ -544,14 +544,14 @@ class RefactorExtractMethodTest extends RefactorTestBase { + " case _: 'Unknown';\n" + " }\n" + "}\n", - 288, Format(1)), + 288, Format(1, false)), ]; checkRefactor(RefactorExtractMethod, {fileName: "testcases/methods/Matcher.hx", posStart: 84, posEnd: 284}, edits, async); } function testMatcherWithReturn(async:Async) { var edits:Array = [ - makeReplaceTestEdit("testcases/methods/Matcher.hx", "return processExtract(value);", 77, 284, Format(2)), + makeReplaceTestEdit("testcases/methods/Matcher.hx", "return processExtract(value);", 77, 284, Format(2, true)), makeInsertTestEdit("testcases/methods/Matcher.hx", "function processExtract(value:Any) {\n" + "return switch value {\n" @@ -561,14 +561,14 @@ class RefactorExtractMethodTest extends RefactorTestBase { + " case _: 'Unknown';\n" + " }\n" + "}\n", - 288, Format(1)), + 288, Format(1, false)), ]; checkRefactor(RefactorExtractMethod, {fileName: "testcases/methods/Matcher.hx", posStart: 76, posEnd: 284}, edits, async); } function testTypeProcessor(async:Async) { var edits:Array = [ - makeReplaceTestEdit("testcases/methods/TypeProcessor.hx", "processExtract(item, compare);", 114, 221, Format(2)), + makeReplaceTestEdit("testcases/methods/TypeProcessor.hx", "processExtract(item, compare);", 114, 221, Format(2, true)), makeInsertTestEdit("testcases/methods/TypeProcessor.hx", "function processExtract(item:T, compare:U):Void {\n" + "var result = item.process();\n" @@ -576,21 +576,21 @@ class RefactorExtractMethodTest extends RefactorTestBase { + " item.update(compare.getValue());\n" + " }\n" + "}\n", - 225, Format(1)), + 225, Format(1, false)), ]; checkRefactor(RefactorExtractMethod, {fileName: "testcases/methods/TypeProcessor.hx", posStart: 113, posEnd: 221}, edits, async); } function testFunctionProcessor(async:Async) { var edits:Array = [ - makeReplaceTestEdit("testcases/methods/FunctionProcessor.hx", "processExtract(callback, value, text);", 143, 211, Format(2)), + makeReplaceTestEdit("testcases/methods/FunctionProcessor.hx", "processExtract(callback, value, text);", 143, 211, Format(2, true)), makeInsertTestEdit("testcases/methods/FunctionProcessor.hx", "function processExtract(callback:(Int, String) -> Bool, value:Int, text:String):Void {\n" + "if (callback(value, text)) {\n" + " trace('Success: $value, $text');\n" + " }\n" + "}\n", - 215, Format(1)), + 215, Format(1, false)), ]; addTypeHint("testcases/methods/FunctionProcessor.hx", 79, FunctionType([LibType("Int", "Int", []), LibType("String", "String", [])], LibType("Bool", "Bool", []))); @@ -601,7 +601,7 @@ class RefactorExtractMethodTest extends RefactorTestBase { function testArrayTools(async:Async) { var edits:Array = [ - makeReplaceTestEdit("testcases/methods/ArrayTools.hx", "return processItemsExtract(arr, fn);", 117, 231, Format(2)), + makeReplaceTestEdit("testcases/methods/ArrayTools.hx", "return processItemsExtract(arr, fn);", 117, 231, Format(2, true)), makeInsertTestEdit("testcases/methods/ArrayTools.hx", "static function processItemsExtract(arr:Array, fn:T -> Bool):Array {\n" + "var results = new Array();\n" @@ -611,7 +611,7 @@ class RefactorExtractMethodTest extends RefactorTestBase { + " }\n" + " return results;\n" + "}\n", - 235, Format(1)), + 235, Format(1, false)), ]; addTypeHint("testcases/methods/ArrayTools.hx", 82, LibType("Array", "Array", [LibType("T", "T", [])])); addTypeHint("testcases/methods/ArrayTools.hx", 102, FunctionType([LibType("T", "T", [])], LibType("Bool", "Bool", []))); @@ -620,26 +620,26 @@ class RefactorExtractMethodTest extends RefactorTestBase { function testExceptionHandlerTry(async:Async) { var edits:Array = [ - makeReplaceTestEdit("testcases/methods/ExceptionHandler.hx", "processExtract();", 86, 152, Format(3)), + makeReplaceTestEdit("testcases/methods/ExceptionHandler.hx", "processExtract();", 86, 152, Format(3, true)), makeInsertTestEdit("testcases/methods/ExceptionHandler.hx", "function processExtract():Void {\n" + "var data = getData();\n" + " validateData(data);\n" + " processData(data);\n" + "}\n", 795, - Format(1)), + Format(1, false)), ]; checkRefactor(RefactorExtractMethod, {fileName: "testcases/methods/ExceptionHandler.hx", posStart: 85, posEnd: 152}, edits, async); } function testExceptionHandlerCatch(async:Async) { var edits:Array = [ - makeReplaceTestEdit("testcases/methods/ExceptionHandler.hx", "processExtract();", 193, 250, Format(3)), + makeReplaceTestEdit("testcases/methods/ExceptionHandler.hx", "processExtract();", 193, 250, Format(3, true)), makeInsertTestEdit("testcases/methods/ExceptionHandler.hx", "function processExtract():Void {\n" + "logError(e);\n" + " throw new ProcessingException(e.message);\n" - + "}\n", 795, Format(1)), + + "}\n", 795, Format(1, false)), ]; addTypeHint("testcases/methods/ExceptionHandler.hx", 69, LibType("Void", "Void", [])); checkRefactor(RefactorExtractMethod, {fileName: "testcases/methods/ExceptionHandler.hx", posStart: 192, posEnd: 250}, edits, async); @@ -647,7 +647,7 @@ class RefactorExtractMethodTest extends RefactorTestBase { function testExceptionHandlerTryWithThrow(async:Async) { var edits:Array = [ - makeReplaceTestEdit("testcases/methods/ExceptionHandler.hx", "processExtract();", 266, 404, Format(3)), + makeReplaceTestEdit("testcases/methods/ExceptionHandler.hx", "processExtract();", 266, 404, Format(3, true)), makeInsertTestEdit("testcases/methods/ExceptionHandler.hx", "function processExtract():Void {\n" + "var data = getData();\n" @@ -656,14 +656,14 @@ class RefactorExtractMethodTest extends RefactorTestBase { + " }\n" + " throw new InvalidDataException(\"value should not be smaller than 11\");\n" + "}\n", - 795, Format(1)), + 795, Format(1, false)), ]; checkRefactor(RefactorExtractMethod, {fileName: "testcases/methods/ExceptionHandler.hx", posStart: 265, posEnd: 404}, edits, async); } function testExceptionHandlerTryWithThrowNotLast(async:Async) { var edits:Array = [ - makeReplaceTestEdit("testcases/methods/ExceptionHandler.hx", "processExtract();", 518, 689, Format(3)), + makeReplaceTestEdit("testcases/methods/ExceptionHandler.hx", "processExtract();", 518, 689, Format(3, true)), makeInsertTestEdit("testcases/methods/ExceptionHandler.hx", "function processExtract():Void {\n" + "var data = getData();\n" @@ -673,7 +673,7 @@ class RefactorExtractMethodTest extends RefactorTestBase { + " validateData(data);\n" + " processData(data);\n" + "}\n", - 795, Format(1)), + 795, Format(1, false)), ]; addTypeHint("testcases/methods/ExceptionHandler.hx", 69, LibType("Void", "Void", [])); checkRefactor(RefactorExtractMethod, {fileName: "testcases/methods/ExceptionHandler.hx", posStart: 517, posEnd: 689}, edits, async); @@ -681,7 +681,7 @@ class RefactorExtractMethodTest extends RefactorTestBase { function testMetadataProcessor(async:Async) { var edits:Array = [ - makeReplaceTestEdit("testcases/methods/MetadataProcessor.hx", "processExtract(meta, results);", 220, 575, Format(2)), + makeReplaceTestEdit("testcases/methods/MetadataProcessor.hx", "processExtract(meta, results);", 220, 575, Format(2, true)), makeInsertTestEdit("testcases/methods/MetadataProcessor.hx", "function processExtract(meta:Dynamic>>, results:Map>):Void {\n" + "for (field in Reflect.fields(meta)) {\n" @@ -695,7 +695,7 @@ class RefactorExtractMethodTest extends RefactorTestBase { + " }\n" + " }\n" + "}\n", - 676, Format(1)), + 676, Format(1, false)), ]; addTypeHint("testcases/methods/MetadataProcessor.hx", 132, LibType("Dynamic", "Dynamic", [ LibType("Dynamic", "Dynamic", [LibType("Array", "Array", [LibType("Dynamic", "Dynamic", [])])]) @@ -709,7 +709,7 @@ class RefactorExtractMethodTest extends RefactorTestBase { function testMetadataProcessorWithResults(async:Async) { var edits:Array = [ - makeReplaceTestEdit("testcases/methods/MetadataProcessor.hx", "var results = processExtract(meta);", 169, 575, Format(2)), + makeReplaceTestEdit("testcases/methods/MetadataProcessor.hx", "var results = processExtract(meta);", 169, 575, Format(2, true)), makeInsertTestEdit("testcases/methods/MetadataProcessor.hx", "function processExtract(meta:Dynamic>>):Map> {\n" + "var results = new Map>();\n\n" @@ -725,7 +725,7 @@ class RefactorExtractMethodTest extends RefactorTestBase { + " }\n" + "return results;\n" + "}\n", - 676, Format(1)), + 676, Format(1, false)), ]; addTypeHint("testcases/methods/MetadataProcessor.hx", 132, LibType("Dynamic", "Dynamic", [ LibType("Dynamic", "Dynamic", [LibType("Array", "Array", [LibType("Dynamic", "Dynamic", [])])]) @@ -739,14 +739,14 @@ class RefactorExtractMethodTest extends RefactorTestBase { function testMetadataProcessorPrint(async:Async) { var edits:Array = [ - makeReplaceTestEdit("testcases/methods/MetadataProcessor.hx", "processExtract(results);", 579, 672, Format(2)), + makeReplaceTestEdit("testcases/methods/MetadataProcessor.hx", "processExtract(results);", 579, 672, Format(2, true)), makeInsertTestEdit("testcases/methods/MetadataProcessor.hx", "function processExtract(results:Map>):Void {\n" + "for (field => values in results) {\n" + " trace('Field: $field, Meta: ${values.join(\", \")}');\n" + " }\n" + "}\n", - 676, Format(1)), + 676, Format(1, false)), ]; addTypeHint("testcases/methods/MetadataProcessor.hx", 179, LibType("Map", "Map", [ LibType("String", "String", []), @@ -757,12 +757,13 @@ class RefactorExtractMethodTest extends RefactorTestBase { function testLambdaExample(async:Async) { var edits:Array = [ - makeReplaceTestEdit("testcases/methods/LambdaExample.hx", "processExtract", 156, 230, Format(2)), + makeReplaceTestEdit("testcases/methods/LambdaExample.hx", "processExtract", 156, 230, Format(2, true)), makeInsertTestEdit("testcases/methods/LambdaExample.hx", "function processExtract(n:Int):Int {\n" + "var temp = n * multiplier;\n" + " return temp + this.multiplier;\n" - + "}\n", 236, Format(1)), + + "}\n", 236, + Format(1, false)), ]; addTypeHint("testcases/methods/LambdaExample.hx", 156, LibType("Int", "Int", [])); addTypeHint("testcases/methods/LambdaExample.hx", 159, LibType("Int", "Int", [])); @@ -771,12 +772,12 @@ class RefactorExtractMethodTest extends RefactorTestBase { function testLambdaExampleCallback(async:Async) { var edits:Array = [ - makeReplaceTestEdit("testcases/methods/LambdaExample.hx", "processCallbackExtract", 281, 337, Format(2)), + makeReplaceTestEdit("testcases/methods/LambdaExample.hx", "processCallbackExtract", 281, 337, Format(2, true)), makeInsertTestEdit("testcases/methods/LambdaExample.hx", "function processCallbackExtract(n:Int, m:Int):Int {\n" + "var temp = n * m;\n" + " return temp + m;\n" - + "}\n", 343, Format(1)), + + "}\n", 343, Format(1, false)), ]; addTypeHint("testcases/methods/LambdaExample.hx", 282, LibType("Int", "Int", [])); addTypeHint("testcases/methods/LambdaExample.hx", 285, LibType("Int", "Int", [])); @@ -786,12 +787,12 @@ class RefactorExtractMethodTest extends RefactorTestBase { function testLambdaExampleCallbackFunc(async:Async) { var edits:Array = [ - makeReplaceTestEdit("testcases/methods/LambdaExample.hx", "processCallbackFuncExtract", 392, 453, Format(2)), + makeReplaceTestEdit("testcases/methods/LambdaExample.hx", "processCallbackFuncExtract", 392, 453, Format(2, true)), makeInsertTestEdit("testcases/methods/LambdaExample.hx", "function processCallbackFuncExtract(n:Int, m:Int):Int {\n" + "var temp = n * m;\n" + " return temp + m;\n" - + "}\n", 459, Format(1)), + + "}\n", 459, Format(1, false)), ]; addTypeHint("testcases/methods/LambdaExample.hx", 401, LibType("Int", "Int", [])); addTypeHint("testcases/methods/LambdaExample.hx", 404, LibType("Int", "Int", [])); @@ -801,8 +802,10 @@ class RefactorExtractMethodTest extends RefactorTestBase { function testLambdaExampleSimple(async:Async) { var edits:Array = [ - makeReplaceTestEdit("testcases/methods/LambdaExample.hx", "processSimpleExtract", 669, 679, Format(2)), - makeInsertTestEdit("testcases/methods/LambdaExample.hx", "function processSimpleExtract(n:Int):Int {\n" + "n * n\n" + "}\n", 685, Format(1)), + makeReplaceTestEdit("testcases/methods/LambdaExample.hx", "processSimpleExtract", 669, 679, Format(2, true)), + makeInsertTestEdit("testcases/methods/LambdaExample.hx", "function processSimpleExtract(n:Int):Int {\n" + + "n * n\n" + + "}\n", 685, Format(1, false)), ]; addTypeHint("testcases/methods/LambdaExample.hx", 669, LibType("Int", "Int", [])); addTypeHint("testcases/methods/LambdaExample.hx", 672, LibType("Int", "Int", [])); @@ -811,12 +814,12 @@ class RefactorExtractMethodTest extends RefactorTestBase { function testLambdaExampleNamedCallbackFunc(async:Async) { var edits:Array = [ - makeReplaceTestEdit("testcases/methods/LambdaExample.hx", "", 727, 806, Format(2)), + makeReplaceTestEdit("testcases/methods/LambdaExample.hx", "", 727, 806, Format(2, true)), makeInsertTestEdit("testcases/methods/LambdaExample.hx", "function processCB(n:Int, m:Int):Int {\n" + "var temp = n * m;\n" + " return temp + m;\n" - + "}\n", 836, Format(1)), + + "}\n", 836, Format(1, false)), ]; addTypeHint("testcases/methods/LambdaExample.hx", 744, LibType("Int", "Int", [])); checkRefactor(RefactorExtractMethod, {fileName: "testcases/methods/LambdaExample.hx", posStart: 726, posEnd: 806}, edits, async); @@ -824,7 +827,7 @@ class RefactorExtractMethodTest extends RefactorTestBase { function testEditDocInner(async:Async) { var edits:Array = [ - makeReplaceTestEdit("testcases/methods/TestEditDoc.hx", "text = addChangeExtract(range, text);", 388, 485, Format(4)), + makeReplaceTestEdit("testcases/methods/TestEditDoc.hx", "text = addChangeExtract(range, text);", 388, 485, Format(4, true)), makeInsertTestEdit("testcases/methods/TestEditDoc.hx", "function addChangeExtract(range:TestRange, text:String):String {\n" + "if (range.start.character != 0) {\n" @@ -833,7 +836,7 @@ class RefactorExtractMethodTest extends RefactorTestBase { + " }\n" + "return text;\n" + "}\n", - 538, Format(1)), + 538, Format(1, false)), ]; addTypeHint("testcases/methods/TestEditDoc.hx", 170, LibType("TestRange", "TestRange", [])); addTypeHint("testcases/methods/TestEditDoc.hx", 235, LibType("String", "String", [])); @@ -842,7 +845,7 @@ class RefactorExtractMethodTest extends RefactorTestBase { function testEditDocInnerWithEdit(async:Async) { var edits:Array = [ - makeReplaceTestEdit("testcases/methods/TestEditDoc.hx", "text = addChangeExtract(text, filePath, range);", 346, 485, Format(4)), + makeReplaceTestEdit("testcases/methods/TestEditDoc.hx", "text = addChangeExtract(text, filePath, range);", 346, 485, Format(4, true)), makeInsertTestEdit("testcases/methods/TestEditDoc.hx", "function addChangeExtract(text:String, filePath:String, range:TestRange):String {\n" + "text = formatSnippet(filePath, text);\n" @@ -852,7 +855,7 @@ class RefactorExtractMethodTest extends RefactorTestBase { + " }\n" + "return text;\n" + "}\n", - 538, Format(1)), + 538, Format(1, false)), ]; addTypeHint("testcases/methods/TestEditDoc.hx", 170, LibType("TestRange", "TestRange", [])); addTypeHint("testcases/methods/TestEditDoc.hx", 235, LibType("String", "String", [])); @@ -862,7 +865,7 @@ class RefactorExtractMethodTest extends RefactorTestBase { function testEditDocInnerWithSwitch(async:Async) { var edits:Array = [ - makeReplaceTestEdit("testcases/methods/TestEditDoc.hx", "var text = addChangeExtract(range);", 193, 489, Format(2)), + makeReplaceTestEdit("testcases/methods/TestEditDoc.hx", "var text = addChangeExtract(range);", 193, 489, Format(2, true)), makeInsertTestEdit("testcases/methods/TestEditDoc.hx", "function addChangeExtract(range:TestRange):String {\n" + "final f:FormatType = Format(10);\n" @@ -879,7 +882,7 @@ class RefactorExtractMethodTest extends RefactorTestBase { + " }\n" + "return text;\n" + "}\n", - 538, Format(1)), + 538, Format(1, false)), ]; addTypeHint("testcases/methods/TestEditDoc.hx", 170, LibType("TestRange", "TestRange", [])); addTypeHint("testcases/methods/TestEditDoc.hx", 235, LibType("String", "String", [])); @@ -896,7 +899,7 @@ class RefactorExtractMethodTest extends RefactorTestBase { + "range = data.range;\n" + "text = data.text;\n" + "}", 160, - 489, Format(2)), + 489, Format(2, true)), makeInsertTestEdit("testcases/methods/TestEditDoc.hx", "function addChangeExtract():{range:TestRange, text:String} {\n" + "final range = posToRange(100);\n" @@ -917,7 +920,7 @@ class RefactorExtractMethodTest extends RefactorTestBase { + "text: text,\n" + "};\n" + "}\n", - 538, Format(1)), + 538, Format(1, false)), ]; addTypeHint("testcases/methods/TestEditDoc.hx", 170, LibType("TestRange", "TestRange", [])); addTypeHint("testcases/methods/TestEditDoc.hx", 235, LibType("String", "String", [])); diff --git a/test/refactor/refactor/RefactorTypedefTest.hx b/test/refactor/refactor/RefactorTypedefTest.hx index c772711..31e8cfb 100644 --- a/test/refactor/refactor/RefactorTypedefTest.hx +++ b/test/refactor/refactor/RefactorTypedefTest.hx @@ -29,7 +29,7 @@ class RefactorTypedefTest extends RefactorTestBase { + " var maxCompletionItems:Int;\n" + " var renameSourceFolders:Array;\n" + "}", - 0, Format(0)), + 0, Format(0, false)), ]; checkRefactor(RefactorExtractType, {fileName: "testcases/typedefs/Types.hx", posStart: 260, posEnd: 260}, edits, async); } From fe2a4879980005da4628325875817780c5b14b51 Mon Sep 17 00:00:00 2001 From: AlexHaxe Date: Tue, 3 Dec 2024 22:18:48 +0100 Subject: [PATCH 45/59] fixed vars to finals with modifiers --- src/refactor/refactor/RewriteVarsToFinals.hx | 19 +++++++++++------- test/refactor/refactor/RefactorTypedefTest.hx | 20 +++++++++++++++++++ 2 files changed, 32 insertions(+), 7 deletions(-) diff --git a/src/refactor/refactor/RewriteVarsToFinals.hx b/src/refactor/refactor/RewriteVarsToFinals.hx index 1646a1e..a653867 100644 --- a/src/refactor/refactor/RewriteVarsToFinals.hx +++ b/src/refactor/refactor/RewriteVarsToFinals.hx @@ -56,21 +56,26 @@ class RewriteVarsToFinals { // find corresponding tokens in tokentree, selection start/end in whitespace final tokensStart:TokensAtPos = RefactorHelper.findTokensAtPos(root, context.what.posStart); - final tokensEnd:TokensAtPos = RefactorHelper.findTokensAtPos(root, context.what.posEnd); - if (tokensStart.after == null || tokensEnd.before == null) { + if (tokensStart.after == null) { return null; } final tokenStart:Null = tokensStart.after; - final tokenEnd:Null = tokensEnd.before; - if (tokenStart == null || tokenEnd == null) { + if (tokenStart == null) { return null; } - if (tokenStart.index >= tokenEnd.index) { - return null; + var parent = tokenStart.parent; + while (parent != null) { + switch (parent.tok) { + case Root: + break; + case BrOpen: + break; + default: + parent = parent.parent; + } } - final parent = tokenStart.parent; if (parent == null) { return null; } diff --git a/test/refactor/refactor/RefactorTypedefTest.hx b/test/refactor/refactor/RefactorTypedefTest.hx index 31e8cfb..8b9efb7 100644 --- a/test/refactor/refactor/RefactorTypedefTest.hx +++ b/test/refactor/refactor/RefactorTypedefTest.hx @@ -33,4 +33,24 @@ class RefactorTypedefTest extends RefactorTestBase { ]; checkRefactor(RefactorExtractType, {fileName: "testcases/typedefs/Types.hx", posStart: 260, posEnd: 260}, edits, async); } + + function testRewriteVarsToFinalsTestFormatterInputData(async:Async) { + var edits:Array = [ + makeReplaceTestEdit("testcases/typedefs/codedata/TestFormatterInputData.hx", "final", 257, 260, NoFormat), + makeReplaceTestEdit("testcases/typedefs/codedata/TestFormatterInputData.hx", "final", 297, 300, NoFormat), + makeReplaceTestEdit("testcases/typedefs/codedata/TestFormatterInputData.hx", "final", 334, 337, NoFormat), + makeReplaceTestEdit("testcases/typedefs/codedata/TestFormatterInputData.hx", "final", 382, 385, NoFormat), + makeReplaceTestEdit("testcases/typedefs/codedata/TestFormatterInputData.hx", "final", 420, 423, NoFormat), + makeReplaceTestEdit("testcases/typedefs/codedata/TestFormatterInputData.hx", "final", 452, 455, NoFormat), + ]; + checkRefactor(RefactorRewriteVarsToFinals(true), {fileName: "testcases/typedefs/codedata/TestFormatterInputData.hx", posStart: 245, posEnd: 473}, + edits, async); + } + + function testRewriteFinalsToVarsTestFormatterInputData(async:Async) { + failCanRefactor(RefactorRewriteVarsToFinals(false), {fileName: "testcases/typedefs/codedata/TestFormatterInputData.hx", posStart: 245, posEnd: 473}, + "unsupported"); + failRefactor(RefactorRewriteVarsToFinals(false), {fileName: "testcases/typedefs/codedata/TestFormatterInputData.hx", posStart: 245, posEnd: 473}, + "failed to collect rewrite vars/finals data", async); + } } From 750f44598d0ac9c7ae9f959db40a0c63014cedd7 Mon Sep 17 00:00:00 2001 From: AlexHaxe Date: Thu, 5 Dec 2024 21:03:54 +0100 Subject: [PATCH 46/59] fixed vars to finals changing property vars --- src/refactor/refactor/RewriteVarsToFinals.hx | 3 +++ test/refactor/refactor/RefactorClassTest.hx | 6 +++--- testcases/classes/JsonClass.hx | 2 ++ 3 files changed, 8 insertions(+), 3 deletions(-) diff --git a/src/refactor/refactor/RewriteVarsToFinals.hx b/src/refactor/refactor/RewriteVarsToFinals.hx index a653867..fd7d183 100644 --- a/src/refactor/refactor/RewriteVarsToFinals.hx +++ b/src/refactor/refactor/RewriteVarsToFinals.hx @@ -88,6 +88,9 @@ class RewriteVarsToFinals { } return switch (token.tok) { case Kwd(KwdVar) if (toFinals): + if (token.access().firstChild().firstOf(POpen).exists()) { + return SkipSubtree; + } FoundSkipSubtree; case Kwd(KwdVar): SkipSubtree; diff --git a/test/refactor/refactor/RefactorClassTest.hx b/test/refactor/refactor/RefactorClassTest.hx index 19500de..5440d59 100644 --- a/test/refactor/refactor/RefactorClassTest.hx +++ b/test/refactor/refactor/RefactorClassTest.hx @@ -129,12 +129,12 @@ class RefactorClassTest extends RefactorTestBase { makeReplaceTestEdit("testcases/classes/JsonClass.hx", "final", 84, 87, NoFormat), makeReplaceTestEdit("testcases/classes/JsonClass.hx", "final", 301, 304, NoFormat), ]; - checkRefactor(RefactorRewriteVarsToFinals(true), {fileName: "testcases/classes/JsonClass.hx", posStart: 18, posEnd: 658}, edits, async); + checkRefactor(RefactorRewriteVarsToFinals(true), {fileName: "testcases/classes/JsonClass.hx", posStart: 18, posEnd: 696}, edits, async); } function testRewriteFinalsToVarsJsonClass(async:Async) { - failCanRefactor(RefactorRewriteVarsToFinals(false), {fileName: "testcases/classes/JsonClass.hx", posStart: 18, posEnd: 658}, "unsupported"); - failRefactor(RefactorRewriteVarsToFinals(false), {fileName: "testcases/classes/JsonClass.hx", posStart: 18, posEnd: 658}, + failCanRefactor(RefactorRewriteVarsToFinals(false), {fileName: "testcases/classes/JsonClass.hx", posStart: 18, posEnd: 696}, "unsupported"); + failRefactor(RefactorRewriteVarsToFinals(false), {fileName: "testcases/classes/JsonClass.hx", posStart: 18, posEnd: 696}, "failed to collect rewrite vars/finals data", async); } } diff --git a/testcases/classes/JsonClass.hx b/testcases/classes/JsonClass.hx index 71ed5fd..a9bc0df 100644 --- a/testcases/classes/JsonClass.hx +++ b/testcases/classes/JsonClass.hx @@ -31,6 +31,8 @@ class JsonClass { maxWidth: maxWidth }; } + + public var prop(default, null):Int; } typedef JsonData = Any; From 222e156eef9a32fb86ad015940248a683268cd7f Mon Sep 17 00:00:00 2001 From: AlexHaxe Date: Sun, 8 Dec 2024 13:50:49 +0100 Subject: [PATCH 47/59] removed edited flag from Identifier fixed null pointer error on identifiers without a defineType --- src/refactor/discover/Identifier.hx | 6 ------ src/refactor/discover/NameMap.hx | 3 +-- src/refactor/edits/Changelist.hx | 6 ++++-- src/refactor/rename/RenameHelper.hx | 5 +++++ src/refactor/typing/TypingHelper.hx | 3 +++ test.hxml | 2 ++ test/refactor/TestBase.hx | 5 ----- testCoverage.hxml | 4 +++- 8 files changed, 18 insertions(+), 16 deletions(-) diff --git a/src/refactor/discover/Identifier.hx b/src/refactor/discover/Identifier.hx index 1b5329e..d6845d2 100644 --- a/src/refactor/discover/Identifier.hx +++ b/src/refactor/discover/Identifier.hx @@ -8,7 +8,6 @@ class Identifier { public var file:File; public var parent:Null; public var defineType:Null; - public var edited:Bool; public function new(type:IdentifierType, name:String, pos:IdentifierPos, nameMap:NameMap, file:File, defineType:Null) { this.type = type; @@ -17,7 +16,6 @@ class Identifier { this.file = file; this.defineType = defineType; parent = null; - edited = false; if (defineType != null) { defineType.addIdentifier(this); @@ -25,10 +23,6 @@ class Identifier { nameMap.addIdentifier(this); } - public function reset() { - edited = false; - } - public function addUse(identifier:Null) { if (identifier == null) { return; diff --git a/src/refactor/discover/NameMap.hx b/src/refactor/discover/NameMap.hx index 9fb1361..607d4f8 100644 --- a/src/refactor/discover/NameMap.hx +++ b/src/refactor/discover/NameMap.hx @@ -48,7 +48,6 @@ class NameMap { list.push(identifier); } } - identifier.reset(); addToMap(names, identifier.name); var nameParts:Array = identifier.name.split("."); for (part in nameParts) { @@ -73,7 +72,7 @@ class NameMap { return []; } if (unused) { - results = results.filter(i -> !i.edited); + // results = results.filter(i -> !i.edited); results.sort(Identifier.sortIdentifier); } return results; diff --git a/src/refactor/edits/Changelist.hx b/src/refactor/edits/Changelist.hx index 62af146..348b998 100644 --- a/src/refactor/edits/Changelist.hx +++ b/src/refactor/edits/Changelist.hx @@ -10,18 +10,20 @@ import refactor.rename.RenameContext; class Changelist { var changes:Map>; var context:EditContext; + var editedIdentifiers:Array; public function new(context:EditContext) { changes = new Map>(); this.context = context; + editedIdentifiers = []; } public function addChange(fileName:String, change:FileEdit, identifier:Null) { if (identifier != null) { - if (identifier.edited) { + if (editedIdentifiers.contains(identifier)) { return; } - identifier.edited = true; + editedIdentifiers.push(identifier); } var fileChanges:Null> = changes.get(fileName); if (fileChanges == null) { diff --git a/src/refactor/rename/RenameHelper.hx b/src/refactor/rename/RenameHelper.hx index 2ca00b8..8f132ca 100644 --- a/src/refactor/rename/RenameHelper.hx +++ b/src/refactor/rename/RenameHelper.hx @@ -52,6 +52,11 @@ class RenameHelper { var innerChanges:Array> = []; for (use in allUses) { + switch (use.type) { + case PackageName | ImportModul | ImportAlias | UsingModul: + continue; + default: + } var object:String = ""; if (use.name == identifier.name) { if (use.parent != null) { diff --git a/src/refactor/typing/TypingHelper.hx b/src/refactor/typing/TypingHelper.hx index cfbdfa0..3267b38 100644 --- a/src/refactor/typing/TypingHelper.hx +++ b/src/refactor/typing/TypingHelper.hx @@ -190,6 +190,9 @@ class TypingHelper { } public static function findFieldOrScopedLocal(context:CacheAndTyperContext, containerType:Type, name:String, pos:Int):Promise { + if (containerType == null) { + return Promise.resolve(null); + } return findTypeWithTyper(context, containerType.file.name, pos).catchError(function(msg):Promise { // trace("Haxe typer failed for " + name); var allUses:Array = containerType.getIdentifiers(name); diff --git a/test.hxml b/test.hxml index 4cadc3e..b348e92 100644 --- a/test.hxml +++ b/test.hxml @@ -14,4 +14,6 @@ --js out/tests.js +# -D dump-dependencies + --cmd node out/tests.js diff --git a/test/refactor/TestBase.hx b/test/refactor/TestBase.hx index 06d2982..984842b 100644 --- a/test/refactor/TestBase.hx +++ b/test/refactor/TestBase.hx @@ -49,11 +49,6 @@ class TestBase implements ITest { @:access(refactor.discover.NameMap) function setup() { typer.clear(); - for (key => list in usageContext.nameMap.names) { - for (identifier in list) { - identifier.edited = false; - } - } } public function addTypeHint(fileName:String, pos:Int, typeHint:TypeHintType) { diff --git a/testCoverage.hxml b/testCoverage.hxml index f60de85..8ef51c0 100644 --- a/testCoverage.hxml +++ b/testCoverage.hxml @@ -13,7 +13,7 @@ -D coverage-console-summary-reporter -D coverage-console-package-summary-reporter -# -D coverage-console-file-summary-reporter +-D coverage-console-file-summary-reporter -D coverage-lcov-reporter -D coverage-codecov-reporter @@ -24,6 +24,8 @@ # -D debug_log_expression -D instrument-quiet +# -D dump-dependencies + --main TestMain --js out/tests.js From 6d9f509e7f039ead7f47fdfddc2cf5ee3ecf169d Mon Sep 17 00:00:00 2001 From: AlexHaxe Date: Sun, 8 Dec 2024 17:37:18 +0100 Subject: [PATCH 48/59] cleanup --- src/refactor/discover/NameMap.hx | 1 - src/refactor/refactor/ExtractMethod.hx | 3 ++- src/refactor/rename/RenameHelper.hx | 4 ++++ src/refactor/typing/TypingHelper.hx | 4 +++- 4 files changed, 9 insertions(+), 3 deletions(-) diff --git a/src/refactor/discover/NameMap.hx b/src/refactor/discover/NameMap.hx index 607d4f8..d388ff4 100644 --- a/src/refactor/discover/NameMap.hx +++ b/src/refactor/discover/NameMap.hx @@ -72,7 +72,6 @@ class NameMap { return []; } if (unused) { - // results = results.filter(i -> !i.edited); results.sort(Identifier.sortIdentifier); } return results; diff --git a/src/refactor/refactor/ExtractMethod.hx b/src/refactor/refactor/ExtractMethod.hx index bebbe47..6e2e1ef 100644 --- a/src/refactor/refactor/ExtractMethod.hx +++ b/src/refactor/refactor/ExtractMethod.hx @@ -633,7 +633,6 @@ class ExtractMethod { continue; } scopedVarUses.push(scopedVar); - trace("leaking " + varsValidAfterSelection.get(part)); } return scopedVarUses; } @@ -642,7 +641,9 @@ class ExtractMethod { var promises:Array> = []; for (identifier in neededIdentifiers) { final promise = findTypeOfIdentifier(context, identifier).then(function(typeHint):Promise { + #if debug trace("typehint resolved: " + identifier + " " + PrintHelper.typeHintToString(typeHint)); + #end return Promise.resolve(buildParameter(identifier, typeHint)); }); diff --git a/src/refactor/rename/RenameHelper.hx b/src/refactor/rename/RenameHelper.hx index 8f132ca..01997c2 100644 --- a/src/refactor/rename/RenameHelper.hx +++ b/src/refactor/rename/RenameHelper.hx @@ -184,9 +184,13 @@ class RenameHelper { changelist.addChange(use.pos.fileName, ReplaceText(context.what.toName, pos, NoFormat), use); } case LibType(_, _) | UnknownType(_): + #if debug trace("TODO " + typeHint.typeHintToString()); + #end case FunctionType(_, _) | StructType(_): + #if debug trace("TODO " + typeHint.typeHintToString()); + #end } }); } diff --git a/src/refactor/typing/TypingHelper.hx b/src/refactor/typing/TypingHelper.hx index 3267b38..3f00fc1 100644 --- a/src/refactor/typing/TypingHelper.hx +++ b/src/refactor/typing/TypingHelper.hx @@ -194,7 +194,9 @@ class TypingHelper { return Promise.resolve(null); } return findTypeWithTyper(context, containerType.file.name, pos).catchError(function(msg):Promise { - // trace("Haxe typer failed for " + name); + #if debug + trace("Haxe typer failed for " + '$name in ${containerType.file.name}@$pos'); + #end var allUses:Array = containerType.getIdentifiers(name); var candidate:Null = null; var fieldCandidate:Null = null; From 031091ba12a60b45d523f551adccc2cf3f1bf7d6 Mon Sep 17 00:00:00 2001 From: AlexHaxe Date: Sun, 8 Dec 2024 20:12:34 +0100 Subject: [PATCH 49/59] fixed type rename resolving type identifier --- src/refactor/PrintHelper.hx | 2 +- src/refactor/rename/RenameTypeName.hx | 6 ++++++ test/refactor/rename/RenameClassTest.hx | 11 +++++++++++ testcases/classes/pack/UseDocModule.hx | 4 ++++ 4 files changed, 22 insertions(+), 1 deletion(-) diff --git a/src/refactor/PrintHelper.hx b/src/refactor/PrintHelper.hx index 2cb4c1c..0080f6f 100644 --- a/src/refactor/PrintHelper.hx +++ b/src/refactor/PrintHelper.hx @@ -45,7 +45,7 @@ class PrintHelper { } } - public static function typeHintToString(hintType:TypeHintType):String { + public static function typeHintToString(hintType:Null):String { if (hintType == null) { return "null"; } diff --git a/src/refactor/rename/RenameTypeName.hx b/src/refactor/rename/RenameTypeName.hx index ad9b03f..60d0c79 100644 --- a/src/refactor/rename/RenameTypeName.hx +++ b/src/refactor/rename/RenameTypeName.hx @@ -54,6 +54,12 @@ class RenameTypeName { } case Global | SamePackage | Imported | StarImported: } + switch (use.type) { + case Abstract | Class | Enum | Interface | Typedef: + changelist.addChange(use.pos.fileName, ReplaceText(context.what.toName, use.pos, NoFormat), use); + continue; + default: + } final searchName:String = if (use.name.startsWith(identifier.name)) identifier.name; else identifier.defineType.fullModuleName; changes.push(TypingHelper.findTypeOfIdentifier(context, { name: searchName, diff --git a/test/refactor/rename/RenameClassTest.hx b/test/refactor/rename/RenameClassTest.hx index a465df8..2a8c3a7 100644 --- a/test/refactor/rename/RenameClassTest.hx +++ b/test/refactor/rename/RenameClassTest.hx @@ -366,4 +366,15 @@ class RenameClassTest extends RenameTestBase { ]; checkRename({fileName: "testcases/classes/Printer.hx", toName: "resultRenamed", pos: 833}, edits, async); } + + public function testRenameNotDocModule(async:Async) { + var edits:Array = [ + makeReplaceTestEdit("testcases/classes/DocModule.hx", "RenamedDocModule", 68, 80), + makeReplaceTestEdit("testcases/classes/ForceRenameCrash.hx", "RenamedDocModule", 104, 116), + makeReplaceTestEdit("testcases/classes/ForceRenameCrash.hx", "RenamedDocModule", 642, 654), + makeReplaceTestEdit("testcases/classes/pack/UseDocModule.hx", "RenamedDocModule", 95, 107), + makeReplaceTestEdit("testcases/classes/pack/UseDocModule.hx", "RenamedDocModule", 150, 162), + ]; + checkRename({fileName: "testcases/classes/DocModule.hx", toName: "RenamedDocModule", pos: 69}, edits, async); + } } diff --git a/testcases/classes/pack/UseDocModule.hx b/testcases/classes/pack/UseDocModule.hx index 25f6b61..755c831 100644 --- a/testcases/classes/pack/UseDocModule.hx +++ b/testcases/classes/pack/UseDocModule.hx @@ -6,4 +6,8 @@ class UseDocModule { function new() { new NotDocModule(); } + + function factory(name) { + return NotDocModule.new; + } } From 0cdbd5c339a54fb0635a825e557e39039dbe2e32 Mon Sep 17 00:00:00 2001 From: AlexHaxe Date: Sun, 8 Dec 2024 21:46:19 +0100 Subject: [PATCH 50/59] fixed matching type hints with one of them Null --- src/refactor/typing/TypingHelper.hx | 10 ++++++++++ test/refactor/rename/RenameEnumTest.hx | 5 +++++ testcases/enums/Main.hx | 19 +++++++++++++++++++ 3 files changed, 34 insertions(+) diff --git a/src/refactor/typing/TypingHelper.hx b/src/refactor/typing/TypingHelper.hx index 3f00fc1..b24e154 100644 --- a/src/refactor/typing/TypingHelper.hx +++ b/src/refactor/typing/TypingHelper.hx @@ -94,6 +94,16 @@ class TypingHelper { } } return true; + case [LibType(_, "Null", params1), _]: + if (params1.length != 1) { + return false; + } + return typeHintsEqual(params1[0], typeHint2); + case [_, LibType(_, "Null", params2)]: + if (params2.length != 1) { + return false; + } + return typeHintsEqual(typeHint1, params2[0]); case [ClasspathType(type1, params1), ClasspathType(type2, params2)]: if (type1.fullModuleName != type2.fullModuleName) { return false; diff --git a/test/refactor/rename/RenameEnumTest.hx b/test/refactor/rename/RenameEnumTest.hx index bd6213d..a1e7d6f 100644 --- a/test/refactor/rename/RenameEnumTest.hx +++ b/test/refactor/rename/RenameEnumTest.hx @@ -21,6 +21,7 @@ class RenameEnumTest extends RenameTestBase { makeReplaceTestEdit("testcases/enums/Main.hx", "IdentType", 2428, 2442), makeReplaceTestEdit("testcases/enums/Main.hx", "IdentType", 2481, 2495), makeReplaceTestEdit("testcases/enums/Main.hx", "IdentType", 2531, 2545), + makeReplaceTestEdit("testcases/enums/Main.hx", "IdentType", 3464, 3478), ]; checkRename({fileName: "testcases/enums/IdentifierType.hx", toName: "IdentType", pos: 30}, edits, async); } @@ -35,6 +36,7 @@ class RenameEnumTest extends RenameTestBase { makeReplaceTestEdit("testcases/enums/Main.hx", "LocalScopeVar", 1869, 1880), makeReplaceTestEdit("testcases/enums/Main.hx", "LocalScopeVar", 2316, 2327), makeReplaceTestEdit("testcases/enums/Main.hx", "LocalScopeVar", 2496, 2507), + makeReplaceTestEdit("testcases/enums/Main.hx", "LocalScopeVar", 3610, 3621), ]; checkRename({fileName: "testcases/enums/IdentifierType.hx", toName: "LocalScopeVar", pos: 77}, edits, async); } @@ -48,6 +50,8 @@ class RenameEnumTest extends RenameTestBase { makeReplaceTestEdit("testcases/enums/Main.hx", "FunctionCall", 1395, 1399), makeReplaceTestEdit("testcases/enums/Main.hx", "FunctionCall", 1702, 1706), makeReplaceTestEdit("testcases/enums/Main.hx", "FunctionCall", 2254, 2258), + makeReplaceTestEdit("testcases/enums/Main.hx", "FunctionCall", 3553, 3557), + makeReplaceTestEdit("testcases/enums/Main.hx", "FunctionCall", 3586, 3590), ]; checkRename({fileName: "testcases/enums/IdentifierType.hx", toName: "FunctionCall", pos: 55}, edits, async); } @@ -62,6 +66,7 @@ class RenameEnumTest extends RenameTestBase { makeReplaceTestEdit("testcases/enums/Main.hx", "GlobalScopeVar", 1904, 1916), makeReplaceTestEdit("testcases/enums/Main.hx", "GlobalScopeVar", 2395, 2407), makeReplaceTestEdit("testcases/enums/Main.hx", "GlobalScopeVar", 2546, 2558), + makeReplaceTestEdit("testcases/enums/Main.hx", "GlobalScopeVar", 3742, 3754), ]; checkRename({fileName: "testcases/enums/IdentifierType.hx", toName: "GlobalScopeVar", pos: 103}, edits, async); } diff --git a/testcases/enums/Main.hx b/testcases/enums/Main.hx index 6cc0972..8a1f2c8 100644 --- a/testcases/enums/Main.hx +++ b/testcases/enums/Main.hx @@ -146,4 +146,23 @@ class Main { } trace(SmokeDetector.Bedroom1.available()); } + + function testNull(type:Null) { + switch (type) { + case PackageName: + new PackageName(); + case Call | Access: + callOrAccess([Call, Access]); + case ScopedLocal(scopeEnd): + new ScopedLocal(scopeEnd); + case StringConst: + default: + } + + switch (type) { + case StringConst | ScopedGlobal(_): + callOrAccess([StringConst, Access]); + default: + } + } } From 6158060a1a32915c31c8df5b092fc218ec8f60a5 Mon Sep 17 00:00:00 2001 From: AlexHaxe Date: Sun, 8 Dec 2024 21:49:59 +0100 Subject: [PATCH 51/59] fixed name match for Null --- src/refactor/typing/TypingHelper.hx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/refactor/typing/TypingHelper.hx b/src/refactor/typing/TypingHelper.hx index b24e154..1e750c7 100644 --- a/src/refactor/typing/TypingHelper.hx +++ b/src/refactor/typing/TypingHelper.hx @@ -94,12 +94,12 @@ class TypingHelper { } } return true; - case [LibType(_, "Null", params1), _]: + case [LibType("Null", _, params1), _]: if (params1.length != 1) { return false; } return typeHintsEqual(params1[0], typeHint2); - case [_, LibType(_, "Null", params2)]: + case [_, LibType("Null", _, params2)]: if (params2.length != 1) { return false; } From b7717b4fa655f9b5d20e9a5952ecb72d7af39060 Mon Sep 17 00:00:00 2001 From: AlexHaxe Date: Mon, 9 Dec 2024 01:22:55 +0100 Subject: [PATCH 52/59] fixed identifier discovery for field access of arrays or after calls --- src/refactor/PrintHelper.hx | 2 +- src/refactor/discover/UsageCollector.hx | 2 +- src/refactor/rename/RenameHelper.hx | 50 ++++++++++++++------- src/refactor/typing/TypingHelper.hx | 14 ++---- test.hxml | 2 + test/refactor/refactor/RefactorClassTest.hx | 11 +++-- test/refactor/rename/RenameClassTest.hx | 48 +++++++++++++++----- testcases/classes/DocModule.hx | 4 ++ testcases/classes/pack/UseDocModule.hx | 9 ++++ 9 files changed, 98 insertions(+), 44 deletions(-) diff --git a/src/refactor/PrintHelper.hx b/src/refactor/PrintHelper.hx index 0080f6f..76f4012 100644 --- a/src/refactor/PrintHelper.hx +++ b/src/refactor/PrintHelper.hx @@ -55,7 +55,7 @@ class PrintHelper { final params = paramList.map(p -> typeHintToString(p)); return 'ClasspathType(${type.name.name}<${params.join(", ")}>)'; } - 'ClasspathType(${type.name.name}, <>)'; + 'ClasspathType(${type?.name.name}, <>)'; case LibType(name, fullName, paramList): if (paramList.length > 0) { final params = paramList.map(p -> typeHintToString(p)); diff --git a/src/refactor/discover/UsageCollector.hx b/src/refactor/discover/UsageCollector.hx index 149cd66..d914c6e 100644 --- a/src/refactor/discover/UsageCollector.hx +++ b/src/refactor/discover/UsageCollector.hx @@ -609,7 +609,7 @@ class UsageCollector { var prev:Null = parent.previousSibling; if (prev != null) { switch (prev.tok) { - case BkClose | PClose: + case BkOpen | BkClose | POpen | PClose: makeIdentifier(context, token, ArrayAccess(prev.pos.min), identifier); default: } diff --git a/src/refactor/rename/RenameHelper.hx b/src/refactor/rename/RenameHelper.hx index 01997c2..b36021c 100644 --- a/src/refactor/rename/RenameHelper.hx +++ b/src/refactor/rename/RenameHelper.hx @@ -162,6 +162,20 @@ class RenameHelper { posClosing:Int):Promise { var name:String = use.name; + function addChanges(type:Type) { + for (t in types) { + if (t != type) { + continue; + } + var pos:IdentifierPos = { + fileName: use.pos.fileName, + start: use.pos.start, + end: use.pos.end + }; + pos.end = pos.start + fromName.length; + changelist.addChange(use.pos.fileName, ReplaceText(context.what.toName, pos, NoFormat), use); + } + } var search:SearchTypeOf = { name: name, pos: posClosing, @@ -171,26 +185,30 @@ class RenameHelper { switch (typeHint) { case null: case ClasspathType(type, _): - for (t in types) { - if (t != type) { - continue; - } - var pos:IdentifierPos = { - fileName: use.pos.fileName, - start: use.pos.start, - end: use.pos.end - }; - pos.end = pos.start + fromName.length; - changelist.addChange(use.pos.fileName, ReplaceText(context.what.toName, pos, NoFormat), use); + addChanges(type); + case LibType("Null", _, [ClasspathType(type, _)]): + addChanges(type); + case LibType(_, _): + return; + case FunctionType(_, retVal): + if (retVal == null) { + return; } - case LibType(_, _) | UnknownType(_): - #if debug - trace("TODO " + typeHint.typeHintToString()); - #end - case FunctionType(_, _) | StructType(_): + switch (retVal) { + case ClasspathType(type, _): + addChanges(type); + case LibType("Null", _, [ClasspathType(type, _)]): + addChanges(type); + default: + #if debug + trace("TODO " + typeHint.typeHintToString()); + #end + } + case StructType(_): #if debug trace("TODO " + typeHint.typeHintToString()); #end + case UnknownType(_): } }); } diff --git a/src/refactor/typing/TypingHelper.hx b/src/refactor/typing/TypingHelper.hx index 1e750c7..41bc575 100644 --- a/src/refactor/typing/TypingHelper.hx +++ b/src/refactor/typing/TypingHelper.hx @@ -94,16 +94,10 @@ class TypingHelper { } } return true; - case [LibType("Null", _, params1), _]: - if (params1.length != 1) { - return false; - } - return typeHintsEqual(params1[0], typeHint2); - case [_, LibType("Null", _, params2)]: - if (params2.length != 1) { - return false; - } - return typeHintsEqual(typeHint1, params2[0]); + case [LibType("Null", _, [paramType1]), _]: + return typeHintsEqual(paramType1, typeHint2); + case [_, LibType("Null", _, [paramType2])]: + return typeHintsEqual(typeHint1, paramType2); case [ClasspathType(type1, params1), ClasspathType(type2, params2)]: if (type1.fullModuleName != type2.fullModuleName) { return false; diff --git a/test.hxml b/test.hxml index b348e92..55f83be 100644 --- a/test.hxml +++ b/test.hxml @@ -16,4 +16,6 @@ # -D dump-dependencies +-debug + --cmd node out/tests.js diff --git a/test/refactor/refactor/RefactorClassTest.hx b/test/refactor/refactor/RefactorClassTest.hx index 5440d59..918e205 100644 --- a/test/refactor/refactor/RefactorClassTest.hx +++ b/test/refactor/refactor/RefactorClassTest.hx @@ -67,7 +67,7 @@ class RefactorClassTest extends RefactorTestBase { function testExtractTypeNotDocModule(async:Async) { var edits:Array = [ - makeRemoveTestEdit("testcases/classes/DocModule.hx", 62, 110), + makeRemoveTestEdit("testcases/classes/DocModule.hx", 62, 169), makeReplaceTestEdit("testcases/classes/ForceRenameCrash.hx", "classes.NotDocModule", 86, 116), makeCreateTestEdit("testcases/classes/NotDocModule.hx"), makeInsertTestEdit("testcases/classes/NotDocModule.hx", @@ -76,9 +76,12 @@ class RefactorClassTest extends RefactorTestBase { + " */\n\n" + "package classes;\n\n" + "class NotDocModule {\n" - + " public function new() {}\n" - + "}", 0, - Format(0, false)), + + " public function new() {}\n\n" + + " public function doSomething() {\n" + + " trace(\"something\");\n" + + " }\n" + + "}", + 0, Format(0, false)), makeInsertTestEdit("testcases/classes/pack/UseDocModule.hx", "import classes.NotDocModule;\n", 23), ]; checkRefactor(RefactorExtractType, {fileName: "testcases/classes/DocModule.hx", posStart: 73, posEnd: 73}, edits, async); diff --git a/test/refactor/rename/RenameClassTest.hx b/test/refactor/rename/RenameClassTest.hx index 2a8c3a7..ff2bbe9 100644 --- a/test/refactor/rename/RenameClassTest.hx +++ b/test/refactor/rename/RenameClassTest.hx @@ -159,18 +159,23 @@ class RenameClassTest extends RenameTestBase { checkRename({fileName: "testcases/classes/MyIdentifier.hx", toName: "position", pos: 254}, edits, async); } - // TODO requires external typer since built-in will not resolve array access - // public function testRenameIdentifierName(async:Async) { - // var edits:Array = [ - // makeReplaceTestEdit("testcases/classes/ChildClass.hx", "id", 454, 458), - // makeReplaceTestEdit("testcases/classes/ChildClass.hx", "id", 473, 477), - // makeReplaceTestEdit("testcases/classes/ChildClass.hx", "id", 516, 520), - // makeReplaceTestEdit("testcases/classes/ChildClass.hx", "id", 707, 711), - // makeReplaceTestEdit("testcases/classes/ChildClass.hx", "id", 796, 800), - // makeReplaceTestEdit("testcases/classes/MyIdentifier.hx", "id", 228, 232), - // ]; - // checkRename({fileName: "testcases/classes/MyIdentifier.hx", toName: "id", pos: 229}, edits, async); - // } + public function testRenameIdentifierName(async:Async) { + var edits:Array = [ + makeReplaceTestEdit("testcases/classes/ChildClass.hx", "id", 454, 458), + makeReplaceTestEdit("testcases/classes/ChildClass.hx", "id", 473, 477), + makeReplaceTestEdit("testcases/classes/ChildClass.hx", "id", 516, 520), + makeReplaceTestEdit("testcases/classes/ChildClass.hx", "id", 707, 711), + makeReplaceTestEdit("testcases/classes/ChildClass.hx", "id", 796, 800), + makeReplaceTestEdit("testcases/classes/MyIdentifier.hx", "id", 228, 232), + ]; + addTypeHint("testcases/classes/ChildClass.hx", 448, ClasspathType(usageContext.typeList.getType("classes.MyIdentifier"), [])); + addTypeHint("testcases/classes/ChildClass.hx", 462, ClasspathType(usageContext.typeList.getType("classes.MyIdentifier"), [])); + addTypeHint("testcases/classes/ChildClass.hx", 510, ClasspathType(usageContext.typeList.getType("classes.MyIdentifier"), [])); + addTypeHint("testcases/classes/ChildClass.hx", 704, ClasspathType(usageContext.typeList.getType("classes.MyIdentifier"), [])); + addTypeHint("testcases/classes/ChildClass.hx", 794, ClasspathType(usageContext.typeList.getType("classes.MyIdentifier"), [])); + + checkRename({fileName: "testcases/classes/MyIdentifier.hx", toName: "id", pos: 229}, edits, async, true); + } public function testRenameJsonClassWidth(async:Async) { var edits:Array = [ @@ -374,7 +379,26 @@ class RenameClassTest extends RenameTestBase { makeReplaceTestEdit("testcases/classes/ForceRenameCrash.hx", "RenamedDocModule", 642, 654), makeReplaceTestEdit("testcases/classes/pack/UseDocModule.hx", "RenamedDocModule", 95, 107), makeReplaceTestEdit("testcases/classes/pack/UseDocModule.hx", "RenamedDocModule", 150, 162), + makeReplaceTestEdit("testcases/classes/pack/UseDocModule.hx", "RenamedDocModule", 209, 221), + makeReplaceTestEdit("testcases/classes/pack/UseDocModule.hx", "RenamedDocModule", 326, 338), ]; checkRename({fileName: "testcases/classes/DocModule.hx", toName: "RenamedDocModule", pos: 69}, edits, async); } + + public function testRenameNotDocModuleDoSomething(async:Async) { + var edits:Array = [ + makeReplaceTestEdit("testcases/classes/DocModule.hx", "doMore", 127, 138), + makeReplaceTestEdit("testcases/classes/pack/UseDocModule.hx", "doMore", 224, 235), + makeReplaceTestEdit("testcases/classes/pack/UseDocModule.hx", "doMore", 264, 275), + ]; + addTypeHint("testcases/classes/pack/UseDocModule.hx", 222, ClasspathType(usageContext.typeList.getType("classes.DocModule.NotDocModule"), [])); + addTypeHint("testcases/classes/pack/UseDocModule.hx", 261, + FunctionType([], ClasspathType(usageContext.typeList.getType("classes.DocModule.NotDocModule"), []))); + addTypeHint("testcases/classes/pack/UseChild.hx", 215, ClasspathType(usageContext.typeList.getType("classes.ChildClass"), [])); + addTypeHint("testcases/classes/pack/UseChild.hx", 332, ClasspathType(usageContext.typeList.getType("classes.ChildClass"), [])); + addTypeHint("testcases/classes/pack/UseChild.hx", 440, ClasspathType(usageContext.typeList.getType("classes.ChildClass"), [])); + addTypeHint("testcases/classes/pack/UseChild.hx", 628, ClasspathType(usageContext.typeList.getType("classes.ChildClass"), [])); + + checkRename({fileName: "testcases/classes/DocModule.hx", toName: "doMore", pos: 128}, edits, async, true); + } } diff --git a/testcases/classes/DocModule.hx b/testcases/classes/DocModule.hx index 2e2c70b..f6aae55 100644 --- a/testcases/classes/DocModule.hx +++ b/testcases/classes/DocModule.hx @@ -8,4 +8,8 @@ class DocModule {} class NotDocModule { public function new() {} + + public function doSomething() { + trace("something"); + } } diff --git a/testcases/classes/pack/UseDocModule.hx b/testcases/classes/pack/UseDocModule.hx index 755c831..303ec12 100644 --- a/testcases/classes/pack/UseDocModule.hx +++ b/testcases/classes/pack/UseDocModule.hx @@ -10,4 +10,13 @@ class UseDocModule { function factory(name) { return NotDocModule.new; } + + function doSommething(name) { + new NotDocModule().doSomething(); + this.getNotDocModule().doSomething(); + } + + function getNotDocModule() { + return new NotDocModule(); + } } From 2c0e1c290f84b8af946adbb40814b01ec4eb8dce Mon Sep 17 00:00:00 2001 From: AlexHaxe Date: Mon, 9 Dec 2024 22:54:35 +0100 Subject: [PATCH 53/59] fixed extract method from local function fixed discovery of switch / case capture variables --- src/refactor/Rename.hx | 2 +- src/refactor/discover/Identifier.hx | 4 +- src/refactor/discover/UsageCollector.hx | 86 +++++++++++++++---- .../refactor/ExtractConstructorParams.hx | 2 +- .../refactor/RefactorExtractMethodTest.hx | 25 ++++++ testcases/methods/SomeHelper.hx | 44 ++++++++++ 6 files changed, 141 insertions(+), 22 deletions(-) create mode 100644 testcases/methods/SomeHelper.hx diff --git a/src/refactor/Rename.hx b/src/refactor/Rename.hx index 8dc24e7..e0c2bac 100644 --- a/src/refactor/Rename.hx +++ b/src/refactor/Rename.hx @@ -94,7 +94,7 @@ class Rename { context.verboseLog('rename ${isStatic ? "static" : ""} field "${identifier.name}" to "${context.what.toName}"'); RenameField.refactorField(context, file, identifier, isStatic); case Method(isStatic): - context.verboseLog('rename ${isStatic ? "static" : ""} class method "${identifier.name}" to "${context.what.toName}"'); + context.verboseLog('rename ${isStatic ? "static " : ""}class method "${identifier.name}" to "${context.what.toName}"'); RenameField.refactorField(context, file, identifier, isStatic); case TypedParameter: Promise.reject(RefactorResult.Unsupported(identifier.toString()).printRenameResult()); diff --git a/src/refactor/discover/Identifier.hx b/src/refactor/discover/Identifier.hx index d6845d2..0e1257e 100644 --- a/src/refactor/discover/Identifier.hx +++ b/src/refactor/discover/Identifier.hx @@ -33,7 +33,9 @@ class Identifier { if (!uses.contains(identifier)) { uses.push(identifier); } - identifier.parent = this; + if (identifier.parent == null) { + identifier.parent = this; + } } public function containsPos(offset:Int):Bool { diff --git a/src/refactor/discover/UsageCollector.hx b/src/refactor/discover/UsageCollector.hx index d914c6e..d0559f7 100644 --- a/src/refactor/discover/UsageCollector.hx +++ b/src/refactor/discover/UsageCollector.hx @@ -402,6 +402,7 @@ class UsageCollector { } for (child in token.children) { switch (child.tok) { + case Comment(_) | CommentLine(_): case Const(CIdent(_)): var enumField:Identifier = makeIdentifier(context, child, EnumField([]), identifier); if (enumField == null) { @@ -416,6 +417,7 @@ class UsageCollector { } var params:Array = readParameter(context, enumField, pOpen, pOpen.pos.max); enumField.type = EnumField(params); + copyUsesToParent(identifier, enumField); case Sharp("if") | Sharp("elseif"): readExpression(context, identifier, child.getFirstChild()); for (index in 1...child.children.length - 1) { @@ -443,6 +445,7 @@ class UsageCollector { } for (child in assignToken.children) { switch (child.tok) { + case Comment(_) | CommentLine(_): case BrOpen: readAnonStructure(context, identifier, child); case Const(CIdent(_)): @@ -458,6 +461,7 @@ class UsageCollector { } for (child in token.children) { switch (child.tok) { + case Comment(_) | CommentLine(_): case Kwd(KwdExtends): makeIdentifier(context, child.getFirstChild(), Extends, identifier); case Kwd(KwdImplements): @@ -470,6 +474,7 @@ class UsageCollector { var nameToken:TokenTree = child.getFirstChild(); var method:Identifier = makeIdentifier(context, nameToken, Method(nameToken.access().firstOf(Kwd(KwdStatic)).exists()), identifier); readMethod(context, method, nameToken); + copyUsesToParent(identifier, method); case Kwd(KwdVar) | Kwd(KwdFinal): var nameToken:TokenTree = child.getFirstChild(); makeIdentifier(context, nameToken, FieldVar(nameToken.access().firstOf(Kwd(KwdStatic)).exists()), identifier); @@ -486,6 +491,7 @@ class UsageCollector { var block:Null = null; for (child in token.children) { switch (child.tok) { + case Comment(_) | CommentLine(_): case Kwd(KwdEnum): staticVars = true; case Const(CIdent("from")): @@ -509,12 +515,14 @@ class UsageCollector { var nameToken:TokenTree = child.getFirstChild(); var method:Identifier = makeIdentifier(context, nameToken, Method(nameToken.access().firstOf(Kwd(KwdStatic)).exists()), identifier); readMethod(context, method, nameToken); + copyUsesToParent(identifier, method); case Kwd(KwdVar) | Kwd(KwdFinal): var nameToken:TokenTree = child.getFirstChild(); var variable:Identifier = makeIdentifier(context, nameToken, FieldVar(staticVars), identifier); if (nameToken.access().firstChild().matches(POpen).exists()) { variable.type = Property; } + copyUsesToParent(identifier, variable); default: } }; @@ -538,6 +546,7 @@ class UsageCollector { var fullPos:Position = token.getPos(); for (child in token.children) { switch (child.tok) { + case Comment(_) | CommentLine(_): case POpen: readParameter(context, identifier, child, fullPos.max); ignore = false; @@ -563,10 +572,12 @@ class UsageCollector { var names:Array = []; for (child in token.children) { switch (child.tok) { + case Comment(_) | CommentLine(_): case Const(CIdent(s)): names.push(s); var field:Identifier = makeIdentifier(context, child, StructureField(names), identifier); readExpression(context, field, child.getFirstChild()); + copyUsesToParent(identifier, field); case BrClose: break; default: @@ -575,6 +586,18 @@ class UsageCollector { } } + function copyUsesToParent(identifier:Null, child:Identifier):Void { + if (identifier == null) { + return; + } + if (child.uses == null) { + return; + } + for (use in child.uses) { + identifier.addUse(use); + } + } + function readBlock(context:UsageContext, identifier:Identifier, token:TokenTree) { if (!token.hasChildren()) { return; @@ -584,6 +607,7 @@ class UsageCollector { var scopeEnd:Int = fullPos.max; for (child in token.children) { switch (child.tok) { + case Comment(_) | CommentLine(_): case Kwd(KwdVar): child = child.getFirstChild(); makeIdentifier(context, child, ScopedLocal(child.getPos().max, scopeEnd, Var), identifier); @@ -594,6 +618,7 @@ class UsageCollector { child = child.getFirstChild(); var method:Identifier = makeIdentifier(context, child, ScopedLocal(child.pos.min, scopeEnd, Var), identifier); readMethod(context, method, child); + copyUsesToParent(identifier, method); case Dot: case Semicolon: default: @@ -625,12 +650,14 @@ class UsageCollector { default: makeIdentifier(context, token, Access, identifier); } + if (token.hasChildren()) { for (child in token.children) { switch (child.tok) { + case Comment(_) | CommentLine(_): case Dot | Comma: case POpen: - readCallParams(context, child); + readCallParams(context, identifier, child); case Binop(OpAssign): default: readExpression(context, identifier, child); @@ -639,12 +666,12 @@ class UsageCollector { } } - function readCallParams(context:UsageContext, token:Null) { + function readCallParams(context:UsageContext, identifier:Identifier, token:Null) { if (!token.hasChildren()) { return; } for (child in token.children) { - readExpression(context, null, child); + readExpression(context, identifier, child); } } @@ -654,6 +681,7 @@ class UsageCollector { } switch (token.tok) { + case Comment(_) | CommentLine(_): case Const(CIdent(_)): readIdentifier(context, identifier, token); return; @@ -663,16 +691,19 @@ class UsageCollector { var token:TokenTree = token.getFirstChild(); var variable:Identifier = makeIdentifier(context, token, ScopedLocal(token.getPos().max, scopeEnd, Var), identifier); readVarInit(context, variable, token); + copyUsesToParent(identifier, variable); return; case Kwd(KwdFunction): var fullPos:Position = token.parent.getPos(); var scopeEnd:Int = fullPos.max; var child:TokenTree = token.getFirstChild(); - var method:Null = makeIdentifier(context, child, ScopedLocal(child.pos.min, scopeEnd, Var), identifier); - if (method == null) { - readMethod(context, identifier, token); - } else { - readMethod(context, method, child); + switch (child.tok) { + case Const(_): + var method:Null = makeIdentifier(context, child, ScopedLocal(child.pos.min, scopeEnd, Var), identifier); + readMethod(context, method, child); + copyUsesToParent(identifier, method); + default: + readMethod(context, identifier, token); } return; case Kwd(KwdThis): @@ -728,6 +759,7 @@ class UsageCollector { } for (child in brOpen.children) { switch (child.tok) { + case Comment(_) | CommentLine(_): case Kwd(KwdCase): readCase(context, switchIdent, child); case Kwd(KwdDefault): @@ -752,6 +784,7 @@ class UsageCollector { break; } } + copyUsesToParent(identifier, switchIdent); } function readFor(context:UsageContext, identifier:Identifier, token:TokenTree) { @@ -761,6 +794,7 @@ class UsageCollector { var skip:Bool = true; for (child in token.children) { switch (child.tok) { + case Comment(_) | CommentLine(_): case POpen: var fullPos:Position = token.getPos(); var scopeEnd:Int = fullPos.max; @@ -793,8 +827,12 @@ class UsageCollector { case Binop(OpArrow): ident = makeIdentifier(context, child.getFirstChild(), ScopedLocal(scopeStart, scopeEnd, ForLoop(loopIdentifiers)), identifier); loopIdentifiers.push(ident); + // case Kwd(KwdIn): + // readExpression(context, ident, child.getFirstChild()); + // copyUsesToParent(identifier, ident); default: readExpression(context, ident, child); + copyUsesToParent(identifier, ident); if (ident?.uses != null) { for (use in ident.uses) { switch (use.type) { @@ -818,6 +856,7 @@ class UsageCollector { for (child in token.children) { switch (child.tok) { + case Comment(_) | CommentLine(_): case Const(CIdent(_)): readCaseConst(context, identifier, child, scopeEnd); case Kwd(KwdVar): @@ -842,15 +881,22 @@ class UsageCollector { } var pOpen:Array = token.filterCallback(function(token:TokenTree, index:Int):FilterResult { return switch (token.tok) { - case POpen: - FoundSkipSubtree; + case POpen | BkOpen: + FoundGoDeeper; default: GoDeeper; } }); for (child in pOpen) { - readParameter(context, caseIdent, child, scopeEnd); + switch (child.tok) { + case BkOpen: + readCaseArray(context, caseIdent, child, scopeEnd); + case POpen: + readParameter(context, caseIdent, child, scopeEnd); + default: + } } + copyUsesToParent(identifier, caseIdent); } function readCaseArray(context:UsageContext, identifier:Identifier, token:TokenTree, scopeEnd:Int) { @@ -880,8 +926,10 @@ class UsageCollector { switch (valueChild.tok) { case Kwd(_) | Const(_) | Dollar(_) | Unop(_) | Binop(_): readExpression(context, field, valueChild); + copyUsesToParent(identifier, field); case BkOpen | BrOpen: readCaseStructure(context, field, valueChild, scopeEnd); + copyUsesToParent(identifier, field); continue; default: } @@ -1094,31 +1142,31 @@ class UsageCollector { for (child in token.children) { switch (child.tok) { case Const(CIdent(_)): - var identifier:Identifier = makeIdentifier(context, child, TypedefField(fields), identifier); + var ident:Identifier = makeIdentifier(context, child, TypedefField(fields), identifier); if (child.access() .firstOf(At) .firstOf(DblDot) .firstOf(Const(CIdent("optional"))) .exists()) { - fields.push(Optional(identifier)); + fields.push(Optional(ident)); } else { - fields.push(Required(identifier)); + fields.push(Required(ident)); } case Kwd(KwdVar) | Kwd(KwdFinal): var nameToken:TokenTree = child.getFirstChild(); - var identifier:Identifier = makeIdentifier(context, nameToken, TypedefField(fields), identifier); + var ident:Identifier = makeIdentifier(context, nameToken, TypedefField(fields), identifier); if (nameToken.access() .firstOf(At) .firstOf(DblDot) .firstOf(Const(CIdent("optional"))) .exists()) { - fields.push(Optional(identifier)); + fields.push(Optional(ident)); } else { - fields.push(Required(identifier)); + fields.push(Required(ident)); } case Question: - var identifier:Identifier = makeIdentifier(context, child.getFirstChild(), TypedefField(fields), identifier); - fields.push(Optional(identifier)); + var ident:Identifier = makeIdentifier(context, child.getFirstChild(), TypedefField(fields), identifier); + fields.push(Optional(ident)); default: } } diff --git a/src/refactor/refactor/ExtractConstructorParams.hx b/src/refactor/refactor/ExtractConstructorParams.hx index c2d61f9..cc69c61 100644 --- a/src/refactor/refactor/ExtractConstructorParams.hx +++ b/src/refactor/refactor/ExtractConstructorParams.hx @@ -137,7 +137,7 @@ class ExtractConstructorParams { static function findParametersWithNoFields(context:CanRefactorContext, identifierNew:Identifier):Array { final params:Array = []; var paramCandidates:Array = []; - if (identifierNew.uses == null) { + if (identifierNew?.uses == null) { return []; } diff --git a/test/refactor/refactor/RefactorExtractMethodTest.hx b/test/refactor/refactor/RefactorExtractMethodTest.hx index 7a21d1f..e36a342 100644 --- a/test/refactor/refactor/RefactorExtractMethodTest.hx +++ b/test/refactor/refactor/RefactorExtractMethodTest.hx @@ -926,4 +926,29 @@ class RefactorExtractMethodTest extends RefactorTestBase { addTypeHint("testcases/methods/TestEditDoc.hx", 235, LibType("String", "String", [])); checkRefactor(RefactorExtractMethod, {fileName: "testcases/methods/TestEditDoc.hx", posStart: 159, posEnd: 489}, edits, async); } + + function testSomeHelper(async:Async) { + var edits:Array = [ + makeReplaceTestEdit("testcases/methods/SomeHelper.hx", "arrayAccessExtract(types, type, use, fromName, changelist, context);", 808, 1155, + Format(5, true)), + makeInsertTestEdit("testcases/methods/SomeHelper.hx", + "static function arrayAccessExtract(types:Array, type:Type, use:Identifier, fromName:String, changelist:Changelist, context:RenameContext):Void {\n" + + "for (t in types) {\n" + + " if (t != type) {\n" + + " continue;\n" + + " }\n" + + " var pos:IdentifierPos = {\n" + + " fileName: use.pos.fileName,\n" + + " start: use.pos.start,\n" + + " end: use.pos.end\n" + + " };\n" + + " pos.end = pos.start + fromName.length;\n" + + " changelist.addChange(use.pos.fileName, ReplaceText(context.what.toName, pos, NoFormat), use);\n" + + " }\n" + + "}\n", + 1277, Format(1, false)), + ]; + addTypeHint("testcases/methods/SomeHelper.hx", 794, LibType("Type", "Type", [])); + checkRefactor(RefactorExtractMethod, {fileName: "testcases/methods/SomeHelper.hx", posStart: 806, posEnd: 1155}, edits, async); + } } diff --git a/testcases/methods/SomeHelper.hx b/testcases/methods/SomeHelper.hx new file mode 100644 index 0000000..15548e5 --- /dev/null +++ b/testcases/methods/SomeHelper.hx @@ -0,0 +1,44 @@ +package methods; + +import js.lib.Promise; +import refactor.discover.Identifier; +import refactor.discover.IdentifierPos; +import refactor.discover.Type; +import refactor.edits.Changelist; +import refactor.rename.RenameContext; +import refactor.typing.TypeHintType; +import refactor.typing.TypingHelper; + +class SomeHelper { + public static function arrayAccess(context:RenameContext, changelist:Changelist, use:Identifier, fromName:String, types:Array, + posClosing:Int):Promise { + var search:SearchTypeOf = { + name: "search", + pos: posClosing, + defineType: null + }; + return TypingHelper.findTypeOfIdentifier(context, search).then(function(typeHint:TypeHintType) { + switch (typeHint) { + case null: + case ClasspathType(type, _): + case LibType("Null", _, [ClasspathType(type, _)]): + for (t in types) { + if (t != type) { + continue; + } + var pos:IdentifierPos = { + fileName: use.pos.fileName, + start: use.pos.start, + end: use.pos.end + }; + pos.end = pos.start + fromName.length; + changelist.addChange(use.pos.fileName, ReplaceText(context.what.toName, pos, NoFormat), use); + } + case LibType(_, _): + case FunctionType(_, retVal): + case StructType(_): + case UnknownType(_): + } + }); + } +} From 8de3607c938e632d296ace6b522a563857b46eb6 Mon Sep 17 00:00:00 2001 From: AlexHaxe Date: Tue, 10 Dec 2024 00:43:34 +0100 Subject: [PATCH 54/59] fixed discovery of typed parameters fixed discovery of type checks --- src/refactor/discover/UsageCollector.hx | 8 ++++++++ test/refactor/rename/RenameClassTest.hx | 3 +++ testcases/classes/pack/UseChild.hx | 10 ++++++++++ 3 files changed, 21 insertions(+) diff --git a/src/refactor/discover/UsageCollector.hx b/src/refactor/discover/UsageCollector.hx index d0559f7..b822970 100644 --- a/src/refactor/discover/UsageCollector.hx +++ b/src/refactor/discover/UsageCollector.hx @@ -974,6 +974,8 @@ class UsageCollector { return null; } switch (nameToken.tok) { + case Const(CIdent("is")): + return null; case Kwd(KwdNew) | Kwd(KwdThis) | Kwd(KwdNull) | Const(CIdent(_)): default: return null; @@ -994,6 +996,8 @@ class UsageCollector { } for (child in parentPart.children) { switch (child.tok) { + case Const(CIdent("is")): + return; case Kwd(KwdNew) | Kwd(KwdThis) | Kwd(KwdNull) | Const(_): pack.push(child.toString()); case Dot: @@ -1108,8 +1112,12 @@ class UsageCollector { makeIdentifier(context, child, TypedParameter, identifier); case POpen: readParameter(context, identifier, child, token.getPos().max); + case BrOpen: + readBlock(context, identifier, child); case Binop(OpGt): break; + case DblDot: + readTypeHint(context, identifier, child, TypeHint); default: } } diff --git a/test/refactor/rename/RenameClassTest.hx b/test/refactor/rename/RenameClassTest.hx index ff2bbe9..f209a6e 100644 --- a/test/refactor/rename/RenameClassTest.hx +++ b/test/refactor/rename/RenameClassTest.hx @@ -78,6 +78,9 @@ class RenameClassTest extends RenameTestBase { makeReplaceTestEdit("testcases/classes/pack/UseChild.hx", "ItemClass", 412, 422), makeReplaceTestEdit("testcases/classes/pack/UseChild.hx", "ItemClass", 499, 509), makeReplaceTestEdit("testcases/classes/pack/UseChild.hx", "ItemClass", 612, 622), + makeReplaceTestEdit("testcases/classes/pack/UseChild.hx", "ItemClass", 978, 988), + makeReplaceTestEdit("testcases/classes/pack/UseChild.hx", "ItemClass", 1039, 1049), + makeReplaceTestEdit("testcases/classes/pack/UseChild.hx", "ItemClass", 1081, 1091), ]; checkRename({fileName: "testcases/classes/ChildClass.hx", toName: "ItemClass", pos: 28}, edits, async); } diff --git a/testcases/classes/pack/UseChild.hx b/testcases/classes/pack/UseChild.hx index ff4ccc0..16e7551 100644 --- a/testcases/classes/pack/UseChild.hx +++ b/testcases/classes/pack/UseChild.hx @@ -53,4 +53,14 @@ class UseChild { null; } } + + function typeCheckChildClass(child:Any) { + if ((child is ChildClass)) { + trace("yes"); + } + } +} + +class TypedChild { + var memebers:Array<{sprite:ChildClass, originalX:Float, originalY:Float}> = null; } From d069790b47a68b1adf27fa55e9475ca0f0cb02f0 Mon Sep 17 00:00:00 2001 From: AlexHaxe Date: Thu, 19 Dec 2024 01:46:46 +0100 Subject: [PATCH 55/59] changed order of type resolution to built-in then external typer reworked identifier discovery reworked typhint handling --- src/refactor/PrintHelper.hx | 6 +- src/refactor/Refactoring.hx | 17 +- src/refactor/Rename.hx | 6 +- src/refactor/discover/File.hx | 5 + src/refactor/discover/Identifier.hx | 22 + src/refactor/discover/IdentifierType.hx | 5 +- src/refactor/discover/TypeHintFromTree.hx | 325 ++++++++ src/refactor/discover/UsageCollector.hx | 691 ++++++++++++++---- .../refactor/ExtractConstructorParams.hx | 4 +- src/refactor/refactor/ExtractInterface.hx | 6 +- src/refactor/refactor/ExtractMethod.hx | 75 +- src/refactor/refactor/ExtractType.hx | 4 +- src/refactor/refactor/RefactorHelper.hx | 105 +++ src/refactor/refactor/RefactorType.hx | 1 + src/refactor/refactor/RewriteVarsToFinals.hx | 4 +- .../refactor/RewriteWrapWithTryCatch.hx | 111 +++ .../refactor/extractmethod/CodeGenBase.hx | 2 +- .../extractmethod/CodeGenEmptyReturn.hx | 9 +- .../extractmethod/CodeGenLocalFunction.hx | 2 +- src/refactor/rename/RenameAnonStructField.hx | 4 +- src/refactor/rename/RenameEnumField.hx | 4 +- src/refactor/rename/RenameField.hx | 2 +- src/refactor/rename/RenameHelper.hx | 29 +- src/refactor/rename/RenamePackage.hx | 2 +- src/refactor/rename/RenameTypeName.hx | 4 +- src/refactor/typing/TypeHintType.hx | 1 + src/refactor/typing/TypingHelper.hx | 433 +++++++---- test/TestMain.hx | 4 + test/import.hx | 1 + test/refactor/refactor/RefactorClassTest.hx | 64 +- .../RefactorExtractConstructorParams.hx | 8 +- .../refactor/RefactorExtractMethodTest.hx | 30 +- test/refactor/refactor/RefactorTestBase.hx | 8 +- test/refactor/refactor/RefactorTypedefTest.hx | 2 +- test/refactor/rename/RenameClassTest.hx | 35 +- test/refactor/rename/RenameTestBase.hx | 22 +- test/refactor/rename/RenameTypedefTest.hx | 2 +- test/refactor/typing/TypeHintFromTreeTest.hx | 51 ++ test/refactor/typing/TypingTest.hx | 95 +++ testcases/classes/StaticUsing.hx | 7 +- testcases/methods/SomeHelper.hx | 3 +- testcases/typehints/Main.hx | 18 + 42 files changed, 1777 insertions(+), 452 deletions(-) create mode 100644 src/refactor/discover/TypeHintFromTree.hx create mode 100644 src/refactor/refactor/RewriteWrapWithTryCatch.hx create mode 100644 test/refactor/typing/TypeHintFromTreeTest.hx create mode 100644 test/refactor/typing/TypingTest.hx create mode 100644 testcases/typehints/Main.hx diff --git a/src/refactor/PrintHelper.hx b/src/refactor/PrintHelper.hx index 76f4012..d5a0157 100644 --- a/src/refactor/PrintHelper.hx +++ b/src/refactor/PrintHelper.hx @@ -71,6 +71,8 @@ class PrintHelper { case StructType(fieldTypes): final fields = fieldTypes.map(f -> typeHintToString(f)); 'StructType({${fields.join(";")}})'; + case NamedType(name, namedHint): + 'NamedType($name, ${typeHintToString(namedHint)})'; case UnknownType(name): 'UnknownType($name)'; } @@ -101,7 +103,9 @@ class PrintHelper { return '(${args.join(", ")}) -> ${printTypeHint(retVal)}'; case StructType(fieldTypes): final fields = fieldTypes.map(f -> printTypeHint(f)); - '{${fields.join(";")}}'; + '{${fields.join(", ")}}'; + case NamedType(name, namedHint): + '$name:${printTypeHint(namedHint)}'; case UnknownType(name): '$name'; } diff --git a/src/refactor/Refactoring.hx b/src/refactor/Refactoring.hx index ca8ca34..36a132b 100644 --- a/src/refactor/Refactoring.hx +++ b/src/refactor/Refactoring.hx @@ -9,20 +9,23 @@ import refactor.refactor.ExtractType; import refactor.refactor.RefactorContext; import refactor.refactor.RefactorType; import refactor.refactor.RewriteVarsToFinals; +import refactor.refactor.RewriteWrapWithTryCatch; class Refactoring { - public static function canRefactor(refactorType:RefactorType, context:CanRefactorContext):CanRefactorResult { + public static function canRefactor(refactorType:RefactorType, context:CanRefactorContext, isRangeSameScope:Bool):CanRefactorResult { switch (refactorType) { case RefactorExtractInterface: - return ExtractInterface.canRefactor(context); + return ExtractInterface.canRefactor(context, isRangeSameScope); case RefactorExtractMethod: - return ExtractMethod.canRefactor(context); + return ExtractMethod.canRefactor(context, isRangeSameScope); case RefactorExtractType: - return ExtractType.canRefactor(context); + return ExtractType.canRefactor(context, isRangeSameScope); case RefactorExtractConstructorParams(asFinal): - return ExtractConstructorParams.canRefactor(context, asFinal); + return ExtractConstructorParams.canRefactor(context, isRangeSameScope, asFinal); case RefactorRewriteVarsToFinals(toFinals): - return RewriteVarsToFinals.canRefactor(context, toFinals); + return RewriteVarsToFinals.canRefactor(context, isRangeSameScope, toFinals); + case RefactorRewriteWrapWithTryCatch: + return RewriteWrapWithTryCatch.canRefactor(context, isRangeSameScope); } return null; } @@ -39,6 +42,8 @@ class Refactoring { return ExtractConstructorParams.doRefactor(context, asFinal); case RefactorRewriteVarsToFinals(toFinals): return RewriteVarsToFinals.doRefactor(context, toFinals); + case RefactorRewriteWrapWithTryCatch: + return RewriteWrapWithTryCatch.doRefactor(context); } return Promise.reject("no refactor type selected"); } diff --git a/src/refactor/Rename.hx b/src/refactor/Rename.hx index e0c2bac..2797a8f 100644 --- a/src/refactor/Rename.hx +++ b/src/refactor/Rename.hx @@ -31,7 +31,7 @@ class Rename { ScopedLocal(_, _): Promise.resolve({name: identifier.name, pos: identifier.pos}); case ImportModul | UsingModul | Extends | Implements | AbstractOver | AbstractFrom | AbstractTo | TypeHint | StringConst | TypedParameter | - TypedefBase | Call(true) | CaseLabel(_): + TypedefBase | Call(true) | CaseLabel(_) | Meta: Promise.reject(RefactorResult.Unsupported(identifier.toString()).printRenameResult()); case Call(false) | Access | ArrayAccess(_) | ForIterator: var candidate:Null = findActualWhat(context, file, identifier); @@ -85,13 +85,13 @@ class Rename { case ModuleLevelStaticVar | ModuleLevelStaticMethod: context.verboseLog('rename module level static "${identifier.name}" to "${context.what.toName}"'); RenameModuleLevelStatic.refactorModuleLevelStatic(context, file, identifier); - case Extends | Implements | AbstractOver | AbstractFrom | AbstractTo | TypeHint | StringConst: + case Extends | Implements | AbstractOver | AbstractFrom | AbstractTo | TypeHint | StringConst | Meta: Promise.reject(RefactorResult.Unsupported(identifier.toString()).printRenameResult()); case Property: context.verboseLog('rename property "${identifier.name}" to "${context.what.toName}"'); RenameField.refactorField(context, file, identifier, false); case FieldVar(isStatic): - context.verboseLog('rename ${isStatic ? "static" : ""} field "${identifier.name}" to "${context.what.toName}"'); + context.verboseLog('rename ${isStatic ? "static " : ""}field "${identifier.name}" to "${context.what.toName}"'); RenameField.refactorField(context, file, identifier, isStatic); case Method(isStatic): context.verboseLog('rename ${isStatic ? "static " : ""}class method "${identifier.name}" to "${context.what.toName}"'); diff --git a/src/refactor/discover/File.hx b/src/refactor/discover/File.hx index c549016..1160741 100644 --- a/src/refactor/discover/File.hx +++ b/src/refactor/discover/File.hx @@ -71,6 +71,10 @@ class File { } } } + final parentPackage = '$packName.'; + if (getPackage().startsWith(parentPackage)) { + return ParentPackage; + } if (importHxFile == null) { if (packName == getPackage()) { @@ -169,6 +173,7 @@ enum ImportStatus { None; Global; SamePackage; + ParentPackage; Imported; ImportedWithAlias(alias:String); StarImported; diff --git a/src/refactor/discover/Identifier.hx b/src/refactor/discover/Identifier.hx index 0e1257e..dbbee30 100644 --- a/src/refactor/discover/Identifier.hx +++ b/src/refactor/discover/Identifier.hx @@ -9,6 +9,9 @@ class Identifier { public var parent:Null; public var defineType:Null; + var typeHint:Null; + var typeHintResolved:Bool; + public function new(type:IdentifierType, name:String, pos:IdentifierPos, nameMap:NameMap, file:File, defineType:Null) { this.type = type; this.name = name; @@ -16,6 +19,8 @@ class Identifier { this.file = file; this.defineType = defineType; parent = null; + typeHint = null; + typeHintResolved = false; if (defineType != null) { defineType.addIdentifier(this); @@ -89,6 +94,11 @@ class Identifier { return 0; } + public function setTypeHint(typeHint:TypeHintType) { + this.typeHint = typeHint; + typeHintResolved = false; + } + public function getTypeHint():Null { if (uses == null) { return null; @@ -103,6 +113,18 @@ class Identifier { return null; } + public function getTypeHintNew(types:TypeList):Null { + if (typeHint == null) { + return null; + } + if (typeHintResolved) { + return typeHint; + } + typeHint = TypeHintFromTree.resolveTypeHint(typeHint, types, file); + typeHintResolved = true; + return typeHint; + } + public function toString():String { return '$name ${pos.fileName}@${pos.start}-${pos.end} (${type.typeToString()})'; } diff --git a/src/refactor/discover/IdentifierType.hx b/src/refactor/discover/IdentifierType.hx index 6318673..0b0796e 100644 --- a/src/refactor/discover/IdentifierType.hx +++ b/src/refactor/discover/IdentifierType.hx @@ -33,17 +33,18 @@ enum IdentifierType { EnumField(params:Array); CaseLabel(switchIdentifier:Identifier); Call(isNew:Bool); - ArrayAccess(posClosing:Int); + ArrayAccess(arrayIdentifier:Identifier); Access; ForIterator; ScopedLocal(scopeStart:Int, scopeEnd:Int, scopeType:ScopedLocalType); StringConst; + Meta; } enum ScopedLocalType { Parameter(params:Array); Var; - CaseCapture; + CaseCapture(origin:Null, index:Int); ForLoop(loopIdentifiers:Array); } diff --git a/src/refactor/discover/TypeHintFromTree.hx b/src/refactor/discover/TypeHintFromTree.hx new file mode 100644 index 0000000..ea0be45 --- /dev/null +++ b/src/refactor/discover/TypeHintFromTree.hx @@ -0,0 +1,325 @@ +package refactor.discover; + +class TypeHintFromTree { + public static function makeTypeHint(token:Null):TypeHintType { + if (token == null) { + return null; + } + switch (token.tok) { + case Kwd(KwdNull) | Const(CIdent(_)) | Dollar(_): + final arrowChild = findArrow(token); + if (arrowChild != null) { + final arrowType = TokenTreeCheckUtils.getArrowType(arrowChild); + switch (arrowType) { + case ArrowFunction: + return null; + case OldFunctionType: + return makeOldFunctionTypeHint(token); + case NewFunctionType: + return makeFunctionTypeHint(token); + } + } + return makeLibTypeHint(token); + case BrOpen: + return makeStructTypeHint(token); + case POpen: + return makeFunctionTypeHint(token); + default: + return null; + } + } + + static function findArrow(token:TokenTree):Null { + while (token != null) { + switch (token.tok) { + case Const(CIdent(_)) | Kwd(KwdNull) | Kwd(KwdMacro) | Dollar(_): + token = token.getFirstChild(); + case Dot: + token = token.getFirstChild(); + case Binop(OpLt): + token = token.nextSibling; + case DblDot: + token = token.getFirstChild(); + case Arrow: + return token; + case Comment(_) | CommentLine(_): + return null; + case Comma, Semicolon: + return null; + case Binop(OpAssign) | Binop(OpAssignOp(_)): + return null; + default: + return null; + } + } + + return null; + } + + static function makeLibTypeHint(token:Null):TypeHintType { + if (token == null) { + return null; + } + + var parts:Array = []; + while (token != null) { + switch (token.tok) { + case Const(CIdent(name)): + parts.push(name); + case Dollar(name): + parts.push(name); + case Kwd(KwdNull): + parts.push("Null"); + case Kwd(KwdMacro): + parts.push("macro"); + case Comma: + break; + case Semicolon: + break; + default: + } + token = token.getFirstChild(); + if (token == null) { + break; + } + switch (token.tok) { + case Dot: + token = token.getFirstChild(); + continue; + case Binop(OpLt): + break; + case Comma | Semicolon: + break; + case Arrow: + break; + case DblDot: + break; + case Binop(OpAssign) | Binop(OpAssignOp(_)): + break; + default: + } + } + if (parts.length <= 0) { + return null; + } + final fullName = parts.join("."); + final name = fullName; // parts.pop(); + + if (token == null) { + return LibType(name, fullName, []); + } + return switch (token.tok) { + case Binop(OpLt): + LibType(name, fullName, makeTypeHintParams(token)); + case DblDot: + NamedType(name, makeTypeHint(token.getFirstChild())); + default: + LibType(name, fullName, []); + } + } + + static function makeTypeHintParams(token:Null):Array { + var params:Array = []; + for (child in token.children) { + switch (child.tok) { + case Const(CIdent(paramName)) | Dollar(paramName): + final firstChild = child.getFirstChild(); + if (firstChild == null) { + params.push(LibType(paramName, paramName, [])); + continue; + } + switch (firstChild.tok) { + case Dot: + final accessTypeHint = makeTypeHint(child); + params.push(accessTypeHint); + continue; + case Binop(OpLt): + params.push(LibType(paramName, paramName, makeTypeHintParams(firstChild))); + default: + } + case Comma: + case Binop(OpGt): + default: + } + } + return params; + } + + static function makeStructTypeHint(token:Null):TypeHintType { + if (token == null) { + return null; + } + if (!token.hasChildren()) { + return null; + } + var fields:Array = []; + for (child in token.children) { + switch (child.tok) { + case Const(CIdent(name)) | Dollar(name): + final fieldType = makeTypeHint(child.access().firstOf(DblDot).firstChild().token); + fields.push(NamedType(name, fieldType)); + case Kwd(KwdVar): + var nameToken = child.getFirstChild(); + if (nameToken == null) { + continue; + } + final name = switch (nameToken.tok) { + case Kwd(KwdNull): + "NUll"; + case Const(CIdent(name)): + name; + default: + '$nameToken'; + } + final fieldType = makeTypeHint(nameToken.access().firstOf(DblDot).firstChild().token); + fields.push(NamedType(name, fieldType)); + case BrClose: + break; + default: + } + } + return StructType(fields); + } + + static function makeOldFunctionTypeHint(token:Null):TypeHintType { + if (token == null) { + return null; + } + if (!token.hasChildren()) { + return null; + } + final args:Array = []; + var pack:Array = []; + while (token != null) { + if (token.matches(Arrow)) { + token = token.getFirstChild(); + continue; + } + final typeHint = makeLibTypeHint(token); + if (typeHint != null) { + args.push(typeHint); + } + while (token != null) { + if (!token.hasChildren()) { + token = null; + break; + } + var newToken:TokenTree = null; + for (child in token.children) { + switch (child.tok) { + case Const(CIdent(_)) | Dot: + newToken = child; + break; + case Binop(OpLt): + case Arrow: + newToken = child; + break; + case Semicolon: + break; + default: + } + } + token = newToken; + + if (token == null) { + break; + } + if (token.matches(Arrow)) { + break; + } + } + } + if (args.length < 2) { + return null; + } + final retVal = args.pop(); + return FunctionType(args, retVal); + } + + static function makeFunctionTypeHint(token:Null):TypeHintType { + if (token == null) { + return null; + } + if (!token.hasChildren()) { + return null; + } + final args:Array = []; + for (child in token.children) { + switch (child.tok) { + case Const(CIdent(_)): + args.push(makeTypeHint(child)); + case PClose: + break; + default: + } + } + var retToken = token.access().firstOf(Arrow).firstChild().token; + return FunctionType(args, makeTypeHint(retToken)); + } + + public static function resolveTypeHint(unresolvedTypeHint:TypeHintType, types:TypeList, file:File):TypeHintType { + return switch (unresolvedTypeHint) { + case ClasspathType(type, typeParams): + final newParams:Array = []; + for (param in typeParams) { + newParams.push(resolveTypeHint(param, types, file)); + } + ClasspathType(type, newParams); + case LibType(name, fullName, typeParams): + final newParams:Array = []; + for (param in typeParams) { + newParams.push(resolveTypeHint(param, types, file)); + } + final type = findTypeFromImports(fullName, types, file); + return if (type == null) { + LibType(name, fullName, newParams); + } else { + ClasspathType(type, newParams); + } + case FunctionType(args, retVal): + final newArgs:Array = []; + for (arg in args) { + newArgs.push(resolveTypeHint(arg, types, file)); + } + FunctionType(newArgs, resolveTypeHint(retVal, types, file)); + case StructType(fields): + final newFields:Array = []; + for (field in fields) { + newFields.push(resolveTypeHint(field, types, file)); + } + StructType(newFields); + case NamedType(name, typeHint): + NamedType(name, resolveTypeHint(typeHint, types, file)); + case UnknownType(name): + unresolvedTypeHint; + } + } + + public static function findTypeFromImports(fullName:String, types:TypeList, file:File):Null { + var type = types.getType(fullName); + if (type != null) { + return type; + } + + var typeCandidates = types.findTypeName(fullName); + for (importItem in file.importList) { + if (importItem.alias == null) { + continue; + } + if (importItem.alias.name == fullName) { + final type = types.getType(importItem.moduleName.name); + if (type != null) { + typeCandidates.push(type); + } + } + } + for (candidateType in typeCandidates) { + switch (file.importsModule(candidateType.file.getPackage(), candidateType.file.getMainModulName(), candidateType.name.name)) { + case None: + case Global | ParentPackage | SamePackage | Imported | StarImported | ImportedWithAlias(_): + return candidateType; + } + } + return null; + } +} diff --git a/src/refactor/discover/UsageCollector.hx b/src/refactor/discover/UsageCollector.hx index b822970..8a7040b 100644 --- a/src/refactor/discover/UsageCollector.hx +++ b/src/refactor/discover/UsageCollector.hx @@ -1,6 +1,7 @@ package refactor.discover; import haxe.Exception; +import haxe.PosInfos; import haxe.io.Path; import byte.ByteData; import haxeparser.HaxeLexer; @@ -44,6 +45,9 @@ class UsageCollector { } try { var file:File = new File(context.fileName); + #if debug + trace("[RefactorCache] parsing " + context.fileName); + #end context.file = file; context.type = null; var packageName:Null = readPackageName(root, context); @@ -162,7 +166,7 @@ class UsageCollector { token = token.getFirstChild(); var pos:IdentifierPos = makePosition(context.fileName, token); - while (true) { + while (token != null) { switch (token.tok) { case Const(CIdent("as")) | Binop(OpIn): alias = makeIdentifier(context, token.getFirstChild(), ImportAlias, null); @@ -236,6 +240,9 @@ class UsageCollector { var newType:Type = new Type(context.file); context.type = newType; var identifier:Identifier = makeIdentifier(context, nameToken, type, null); + if (identifier == null) { + return null; + } newType.name = identifier; context.typeList.addType(newType); @@ -243,11 +250,11 @@ class UsageCollector { case Abstract: addAbstractFields(context, identifier, nameToken); case Class: - addFields(context, identifier, nameToken); + addClassInterface(context, identifier, nameToken); case Enum: readEnum(context, identifier, nameToken.getFirstChild()); case Interface: - addFields(context, identifier, nameToken); + addClassInterface(context, identifier, nameToken); if (identifier.uses != null) { for (use in identifier.uses) { switch (use.type) { @@ -267,6 +274,7 @@ class UsageCollector { readVarInit(context, identifier, nameToken); case ModuleLevelStaticMethod: readMethod(context, identifier, nameToken); + default: } readStrings(context, identifier, nameToken); @@ -344,7 +352,7 @@ class UsageCollector { function findParentIdentifier(context:UsageContext, stringToken:TokenTree):Null { var parent = stringToken.parent; - while (true) { + while (parent != null) { switch (parent.tok) { case Kwd(KwdFunction) | Kwd(KwdVar) | Kwd(KwdFinal) | Kwd(KwdAbstract) | Kwd(KwdClass) | Kwd(KwdEnum) | Kwd(KwdInterface) | Kwd(KwdTypedef): var child = parent.getFirstChild(); @@ -385,6 +393,7 @@ class UsageCollector { tokens.push(t); t = lexer.token(haxeparser.HaxeLexer.tok); } + tokentree.TokenStream.MODE = Relaxed; root = TokenTreeBuilder.buildTokenTree(tokens, content, ExpressionLevel); readExpression(context, identifier, root); } catch (e:ParserError) { @@ -449,13 +458,26 @@ class UsageCollector { case BrOpen: readAnonStructure(context, identifier, child); case Const(CIdent(_)): - makeIdentifier(context, child, TypedefBase, identifier); + final ident = makeIdentifier(context, child, TypedefBase, identifier); + final afterIdentifier = findIdentifierChild(child); + if (afterIdentifier != null) { + switch (afterIdentifier.tok) { + case Semicolon: + case Binop(OpLt): + addTypeParameter(context, ident, afterIdentifier); + case Binop(OpAnd): + readExpression(context, identifier, afterIdentifier); + case Arrow: + readTypeHint(context, ident, afterIdentifier, TypeHint); + default: + } + } default: } } } - function addFields(context:UsageContext, identifier:Identifier, token:Null) { + function addClassInterface(context:UsageContext, identifier:Identifier, token:Null) { if (token == null || !token.hasChildren()) { return; } @@ -468,6 +490,37 @@ class UsageCollector { makeIdentifier(context, child.getFirstChild(), Implements, identifier); case BrOpen: addFields(context, identifier, child); + case Binop(OpLt): + addTypeParameter(context, identifier, child); + case At: + readMetadata(context, identifier, child); + case Kwd(KwdPrivate) | Kwd(KwdExtern) | Kwd(KwdAbstract): + case Sharp(_): + readExpression(context, identifier, child); + default: + } + } + } + + function addFields(context:UsageContext, identifier:Identifier, token:Null) { + if (token == null || !token.hasChildren()) { + return; + } + var first = true; + for (child in token.children) { + if (first) { + first = false; + final parent = child.parent; + if (parent != null) { + switch (parent.tok) { + case Sharp("if") | Sharp("elseif"): + continue; + default: + } + } + } + switch (child.tok) { + case Comment(_) | CommentLine(_): case Sharp(_): addFields(context, identifier, child); case Kwd(KwdFunction): @@ -476,11 +529,17 @@ class UsageCollector { readMethod(context, method, nameToken); copyUsesToParent(identifier, method); case Kwd(KwdVar) | Kwd(KwdFinal): - var nameToken:TokenTree = child.getFirstChild(); - makeIdentifier(context, nameToken, FieldVar(nameToken.access().firstOf(Kwd(KwdStatic)).exists()), identifier); + readVar(context, identifier, child, FieldVar(child.access().firstChild().firstOf(Kwd(KwdStatic)).exists())); + case Kwd(KwdPublic) | Kwd(KwdPrivate) | Kwd(KwdInline) | Kwd(KwdStatic) | Kwd(KwdExtern) | Kwd(KwdOverride) | Kwd(KwdMacro) | Kwd(KwdAbstract): + case BrClose: + case Semicolon: + case Binop(_): + readExpression(context, identifier, child); + case At: + readMetadata(context, identifier, child); default: } - }; + } } function addAbstractFields(context:UsageContext, identifier:Identifier, token:Null) { @@ -517,25 +576,77 @@ class UsageCollector { readMethod(context, method, nameToken); copyUsesToParent(identifier, method); case Kwd(KwdVar) | Kwd(KwdFinal): - var nameToken:TokenTree = child.getFirstChild(); - var variable:Identifier = makeIdentifier(context, nameToken, FieldVar(staticVars), identifier); - if (nameToken.access().firstChild().matches(POpen).exists()) { - variable.type = Property; - } - copyUsesToParent(identifier, variable); + readVar(context, identifier, child, FieldVar(child.access().firstChild().firstOf(Kwd(KwdStatic)).exists())); default: } }; } - function readVarInit(context:UsageContext, identifier:Identifier, token:TokenTree) { - if (!token.hasChildren()) { + function readVar(context:UsageContext, identifier:Identifier, child:Null, type:IdentifierType):Void { + var nameToken:TokenTree = child.getFirstChild(); + while (nameToken != null) { + var variable:Identifier = makeIdentifier(context, nameToken, type, identifier); + if (variable == null) { + return; + } + if (!nameToken.hasChildren()) { + copyUsesToParent(identifier, variable); + return; + } + for (nameChild in nameToken.children) { + switch (nameChild.tok) { + case POpen: + variable.type = Property; + case DblDot: + readTypeHint(context, variable, nameChild, TypeHint); + case Binop(OpAssign) | Binop(OpAssignOp(_)): + readExpression(context, variable, nameChild); + case Kwd(KwdPublic) | Kwd(KwdPrivate) | Kwd(KwdInline) | Kwd(KwdStatic) | Kwd(KwdExtern) | Kwd(KwdOverride) | Kwd(KwdMacro) | + Kwd(KwdAbstract): + case At: + readMetadata(context, variable, nameChild); + case Comma: + case Semicolon: + case Comment(_) | CommentLine(_): + default: + } + } + copyUsesToParent(identifier, variable); + nameToken = nameToken.nextSibling; + } + } + + function readMetadata(context:UsageContext, identifier:Identifier, token:TokenTree) { + if (token == null) { return; } + token = token.getFirstChild(); + if (token == null) { + return; + } + switch (token.tok) { + case Const(_): + var metadata:Identifier = makeIdentifier(context, token, Meta, identifier); + readExpression(context, metadata, findIdentifierChild(token)); + copyUsesToParent(identifier, metadata); + case Kwd(KwdFinal): + case DblDot: + readMetadata(context, identifier, token); + default: + } + } + + function readVarInit(context:UsageContext, identifier:Identifier, token:TokenTree) { for (child in token.children) { switch (child.tok) { case Binop(OpAssign): readExpression(context, identifier, child.getFirstChild()); + case DblDot: + switch (TokenTreeCheckUtils.getColonType(child)) { + case TypeHint: + readTypeHint(context, identifier, child, TypeHint); + case SwitchCase | TypeCheck | Ternary | ObjectLiteral | At | Unknown: + } default: } } @@ -547,15 +658,21 @@ class UsageCollector { for (child in token.children) { switch (child.tok) { case Comment(_) | CommentLine(_): + case Binop(OpLt): + addTypeParameter(context, identifier, child); case POpen: readParameter(context, identifier, child, fullPos.max); ignore = false; case DblDot: + readTypeHint(context, identifier, child, TypeHint); case BrOpen: if (ignore) { continue; } readBlock(context, identifier, child); + case At: + readMetadata(context, identifier, child); + case Kwd(KwdPublic) | Kwd(KwdPrivate) | Kwd(KwdInline) | Kwd(KwdStatic) | Kwd(KwdExtern) | Kwd(KwdOverride) | Kwd(KwdMacro) | Kwd(KwdAbstract): default: if (ignore) { continue; @@ -576,7 +693,7 @@ class UsageCollector { case Const(CIdent(s)): names.push(s); var field:Identifier = makeIdentifier(context, child, StructureField(names), identifier); - readExpression(context, field, child.getFirstChild()); + readExpression(context, field, findIdentifierChild(child)); copyUsesToParent(identifier, field); case BrClose: break; @@ -586,18 +703,6 @@ class UsageCollector { } } - function copyUsesToParent(identifier:Null, child:Identifier):Void { - if (identifier == null) { - return; - } - if (child.uses == null) { - return; - } - for (use in child.uses) { - identifier.addUse(use); - } - } - function readBlock(context:UsageContext, identifier:Identifier, token:TokenTree) { if (!token.hasChildren()) { return; @@ -608,18 +713,18 @@ class UsageCollector { for (child in token.children) { switch (child.tok) { case Comment(_) | CommentLine(_): - case Kwd(KwdVar): - child = child.getFirstChild(); - makeIdentifier(context, child, ScopedLocal(child.getPos().max, scopeEnd, Var), identifier); - case Kwd(KwdFinal): - child = child.getFirstChild(); - makeIdentifier(context, child, ScopedLocal(child.getPos().max, scopeEnd, Var), identifier); + case Kwd(KwdVar) | Kwd(KwdFinal): + readVar(context, identifier, child, ScopedLocal(child.getPos().max, scopeEnd, Var)); case Kwd(KwdFunction): child = child.getFirstChild(); var method:Identifier = makeIdentifier(context, child, ScopedLocal(child.pos.min, scopeEnd, Var), identifier); - readMethod(context, method, child); - copyUsesToParent(identifier, method); - case Dot: + if (method == null) { + readMethod(context, identifier, child); + } else { + readMethod(context, method, child); + copyUsesToParent(identifier, method); + } + case Dot | QuestionDot: case Semicolon: default: readExpression(context, identifier, child); @@ -627,15 +732,48 @@ class UsageCollector { } } - function readIdentifier(context:UsageContext, identifier:Identifier, token:Null) { + function readIdentifier(context:UsageContext, identifier:Identifier, token:Null, ?pos:PosInfos) { + if (token == null) { + return; + } var parent:TokenTree = token.parent; switch (parent.tok) { - case Dot: + case Dot | QuestionDot: var prev:Null = parent.previousSibling; if (prev != null) { switch (prev.tok) { case BkOpen | BkClose | POpen | PClose: - makeIdentifier(context, token, ArrayAccess(prev.pos.min), identifier); + var accessIdent:Identifier = null; + var accessParent = parent?.parent?.parent; + if (accessParent != null) { + accessIdent = context.type.findIdentifier(accessParent.pos.min); + } + var identType:IdentifierType = Access; + if (accessIdent != null) { + identType = ArrayAccess(accessIdent); + } + makeIdentifier(context, token, identType, identifier); + var identChild = findIdentifierChild(token); + while (identChild != null) { + switch (identChild.tok) { + case POpen: + readCallParams(context, identifier, identChild); + case BkOpen: + readCallParams(context, identifier, identChild); + case Binop(_): + readExpression(context, identifier, identChild); + case Dot | QuestionDot: + readExpression(context, identifier, identChild); + case Question: + if (TokenTreeCheckUtils.isTernary(identChild)) { + readExpression(context, identifier, identChild); + } + case Comment(_) | CommentLine(_): + case Unop(_): + default: + } + identChild = identChild.nextSibling; + } default: } } @@ -643,19 +781,134 @@ class UsageCollector { switch (TokenTreeCheckUtils.getPOpenType(parent)) { case Parameter: var posScope = parent.getPos(); - makeIdentifier(context, token, ScopedLocal(posScope.min, posScope.max, Parameter([])), identifier); + final parameterIdent = makeIdentifier(context, token, ScopedLocal(posScope.min, posScope.max, Parameter([])), identifier); + var parameterChild = findIdentifierChild(token); + while (parameterChild != null) { + switch (parameterChild.tok) { + case Comma: + case DblDot: + switch (TokenTreeCheckUtils.getColonType(parameterChild)) { + case TypeHint: + readTypeHint(context, parameterIdent, parameterChild, TypeHint); + copyUsesToParent(identifier, parameterIdent); + case TypeCheck: + readExpression(context, identifier, parameterChild); + case SwitchCase | Ternary | ObjectLiteral | At: + case Unknown: + readTypeHint(context, parameterIdent, parameterChild, TypeHint); + copyUsesToParent(identifier, parameterIdent); + } + case Binop(OpLt): + addTypeParameter(context, parameterIdent, parameterChild); + copyUsesToParent(identifier, parameterIdent); + default: + } + parameterChild = parameterChild.nextSibling; + } + return; + case At | Call | SwitchCondition | WhileCondition | IfCondition | SharpCondition | Catch | ForLoop | Expression: - makeIdentifier(context, token, Access, identifier); + final accessIdent = makeIdentifier(context, token, Access, identifier); + var accessChild = findIdentifierChild(token); + if (accessChild == null) { + accessChild = token.getFirstChild(); + } + while (accessChild != null) { + switch (accessChild.tok) { + case Comment(_) | CommentLine(_): + case Comma: + case Dot | QuestionDot: + case Unop(_): + case Binop(_): + readExpression(context, identifier, accessChild); + case Arrow: + readExpression(context, identifier, accessChild); + case POpen: + readCallParams(context, identifier, accessChild); + case BkOpen: + readCallParams(context, identifier, accessChild); + case DblDot: + switch (TokenTreeCheckUtils.getColonType(accessChild)) { + case TypeHint: + readTypeHint(context, accessIdent, accessChild, TypeHint); + copyUsesToParent(identifier, accessIdent); + case TypeCheck: + readExpression(context, identifier, accessChild); + case SwitchCase | Ternary | ObjectLiteral | At: + case Unknown: + readTypeHint(context, accessIdent, accessChild, TypeHint); + copyUsesToParent(identifier, accessIdent); + } + case Const(CIdent("is")): + final child = accessChild.getFirstChild(); + if (child != null) { + readExpression(context, identifier, child); + } + case Question: + if (TokenTreeCheckUtils.isTernary(accessChild)) { + readExpression(context, identifier, accessChild); + } + default: + } + accessChild = accessChild.nextSibling; + } + return; } default: - makeIdentifier(context, token, Access, identifier); + var ident = makeIdentifier(context, token, Access, identifier); + if (ident == null) { + ident = identifier; + } + var identToken = findIdentifierChild(token); + var directChildrenDone:Bool = false; + if (identToken != null && (identToken == token.getFirstChild())) { + directChildrenDone = true; + } + while (identToken != null) { + switch (identToken.tok) { + case POpen | BkOpen | Dot | QuestionDot: + readCallParams(context, ident, identToken); + copyUsesToParent(identifier, ident); + case Binop(_): + readExpression(context, ident, identToken); + copyUsesToParent(identifier, ident); + case Unop(_): + case Semicolon: + case Comment(_) | CommentLine(_): + case Comma: + case DblDot: + switch (TokenTreeCheckUtils.getColonType(identToken)) { + case TypeHint: + readTypeHint(context, ident, identToken, TypeHint); + copyUsesToParent(identifier, ident); + case TypeCheck: + readExpression(context, identifier, identToken); + case SwitchCase | Ternary | ObjectLiteral | At: + case Unknown: + readTypeHint(context, ident, identToken, TypeHint); + copyUsesToParent(identifier, ident); + } + case Question: + if (TokenTreeCheckUtils.isTernary(identToken)) { + readExpression(context, identifier, identToken); + } + case Spread: + readExpression(context, identifier, identToken); + case Arrow: + readExpression(context, identifier, identToken); + default: + } + identToken = identToken.nextSibling; + } + if (directChildrenDone) { + return; + } } - if (token.hasChildren()) { for (child in token.children) { switch (child.tok) { case Comment(_) | CommentLine(_): - case Dot | Comma: + case Dot | QuestionDot | Comma: case POpen: readCallParams(context, identifier, child); case Binop(OpAssign): @@ -667,6 +920,9 @@ class UsageCollector { } function readCallParams(context:UsageContext, identifier:Identifier, token:Null) { + if (token == null) { + return; + } if (!token.hasChildren()) { return; } @@ -675,7 +931,7 @@ class UsageCollector { } } - function readExpression(context:UsageContext, identifier:Identifier, token:Null) { + function readExpression(context:UsageContext, identifier:Identifier, token:Null, ?pos:PosInfos) { if (token == null) { return; } @@ -685,13 +941,10 @@ class UsageCollector { case Const(CIdent(_)): readIdentifier(context, identifier, token); return; + case Binop(_): case Kwd(KwdVar): - var fullPos:Position = token.parent.getPos(); - var scopeEnd:Int = fullPos.max; - var token:TokenTree = token.getFirstChild(); - var variable:Identifier = makeIdentifier(context, token, ScopedLocal(token.getPos().max, scopeEnd, Var), identifier); - readVarInit(context, variable, token); - copyUsesToParent(identifier, variable); + final fullPos:Position = token.parent.getPos(); + readVar(context, identifier, token, ScopedLocal(token.getPos().max, fullPos.max, Var)); return; case Kwd(KwdFunction): var fullPos:Position = token.parent.getPos(); @@ -707,7 +960,36 @@ class UsageCollector { } return; case Kwd(KwdThis): - makeIdentifier(context, token, Access, identifier); + final thisIdent = makeIdentifier(context, token, Access, identifier); + var identChild = findIdentifierChild(token); + while (identChild != null) { + switch (identChild.tok) { + case Binop(_): + readExpression(context, identifier, identChild); + case Unop(_): + case POpen: + readCallParams(context, thisIdent, identChild); + copyUsesToParent(identifier, thisIdent); + case BkOpen: + readCallParams(context, thisIdent, identChild); + copyUsesToParent(identifier, thisIdent); + case Dot | QuestionDot: + readIdentifier(context, thisIdent, identChild); + copyUsesToParent(identifier, thisIdent); + case Comment(_) | CommentLine(_): + case Semicolon: + case Comma: + case DblDot: + readTypeHint(context, thisIdent, identChild, TypeHint); + copyUsesToParent(identifier, thisIdent); + case Question: + if (TokenTreeCheckUtils.isTernary(identChild)) { + readExpression(context, identifier, identChild); + } + default: + } + identChild = identChild.nextSibling; + } return; case BrOpen: switch (TokenTreeCheckUtils.getBrOpenType(token)) { @@ -827,9 +1109,6 @@ class UsageCollector { case Binop(OpArrow): ident = makeIdentifier(context, child.getFirstChild(), ScopedLocal(scopeStart, scopeEnd, ForLoop(loopIdentifiers)), identifier); loopIdentifiers.push(ident); - // case Kwd(KwdIn): - // readExpression(context, ident, child.getFirstChild()); - // copyUsesToParent(identifier, ident); default: readExpression(context, ident, child); copyUsesToParent(identifier, ident); @@ -861,7 +1140,7 @@ class UsageCollector { readCaseConst(context, identifier, child, scopeEnd); case Kwd(KwdVar): child = child.getFirstChild(); - makeIdentifier(context, child, ScopedLocal(child.pos.min, scopeEnd, CaseCapture), identifier); + makeIdentifier(context, child, ScopedLocal(child.pos.min, scopeEnd, CaseCapture(null, 0)), identifier); case BkOpen: readCaseArray(context, identifier, child, scopeEnd); case BrOpen: @@ -879,32 +1158,69 @@ class UsageCollector { if (!token.hasChildren()) { return; } - var pOpen:Array = token.filterCallback(function(token:TokenTree, index:Int):FilterResult { - return switch (token.tok) { - case POpen | BkOpen: - FoundGoDeeper; - default: - GoDeeper; - } - }); - for (child in pOpen) { + final pOpen = findIdentifierChild(token); + if (pOpen == null) { + copyUsesToParent(identifier, caseIdent); + return; + } + readCasePOpen(context, caseIdent, pOpen, scopeEnd); + if (caseIdent != null) { + copyUsesToParent(identifier, caseIdent); + } + } + + function readCasePOpen(context:UsageContext, caseIdent:Identifier, pOpen:TokenTree, scopeEnd:Int) { + switch (pOpen.tok) { + case BkOpen: + readCaseArray(context, caseIdent, pOpen, scopeEnd); + case POpen: + readCaseParameter(context, caseIdent, pOpen, scopeEnd); + default: + } + } + + function readCaseParameter(context:UsageContext, identifier:Identifier, token:TokenTree, scopeEnd:Int):Array { + var params:Array = []; + var index:Int = 0; + for (child in token.children) { switch (child.tok) { + case Question: + child = child.getFirstChild(); + var paramIdent:Identifier = makeIdentifier(context, child, ScopedLocal(child.pos.min, scopeEnd, Parameter(params)), identifier); + params.push(paramIdent); + case Const(CIdent(s)): + var paramIdent:Identifier = makeIdentifier(context, child, ScopedLocal(child.pos.min, scopeEnd, CaseCapture(identifier, index)), + identifier); + params.push(paramIdent); + index++; + final pOpen = findIdentifierChild(child); + if (pOpen == null) { + continue; + } + if (pOpen.parent == child) { + continue; + } + readCasePOpen(context, paramIdent, pOpen, scopeEnd); + case Const(_): + index++; case BkOpen: - readCaseArray(context, caseIdent, child, scopeEnd); - case POpen: - readParameter(context, caseIdent, child, scopeEnd); + readCaseArray(context, identifier, child, scopeEnd); + case PClose: + break; default: } } - copyUsesToParent(identifier, caseIdent); + return params; } function readCaseArray(context:UsageContext, identifier:Identifier, token:TokenTree, scopeEnd:Int) { if (!token.hasChildren()) { return; } + var index = 0; for (child in token.children) { - makeIdentifier(context, child, ScopedLocal(child.pos.max, scopeEnd, CaseCapture), identifier); + readCaseConst(context, identifier, child, scopeEnd); + index++; } } @@ -935,7 +1251,7 @@ class UsageCollector { } if (field.uses != null) { for (use in field.uses) { - use.type = ScopedLocal(use.pos.start, scopeEnd, CaseCapture); + use.type = ScopedLocal(use.pos.start, scopeEnd, CaseCapture(identifier, 0)); } } default: @@ -950,11 +1266,49 @@ class UsageCollector { switch (child.tok) { case Question: child = child.getFirstChild(); - var paramIdent:Identifier = makeIdentifier(context, child, ScopedLocal(child.pos.min, scopeEnd, Parameter(params)), identifier); + final paramIdent:Identifier = makeIdentifier(context, child, ScopedLocal(child.pos.min, scopeEnd, Parameter(params)), identifier); + var paramChild = findIdentifierChild(child); + while (paramChild != null) { + switch (paramChild.tok) { + case DblDot: + switch (TokenTreeCheckUtils.getColonType(paramChild)) { + case TypeHint: + readTypeHint(context, paramIdent, paramChild, TypeHint); + copyUsesToParent(identifier, paramIdent); + case TypeCheck: + readExpression(context, identifier, paramChild); + case Ternary: + readExpression(context, identifier, paramChild); + case SwitchCase | ObjectLiteral | At | Unknown: + } + case Binop(OpAssign): + readExpression(context, identifier, paramChild); + case Comma: + default: + } + paramChild = paramChild.nextSibling; + } + copyUsesToParent(identifier, paramIdent); params.push(paramIdent); case Const(CIdent(_)): var paramIdent:Identifier = makeIdentifier(context, child, ScopedLocal(child.pos.min, scopeEnd, Parameter(params)), identifier); params.push(paramIdent); + var paramChild = findIdentifierChild(child); + while (paramChild != null) { + switch (paramChild.tok) { + case DblDot: + readTypeHint(context, paramIdent, paramChild, TypeHint); + case Comma: + if (paramChild.parent == child) { + break; + } + case Binop(OpAssign): + readExpression(context, paramIdent, paramChild); + default: + } + paramChild = paramChild.nextSibling; + } + copyUsesToParent(identifier, paramIdent); default: } } @@ -976,20 +1330,24 @@ class UsageCollector { switch (nameToken.tok) { case Const(CIdent("is")): return null; - case Kwd(KwdNew) | Kwd(KwdThis) | Kwd(KwdNull) | Const(CIdent(_)): + case Kwd(KwdNew) | Kwd(KwdThis) | Kwd(KwdNull) | Const(CIdent(_)) | Dollar(_): + case Question: + nameToken = nameToken.getFirstChild(); + if (nameToken == null) { + return null; + } default: return null; } var pos:IdentifierPos = makePosition(context.fileName, nameToken); var pack:Array = []; - var typeParamLt:Null = null; - var typeHintColon:Null = null; - var pOpenToken:Array = []; + var parent:TokenTree = nameToken.parent; var lastNamePart:TokenTree = nameToken; var parameterList:Array = []; + var needsDot:Bool = true; function findAllNames(parentPart:TokenTree) { if (!parentPart.hasChildren()) { return; @@ -998,9 +1356,18 @@ class UsageCollector { switch (child.tok) { case Const(CIdent("is")): return; - case Kwd(KwdNew) | Kwd(KwdThis) | Kwd(KwdNull) | Const(_): + case Kwd(KwdNew) | Kwd(KwdThis) | Kwd(KwdNull) | Kwd(KwdMacro) | Const(_) | Dollar(_): + if (needsDot) { + return; + } pack.push(child.toString()); + needsDot = true; case Dot: + pack.push("."); + needsDot = false; + case QuestionDot: + pack.push("?."); + needsDot = false; case POpen: switch (parent.tok) { case Kwd(KwdVar) | Kwd(KwdFinal): @@ -1011,6 +1378,8 @@ class UsageCollector { return; case Unop(_) | Binop(_): return; + case At: + continue; default: continue; } @@ -1024,12 +1393,6 @@ class UsageCollector { if (lastNamePart.hasChildren()) { for (child in lastNamePart.children) { switch (child.tok) { - case Binop(OpLt): - if (TokenTreeCheckUtils.isTypeParameter(child)) { - typeParamLt = child; - } else { - pOpenToken.push(child); - } case Arrow: var scopePos = child.getPos(); type = ScopedLocal(nameToken.pos.min, scopePos.max, Parameter(parameterList)); @@ -1040,35 +1403,7 @@ class UsageCollector { } else { type = Call(false); } - pOpenToken.push(child); - } - case Binop(OpAssign): - if (!parent.matches(Kwd(KwdTypedef))) { - pOpenToken.push(child); - } - case Binop(OpIn): - case Binop(OpArrow): - switch (type) { - case ScopedLocal(_, _, ForLoop(_)): - default: - pOpenToken.push(child); } - case BkOpen | Binop(_): - pOpenToken.push(child); - case DblDot: - switch (TokenTreeCheckUtils.getColonType(child)) { - case SwitchCase: - case TypeHint: - typeHintColon = child; - case TypeCheck: - typeHintColon = child; - case Ternary: - case ObjectLiteral: - case At: - case Unknown: - } - case Dot: - pOpenToken.push(child); default: } } @@ -1077,7 +1412,7 @@ class UsageCollector { if (pack.length <= 0) { return null; } - var name:String = pack.join("."); + var name:String = pack.join(""); var identifier:Null = context.nameMap.getIdentifier(name, context.file.name, pos.start); if (identifier == null) { identifier = new Identifier(type, name, pos, context.nameMap, context.file, context.type); @@ -1087,18 +1422,7 @@ class UsageCollector { if (parentIdentifier != null) { parentIdentifier.addUse(identifier); } - if (typeParamLt != null) { - addTypeParameter(context, identifier, typeParamLt); - } - if (typeHintColon != null) { - readTypeHint(context, identifier, typeHintColon, TypeHint); - } - for (child in pOpenToken) { - readExpression(context, identifier, child); - if (child.nextSibling != null) { - readExpression(context, identifier, child.nextSibling); - } - } + return identifier; } @@ -1108,35 +1432,83 @@ class UsageCollector { } for (child in token.children) { switch (child.tok) { - case Const(CIdent(_)): + case Const(CIdent(_)) | Dollar(_): makeIdentifier(context, child, TypedParameter, identifier); + final afterIdentifier = findIdentifierChild(child); + if (afterIdentifier != null) { + switch (afterIdentifier.tok) { + case Binop(OpLt): + addTypeParameter(context, identifier, afterIdentifier); + case Arrow: + addTypeParameter(context, identifier, afterIdentifier); + default: + } + } case POpen: readParameter(context, identifier, child, token.getPos().max); case BrOpen: readBlock(context, identifier, child); case Binop(OpGt): break; + case Binop(OpAnd): + addTypeParameter(context, identifier, child); case DblDot: readTypeHint(context, identifier, child, TypeHint); + case Sharp(_): + readExpression(context, identifier, child); + case Comma: + case Semicolon: + case Const(_): default: } } } function readTypeHint(context:UsageContext, identifier:Identifier, token:TokenTree, type:IdentifierType) { + if (token == null) { + return; + } if (!token.hasChildren()) { return; } for (child in token.children) { switch (child.tok) { - case Const(CIdent(_)): + case Const(CIdent(_)) | Dollar(_): makeIdentifier(context, child, type, identifier); + if (token.matches(DblDot) && identifier != null) { + identifier.setTypeHint(TypeHintFromTree.makeTypeHint(child)); + } + final afterIdentifier = findIdentifierChild(child); + if (afterIdentifier != null) { + switch (afterIdentifier.tok) { + case Comment(_) | CommentLine(_): + case Semicolon: + case Arrow: + readTypeHint(context, identifier, afterIdentifier, TypeHint); + case Binop(OpLt): + addTypeParameter(context, identifier, afterIdentifier); + case PClose: + case Sharp(_): + readExpression(context, identifier, afterIdentifier); + default: + } + } case BrOpen: readAnonStructure(context, identifier, child); + if (identifier != null) { + identifier.setTypeHint(TypeHintFromTree.makeTypeHint(child)); + } break; case POpen: readExpression(context, identifier, child); + if (identifier != null) { + identifier.setTypeHint(TypeHintFromTree.makeTypeHint(child)); + } break; + case Sharp(_): + readExpression(context, identifier, child); + case Semicolon: + case PClose: default: } } @@ -1149,6 +1521,7 @@ class UsageCollector { var fields:Array = []; for (child in token.children) { switch (child.tok) { + case Comment(_) | CommentLine(_): case Const(CIdent(_)): var ident:Identifier = makeIdentifier(context, child, TypedefField(fields), identifier); if (child.access() @@ -1160,8 +1533,13 @@ class UsageCollector { } else { fields.push(Required(ident)); } + final afterIdent = findIdentifierChild(child); + readTypeHint(context, ident, afterIdent, TypeHint); case Kwd(KwdVar) | Kwd(KwdFinal): - var nameToken:TokenTree = child.getFirstChild(); + final nameToken:TokenTree = child.getFirstChild(); + if (nameToken == null) { + continue; + } var ident:Identifier = makeIdentifier(context, nameToken, TypedefField(fields), identifier); if (nameToken.access() .firstOf(At) @@ -1172,11 +1550,62 @@ class UsageCollector { } else { fields.push(Required(ident)); } + final afterIdent = findIdentifierChild(nameToken); + readTypeHint(context, ident, afterIdent, TypeHint); case Question: - var ident:Identifier = makeIdentifier(context, child.getFirstChild(), TypedefField(fields), identifier); + final question:TokenTree = child.getFirstChild(); + if (question == null) { + continue; + } + var ident:Identifier = makeIdentifier(context, question, TypedefField(fields), identifier); fields.push(Optional(ident)); + final afterIdent = findIdentifierChild(question); + readTypeHint(context, ident, afterIdent, TypeHint); + case Binop(OpAnd): + readAnonStructure(context, identifier, child); + case BrClose: + case Semicolon: default: } } } + + function findIdentifierChild(token:TokenTree):Null { + while (token != null) { + token = token.getFirstChild(); + if (token == null) { + return null; + } + if (token.matches(At)) { + token = token.nextSibling; + } + if (token == null) { + return null; + } + switch (token.tok) { + case Const(CIdent(_)) | Dot | QuestionDot | Kwd(KwdNew) | Kwd(KwdMacro) | Dollar(_): + default: + return token; + } + } + return null; + } + + function copyUsesToParent(identifier:Null, child:Identifier):Void { + if (identifier == null) { + return; + } + if (child == null) { + return; + } + if (child.uses == null) { + return; + } + if (identifier == child) { + return; + } + for (use in child.uses) { + identifier.addUse(use); + } + } } diff --git a/src/refactor/refactor/ExtractConstructorParams.hx b/src/refactor/refactor/ExtractConstructorParams.hx index cc69c61..3d306c8 100644 --- a/src/refactor/refactor/ExtractConstructorParams.hx +++ b/src/refactor/refactor/ExtractConstructorParams.hx @@ -6,7 +6,7 @@ import refactor.edits.Changelist; import refactor.refactor.RefactorHelper.TokensAtPos; class ExtractConstructorParams { - public static function canRefactor(context:CanRefactorContext, asFinal:Bool):CanRefactorResult { + public static function canRefactor(context:CanRefactorContext, isRangeSameScope:Bool, asFinal:Bool):CanRefactorResult { final extractData = makeExtractConstructorParamsData(context); if (extractData == null) { return Unsupported; @@ -21,7 +21,7 @@ class ExtractConstructorParams { public static function doRefactor(context:RefactorContext, asFinal:Bool):Promise { final extractData = makeExtractConstructorParamsData(context); if (extractData == null) { - return Promise.reject("failed to collect extract method data"); + return Promise.reject("failed to collect data for extract constructor params"); } final changelist:Changelist = new Changelist(context); diff --git a/src/refactor/refactor/ExtractInterface.hx b/src/refactor/refactor/ExtractInterface.hx index a018fcf..5ffce06 100644 --- a/src/refactor/refactor/ExtractInterface.hx +++ b/src/refactor/refactor/ExtractInterface.hx @@ -9,7 +9,7 @@ import refactor.edits.Changelist; import refactor.refactor.RefactorHelper.TokensAtPos; class ExtractInterface { - public static function canRefactor(context:CanRefactorContext):CanRefactorResult { + public static function canRefactor(context:CanRefactorContext, isRangeSameScope:Bool):CanRefactorResult { final extractData = makeExtractInterfaceData(context); if (extractData == null) { return Unsupported; @@ -20,7 +20,7 @@ class ExtractInterface { public static function doRefactor(context:RefactorContext):Promise { final extractData = makeExtractInterfaceData(context); if (extractData == null) { - return Promise.reject("failed to collect extract interface data"); + return Promise.reject("failed to collect data for extract interface"); } final changelist:Changelist = new Changelist(context); @@ -366,6 +366,8 @@ class ExtractInterface { return switch (typeHint) { case null | ClasspathType(_) | LibType(_) | StructType(_) | UnknownType(_): Promise.resolve(typeHint); + case NamedType(_, fieldHint): + Promise.resolve(fieldHint); case FunctionType(args, retVal): Promise.resolve(retVal); } diff --git a/src/refactor/refactor/ExtractMethod.hx b/src/refactor/refactor/ExtractMethod.hx index 6e2e1ef..455122e 100644 --- a/src/refactor/refactor/ExtractMethod.hx +++ b/src/refactor/refactor/ExtractMethod.hx @@ -15,7 +15,10 @@ import refactor.refactor.extractmethod.ICodeGen; import refactor.typing.TypeHintType; class ExtractMethod { - public static function canRefactor(context:CanRefactorContext):CanRefactorResult { + public static function canRefactor(context:CanRefactorContext, isRangeSameScope:Bool):CanRefactorResult { + if (!isRangeSameScope) { + return Unsupported; + } final extractData = makeExtractMethodData(context); if (extractData == null) { return Unsupported; @@ -26,7 +29,7 @@ class ExtractMethod { public static function doRefactor(context:RefactorContext):Promise { final extractData = makeExtractMethodData(context); if (extractData == null) { - return Promise.reject("failed to collect extract method data"); + return Promise.reject("failed to collect data for extract method"); } final changelist:Changelist = new Changelist(context); @@ -102,6 +105,9 @@ class ExtractMethod { } static function makeExtractMethodData(context:CanRefactorContext):Null { + if (context.what.posStart >= context.what.posEnd) { + return null; + } final fileContent = context.fileReader(context.what.fileName); var content:String; var root:TokenTree; @@ -181,7 +187,7 @@ class ExtractMethod { } // extracting only works if parent of start token is also grand…parent of end token - if (!shareSameParent(tokenStart, tokenEnd)) { + if (!RefactorHelper.shareSameParent(tokenStart, tokenEnd)) { return null; } @@ -237,56 +243,6 @@ class ExtractMethod { }; } - static function shareSameParent(tokenA:TokenTree, tokenB:TokenTree):Bool { - var parentA = tokenA.parent; - if (parentA == null) { - return false; - } - switch (parentA.tok) { - case POpen: - final closeToken = parentA.access().firstOf(PClose).token; - if (closeToken == null) { - return false; - } - if (closeToken.index < tokenB.index) { - return false; - } - case BrOpen: - final closeToken = parentA.access().firstOf(BrClose).token; - if (closeToken == null) { - return false; - } - if (closeToken.index < tokenB.index) { - return false; - } - case BkOpen: - final closeToken = parentA.access().firstOf(BkClose).token; - if (closeToken == null) { - return false; - } - if (closeToken.index < tokenB.index) { - return false; - } - default: - } - var parentB = tokenB.parent; - var oldParentB = tokenB; - while (true) { - if (parentB == null) { - return false; - } - if (parentA.index == parentB.index) { - final lastToken = TokenTreeCheckUtils.getLastToken(oldParentB); - if (lastToken == null) { - return false; - } - return (lastToken.index <= tokenB.index); - } - oldParentB = parentB; - parentB = parentB.parent; - } - } - static function getFunctionIdentifier(extractData:ExtractMethodData, context:RefactorContext):Null { final file:Null = context.fileList.getFile(context.what.fileName); if (file == null) { @@ -641,9 +597,6 @@ class ExtractMethod { var promises:Array> = []; for (identifier in neededIdentifiers) { final promise = findTypeOfIdentifier(context, identifier).then(function(typeHint):Promise { - #if debug - trace("typehint resolved: " + identifier + " " + PrintHelper.typeHintToString(typeHint)); - #end return Promise.resolve(buildParameter(identifier, typeHint)); }); @@ -653,6 +606,16 @@ class ExtractMethod { } public static function findTypeOfIdentifier(context:RefactorContext, identifier:Identifier):Promise { + final typeHint = identifier.getTypeHintNew(context.typeList); + if (typeHint != null) { + return Promise.resolve(typeHint); + } + + var typeHint = identifier.getTypeHintNew(context.typeList); + if (typeHint != null) { + return Promise.resolve(typeHint); + } + var hint = identifier.getTypeHint(); if (hint != null) { return TypingHelper.typeFromTypeHint(context, hint); diff --git a/src/refactor/refactor/ExtractType.hx b/src/refactor/refactor/ExtractType.hx index 63c5bda..3664dcf 100644 --- a/src/refactor/refactor/ExtractType.hx +++ b/src/refactor/refactor/ExtractType.hx @@ -13,7 +13,7 @@ import refactor.edits.Changelist; import refactor.refactor.RefactorHelper.TokensAtPos; class ExtractType { - public static function canRefactor(context:CanRefactorContext):CanRefactorResult { + public static function canRefactor(context:CanRefactorContext, isRangeSameScope:Bool):CanRefactorResult { final extractData = makeExtractTypeData(context); if (extractData == null) { return Unsupported; @@ -24,7 +24,7 @@ class ExtractType { public static function doRefactor(context:RefactorContext):Promise { final extractData = makeExtractTypeData(context); if (extractData == null) { - return Promise.reject("failed to collect extract type data"); + return Promise.reject("failed to collect data for extract type"); } // copy header + imports final fileHeader = makeHeader(extractData, context); diff --git a/src/refactor/refactor/RefactorHelper.hx b/src/refactor/refactor/RefactorHelper.hx index 8181dd3..40c3308 100644 --- a/src/refactor/refactor/RefactorHelper.hx +++ b/src/refactor/refactor/RefactorHelper.hx @@ -173,6 +173,111 @@ class RefactorHelper { } return parts.length - 1; } + + public static function rangeInSameScope(context:CanRefactorContext):Bool { + if (context.what.posStart == context.what.posEnd) { + return true; + } + + final fileContent = context.fileReader(context.what.fileName); + var content:String; + var root:TokenTree; + switch (fileContent) { + case Text(_): + return null; + case Token(tokens, text): + content = text; + root = tokens; + } + if (root == null) { + return false; + } + if (content == null) { + return false; + } + + final file:Null = context.fileList.getFile(context.what.fileName); + if (file == null) { + return false; + } + // find corresponding tokens in tokentree, selection start/end in whitespace + final tokensStart:TokensAtPos = RefactorHelper.findTokensAtPos(root, context.what.posStart); + final tokensEnd:TokensAtPos = RefactorHelper.findTokensAtPos(root, context.what.posEnd); + if (tokensStart.after == null || tokensEnd.before == null) { + return false; + } + + final tokenStart:Null = tokensStart.after; + final tokenEnd:Null = tokensEnd.before; + + if (tokenStart == null || tokenEnd == null) { + return false; + } + if (tokenStart.index >= tokenEnd.index) { + return false; + } + + final tokenEndLast:Null = TokenTreeCheckUtils.getLastToken(tokenEnd); + if (tokenEndLast == null) { + return false; + } + if (tokenEnd.index != tokenEndLast.index) { + return false; + } + + // extracting only works if parent of start token is also grand…parent of end token + return shareSameParent(tokenStart, tokenEnd); + } + + public static function shareSameParent(tokenA:TokenTree, tokenB:TokenTree):Bool { + var parentA = tokenA.parent; + if (parentA == null) { + return false; + } + switch (parentA.tok) { + case POpen: + final closeToken = parentA.access().firstOf(PClose).token; + if (closeToken == null) { + return false; + } + if (closeToken.index < tokenB.index) { + return false; + } + case BrOpen: + final closeToken = parentA.access().firstOf(BrClose).token; + if (closeToken == null) { + return false; + } + if (closeToken.index < tokenB.index) { + return false; + } + case BkOpen: + final closeToken = parentA.access().firstOf(BkClose).token; + if (closeToken == null) { + return false; + } + if (closeToken.index < tokenB.index) { + return false; + } + default: + } + var parentB = tokenB.parent; + var oldParentB = tokenB; + while (true) { + if (parentB == null) { + return false; + } + if (parentA.index == parentB.index) { + final lastToken = TokenTreeCheckUtils.getLastToken(oldParentB); + if (lastToken == null) { + return false; + } + return (lastToken.index <= tokenB.index); + } + oldParentB = parentB; + parentB = parentB.parent; + } + } } typedef TokensAtPos = { diff --git a/src/refactor/refactor/RefactorType.hx b/src/refactor/refactor/RefactorType.hx index 1aba504..6d0e04e 100644 --- a/src/refactor/refactor/RefactorType.hx +++ b/src/refactor/refactor/RefactorType.hx @@ -6,4 +6,5 @@ enum RefactorType { RefactorExtractType; RefactorExtractConstructorParams(asFinal:Bool); RefactorRewriteVarsToFinals(toFinals:Bool); + RefactorRewriteWrapWithTryCatch; } diff --git a/src/refactor/refactor/RewriteVarsToFinals.hx b/src/refactor/refactor/RewriteVarsToFinals.hx index fd7d183..40f0447 100644 --- a/src/refactor/refactor/RewriteVarsToFinals.hx +++ b/src/refactor/refactor/RewriteVarsToFinals.hx @@ -4,7 +4,7 @@ import refactor.edits.Changelist; import refactor.refactor.RefactorHelper.TokensAtPos; class RewriteVarsToFinals { - public static function canRefactor(context:CanRefactorContext, toFinals:Bool):CanRefactorResult { + public static function canRefactor(context:CanRefactorContext, isRangeSameScope:Bool, toFinals:Bool):CanRefactorResult { final extractData = makeRewriteVarsToFinalsData(context, toFinals); if (extractData == null) { return Unsupported; @@ -19,7 +19,7 @@ class RewriteVarsToFinals { public static function doRefactor(context:RefactorContext, toFinals:Bool):Promise { final extractData = makeRewriteVarsToFinalsData(context, toFinals); if (extractData == null) { - return Promise.reject("failed to collect rewrite vars/finals data"); + return Promise.reject("failed to collect data for rewrite vars/finals"); } final changelist:Changelist = new Changelist(context); diff --git a/src/refactor/refactor/RewriteWrapWithTryCatch.hx b/src/refactor/refactor/RewriteWrapWithTryCatch.hx new file mode 100644 index 0000000..953fd2a --- /dev/null +++ b/src/refactor/refactor/RewriteWrapWithTryCatch.hx @@ -0,0 +1,111 @@ +package refactor.refactor; + +import refactor.discover.File; +import refactor.edits.Changelist; +import refactor.refactor.RefactorHelper.TokensAtPos; + +class RewriteWrapWithTryCatch { + public static function canRefactor(context:CanRefactorContext, isRangeSameScope:Bool):CanRefactorResult { + if (!isRangeSameScope) { + return Unsupported; + } + final extractData = makeRewriteWrapWithTryCatch(context); + if (extractData == null) { + return Unsupported; + } + return Supported('Wrap With Try…Catch'); + } + + public static function doRefactor(context:RefactorContext):Promise { + final extractData = makeRewriteWrapWithTryCatch(context); + if (extractData == null) { + return Promise.reject("failed to collect data for rewrite wrap with try catch"); + } + final changelist:Changelist = new Changelist(context); + + final selectedSnippet = RefactorHelper.extractText(context.converter, extractData.content, extractData.startToken.pos.min, + extractData.endToken.pos.max); + + final wrappedSnippet = "try {\n" + selectedSnippet + "\n}\ncatch (e:haxe.Exception) {\n// TODO: handle exception\ntrace (e.details());\n}"; + + changelist.addChange(context.what.fileName, + ReplaceText(wrappedSnippet, {fileName: context.what.fileName, start: extractData.startToken.pos.min, end: extractData.endToken.pos.max}, + Format(extractData.snippetIndent, true)), + null); + return Promise.resolve(changelist.execute()); + } + + static function makeRewriteWrapWithTryCatch(context:CanRefactorContext):Null { + if (context.what.posStart >= context.what.posEnd) { + return null; + } + final fileContent = context.fileReader(context.what.fileName); + var content:String; + var root:TokenTree; + switch (fileContent) { + case Text(_): + return null; + case Token(tokens, text): + content = text; + root = tokens; + } + if (root == null) { + return null; + } + if (content == null) { + return null; + } + + final file:Null = context.fileList.getFile(context.what.fileName); + if (file == null) { + return null; + } + // find corresponding tokens in tokentree, selection start/end in whitespace + final tokensStart:TokensAtPos = RefactorHelper.findTokensAtPos(root, context.what.posStart); + final tokensEnd:TokensAtPos = RefactorHelper.findTokensAtPos(root, context.what.posEnd); + if (tokensStart.after == null || tokensEnd.before == null) { + return null; + } + + final tokenStart:Null = tokensStart.after; + final tokenEnd:Null = tokensEnd.before; + + if (tokenStart == null || tokenEnd == null) { + return null; + } + if (tokenStart.index >= tokenEnd.index) { + return null; + } + + final tokenEndLast:Null = TokenTreeCheckUtils.getLastToken(tokenEnd); + if (tokenEndLast == null) { + return null; + } + if (tokenEnd.index != tokenEndLast.index) { + return null; + } + + // extracting only works if parent of start token is also grand…parent of end token + if (!RefactorHelper.shareSameParent(tokenStart, tokenEnd)) { + return null; + } + + final snippetIndent:Int = RefactorHelper.calcIndentation(context, content, context.what.fileName, tokenStart.pos.min); + + return { + content: content, + root: root, + startToken: tokenStart, + endToken: tokenEnd, + snippetIndent: snippetIndent, + }; + } +} + +typedef RewriteWrapWithTryCatchData = { + var content:String; + var root:TokenTree; + var startToken:TokenTree; + var endToken:TokenTree; + var snippetIndent:Int; +} diff --git a/src/refactor/refactor/extractmethod/CodeGenBase.hx b/src/refactor/refactor/extractmethod/CodeGenBase.hx index 639330f..a217c9f 100644 --- a/src/refactor/refactor/extractmethod/CodeGenBase.hx +++ b/src/refactor/refactor/extractmethod/CodeGenBase.hx @@ -45,7 +45,7 @@ abstract class CodeGenBase implements ICodeGen { } return TypingHelper.findTypeWithTyper(context, context.what.fileName, func.pos.max - 1).then(function(typeHint) { return switch (typeHint) { - case null | ClasspathType(_) | LibType(_) | StructType(_) | UnknownType(_): + case null | ClasspathType(_) | LibType(_) | StructType(_) | UnknownType(_) | NamedType(_): Promise.resolve(typeHint); case FunctionType(args, retVal): Promise.resolve(retVal); diff --git a/src/refactor/refactor/extractmethod/CodeGenEmptyReturn.hx b/src/refactor/refactor/extractmethod/CodeGenEmptyReturn.hx index a311ce5..dafd79f 100644 --- a/src/refactor/refactor/extractmethod/CodeGenEmptyReturn.hx +++ b/src/refactor/refactor/extractmethod/CodeGenEmptyReturn.hx @@ -25,11 +25,7 @@ class CodeGenEmptyReturn extends CodeGenBase { case [0, 0]: 'if (!$call) {\nreturn;\n}'; case [1, 0]: - 'switch ($call) {\n' - + 'case None:\n' - + 'return;\n' - + 'case Some(data):\n' - + '${assignments[0].name} = data;\n}'; + 'switch ($call) {\n' + 'case None:\n' + 'return;\n' + 'case Some(data):\n' + '${assignments[0].name} = data;\n}'; case [0, 1]: 'var ${vars[0].name};\n' + 'switch ($call) {\n' @@ -87,9 +83,6 @@ class CodeGenEmptyReturn extends CodeGenBase { } public function makeBody():String { - final selectedSnippet = RefactorHelper.extractText(context.converter, extractData.content, extractData.startToken.pos.min, - extractData.endToken.pos.max); - return switch [assignments.length, vars.length] { case [0, 0]: final snippet = replaceReturnValues(returnTokens, value -> 'false'); diff --git a/src/refactor/refactor/extractmethod/CodeGenLocalFunction.hx b/src/refactor/refactor/extractmethod/CodeGenLocalFunction.hx index cd51898..efa38cd 100644 --- a/src/refactor/refactor/extractmethod/CodeGenLocalFunction.hx +++ b/src/refactor/refactor/extractmethod/CodeGenLocalFunction.hx @@ -62,7 +62,7 @@ class CodeGenLocalFunction extends CodeGenBase { } return TypingHelper.findTypeWithTyper(context, context.what.fileName, func.pos.max - 1).then(function(typeHint) { return switch (typeHint) { - case null | ClasspathType(_) | LibType(_) | StructType(_) | UnknownType(_): + case null | ClasspathType(_) | LibType(_) | StructType(_) | UnknownType(_) | NamedType(_): Promise.resolve(typeHint); case FunctionType(args, retVal): Promise.resolve(retVal); diff --git a/src/refactor/rename/RenameAnonStructField.hx b/src/refactor/rename/RenameAnonStructField.hx index 797da04..3ec893d 100644 --- a/src/refactor/rename/RenameAnonStructField.hx +++ b/src/refactor/rename/RenameAnonStructField.hx @@ -58,7 +58,7 @@ class RenameAnonStructField { switch (use.file.importsModule(packName, mainModuleName, type.name.name)) { case None: continue; - case Global | SamePackage | Imported | ImportedWithAlias(_) | StarImported: + case Global | ParentPackage | SamePackage | Imported | ImportedWithAlias(_) | StarImported: } changelist.addChange(use.pos.fileName, ReplaceText(context.what.toName, use.pos, NoFormat), use); } @@ -137,7 +137,7 @@ class RenameAnonStructField { switch (baseTypeName.file.importsModule(use.file.getPackage(), use.file.getMainModulName(), baseTypeName.name)) { case None: continue; - case Global | SamePackage | Imported | ImportedWithAlias(_) | StarImported: + case Global | ParentPackage | SamePackage | Imported | ImportedWithAlias(_) | StarImported: } return use.defineType; } diff --git a/src/refactor/rename/RenameEnumField.hx b/src/refactor/rename/RenameEnumField.hx index 5b30408..3a2f2d0 100644 --- a/src/refactor/rename/RenameEnumField.hx +++ b/src/refactor/rename/RenameEnumField.hx @@ -24,7 +24,7 @@ class RenameEnumField { if (alias != typeName) { continue; } - case Global | SamePackage | Imported | StarImported: + case Global | ParentPackage | SamePackage | Imported | StarImported: } RenameHelper.replaceTextWithPrefix(use, typeName + ".", context.what.toName, changelist); } @@ -63,7 +63,7 @@ class RenameEnumField { if (alias != typeName) { continue; } - case Global | SamePackage | Imported | StarImported: + case Global | ParentPackage | SamePackage | Imported | StarImported: } default: continue; diff --git a/src/refactor/rename/RenameField.hx b/src/refactor/rename/RenameField.hx index f8843af..090f5b2 100644 --- a/src/refactor/rename/RenameField.hx +++ b/src/refactor/rename/RenameField.hx @@ -125,7 +125,7 @@ class RenameField { continue; case ImportedWithAlias(_): continue; - case Global | SamePackage | Imported | StarImported: + case Global | ParentPackage | SamePackage | Imported | StarImported: } RenameHelper.replaceTextWithPrefix(use, '${type.name.name}.', context.what.toName, changelist); } diff --git a/src/refactor/rename/RenameHelper.hx b/src/refactor/rename/RenameHelper.hx index b36021c..8a6a82f 100644 --- a/src/refactor/rename/RenameHelper.hx +++ b/src/refactor/rename/RenameHelper.hx @@ -78,6 +78,8 @@ class RenameHelper { continue; case UnknownType(name): continue; + case NamedType(_): + continue; } } object = use.name; @@ -110,8 +112,10 @@ class RenameHelper { var index:Int = name.lastIndexOf('.$fromName'); if (index < 0) { switch (use.type) { - case ArrayAccess(posClosing): - return replaceArrayAccess(context, changelist, use, fromName, types, posClosing); + case ArrayAccess(arrayIdentfier): + return replaceArrayAccess(context, changelist, use, fromName, types, arrayIdentfier); + case CaseLabel(switchIdentifier): + return replaceArrayAccess(context, changelist, use, fromName, types, switchIdentifier); default: } return Promise.resolve(null); @@ -150,16 +154,16 @@ class RenameHelper { switch (params[0]) { case ClasspathType(type, _): addMatchToChangelist(type); - case LibType(_) | FunctionType(_) | StructType(_) | UnknownType(_): + case LibType(_) | FunctionType(_) | StructType(_) | UnknownType(_) | NamedType(_): } - case UnknownType(_) | FunctionType(_, _) | StructType(_): + case UnknownType(_) | FunctionType(_, _) | StructType(_) | NamedType(_): } }); } public static function replaceArrayAccess(context:RenameContext, changelist:Changelist, use:Identifier, fromName:String, types:Array, - posClosing:Int):Promise { + arrayIdentfier:Identifier):Promise { var name:String = use.name; function addChanges(type:Type) { @@ -177,8 +181,8 @@ class RenameHelper { } } var search:SearchTypeOf = { - name: name, - pos: posClosing, + name: arrayIdentfier.name, + pos: arrayIdentfier.pos.end, defineType: use.defineType }; return TypingHelper.findTypeOfIdentifier(context, search).then(function(typeHint:TypeHintType) { @@ -188,6 +192,10 @@ class RenameHelper { addChanges(type); case LibType("Null", _, [ClasspathType(type, _)]): addChanges(type); + case LibType("Null", _, [LibType("Array", _, [ClasspathType(type, _)])]): + addChanges(type); + case LibType("Array", _, [ClasspathType(type, _)]): + addChanges(type); case LibType(_, _): return; case FunctionType(_, retVal): @@ -200,15 +208,10 @@ class RenameHelper { case LibType("Null", _, [ClasspathType(type, _)]): addChanges(type); default: - #if debug - trace("TODO " + typeHint.typeHintToString()); - #end } case StructType(_): - #if debug - trace("TODO " + typeHint.typeHintToString()); - #end case UnknownType(_): + case NamedType(_): } }); } diff --git a/src/refactor/rename/RenamePackage.hx b/src/refactor/rename/RenamePackage.hx index bf8d472..ecb97fc 100644 --- a/src/refactor/rename/RenamePackage.hx +++ b/src/refactor/rename/RenamePackage.hx @@ -64,7 +64,7 @@ class RenamePackage { } switch (use.file.importsModule(packageName, mainTypeName, mainTypeName)) { case None: - case Global | SamePackage | StarImported: + case Global | ParentPackage | SamePackage | StarImported: var importPos:IdentifierPos = {fileName: use.pos.fileName, start: use.file.importInsertPos, end: use.file.importInsertPos} changelist.addChange(use.pos.fileName, InsertText('import $newMainModulName;\n', importPos, NoFormat), use); uniqueFiles.push(use.pos.fileName); diff --git a/src/refactor/rename/RenameTypeName.hx b/src/refactor/rename/RenameTypeName.hx index 60d0c79..126b64e 100644 --- a/src/refactor/rename/RenameTypeName.hx +++ b/src/refactor/rename/RenameTypeName.hx @@ -52,7 +52,7 @@ class RenameTypeName { if (alias != identifier.name) { continue; } - case Global | SamePackage | Imported | StarImported: + case Global | ParentPackage | SamePackage | Imported | StarImported: } switch (use.type) { case Abstract | Class | Enum | Interface | Typedef: @@ -76,6 +76,8 @@ class RenameTypeName { return; case StructType(_) | FunctionType(_, _): return; + case NamedType(_): + return; } if (use.name == identifier.name) { changelist.addChange(use.pos.fileName, ReplaceText(context.what.toName, use.pos, NoFormat), use); diff --git a/src/refactor/typing/TypeHintType.hx b/src/refactor/typing/TypeHintType.hx index 68a45bb..0286071 100644 --- a/src/refactor/typing/TypeHintType.hx +++ b/src/refactor/typing/TypeHintType.hx @@ -7,5 +7,6 @@ enum TypeHintType { LibType(name:String, fullName:String, typeParams:Array); FunctionType(args:Array, retVal:Null); StructType(fields:Array); + NamedType(name:String, typeHint:Null); UnknownType(name:String); } diff --git a/src/refactor/typing/TypingHelper.hx b/src/refactor/typing/TypingHelper.hx index 41bc575..7f8797a 100644 --- a/src/refactor/typing/TypingHelper.hx +++ b/src/refactor/typing/TypingHelper.hx @@ -2,6 +2,7 @@ package refactor.typing; import refactor.discover.Identifier; import refactor.discover.Type; +import refactor.discover.TypeHintFromTree; import refactor.discover.TypeList; class TypingHelper { @@ -38,7 +39,7 @@ class TypingHelper { var search:String = switch (use.file.importsModule(baseType.file.getPackage(), baseType.file.getMainModulName(), baseType.name.name)) { case None: continue; - case Global | SamePackage | Imported | StarImported: + case Global | ParentPackage | SamePackage | Imported | StarImported: baseType.name.name; case ImportedWithAlias(alias): alias; @@ -131,6 +132,11 @@ class TypingHelper { } } return true; + case [NamedType(name1, hint1), NamedType(name2, hint2)]: + if (name1 != name2) { + return false; + } + return typeHintsEqual(hint1, hint2); case [UnknownType(name1), UnknownType(name2)]: return (name1 == name2); default: @@ -153,13 +159,50 @@ class TypingHelper { public static function findTypeOfIdentifier(context:CacheAndTyperContext, searchTypeOf:SearchTypeOf):Promise { var parts:Array = searchTypeOf.name.split("."); + if (parts.length > 1) { + final type = context.typeList.getType(searchTypeOf.name); + if (type != null) { + return Promise.resolve(ClasspathType(type, [])); + } + } var part:String = parts.shift(); + switch (part) { + case "super": + final containerType = searchTypeOf.defineType; + if (containerType == null) { + return Promise.reject("cannot resolve super"); + } + final baseClasses = containerType.findAllIdentifiers(i -> i.type == Extends); + if (baseClasses.length != 1) { + return Promise.reject("cannot resolve super"); + } + final type = TypeHintFromTree.findTypeFromImports(baseClasses[0].name, context.typeList, containerType.file); + if (type == null) { + return Promise.reject("cannot resolve super"); + } + return Promise.resolve(ClasspathType(type, [])); + case "this": + if (searchTypeOf.defineType == null) { + return Promise.reject("cannot resolve this"); + } + if (parts.length > 0) { + return findTypeOfIdentifier(context, { + name: parts.join("."), + pos: searchTypeOf.pos, + defineType: searchTypeOf.defineType + }); + } + return Promise.resolve(ClasspathType(searchTypeOf.defineType, [])); + default: + } + return findFieldOrScopedLocal(context, searchTypeOf.defineType, part, searchTypeOf.pos).then(function(type:Null):Promise { var index:Int = 0; function findFieldForPart(partType:TypeHintType):Promise { if (index >= parts.length) { return Promise.resolve(partType); } + var part:String = parts[index++]; switch (partType) { case null: @@ -167,29 +210,33 @@ class TypingHelper { return Promise.reject('unable to determine type of "$part" in ${searchTypeOf.defineType?.file.name}@${searchTypeOf.pos}'); case ClasspathType(t, params): return findField(context, t, part).then(findFieldForPart); - case LibType(t, fullPath, params): - if (t != "Null") { - return Promise.reject('unable to determine type of "$part" in ${searchTypeOf.defineType.name.name}@${searchTypeOf.pos}'); - } - if (params == null || params.length != 1) { - return Promise.reject('unable to determine type of "$part" in ${searchTypeOf.defineType.name.name}@${searchTypeOf.pos}'); - } - switch (params[0]) { - case ClasspathType(t, typeParams): - return findField(context, t, part).then(findFieldForPart); - case LibType(_) | FunctionType(_) | StructType(_) | UnknownType(_): - return Promise.reject('unable to determine type of "$part" in ${searchTypeOf.defineType.name.name}@${searchTypeOf.pos}'); - } - case StructType(fields): + case LibType("Null", _, [nullType]): + return findFieldForPart(nullType); + case LibType(t, _, params): return Promise.reject('unable to determine type of "$part" in ${searchTypeOf.defineType.name.name}@${searchTypeOf.pos}'); - case FunctionType(args, retVal): + case StructType(fields): + for (field in fields) { + switch (field) { + case NamedType(name, typeHint): + if (name == part) { + return findFieldForPart(typeHint); + } + default: + } + } return Promise.reject('unable to determine type of "$part" in ${searchTypeOf.defineType.name.name}@${searchTypeOf.pos}'); + case FunctionType(_, retVal): + return findFieldForPart(retVal); case UnknownType(name): return Promise.reject('unable to determine type of "$part" in ${searchTypeOf.defineType.name.name}@${searchTypeOf.pos}'); + case NamedType(_): + return Promise.reject('unable to determine type of "$part" in ${searchTypeOf.defineType.name.name}@${searchTypeOf.pos}'); } } - return findFieldForPart(type); + return findFieldForPart(type).then(function(typeHint) { + return Promise.resolve(typeHint); + }); }); } @@ -197,144 +244,237 @@ class TypingHelper { if (containerType == null) { return Promise.resolve(null); } - return findTypeWithTyper(context, containerType.file.name, pos).catchError(function(msg):Promise { - #if debug - trace("Haxe typer failed for " + '$name in ${containerType.file.name}@$pos'); - #end - var allUses:Array = containerType.getIdentifiers(name); - var candidate:Null = null; - var fieldCandidate:Null = null; - for (use in allUses) { - switch (use.type) { - case Property | FieldVar(_) | Method(_): - fieldCandidate = use; - case TypedefField(_): - fieldCandidate = use; - case EnumField(_): - return Promise.resolve(ClasspathType(use.defineType, [])); - case ScopedLocal(scopeStart, scopeEnd, _): - if ((pos >= scopeStart) && (pos <= scopeEnd)) { - candidate = use; - } - if (pos == use.pos.start) { - candidate = use; - } - case CaseLabel(switchIdentifier): - if (use.pos.start == pos) { - return findFieldOrScopedLocal(context, containerType, switchIdentifier.name, switchIdentifier.pos.start); + + return findTypeWithBuiltIn(containerType, name, pos, context).then(function(hint) { + return Promise.resolve(hint); + }).catchError(function(msg):Promise { + // built-in failed, let's try external typer + return findTypeWithTyper(context, containerType.file.name, pos).catchError(function(msg):Promise { + #if debug + trace("built-in and external typers failed for " + '$name in ${containerType.file.name} @$pos'); + #end + return Promise.resolve(null); + }); + }); + } + + static function findTypeWithBuiltIn(containerType:Type, name:String, pos:Int, context:CacheAndTyperContext):Promise { + if (containerType == null) { + return Promise.reject("missing containing type"); + } + var allUses:Array = containerType.getIdentifiers(name); + var candidate:Null = null; + var fieldCandidate:Null = null; + for (use in allUses) { + switch (use.type) { + case Property | FieldVar(_) | Method(_): + fieldCandidate = use; + case TypedefField(_): + fieldCandidate = use; + case EnumField(_): + return Promise.resolve(ClasspathType(use.defineType, [])); + case ScopedLocal(scopeStart, scopeEnd, _): + if ((pos >= scopeStart) && (pos <= scopeEnd)) { + candidate = use; + } + if (pos == use.pos.start) { + candidate = use; + } + case CaseLabel(switchIdentifier): + if (use.pos.start <= pos && use.pos.end > pos) { + return findTypeOfIdentifier(context, { + name: switchIdentifier.name, + pos: switchIdentifier.pos.start, + defineType: containerType + }); + } + case Call(true): + if (use.pos.start <= pos && use.pos.end > pos) { + var typeCandidates = context.typeList.findTypeName(name); + for (candidateType in typeCandidates) { + switch (containerType.file.importsModule(candidateType.file.getPackage(), candidateType.file.getMainModulName(), + candidateType.name.name)) { + case None: + case ImportedWithAlias(_): + case Global | ParentPackage | SamePackage | Imported | StarImported: + return Promise.resolve(ClasspathType(candidateType, [])); + } } - default: - } - } - if (candidate == null) { - candidate = fieldCandidate; - } - if (candidate == null) { - var typeCandidates = context.typeList.findTypeName(name); - for (candidateType in typeCandidates) { - switch (containerType.file.importsModule(candidateType.file.getPackage(), candidateType.file.getMainModulName(), candidateType.name.name)) { - case None: - case ImportedWithAlias(_): - case Global | SamePackage | Imported | StarImported: - return Promise.resolve(ClasspathType(candidateType, [])); } - } - if (candidate == null) { - return Promise.resolve(null); - } + default: } - var typeHint:Null = candidate.getTypeHint(); - switch (candidate.type) { - case ScopedLocal(_, _, ForLoop(loopIdent)): - var index:Int = loopIdent.indexOf(candidate); - var changes:Array> = []; - for (child in loopIdent) { - switch (child.type) { - case ScopedLocal(_, _, ForLoop(_)): - continue; - default: - changes.push(findTypeOfIdentifier(context, { - name: child.name, - pos: child.pos.start, - defineType: containerType - }).then(function(data:TypeHintType):Promise { - switch (data) { - case null: - case ClasspathType(_, typeParams) | LibType(_, _, typeParams): - if (typeParams.length <= index) { - return Promise.reject("not enough type parameters"); - } - return Promise.resolve(typeParams[index]); - case UnknownType(_): - case StructType(_) | FunctionType(_): - } - return Promise.reject("not found"); - })); - } + } + if (candidate == null) { + candidate = fieldCandidate; + } + if (candidate == null) { + return findGlobalIdentifiers(context, name, containerType); + } + + final candidateTypeHint = candidate.getTypeHintNew(context.typeList); + + if (candidateTypeHint != null) { + return Promise.resolve(candidateTypeHint); + } + // return Promise.reject("cannot find type hint"); + + var typeHint:Null = candidate.getTypeHint(); + switch (candidate.type) { + case ScopedLocal(_, _, ForLoop(loopIdent)): + var index:Int = loopIdent.indexOf(candidate); + var changes:Array> = []; + for (child in loopIdent) { + switch (child.type) { + case ScopedLocal(_, _, ForLoop(_)): + continue; + default: + changes.push(findTypeOfIdentifier(context, { + name: child.name, + pos: child.pos.start, + defineType: containerType + }).then(function(data:TypeHintType):Promise { + switch (data) { + case null: + case ClasspathType(_, typeParams) | LibType(_, _, typeParams): + if (typeParams.length <= index) { + return Promise.reject("not enough type parameters"); + } + return Promise.resolve(typeParams[index]); + case UnknownType(_): + case StructType(_) | FunctionType(_): + case NamedType(_, _): + } + return Promise.reject("not found"); + })); } + } - var winner:Promise = cast Promise.race(changes); - return winner.catchError(function(data:TypeHintType):Promise { - if (typeHint != null) { - return typeFromTypeHint(context, typeHint); - } - return Promise.reject("type not found"); - }); - case ScopedLocal(_, _, Parameter(params)): + var winner:Promise = cast Promise.race(changes); + return winner.catchError(function(data:TypeHintType):Promise { if (typeHint != null) { - return typeFromTypeHint(context, typeHint).then(function(hint) { - return Promise.resolve(hint); - }); + return typeFromTypeHint(context, typeHint); } - var index:Int = params.indexOf(candidate); - switch (candidate.parent.type) { - case CaseLabel(switchIdentifier): - return findFieldOrScopedLocal(context, containerType, switchIdentifier.name, - switchIdentifier.pos.start).then(function(enumType:TypeHintType) { - switch (enumType) { - case null: - return Promise.resolve(null); - case ClasspathType(type, typeParams): - switch (type.name.type) { - case Enum: - var enumFields:Array = type.findAllIdentifiers((i) -> i.name == candidate.parent.name); - for (field in enumFields) { - switch (field.type) { - case EnumField(params): - if (params.length <= index) { - return Promise.resolve(null); - } - typeHint = params[index].getTypeHint(); - if (typeHint == null) { - return Promise.resolve(null); - } - return typeFromTypeHint(context, typeHint); - default: - return Promise.reject("not an enum field"); - } + return Promise.reject("type not found"); + }); + case ScopedLocal(_, _, Parameter(params)): + if (typeHint != null) { + return typeFromTypeHint(context, typeHint).then(function(hint) { + return Promise.resolve(hint); + }); + } + var index:Int = params.indexOf(candidate); + switch (candidate.parent.type) { + case CaseLabel(switchIdentifier): + return findFieldOrScopedLocal(context, containerType, switchIdentifier.name, + switchIdentifier.pos.start).then(function(enumType:TypeHintType) { + switch (enumType) { + case null: + return Promise.resolve(null); + case ClasspathType(type, typeParams): + switch (type.name.type) { + case Enum: + var enumFields:Array = type.findAllIdentifiers((i) -> i.name == candidate.parent.name); + for (field in enumFields) { + switch (field.type) { + case EnumField(params): + if (params.length <= index) { + return Promise.resolve(null); + } + return Promise.resolve(params[index].getTypeHintNew(context.typeList)); + default: + return Promise.reject("not an enum field"); } - default: + } + default: + } + case LibType(_, _, _): + return Promise.resolve(enumType); + case FunctionType(_, _): + return Promise.reject(""); + case StructType(_): + return Promise.reject(""); + case UnknownType(_): + return Promise.reject(""); + case NamedType(_): + return Promise.reject(""); + } + return Promise.resolve(enumType); + }); + default: + } + case ScopedLocal(_, _, CaseCapture(origin, index)): + return findFieldOrScopedLocal(context, containerType, origin.name, origin.pos.start).then(function(enumType:TypeHintType) { + switch (enumType) { + case ClasspathType(type, typeParams): + var fieldName = origin.name; + if (fieldName.startsWith(type.name.name + ".")) { + fieldName = fieldName.substr(type.name.name.length + 1); + } + if (fieldName.startsWith(type.fullModuleName + ".")) { + fieldName = fieldName.substr(type.fullModuleName.length + 1); + } + var uses = type.getIdentifiers(fieldName); + for (use in uses) { + switch (use.type) { + case EnumField(params): + if (params.length < index) { + return Promise.reject("not found"); } - case LibType(_, _, _): - return Promise.resolve(null); - case FunctionType(_, _): - return Promise.resolve(null); - case StructType(_): - return Promise.resolve(null); - case UnknownType(_): - return Promise.resolve(null); + return Promise.resolve(params[index].getTypeHintNew(context.typeList)); + default: } - return Promise.resolve(enumType); - }); + } default: } - default: + return Promise.reject("cannot resolve type of " + origin.name); + }); + + default: + } + if (typeHint != null) { + return typeFromTypeHint(context, typeHint); + } + return Promise.reject("cannot resolve type of " + name); + } + + static function findGlobalIdentifiers(context:CacheAndTyperContext, name:String, containerType:Type):Promise { + final matches:Array = []; + var typeCandidates = context.typeList.findTypeName(name); + for (candidateType in typeCandidates) { + switch (containerType.file.importsModule(candidateType.file.getPackage(), candidateType.file.getMainModulName(), candidateType.name.name)) { + case None: + case ImportedWithAlias(_): + case Global | ParentPackage | SamePackage | Imported | StarImported: + matches.push(ClasspathType(candidateType, [])); + break; } - if (typeHint != null) { - return typeFromTypeHint(context, typeHint); + } + final allUses = context.nameMap.getIdentifiers(name); + for (use in allUses) { + switch (use.type) { + case ModuleLevelStaticVar: + case ModuleLevelStaticMethod: + case FieldVar(false) | Method(false): + // TODO: is it a base class of using type? + case EnumField(_): + switch (containerType.file.importsModule(use.file.getPackage(), use.file.getMainModulName(), use.defineType.name.name)) { + case None: + case ImportedWithAlias(_): + case Global | ParentPackage | SamePackage | Imported | StarImported: + matches.push(ClasspathType(use.defineType, [])); + break; + } + default: } - return Promise.resolve(null); - }); + } + return switch (matches.length) { + case 0: + Promise.reject("no candidate found"); + case 1: + Promise.resolve(matches[0]); + default: + Promise.reject("too many candidates found " + matches.length); + } } public static function findField(context:CacheAndTyperContext, containerType:Type, name:String):Promise { @@ -350,7 +490,6 @@ class TypingHelper { } if ((candidate == null) || (candidate.uses == null)) { switch (containerType.name.type) { - // case Abstract: case Class: var baseType:Null = findBaseClass(context.typeList, containerType); if (baseType == null) { @@ -363,6 +502,10 @@ class TypingHelper { } return Promise.resolve(null); } + final typeHint = candidate.getTypeHintNew(context.typeList); + if (typeHint != null) { + return Promise.resolve(typeHint); + } for (use in candidate.uses) { switch (use.type) { case TypeHint: @@ -381,7 +524,7 @@ class TypingHelper { switch (type.file.importsModule(candidate.file.getPackage(), candidate.file.getMainModulName(), candidate.name.name)) { case None: case ImportedWithAlias(_): - case Global | SamePackage | Imported | StarImported: + case Global | ParentPackage | SamePackage | Imported | StarImported: return candidate; } } @@ -416,7 +559,7 @@ class TypingHelper { switch (hint.file.importsModule(type.file.getPackage(), type.file.getMainModulName(), type.name.name)) { case None: case ImportedWithAlias(_): - case Global | SamePackage | Imported | StarImported: + case Global | ParentPackage | SamePackage | Imported | StarImported: // TODO recursive type params!!! typeParams.push(ClasspathType(type, [])); } @@ -441,7 +584,7 @@ class TypingHelper { switch (hint.file.importsModule(type.file.getPackage(), type.file.getMainModulName(), type.name.name)) { case None: case ImportedWithAlias(_): - case Global | SamePackage | Imported | StarImported: + case Global | ParentPackage | SamePackage | Imported | StarImported: return Promise.resolve(ClasspathType(type, typeParams)); } } diff --git a/test/TestMain.hx b/test/TestMain.hx index 904a516..df5a04b 100644 --- a/test/TestMain.hx +++ b/test/TestMain.hx @@ -10,6 +10,8 @@ import refactor.rename.RenameModuleLevelStaticTest; import refactor.rename.RenamePackageTest; import refactor.rename.RenameScopedLocalTest; import refactor.rename.RenameTypedefTest; +import refactor.typing.TypeHintFromTreeTest; +import refactor.typing.TypingTest; import utest.Runner; import utest.ui.text.DiagnosticsReport; @@ -18,6 +20,8 @@ class TestMain { var tests:Array = [ new RenameClassTest(), new RenameEnumTest(), + new TypingTest(), + new TypeHintFromTreeTest(), new RenameImportAliasTest(), new RenameInterfaceTest(), new RenameModuleLevelStaticTest(), diff --git a/test/import.hx b/test/import.hx index 70a3a5a..1ee9a83 100644 --- a/test/import.hx +++ b/test/import.hx @@ -4,3 +4,4 @@ import utest.Async; import utest.ITest; using StringTools; +using refactor.PrintHelper; diff --git a/test/refactor/refactor/RefactorClassTest.hx b/test/refactor/refactor/RefactorClassTest.hx index 918e205..beaec40 100644 --- a/test/refactor/refactor/RefactorClassTest.hx +++ b/test/refactor/refactor/RefactorClassTest.hx @@ -5,18 +5,14 @@ class RefactorClassTest extends RefactorTestBase { setupTestSources(["testcases/classes"]); } + // Extract Type + function testFailExtractTypChildClass(async:Async) { failCanRefactor(RefactorExtractType, {fileName: "testcases/classes/ChildClass.hx", posStart: 30, posEnd: 30}, "unsupported"); - failRefactor(RefactorExtractType, {fileName: "testcases/classes/ChildClass.hx", posStart: 30, posEnd: 30}, "failed to collect extract type data", + failRefactor(RefactorExtractType, {fileName: "testcases/classes/ChildClass.hx", posStart: 30, posEnd: 30}, "failed to collect data for extract type", async); } - function testFailExtractInterfaceNoClass(async:Async) { - failCanRefactor(RefactorExtractInterface, {fileName: "testcases/classes/ChildClass.hx", posStart: 875, posEnd: 875}, "unsupported"); - failRefactor(RefactorExtractInterface, {fileName: "testcases/classes/ChildClass.hx", posStart: 875, posEnd: 875}, - "failed to collect extract interface data", async); - } - function testExtractTypeListOfChilds(async:Async) { var edits:Array = [ makeRemoveTestEdit("testcases/classes/ChildClass.hx", 860, 901), @@ -60,9 +56,9 @@ class RefactorClassTest extends RefactorTestBase { + " public static var printFunc:PrintFunc;\n" + "}", 0, Format(0, false)), - makeRemoveTestEdit("testcases/classes/StaticUsing.hx", 484, 566), + makeRemoveTestEdit("testcases/classes/StaticUsing.hx", 557, 639), ]; - checkRefactor(RefactorExtractType, {fileName: "testcases/classes/StaticUsing.hx", posStart: 518, posEnd: 518}, edits, async); + checkRefactor(RefactorExtractType, {fileName: "testcases/classes/StaticUsing.hx", posStart: 590, posEnd: 590}, edits, async); } function testExtractTypeNotDocModule(async:Async) { @@ -87,6 +83,14 @@ class RefactorClassTest extends RefactorTestBase { checkRefactor(RefactorExtractType, {fileName: "testcases/classes/DocModule.hx", posStart: 73, posEnd: 73}, edits, async); } + // Extract Interface + + function testFailExtractInterfaceNoClass(async:Async) { + failCanRefactor(RefactorExtractInterface, {fileName: "testcases/classes/ChildClass.hx", posStart: 875, posEnd: 875}, "unsupported"); + failRefactor(RefactorExtractInterface, {fileName: "testcases/classes/ChildClass.hx", posStart: 875, posEnd: 875}, + "failed to collect data for extract interface", async); + } + function testExtractInterfaceBaseClass(async:Async) { var edits:Array = [ makeInsertTestEdit("testcases/classes/BaseClass.hx", " implements IBaseClass", 33), @@ -110,6 +114,14 @@ class RefactorClassTest extends RefactorTestBase { checkRefactor(RefactorExtractInterface, {fileName: "testcases/classes/BaseClass.hx", posStart: 27, posEnd: 27}, edits, async); } + // Rewrite Finals to Vars + + function testRewriteFinalsToVarsJsonClass(async:Async) { + failCanRefactor(RefactorRewriteVarsToFinals(false), {fileName: "testcases/classes/JsonClass.hx", posStart: 18, posEnd: 696}, "unsupported"); + failRefactor(RefactorRewriteVarsToFinals(false), {fileName: "testcases/classes/JsonClass.hx", posStart: 18, posEnd: 696}, + "failed to collect data for rewrite vars/finals", async); + } + function testRewriteFinalsToVarsPrinter(async:Async) { var edits:Array = [ makeReplaceTestEdit("testcases/classes/Printer.hx", "var", 147, 152, NoFormat), @@ -118,10 +130,12 @@ class RefactorClassTest extends RefactorTestBase { checkRefactor(RefactorRewriteVarsToFinals(false), {fileName: "testcases/classes/Printer.hx", posStart: 129, posEnd: 1079}, edits, async); } + // Rewrite Vars To Finals + function testRewriteVarsToFinalsPrinter(async:Async) { failCanRefactor(RefactorRewriteVarsToFinals(true), {fileName: "testcases/classes/Printer.hx", posStart: 129, posEnd: 1079}, "unsupported"); failRefactor(RefactorRewriteVarsToFinals(true), {fileName: "testcases/classes/Printer.hx", posStart: 129, posEnd: 1079}, - "failed to collect rewrite vars/finals data", async); + "failed to collect data for rewrite vars/finals", async); } function testRewriteVarsToFinalsJsonClass(async:Async) { @@ -135,9 +149,31 @@ class RefactorClassTest extends RefactorTestBase { checkRefactor(RefactorRewriteVarsToFinals(true), {fileName: "testcases/classes/JsonClass.hx", posStart: 18, posEnd: 696}, edits, async); } - function testRewriteFinalsToVarsJsonClass(async:Async) { - failCanRefactor(RefactorRewriteVarsToFinals(false), {fileName: "testcases/classes/JsonClass.hx", posStart: 18, posEnd: 696}, "unsupported"); - failRefactor(RefactorRewriteVarsToFinals(false), {fileName: "testcases/classes/JsonClass.hx", posStart: 18, posEnd: 696}, - "failed to collect rewrite vars/finals data", async); + // Wrap with Try…Catch + + function testFailTryCatchCollectDataEmptyFile(async:Async) { + failCanRefactor(RefactorRewriteWrapWithTryCatch, {fileName: "testcases/classes/BaseClass.hx", posStart: 156, posEnd: 263}, "unsupported"); + failRefactor(RefactorRewriteWrapWithTryCatch, {fileName: "testcases/classes/BaseClass.hx", posStart: 156, posEnd: 263}, + "failed to collect data for rewrite wrap with try catch", async); + } + + function testRewriteWrapTryCatchJsonClass(async:Async) { + var edits:Array = [ + makeReplaceTestEdit("testcases/classes/JsonClass.hx", + "try {\n" + + "var newgroup:Null = new JsonClass(group.id, group.type, group.width);\n" + + " newgroup.id = group.id;\n" + + " newgroup.type = group.type;\n" + + " newgroup.width = group.width;\n" + + " newgroup.maxWidth = group.maxWidth;\n\n" + + " return newgroup;\n" + + "}\n" + + "catch (e:haxe.Exception) {\n" + + "// TODO: handle exception\n" + + "trace (e.details());\n" + + "}", + 301, 527, Format(2, true)), + ]; + checkRefactor(RefactorRewriteWrapWithTryCatch, {fileName: "testcases/classes/JsonClass.hx", posStart: 300, posEnd: 527}, edits, async); } } diff --git a/test/refactor/refactor/RefactorExtractConstructorParams.hx b/test/refactor/refactor/RefactorExtractConstructorParams.hx index a86ef5f..a9aee1f 100644 --- a/test/refactor/refactor/RefactorExtractConstructorParams.hx +++ b/test/refactor/refactor/RefactorExtractConstructorParams.hx @@ -8,19 +8,19 @@ class RefactorExtractConstructorParams extends RefactorTestBase { function testFailExtractParamsNotConstructor(async:Async) { failCanRefactor(RefactorExtractConstructorParams(false), {fileName: "testcases/constructor/Point.hx", posStart: 148, posEnd: 148}, "unsupported"); failRefactor(RefactorExtractConstructorParams(false), {fileName: "testcases/constructor/Point.hx", posStart: 148, posEnd: 148}, - "failed to collect extract method data", async); + "failed to collect data for extract constructor params", async); failCanRefactor(RefactorExtractConstructorParams(true), {fileName: "testcases/constructor/Point.hx", posStart: 148, posEnd: 148}, "unsupported"); failRefactor(RefactorExtractConstructorParams(true), {fileName: "testcases/constructor/Point.hx", posStart: 148, posEnd: 148}, - "failed to collect extract method data", async); + "failed to collect data for extract constructor params", async); } function testExtractParamsEmptyNew(async:Async) { failCanRefactor(RefactorExtractConstructorParams(false), {fileName: "testcases/constructor/Point.hx", posStart: 247, posEnd: 247}, "unsupported"); failRefactor(RefactorExtractConstructorParams(false), {fileName: "testcases/constructor/Point.hx", posStart: 247, posEnd: 247}, - "failed to collect extract method data", async); + "failed to collect data for extract constructor params", async); failCanRefactor(RefactorExtractConstructorParams(true), {fileName: "testcases/constructor/Point.hx", posStart: 247, posEnd: 247}, "unsupported"); failRefactor(RefactorExtractConstructorParams(true), {fileName: "testcases/constructor/Point.hx", posStart: 247, posEnd: 247}, - "failed to collect extract method data", async); + "failed to collect data for extract constructor params", async); } function testExtractParamsYAndTextAsVar(async:Async) { diff --git a/test/refactor/refactor/RefactorExtractMethodTest.hx b/test/refactor/refactor/RefactorExtractMethodTest.hx index e36a342..b1c697f 100644 --- a/test/refactor/refactor/RefactorExtractMethodTest.hx +++ b/test/refactor/refactor/RefactorExtractMethodTest.hx @@ -7,35 +7,38 @@ class RefactorExtractMethodTest extends RefactorTestBase { function testFailCollectDataEmptyFile(async:Async) { failCanRefactor(RefactorExtractMethod, {fileName: "testcases/methods/Empty.hx", posStart: 80, posEnd: 131}, "unsupported"); - failRefactor(RefactorExtractMethod, {fileName: "testcases/methods/Empty.hx", posStart: 80, posEnd: 131}, "failed to collect extract method data", + failRefactor(RefactorExtractMethod, {fileName: "testcases/methods/Empty.hx", posStart: 80, posEnd: 131}, "failed to collect data for extract method", async); } function testFailCollectDataEmptyRange(async:Async) { failCanRefactor(RefactorExtractMethod, {fileName: "testcases/methods/Main.hx", posStart: 0, posEnd: 0}, "unsupported"); - failRefactor(RefactorExtractMethod, {fileName: "testcases/methods/Main.hx", posStart: 0, posEnd: 0}, "failed to collect extract method data", async); + failRefactor(RefactorExtractMethod, {fileName: "testcases/methods/Main.hx", posStart: 0, posEnd: 0}, "failed to collect data for extract method", + async); } function testFailCollectData(async:Async) { failCanRefactor(RefactorExtractMethod, {fileName: "testcases/methods/Main.hx", posStart: 80, posEnd: 131}, "unsupported"); - failRefactor(RefactorExtractMethod, {fileName: "testcases/methods/Main.hx", posStart: 80, posEnd: 131}, "failed to collect extract method data", async); + failRefactor(RefactorExtractMethod, {fileName: "testcases/methods/Main.hx", posStart: 80, posEnd: 131}, "failed to collect data for extract method", + async); } function testFailCollectDataReverse(async:Async) { failCanRefactor(RefactorExtractMethod, {fileName: "testcases/methods/Main.hx", posStart: 131, posEnd: 80}, "unsupported"); - failRefactor(RefactorExtractMethod, {fileName: "testcases/methods/Main.hx", posStart: 131, posEnd: 80}, "failed to collect extract method data", async); + failRefactor(RefactorExtractMethod, {fileName: "testcases/methods/Main.hx", posStart: 131, posEnd: 80}, "failed to collect data for extract method", + async); } function testFailNoParentFunction(async:Async) { failCanRefactor(RefactorExtractMethod, {fileName: "testcases/methods/Main.hx", posStart: 1962, posEnd: 1999}, "unsupported"); - failRefactor(RefactorExtractMethod, {fileName: "testcases/methods/Main.hx", posStart: 1962, posEnd: 1999}, "failed to collect extract method data", - async); + failRefactor(RefactorExtractMethod, {fileName: "testcases/methods/Main.hx", posStart: 1962, posEnd: 1999}, + "failed to collect data for extract method", async); } function testFailAssignmentInLocalFunction(async:Async) { failCanRefactor(RefactorExtractMethod, {fileName: "testcases/methods/LambdaExample.hx", posStart: 675, posEnd: 680}, "unsupported"); failRefactor(RefactorExtractMethod, {fileName: "testcases/methods/LambdaExample.hx", posStart: 675, posEnd: 680}, - "failed to collect extract method data", async); + "failed to collect data for extract method", async); } function testSimpleNoReturns(async:Async) { @@ -468,7 +471,8 @@ class RefactorExtractMethodTest extends RefactorTestBase { var edits:Array = [ makeReplaceTestEdit("testcases/methods/PersonHandler.hx", "handleExtract(person);", 113, 161, Format(2, true)), makeInsertTestEdit("testcases/methods/PersonHandler.hx", - "function handleExtract(person:Any) {\n" + "return \"Name: \" + person.name + \", Age: \" + person.age;\n" + "}\n", 180, Format(1, false)), + "function handleExtract(person:{name:String, age:Int}) {\n" + "return \"Name: \" + person.name + \", Age: \" + person.age;\n" + "}\n", 180, + Format(1, false)), ]; addTypeHint("testcases/methods/PersonHandler.hx", 75, LibType("Int", "Int", [])); addTypeHint("testcases/methods/PersonHandler.hx", 95, LibType("String", "String", [])); @@ -479,7 +483,7 @@ class RefactorExtractMethodTest extends RefactorTestBase { var edits:Array = [ makeReplaceTestEdit("testcases/methods/PersonHandler.hx", "var info = handleExtract(person);", 102, 161, Format(2, true)), makeInsertTestEdit("testcases/methods/PersonHandler.hx", - "function handleExtract(person:Any) {\n" + "function handleExtract(person:{name:String, age:Int}) {\n" + "var info = \"Name: \" + person.name + \", Age: \" + person.age;\n" + "return info;\n" + "}\n", @@ -929,7 +933,7 @@ class RefactorExtractMethodTest extends RefactorTestBase { function testSomeHelper(async:Async) { var edits:Array = [ - makeReplaceTestEdit("testcases/methods/SomeHelper.hx", "arrayAccessExtract(types, type, use, fromName, changelist, context);", 808, 1155, + makeReplaceTestEdit("testcases/methods/SomeHelper.hx", "arrayAccessExtract(types, type, use, fromName, changelist, context);", 821, 1168, Format(5, true)), makeInsertTestEdit("testcases/methods/SomeHelper.hx", "static function arrayAccessExtract(types:Array, type:Type, use:Identifier, fromName:String, changelist:Changelist, context:RenameContext):Void {\n" @@ -946,9 +950,9 @@ class RefactorExtractMethodTest extends RefactorTestBase { + " changelist.addChange(use.pos.fileName, ReplaceText(context.what.toName, pos, NoFormat), use);\n" + " }\n" + "}\n", - 1277, Format(1, false)), + 1313, Format(1, false)), ]; - addTypeHint("testcases/methods/SomeHelper.hx", 794, LibType("Type", "Type", [])); - checkRefactor(RefactorExtractMethod, {fileName: "testcases/methods/SomeHelper.hx", posStart: 806, posEnd: 1155}, edits, async); + addTypeHint("testcases/methods/SomeHelper.hx", 807, LibType("Type", "Type", [])); + checkRefactor(RefactorExtractMethod, {fileName: "testcases/methods/SomeHelper.hx", posStart: 820, posEnd: 1168}, edits, async); } } diff --git a/test/refactor/refactor/RefactorTestBase.hx b/test/refactor/refactor/RefactorTestBase.hx index d049a87..d50f6b3 100644 --- a/test/refactor/refactor/RefactorTestBase.hx +++ b/test/refactor/refactor/RefactorTestBase.hx @@ -52,7 +52,7 @@ class RefactorTestBase extends TestBase { function doCanRefactor(refactorType:RefactorType, what:RefactorWhat, edits:Array, pos:PosInfos):Promise { var editList:TestEditList = new TestEditList(); - var result = Refactoring.canRefactor(refactorType, { + final context:CanRefactorContext = { nameMap: usageContext.nameMap, fileList: usageContext.fileList, typeList: usageContext.typeList, @@ -60,10 +60,12 @@ class RefactorTestBase extends TestBase { verboseLog: function(text:String, ?pos:PosInfos) { Sys.println('${pos.fileName}:${pos.lineNumber}: $text'); }, - typer: null, + typer: typer, converter: (string, byteOffset) -> byteOffset, fileReader: fileReader, - }); + }; + final isRangeSameScope:Bool = RefactorHelper.rangeInSameScope(context); + var result = Refactoring.canRefactor(refactorType, context, isRangeSameScope); return switch (result) { case Unsupported: Promise.reject("unsupported"); diff --git a/test/refactor/refactor/RefactorTypedefTest.hx b/test/refactor/refactor/RefactorTypedefTest.hx index 8b9efb7..db91513 100644 --- a/test/refactor/refactor/RefactorTypedefTest.hx +++ b/test/refactor/refactor/RefactorTypedefTest.hx @@ -51,6 +51,6 @@ class RefactorTypedefTest extends RefactorTestBase { failCanRefactor(RefactorRewriteVarsToFinals(false), {fileName: "testcases/typedefs/codedata/TestFormatterInputData.hx", posStart: 245, posEnd: 473}, "unsupported"); failRefactor(RefactorRewriteVarsToFinals(false), {fileName: "testcases/typedefs/codedata/TestFormatterInputData.hx", posStart: 245, posEnd: 473}, - "failed to collect rewrite vars/finals data", async); + "failed to collect data for rewrite vars/finals", async); } } diff --git a/test/refactor/rename/RenameClassTest.hx b/test/refactor/rename/RenameClassTest.hx index f209a6e..57f6710 100644 --- a/test/refactor/rename/RenameClassTest.hx +++ b/test/refactor/rename/RenameClassTest.hx @@ -26,6 +26,8 @@ class RenameClassTest extends RenameTestBase { makeReplaceTestEdit("testcases/classes/pack/UseChild.hx", "addData", 453, 464), makeReplaceTestEdit("testcases/classes/pack/UseChild.hx", "addData", 641, 652), ]; + addTypeHint("testcases/classes/pack/UseDocModule.hx", 261, + FunctionType([], ClasspathType(usageContext.typeList.getType("classes.DocModule.NotDocModule"), []))); checkRename({fileName: "testcases/classes/BaseClass.hx", toName: "addData", pos: 128}, edits, async); } @@ -66,8 +68,8 @@ class RenameClassTest extends RenameTestBase { makeReplaceTestEdit("testcases/classes/EnumType.hx", "ItemClass", 75, 85), makeReplaceTestEdit("testcases/classes/StaticUsing.hx", "ItemClass", 142, 152), makeReplaceTestEdit("testcases/classes/StaticUsing.hx", "ItemClass", 159, 169), - makeReplaceTestEdit("testcases/classes/StaticUsing.hx", "ItemClass", 264, 274), - makeReplaceTestEdit("testcases/classes/StaticUsing.hx", "ItemClass", 355, 365), + makeReplaceTestEdit("testcases/classes/StaticUsing.hx", "ItemClass", 272, 282), + makeReplaceTestEdit("testcases/classes/StaticUsing.hx", "ItemClass", 363, 373), makeReplaceTestEdit("testcases/classes/pack/SecondChildHelper.hx", "ItemClass", 38, 48), makeReplaceTestEdit("testcases/classes/pack/SecondChildHelper.hx", "ItemClass", 111, 121), makeReplaceTestEdit("testcases/classes/pack/SecondChildHelper.hx", "ItemClass", 163, 173), @@ -92,6 +94,7 @@ class RenameClassTest extends RenameTestBase { makeInsertTestEdit("testcases/classes/ChildHelper.hx", "import classes.pack.ChildClass;\n", 18), makeInsertTestEdit("testcases/classes/EnumType.hx", "import classes.pack.ChildClass;\n", 18), makeInsertTestEdit("testcases/classes/StaticUsing.hx", "import classes.pack.ChildClass;\n", 18), + makeReplaceTestEdit("testcases/classes/StaticUsing.hx", "classes.pack.ChildClass", 264, 282), makeReplaceTestEdit("testcases/classes/pack/SecondChildHelper.hx", "classes.pack.ChildClass", 30, 48), makeReplaceTestEdit("testcases/classes/pack/UseChild.hx", "classes.pack.ChildClass", 48, 66), ]; @@ -126,8 +129,8 @@ class RenameClassTest extends RenameTestBase { public function testRenameStaticExtensionPrint(async:Async) { var edits:Array = [ makeReplaceTestEdit("testcases/classes/StaticUsing.hx", "printChild", 210, 215), - makeReplaceTestEdit("testcases/classes/StaticUsing.hx", "printChild", 368, 373), - makeReplaceTestEdit("testcases/classes/StaticUsing.hx", "printChild", 386, 391), + makeReplaceTestEdit("testcases/classes/StaticUsing.hx", "printChild", 376, 381), + makeReplaceTestEdit("testcases/classes/StaticUsing.hx", "printChild", 394, 399), makeReplaceTestEdit("testcases/classes/pack/SecondChildHelper.hx", "printChild", 151, 156), ]; checkRename({fileName: "testcases/classes/pack/SecondChildHelper.hx", toName: "printChild", pos: 153}, edits, async); @@ -135,7 +138,7 @@ class RenameClassTest extends RenameTestBase { public function testRenameStaticExtensionPrintText(async:Async) { var edits:Array = [ - makeReplaceTestEdit("testcases/classes/StaticUsing.hx", "logText", 323, 332), + makeReplaceTestEdit("testcases/classes/StaticUsing.hx", "logText", 331, 340), makeReplaceTestEdit("testcases/classes/pack/SecondChildHelper.hx", "logText", 225, 234), ]; checkRename({fileName: "testcases/classes/pack/SecondChildHelper.hx", toName: "logText", pos: 229}, edits, async); @@ -171,13 +174,8 @@ class RenameClassTest extends RenameTestBase { makeReplaceTestEdit("testcases/classes/ChildClass.hx", "id", 796, 800), makeReplaceTestEdit("testcases/classes/MyIdentifier.hx", "id", 228, 232), ]; - addTypeHint("testcases/classes/ChildClass.hx", 448, ClasspathType(usageContext.typeList.getType("classes.MyIdentifier"), [])); - addTypeHint("testcases/classes/ChildClass.hx", 462, ClasspathType(usageContext.typeList.getType("classes.MyIdentifier"), [])); - addTypeHint("testcases/classes/ChildClass.hx", 510, ClasspathType(usageContext.typeList.getType("classes.MyIdentifier"), [])); - addTypeHint("testcases/classes/ChildClass.hx", 704, ClasspathType(usageContext.typeList.getType("classes.MyIdentifier"), [])); - addTypeHint("testcases/classes/ChildClass.hx", 794, ClasspathType(usageContext.typeList.getType("classes.MyIdentifier"), [])); - checkRename({fileName: "testcases/classes/MyIdentifier.hx", toName: "id", pos: 229}, edits, async, true); + checkRename({fileName: "testcases/classes/MyIdentifier.hx", toName: "id", pos: 229}, edits, async); } public function testRenameJsonClassWidth(async:Async) { @@ -252,10 +250,10 @@ class RenameClassTest extends RenameTestBase { public function testRenameStaticUsingConstructorCall(async:Async) { var edits:Array = []; - failCanRename({fileName: "testcases/classes/StaticUsing.hx", toName: "NewChildClass", pos: 359}, - "renaming not supported for ChildClass testcases/classes/StaticUsing.hx@355-365 (Call(true))"); - failRename({fileName: "testcases/classes/StaticUsing.hx", toName: "NewChildClass", pos: 359}, - "renaming not supported for ChildClass testcases/classes/StaticUsing.hx@355-365 (Call(true))", async); + failCanRename({fileName: "testcases/classes/StaticUsing.hx", toName: "NewChildClass", pos: 364}, + "renaming not supported for ChildClass testcases/classes/StaticUsing.hx@363-373 (Call(true))"); + failRename({fileName: "testcases/classes/StaticUsing.hx", toName: "NewChildClass", pos: 364}, + "renaming not supported for ChildClass testcases/classes/StaticUsing.hx@363-373 (Call(true))", async); } public function testRenameBaseClassParamterWithShadow2(async:Async) { @@ -394,14 +392,9 @@ class RenameClassTest extends RenameTestBase { makeReplaceTestEdit("testcases/classes/pack/UseDocModule.hx", "doMore", 224, 235), makeReplaceTestEdit("testcases/classes/pack/UseDocModule.hx", "doMore", 264, 275), ]; - addTypeHint("testcases/classes/pack/UseDocModule.hx", 222, ClasspathType(usageContext.typeList.getType("classes.DocModule.NotDocModule"), [])); addTypeHint("testcases/classes/pack/UseDocModule.hx", 261, FunctionType([], ClasspathType(usageContext.typeList.getType("classes.DocModule.NotDocModule"), []))); - addTypeHint("testcases/classes/pack/UseChild.hx", 215, ClasspathType(usageContext.typeList.getType("classes.ChildClass"), [])); - addTypeHint("testcases/classes/pack/UseChild.hx", 332, ClasspathType(usageContext.typeList.getType("classes.ChildClass"), [])); - addTypeHint("testcases/classes/pack/UseChild.hx", 440, ClasspathType(usageContext.typeList.getType("classes.ChildClass"), [])); - addTypeHint("testcases/classes/pack/UseChild.hx", 628, ClasspathType(usageContext.typeList.getType("classes.ChildClass"), [])); - checkRename({fileName: "testcases/classes/DocModule.hx", toName: "doMore", pos: 128}, edits, async, true); + checkRename({fileName: "testcases/classes/DocModule.hx", toName: "doMore", pos: 128}, edits, async); } } diff --git a/test/refactor/rename/RenameTestBase.hx b/test/refactor/rename/RenameTestBase.hx index b51ef44..6173c6e 100644 --- a/test/refactor/rename/RenameTestBase.hx +++ b/test/refactor/rename/RenameTestBase.hx @@ -9,9 +9,9 @@ import refactor.Rename; import refactor.TestEditableDocument; class RenameTestBase extends TestBase { - function checkRename(what:RenameWhat, edits:Array, async:Async, withTyper:Bool = false, ?pos:PosInfos) { + function checkRename(what:RenameWhat, edits:Array, async:Async, ?pos:PosInfos) { try { - doCanRename(what, edits, withTyper, pos).catchError(function(failure) { + doCanRename(what, edits, pos).catchError(function(failure) { Assert.fail('$failure', pos); }).finally(function() { async.done(); @@ -21,9 +21,9 @@ class RenameTestBase extends TestBase { } } - function failCanRename(what:RenameWhat, expected:String, ?async:Async, withTyper:Bool = false, ?pos:PosInfos) { + function failCanRename(what:RenameWhat, expected:String, ?async:Async, ?pos:PosInfos) { try { - doCanRename(what, [], withTyper, pos).then(function(success:RefactorResult) { + doCanRename(what, [], pos).then(function(success:RefactorResult) { Assert.equals(expected, PrintHelper.printRenameResult(success), pos); }).catchError(function(failure) { Assert.equals(expected, '$failure', pos); @@ -37,9 +37,9 @@ class RenameTestBase extends TestBase { } } - function failRename(what:RenameWhat, expected:String, async:Async, withTyper:Bool = false, ?pos:PosInfos) { + function failRename(what:RenameWhat, expected:String, async:Async, ?pos:PosInfos) { try { - doRename(what, [], withTyper, pos).then(function(success:RefactorResult) { + doRename(what, [], pos).then(function(success:RefactorResult) { Assert.equals(expected, PrintHelper.printRenameResult(success), pos); }).catchError(function(failure) { Assert.equals(expected, '$failure', pos); @@ -51,7 +51,7 @@ class RenameTestBase extends TestBase { } } - function doCanRename(what:RenameWhat, edits:Array, withTyper:Bool = false, pos:PosInfos):Promise { + function doCanRename(what:RenameWhat, edits:Array, pos:PosInfos):Promise { var editList:TestEditList = new TestEditList(); return Rename.canRename({ nameMap: usageContext.nameMap, @@ -61,15 +61,15 @@ class RenameTestBase extends TestBase { verboseLog: function(text:String, ?pos:PosInfos) { Sys.println('${pos.fileName}:${pos.lineNumber}: $text'); }, - typer: withTyper ? typer : null, + typer: typer, converter: (string, byteOffset) -> byteOffset, fileReader: fileReader, }).then(function(success:CanRenameResult) { - return doRename(what, edits, withTyper, pos); + return doRename(what, edits, pos); }); } - function doRename(what:RenameWhat, edits:Array, withTyper:Bool = false, pos:PosInfos):Promise { + function doRename(what:RenameWhat, edits:Array, pos:PosInfos):Promise { var editList:TestEditList = new TestEditList(); return Rename.rename({ nameMap: usageContext.nameMap, @@ -81,7 +81,7 @@ class RenameTestBase extends TestBase { verboseLog: function(text:String, ?pos:PosInfos) { Sys.println('${pos.fileName}:${pos.lineNumber}: $text'); }, - typer: withTyper ? typer : null, + typer: typer, converter: (string, byteOffset) -> byteOffset, fileReader: fileReader, }).then(function(success:RefactorResult) { diff --git a/test/refactor/rename/RenameTypedefTest.hx b/test/refactor/rename/RenameTypedefTest.hx index 7af9ba6..8e0c293 100644 --- a/test/refactor/rename/RenameTypedefTest.hx +++ b/test/refactor/rename/RenameTypedefTest.hx @@ -94,6 +94,6 @@ class RenameTypedefTest extends RenameTestBase { makeReplaceTestEdit("testcases/typedefs/TestFormatter.hx", "indentationOffset", 507, 519), makeReplaceTestEdit("testcases/typedefs/codedata/TestFormatterInputData.hx", "indentationOffset", 456, 468), ]; - checkRename({fileName: "testcases/typedefs/codedata/TestFormatterInputData.hx", toName: "indentationOffset", pos: 462}, edits, async, true); + checkRename({fileName: "testcases/typedefs/codedata/TestFormatterInputData.hx", toName: "indentationOffset", pos: 462}, edits, async); } } diff --git a/test/refactor/typing/TypeHintFromTreeTest.hx b/test/refactor/typing/TypeHintFromTreeTest.hx new file mode 100644 index 0000000..9da6604 --- /dev/null +++ b/test/refactor/typing/TypeHintFromTreeTest.hx @@ -0,0 +1,51 @@ +package refactor.typing; + +import haxe.PosInfos; +import js.lib.Promise; + +class TypeHintFromTreeTest extends TestBase { + final intType:TypeHintType = LibType("Int", "Int", []); + final boolType:TypeHintType = LibType("Bool", "Bool", []); + final stringType:TypeHintType = LibType("String", "String", []); + final voidType:TypeHintType = LibType("Void", "Void", []); + + function testTypeHints() { + setupTestSources(["testcases/typehints"]); + + final myTypeType:TypeHintType = makeCp("typehints.Main.MyType", []); + final newTypeMyTypeTypeInt:TypeHintType = makeCp("typehints.Main.NewType", [myTypeType, intType]); + final newTypeMyTypeTypeBool:TypeHintType = makeCp("typehints.Main.NewType", [myTypeType, boolType]); + + findAndCompareTypeHint("intIdent", intType); + findAndCompareTypeHint("boolIdent", boolType); + findAndCompareTypeHint("myTypeIdent", myTypeType); + findAndCompareTypeHint("newTypeIdent", newTypeMyTypeTypeInt); + findAndCompareTypeHint("nullNewTypeIdent", LibType("Null", "Null", [newTypeMyTypeTypeInt])); + findAndCompareTypeHint("fullNullNewTypeIdent", LibType("Null", "Null", [newTypeMyTypeTypeBool])); + findAndCompareTypeHint("intStringBoolIdentOld", FunctionType([intType, stringType], boolType)); + findAndCompareTypeHint("intStringBoolIdentNew", FunctionType([intType, stringType], boolType)); + findAndCompareTypeHint("loopVoidIdent", FunctionType([NamedType("loop", intType)], voidType)); + findAndCompareTypeHint("loop2VoidIdent", FunctionType([NamedType("loop", intType), NamedType("loop2", intType)], voidType)); + findAndCompareTypeHint("intStringVoidNullIdent", FunctionType([intType, stringType], voidType)); + } + + @:access(refactor.typing.TypingHelper) + function findAndCompareTypeHint(fullTypeName:String, expectedTypeHint:TypeHintType, ?pos:PosInfos) { + final idents = usageContext.nameMap.getIdentifiers(fullTypeName); + Assert.equals(1, idents.length, pos); + if (idents.length == 1) { + final actualTypeHint = idents[0].getTypeHintNew(usageContext.typeList); + Assert.notNull(actualTypeHint, pos); + Assert.equals(expectedTypeHint.typeHintToString(), actualTypeHint.typeHintToString(), pos); + Assert.isTrue(TypingHelper.typeHintsEqual(actualTypeHint, expectedTypeHint), pos); + } + } + + function makeCp(fullTypeName:String, params:Array):TypeHintType { + final type = usageContext.typeList.getType(fullTypeName); + if (type == null) { + return LibType(fullTypeName, fullTypeName, params); + } + return ClasspathType(type, params); + } +} diff --git a/test/refactor/typing/TypingTest.hx b/test/refactor/typing/TypingTest.hx new file mode 100644 index 0000000..48811c4 --- /dev/null +++ b/test/refactor/typing/TypingTest.hx @@ -0,0 +1,95 @@ +package refactor.typing; + +import haxe.PosInfos; +import js.lib.Promise; + +class TypingTest extends TestBase { + function testEnums(async:Async) { + setupTestSources(["testcases/enums"]); + + findAndCompareTypeHint("enums.Main", "type", 87, makeCp("enums.IdentifierType", [])); + findAndCompareTypeHint("enums.Main", "type", 120, makeCp("enums.IdentifierType", [])); + findAndCompareTypeHint("enums.Main", "PackageName", 137, makeCp("enums.IdentifierType", [])); + findAndCompareTypeHint("enums.Main", "ScopedLocal", 237, makeCp("enums.IdentifierType", [])); + findAndCompareTypeHint("enums.Main", "scopeEnd", 250, LibType("Int", "Int", [])); + findAndCompareTypeHint("enums.Main", "ScopedLocal", 270, makeCp("enums.ScopedLocal", [])); + findAndCompareTypeHint("enums.Main", "scopeEnd", 283, LibType("Int", "Int", [])); + findAndCompareTypeHint("enums.Main", "StringConst", 409, makeCp("enums.IdentifierType", [])); + + findAndCompareTypeHint("enums.Main", "PackageName", 709, makeCp("enums.IdentifierTypeCopy", [])); + findAndCompareTypeHint("enums.Main", "scopeEnd", 786, LibType("Int", "Int", [])); + + findAndCompareTypeHint("enums.Main", "PackageName", 1084, makeCp("enums.IdentifierType", [])); + findAndCompareTypeHint("enums.Main", "scopeEnd", 1149, LibType("Int", "Int", [])); + + findAndCompareTypeHint("enums.Main", "PackageName", 1377, makeCp("enums.IdentifierType", [])); + findAndCompareTypeHint("enums.Main", "scopeEnd", 1444, LibType("Int", "Int", [])); + + findAndCompareTypeHint("enums.Main", "PackageName", 1686, makeCp("enums.IdentifierType", [])); + findAndCompareTypeHint("enums.Main", "scopeEnd", 1750, LibType("Int", "Int", [])); + + failTypeHint("enums.Main", "ScopedLocal", 1875); + + findAndCompareTypeHint("enums.Main", "IdentifierType.PackageName", 2222, makeCp("enums.IdentifierType", [])); + + findAndCompareTypeHint("enums.Main", "scopeEnd", 2331, LibType("Int", "Int", [])); + + findAndCompareTypeHint("enums.Identifier", "children", 84, LibType("Array", "Array", [makeCp("enums.Identifier", [])])); + + findAndCompareTypeHint("enums.Main", "type", 87, makeCp("enums.IdentifierType", []), async); + } + + @:access(refactor.typing.TypingHelper) + function findAndCompareTypeHint(fullTypeName:String, searchName:String, searchPos:Int, expectedTypeHint:TypeHintType, ?async:Async, ?pos:PosInfos) { + findTypeHint(fullTypeName, searchName, searchPos).then(function(actualTypeHint) { + Assert.notNull(actualTypeHint, pos); + Assert.equals(expectedTypeHint.typeHintToString(), actualTypeHint.typeHintToString(), pos); + Assert.isTrue(TypingHelper.typeHintsEqual(actualTypeHint, expectedTypeHint), pos); + }).catchError(function(error) { + trace("error: " + error); + Assert.fail(error, pos); + }).finally(function() { + if (async != null) { + async.done(); + } + }); + } + + @:access(refactor.typing.TypingHelper) + function failTypeHint(fullTypeName:String, searchName:String, searchPos:Int, ?async:Async, ?pos:PosInfos) { + findTypeHint(fullTypeName, searchName, searchPos).then(function(actualTypeHint) { + Assert.fail("should fail to produce type hint", pos); + }).catchError(function(error) { + trace("error: " + error); + Assert.notNull(error, pos); + }).finally(function() { + if (async != null) { + async.done(); + } + }); + } + + function makeCp(fullTypeName:String, params:Array):TypeHintType { + final type = usageContext.typeList.getType(fullTypeName); + if (type == null) { + return LibType(fullTypeName, fullTypeName, params); + } + return ClasspathType(type, params); + } + + @:access(refactor.typing.TypingHelper) + function findTypeHint(fullTypeName:String, searchName:String, searchPos:Int):Promise { + final containerType = usageContext.typeList.getType(fullTypeName); + return TypingHelper.findTypeWithBuiltIn(containerType, searchName, searchPos, { + nameMap: usageContext.nameMap, + fileList: usageContext.fileList, + typeList: usageContext.typeList, + verboseLog: function(text:String, ?pos:PosInfos) { + Sys.println('${pos.fileName}:${pos.lineNumber}: $text'); + }, + typer: typer, + fileReader: fileReader, + converter: (string, byteOffset) -> byteOffset, + }); + } +} diff --git a/testcases/classes/StaticUsing.hx b/testcases/classes/StaticUsing.hx index 166470e..f71f900 100644 --- a/testcases/classes/StaticUsing.hx +++ b/testcases/classes/StaticUsing.hx @@ -12,7 +12,7 @@ class StaticUsing { Sys.println(child.print()); var text:String; - var texts:{text:Array} = { + var texts:{text:Array} = { text: [] }; var index = 0; @@ -24,6 +24,11 @@ class StaticUsing { texts.text[index].print(); (Context.printFunc : PrintFunc).print(); } + + function hasIdent(name:T):T { + return null; + } } /** diff --git a/testcases/methods/SomeHelper.hx b/testcases/methods/SomeHelper.hx index 15548e5..50ec232 100644 --- a/testcases/methods/SomeHelper.hx +++ b/testcases/methods/SomeHelper.hx @@ -20,7 +20,7 @@ class SomeHelper { return TypingHelper.findTypeOfIdentifier(context, search).then(function(typeHint:TypeHintType) { switch (typeHint) { case null: - case ClasspathType(type, _): + case TypeHintType.ClasspathType(type, _): case LibType("Null", _, [ClasspathType(type, _)]): for (t in types) { if (t != type) { @@ -37,6 +37,7 @@ class SomeHelper { case LibType(_, _): case FunctionType(_, retVal): case StructType(_): + case NamedType(_): case UnknownType(_): } }); diff --git a/testcases/typehints/Main.hx b/testcases/typehints/Main.hx new file mode 100644 index 0000000..91c76f8 --- /dev/null +++ b/testcases/typehints/Main.hx @@ -0,0 +1,18 @@ +package typehints; + +class Main { + var intIdent:Int; + var boolIdent:Bool; + var myTypeIdent:MyType; + var newTypeIdent:NewType; + var nullNewTypeIdent:Null>; + var fullNullNewTypeIdent:Null>; + var intStringBoolIdentOld:Int->String->Bool; + var intStringBoolIdentNew:(Int, String) -> Bool; + var loopVoidIdent:(loop:Int) -> Void; + var loop2VoidIdent:(loop:Int, loop2:Int) -> Void; + var intStringVoidNullIdent:(Int, String) -> Void = null; +} + +class MyType {} +class NewType {} From 32b556bf0fe311543ec2f92c7f5acbb28f9c846b Mon Sep 17 00:00:00 2001 From: AlexHaxe Date: Sun, 22 Dec 2024 13:07:34 +0100 Subject: [PATCH 56/59] added info about recently renamed filenames to help vshaxe ignore them better --- src/refactor/discover/FileList.hx | 16 ++++++++++++++++ src/refactor/discover/TypeList.hx | 2 +- src/refactor/rename/RenameHelper.hx | 14 ++------------ src/refactor/rename/RenamePackage.hx | 1 + src/refactor/rename/RenameTypeName.hx | 12 ++++++------ 5 files changed, 26 insertions(+), 19 deletions(-) diff --git a/src/refactor/discover/FileList.hx b/src/refactor/discover/FileList.hx index 2e0340c..80d3c0d 100644 --- a/src/refactor/discover/FileList.hx +++ b/src/refactor/discover/FileList.hx @@ -3,10 +3,25 @@ package refactor.discover; class FileList { public final files:Map = []; + var recentlyRenamed:Array = []; + public function new() {} public function addFile(file:File) { files.set(file.name, file); + recentlyRenamed.remove(file.name); + } + + public function addRecentlyRenamed(file:File) { + recentlyRenamed.push(file.name); + } + + public function wasRecentlyRenamed(fileName:String):Bool { + if (recentlyRenamed.contains(fileName)) { + recentlyRenamed.remove(fileName); + return true; + } + return false; } public function getFile(fileName:String):Null { @@ -19,5 +34,6 @@ class FileList { public function clear() { files.clear(); + recentlyRenamed = []; } } diff --git a/src/refactor/discover/TypeList.hx b/src/refactor/discover/TypeList.hx index 7eeaa09..51ea07b 100644 --- a/src/refactor/discover/TypeList.hx +++ b/src/refactor/discover/TypeList.hx @@ -28,7 +28,7 @@ class TypeList implements ITypeList { public function removeFile(fileName:String) { var fullNames:Array = []; for (key => type in types) { - if (type.name.pos.fileName == fileName) { + if (type.file.name == fileName) { fullNames.push(type.fullModuleName); } } diff --git a/src/refactor/rename/RenameHelper.hx b/src/refactor/rename/RenameHelper.hx index 8a6a82f..6fdaf59 100644 --- a/src/refactor/rename/RenameHelper.hx +++ b/src/refactor/rename/RenameHelper.hx @@ -72,13 +72,7 @@ class RenameHelper { changelist.addChange(use.pos.fileName, ReplaceText(context.what.toName, use.pos, NoFormat), use); continue; } - case StructType(fields): - continue; - case FunctionType(args, retVal): - continue; - case UnknownType(name): - continue; - case NamedType(_): + case StructType(_) | FunctionType(_, _) | UnknownType(_) | NamedType(_): continue; } } @@ -196,8 +190,6 @@ class RenameHelper { addChanges(type); case LibType("Array", _, [ClasspathType(type, _)]): addChanges(type); - case LibType(_, _): - return; case FunctionType(_, retVal): if (retVal == null) { return; @@ -209,9 +201,7 @@ class RenameHelper { addChanges(type); default: } - case StructType(_): - case UnknownType(_): - case NamedType(_): + case LibType(_, _) | StructType(_) | UnknownType(_) | NamedType(_): } }); } diff --git a/src/refactor/rename/RenamePackage.hx b/src/refactor/rename/RenamePackage.hx index ecb97fc..0daf99e 100644 --- a/src/refactor/rename/RenamePackage.hx +++ b/src/refactor/rename/RenamePackage.hx @@ -95,5 +95,6 @@ class RenamePackage { pathParts.unshift(Path.removeTrailingSlashes(rootPath)); pathParts.push('${mainTypeName}.hx'); changelist.addChange(file.name, Move(Path.join(pathParts)), null); + context.fileList.addRecentlyRenamed(file); } } diff --git a/src/refactor/rename/RenameTypeName.hx b/src/refactor/rename/RenameTypeName.hx index 126b64e..93511f7 100644 --- a/src/refactor/rename/RenameTypeName.hx +++ b/src/refactor/rename/RenameTypeName.hx @@ -16,11 +16,13 @@ class RenameTypeName { var packName:String = file.getPackage(); var mainModuleName:String = file.getMainModulName(); var path:Path = new Path(file.name); + if (mainModuleName == identifier.name) { // type and filename are identical -> move file var newFileName:String = Path.join([path.dir, context.what.toName]) + "." + path.ext; changelist.addChange(file.name, Move(newFileName), null); } + // replace self changelist.addChange(identifier.pos.fileName, ReplaceText(context.what.toName, identifier.pos, NoFormat), identifier); @@ -56,7 +58,6 @@ class RenameTypeName { } switch (use.type) { case Abstract | Class | Enum | Interface | Typedef: - changelist.addChange(use.pos.fileName, ReplaceText(context.what.toName, use.pos, NoFormat), use); continue; default: } @@ -72,11 +73,7 @@ class RenameTypeName { if (type.fullModuleName != identifier.defineType.fullModuleName) { return; } - case LibType(_) | UnknownType(_): - return; - case StructType(_) | FunctionType(_, _): - return; - case NamedType(_): + case LibType(_) | UnknownType(_) | StructType(_) | FunctionType(_, _) | NamedType(_): return; } if (use.name == identifier.name) { @@ -95,6 +92,9 @@ class RenameTypeName { } return Promise.all(changes).then(function(_) { + if (mainModuleName == identifier.name) { + context.fileList.addRecentlyRenamed(file); + } return Promise.resolve(changelist.execute()); }); } From 26d5f47bc76d7cbfd859df30d8b5c65e2099d3a6 Mon Sep 17 00:00:00 2001 From: AlexHaxe Date: Sun, 22 Dec 2024 19:35:20 +0100 Subject: [PATCH 57/59] added findIdentifierChilds to visit all childnodes of identifiers cleanup --- src/refactor/discover/UsageCollector.hx | 189 +++++++++--------- src/refactor/typing/TypingHelper.hx | 16 +- test/TestMain.hx | 15 +- test/refactor/refactor/RefactorClassTest.hx | 179 ----------------- .../refactor/RefactorExtractInterfaceTest.hx | 36 ++++ .../refactor/RefactorExtractTypeTest.hx | 112 +++++++++++ test/refactor/refactor/RefactorTypedefTest.hx | 56 ------ .../refactor/RewriteFinalsToVarsTest.hx | 21 ++ .../refactor/RewriteVarsToFinalsTest.hx | 50 +++++ .../refactor/RewriteWrapWithTryCatchTest.hx | 39 ++++ test/refactor/rename/RenameTypedefTest.hx | 9 + .../typedefs/ExperimentalCapabilities.hx | 19 ++ 12 files changed, 399 insertions(+), 342 deletions(-) delete mode 100644 test/refactor/refactor/RefactorClassTest.hx create mode 100644 test/refactor/refactor/RefactorExtractInterfaceTest.hx create mode 100644 test/refactor/refactor/RefactorExtractTypeTest.hx delete mode 100644 test/refactor/refactor/RefactorTypedefTest.hx create mode 100644 test/refactor/refactor/RewriteFinalsToVarsTest.hx create mode 100644 test/refactor/refactor/RewriteVarsToFinalsTest.hx create mode 100644 test/refactor/refactor/RewriteWrapWithTryCatchTest.hx create mode 100644 testcases/typedefs/ExperimentalCapabilities.hx diff --git a/src/refactor/discover/UsageCollector.hx b/src/refactor/discover/UsageCollector.hx index 8a7040b..8e5b824 100644 --- a/src/refactor/discover/UsageCollector.hx +++ b/src/refactor/discover/UsageCollector.hx @@ -459,16 +459,15 @@ class UsageCollector { readAnonStructure(context, identifier, child); case Const(CIdent(_)): final ident = makeIdentifier(context, child, TypedefBase, identifier); - final afterIdentifier = findIdentifierChild(child); - if (afterIdentifier != null) { - switch (afterIdentifier.tok) { + for (identChild in findIdentifierChilds(child)) { + switch (identChild.tok) { case Semicolon: case Binop(OpLt): - addTypeParameter(context, ident, afterIdentifier); + addTypeParameter(context, ident, identChild); case Binop(OpAnd): - readExpression(context, identifier, afterIdentifier); + readExpression(context, identifier, identChild); case Arrow: - readTypeHint(context, ident, afterIdentifier, TypeHint); + readTypeHint(context, ident, identChild, TypeHint); default: } } @@ -753,8 +752,7 @@ class UsageCollector { identType = ArrayAccess(accessIdent); } makeIdentifier(context, token, identType, identifier); - var identChild = findIdentifierChild(token); - while (identChild != null) { + for (identChild in findIdentifierChilds(token)) { switch (identChild.tok) { case POpen: readCallParams(context, identifier, identChild); @@ -772,7 +770,6 @@ class UsageCollector { case Unop(_): default: } - identChild = identChild.nextSibling; } default: } @@ -782,75 +779,66 @@ class UsageCollector { case Parameter: var posScope = parent.getPos(); final parameterIdent = makeIdentifier(context, token, ScopedLocal(posScope.min, posScope.max, Parameter([])), identifier); - var parameterChild = findIdentifierChild(token); - while (parameterChild != null) { - switch (parameterChild.tok) { + for (identChild in findIdentifierChilds(token)) { + switch (identChild.tok) { case Comma: case DblDot: - switch (TokenTreeCheckUtils.getColonType(parameterChild)) { + switch (TokenTreeCheckUtils.getColonType(identChild)) { case TypeHint: - readTypeHint(context, parameterIdent, parameterChild, TypeHint); + readTypeHint(context, parameterIdent, identChild, TypeHint); copyUsesToParent(identifier, parameterIdent); case TypeCheck: - readExpression(context, identifier, parameterChild); + readExpression(context, identifier, identChild); case SwitchCase | Ternary | ObjectLiteral | At: case Unknown: - readTypeHint(context, parameterIdent, parameterChild, TypeHint); + readTypeHint(context, parameterIdent, identChild, TypeHint); copyUsesToParent(identifier, parameterIdent); } case Binop(OpLt): - addTypeParameter(context, parameterIdent, parameterChild); + addTypeParameter(context, parameterIdent, identChild); copyUsesToParent(identifier, parameterIdent); default: } - parameterChild = parameterChild.nextSibling; } return; case At | Call | SwitchCondition | WhileCondition | IfCondition | SharpCondition | Catch | ForLoop | Expression: final accessIdent = makeIdentifier(context, token, Access, identifier); - var accessChild = findIdentifierChild(token); - if (accessChild == null) { - accessChild = token.getFirstChild(); - } - while (accessChild != null) { - switch (accessChild.tok) { + for (identChild in findIdentifierChilds(token)) { + switch (identChild.tok) { case Comment(_) | CommentLine(_): - case Comma: - case Dot | QuestionDot: - case Unop(_): + case Comma | Dot | QuestionDot | Unop(_): case Binop(_): - readExpression(context, identifier, accessChild); + readExpression(context, identifier, identChild); case Arrow: - readExpression(context, identifier, accessChild); + readExpression(context, identifier, identChild); case POpen: - readCallParams(context, identifier, accessChild); + readCallParams(context, identifier, identChild); case BkOpen: - readCallParams(context, identifier, accessChild); + readCallParams(context, identifier, identChild); case DblDot: - switch (TokenTreeCheckUtils.getColonType(accessChild)) { + switch (TokenTreeCheckUtils.getColonType(identChild)) { case TypeHint: - readTypeHint(context, accessIdent, accessChild, TypeHint); + readTypeHint(context, accessIdent, identChild, TypeHint); copyUsesToParent(identifier, accessIdent); case TypeCheck: - readExpression(context, identifier, accessChild); + readExpression(context, identifier, identChild); case SwitchCase | Ternary | ObjectLiteral | At: case Unknown: - readTypeHint(context, accessIdent, accessChild, TypeHint); + readTypeHint(context, accessIdent, identChild, TypeHint); copyUsesToParent(identifier, accessIdent); } case Const(CIdent("is")): - final child = accessChild.getFirstChild(); + final child = identChild.getFirstChild(); if (child != null) { readExpression(context, identifier, child); } case Question: - if (TokenTreeCheckUtils.isTernary(accessChild)) { - readExpression(context, identifier, accessChild); + if (TokenTreeCheckUtils.isTernary(identChild)) { + readExpression(context, identifier, identChild); } default: } - accessChild = accessChild.nextSibling; } return; } @@ -859,46 +847,42 @@ class UsageCollector { if (ident == null) { ident = identifier; } - var identToken = findIdentifierChild(token); var directChildrenDone:Bool = false; - if (identToken != null && (identToken == token.getFirstChild())) { - directChildrenDone = true; - } - while (identToken != null) { - switch (identToken.tok) { + for (identChild in findIdentifierChilds(token)) { + if (identChild == token.getFirstChild()) { + directChildrenDone = true; + } + switch (identChild.tok) { + case Comment(_) | CommentLine(_): + case Unop(_) | Semicolon | Comma: case POpen | BkOpen | Dot | QuestionDot: - readCallParams(context, ident, identToken); + readCallParams(context, ident, identChild); copyUsesToParent(identifier, ident); case Binop(_): - readExpression(context, ident, identToken); + readExpression(context, ident, identChild); copyUsesToParent(identifier, ident); - case Unop(_): - case Semicolon: - case Comment(_) | CommentLine(_): - case Comma: case DblDot: - switch (TokenTreeCheckUtils.getColonType(identToken)) { + switch (TokenTreeCheckUtils.getColonType(identChild)) { + case SwitchCase | Ternary | ObjectLiteral | At: case TypeHint: - readTypeHint(context, ident, identToken, TypeHint); + readTypeHint(context, ident, identChild, TypeHint); copyUsesToParent(identifier, ident); case TypeCheck: - readExpression(context, identifier, identToken); - case SwitchCase | Ternary | ObjectLiteral | At: + readExpression(context, identifier, identChild); case Unknown: - readTypeHint(context, ident, identToken, TypeHint); + readTypeHint(context, ident, identChild, TypeHint); copyUsesToParent(identifier, ident); } case Question: - if (TokenTreeCheckUtils.isTernary(identToken)) { - readExpression(context, identifier, identToken); + if (TokenTreeCheckUtils.isTernary(identChild)) { + readExpression(context, identifier, identChild); } case Spread: - readExpression(context, identifier, identToken); + readExpression(context, identifier, identChild); case Arrow: - readExpression(context, identifier, identToken); + readExpression(context, identifier, identChild); default: } - identToken = identToken.nextSibling; } if (directChildrenDone) { return; @@ -961,8 +945,7 @@ class UsageCollector { return; case Kwd(KwdThis): final thisIdent = makeIdentifier(context, token, Access, identifier); - var identChild = findIdentifierChild(token); - while (identChild != null) { + for (identChild in findIdentifierChilds(token)) { switch (identChild.tok) { case Binop(_): readExpression(context, identifier, identChild); @@ -988,7 +971,6 @@ class UsageCollector { } default: } - identChild = identChild.nextSibling; } return; case BrOpen: @@ -1267,46 +1249,42 @@ class UsageCollector { case Question: child = child.getFirstChild(); final paramIdent:Identifier = makeIdentifier(context, child, ScopedLocal(child.pos.min, scopeEnd, Parameter(params)), identifier); - var paramChild = findIdentifierChild(child); - while (paramChild != null) { - switch (paramChild.tok) { + for (identChild in findIdentifierChilds(child)) { + switch (identChild.tok) { case DblDot: - switch (TokenTreeCheckUtils.getColonType(paramChild)) { + switch (TokenTreeCheckUtils.getColonType(identChild)) { case TypeHint: - readTypeHint(context, paramIdent, paramChild, TypeHint); + readTypeHint(context, paramIdent, identChild, TypeHint); copyUsesToParent(identifier, paramIdent); case TypeCheck: - readExpression(context, identifier, paramChild); + readExpression(context, identifier, identChild); case Ternary: - readExpression(context, identifier, paramChild); + readExpression(context, identifier, identChild); case SwitchCase | ObjectLiteral | At | Unknown: } case Binop(OpAssign): - readExpression(context, identifier, paramChild); + readExpression(context, identifier, identChild); case Comma: default: } - paramChild = paramChild.nextSibling; } copyUsesToParent(identifier, paramIdent); params.push(paramIdent); case Const(CIdent(_)): var paramIdent:Identifier = makeIdentifier(context, child, ScopedLocal(child.pos.min, scopeEnd, Parameter(params)), identifier); params.push(paramIdent); - var paramChild = findIdentifierChild(child); - while (paramChild != null) { - switch (paramChild.tok) { + for (identChild in findIdentifierChilds(child)) { + switch (identChild.tok) { case DblDot: - readTypeHint(context, paramIdent, paramChild, TypeHint); + readTypeHint(context, paramIdent, identChild, TypeHint); case Comma: - if (paramChild.parent == child) { + if (identChild.parent == child) { break; } case Binop(OpAssign): - readExpression(context, paramIdent, paramChild); + readExpression(context, paramIdent, identChild); default: } - paramChild = paramChild.nextSibling; } copyUsesToParent(identifier, paramIdent); default: @@ -1434,13 +1412,12 @@ class UsageCollector { switch (child.tok) { case Const(CIdent(_)) | Dollar(_): makeIdentifier(context, child, TypedParameter, identifier); - final afterIdentifier = findIdentifierChild(child); - if (afterIdentifier != null) { - switch (afterIdentifier.tok) { + for (identChild in findIdentifierChilds(child)) { + switch (identChild.tok) { case Binop(OpLt): - addTypeParameter(context, identifier, afterIdentifier); + addTypeParameter(context, identifier, identChild); case Arrow: - addTypeParameter(context, identifier, afterIdentifier); + addTypeParameter(context, identifier, identChild); default: } } @@ -1478,18 +1455,17 @@ class UsageCollector { if (token.matches(DblDot) && identifier != null) { identifier.setTypeHint(TypeHintFromTree.makeTypeHint(child)); } - final afterIdentifier = findIdentifierChild(child); - if (afterIdentifier != null) { - switch (afterIdentifier.tok) { + for (identChild in findIdentifierChilds(child)) { + switch (identChild.tok) { case Comment(_) | CommentLine(_): case Semicolon: case Arrow: - readTypeHint(context, identifier, afterIdentifier, TypeHint); + readTypeHint(context, identifier, identChild, TypeHint); case Binop(OpLt): - addTypeParameter(context, identifier, afterIdentifier); + addTypeParameter(context, identifier, identChild); case PClose: case Sharp(_): - readExpression(context, identifier, afterIdentifier); + readExpression(context, identifier, identChild); default: } } @@ -1591,6 +1567,37 @@ class UsageCollector { return null; } + function findIdentifierChilds(token:TokenTree):Array { + var childs:Array = []; + while (token != null) { + if (!token.hasChildren()) { + return childs; + } + var identifierPart:Null = null; + var first:Bool = true; + for (child in token.children) { + switch (child.tok) { + case Const(CIdent("is")): + childs.push(child); + case Const(CIdent(_)) | Dot | QuestionDot | Kwd(KwdNew) | Kwd(KwdMacro) | Dollar(_): + if (first) { + if (identifierPart == null) { + identifierPart = child; + } + continue; + } + childs.push(child); + case At | Semicolon: + default: + childs.push(child); + } + first = false; + } + token = identifierPart; + } + return childs; + } + function copyUsesToParent(identifier:Null, child:Identifier):Void { if (identifier == null) { return; diff --git a/src/refactor/typing/TypingHelper.hx b/src/refactor/typing/TypingHelper.hx index 7f8797a..e4de116 100644 --- a/src/refactor/typing/TypingHelper.hx +++ b/src/refactor/typing/TypingHelper.hx @@ -22,9 +22,7 @@ class TypingHelper { for (type in types) { for (use in type.getIdentifiers(search)) { switch (use.type) { - case Extends | Implements: - pushType(use.defineType); - case AbstractOver: + case Extends | Implements | AbstractOver: pushType(use.defineType); default: } @@ -45,9 +43,7 @@ class TypingHelper { alias; } searchImplementingTypes(use.file.typeList, search); - case Extends | Implements: - pushType(use.defineType); - case AbstractOver: + case Extends | Implements | AbstractOver: pushType(use.defineType); default: } @@ -55,9 +51,7 @@ class TypingHelper { allUses = context.nameMap.getIdentifiers(baseType.name.name); for (use in allUses) { switch (use.type) { - case Extends | Implements: - pushType(use.defineType); - case AbstractOver: + case Extends | Implements | AbstractOver: pushType(use.defineType); default: } @@ -340,9 +334,7 @@ class TypingHelper { return Promise.reject("not enough type parameters"); } return Promise.resolve(typeParams[index]); - case UnknownType(_): - case StructType(_) | FunctionType(_): - case NamedType(_, _): + case UnknownType(_) | StructType(_) | FunctionType(_) | NamedType(_, _): } return Promise.reject("not found"); })); diff --git a/test/TestMain.hx b/test/TestMain.hx index df5a04b..116b34f 100644 --- a/test/TestMain.hx +++ b/test/TestMain.hx @@ -1,7 +1,10 @@ -import refactor.refactor.RefactorClassTest; import refactor.refactor.RefactorExtractConstructorParams; +import refactor.refactor.RefactorExtractInterfaceTest; import refactor.refactor.RefactorExtractMethodTest; -import refactor.refactor.RefactorTypedefTest; +import refactor.refactor.RefactorExtractTypeTest; +import refactor.refactor.RewriteFinalsToVarsTest; +import refactor.refactor.RewriteVarsToFinalsTest; +import refactor.refactor.RewriteWrapWithTryCatchTest; import refactor.rename.RenameClassTest; import refactor.rename.RenameEnumTest; import refactor.rename.RenameImportAliasTest; @@ -28,8 +31,12 @@ class TestMain { new RenamePackageTest(), new RenameScopedLocalTest(), new RenameTypedefTest(), - new RefactorClassTest(), - new RefactorTypedefTest(), + new RefactorExtractTypeTest(), + new RefactorExtractInterfaceTest(), + new RewriteFinalsToVarsTest(), + new RewriteVarsToFinalsTest(), + new RewriteWrapWithTryCatchTest(), + new RefactorExtractMethodTest(), new RefactorExtractConstructorParams(), ]; diff --git a/test/refactor/refactor/RefactorClassTest.hx b/test/refactor/refactor/RefactorClassTest.hx deleted file mode 100644 index beaec40..0000000 --- a/test/refactor/refactor/RefactorClassTest.hx +++ /dev/null @@ -1,179 +0,0 @@ -package refactor.refactor; - -class RefactorClassTest extends RefactorTestBase { - function setupClass() { - setupTestSources(["testcases/classes"]); - } - - // Extract Type - - function testFailExtractTypChildClass(async:Async) { - failCanRefactor(RefactorExtractType, {fileName: "testcases/classes/ChildClass.hx", posStart: 30, posEnd: 30}, "unsupported"); - failRefactor(RefactorExtractType, {fileName: "testcases/classes/ChildClass.hx", posStart: 30, posEnd: 30}, "failed to collect data for extract type", - async); - } - - function testExtractTypeListOfChilds(async:Async) { - var edits:Array = [ - makeRemoveTestEdit("testcases/classes/ChildClass.hx", 860, 901), - makeCreateTestEdit("testcases/classes/ListOfChilds.hx"), - makeInsertTestEdit("testcases/classes/ListOfChilds.hx", "package classes;\n\ntypedef ListOfChilds = Array;", 0, Format(0, false)), - makeInsertTestEdit("testcases/classes/pack/UseChild.hx", "import classes.ListOfChilds;\n", 23), - ]; - checkRefactor(RefactorExtractType, {fileName: "testcases/classes/ChildClass.hx", posStart: 873, posEnd: 873}, edits, async); - } - - function testExtractTypeTextLoader(async:Async) { - var edits:Array = [ - makeRemoveTestEdit("testcases/classes/Printer.hx", 1262, 1397), - makeCreateTestEdit("testcases/classes/TextLoader.hx"), - makeInsertTestEdit("testcases/classes/TextLoader.hx", - "package classes;\n\n" - + "import js.lib.Promise;\n\n" - + "class TextLoader {\n" - + " public function new() {}\n\n" - + " public function load(text:String):Promise {\n" - + " return Promise.resolve(text);\n" - + " }\n" - + "}", - 0, Format(0, false)), - makeInsertTestEdit("testcases/classes/pack/UsePrinter.hx", "import classes.TextLoader;\n", 23), - ]; - checkRefactor(RefactorExtractType, {fileName: "testcases/classes/Printer.hx", posStart: 1273, posEnd: 1283}, edits, async); - } - - function testExtractTypeContextWithDocComment(async:Async) { - var edits:Array = [ - makeCreateTestEdit("testcases/classes/Context.hx"), - makeInsertTestEdit("testcases/classes/Context.hx", - "package classes;\n\n" - + "using classes.ChildHelper;\n" - + "using classes.pack.SecondChildHelper;\n\n" - + "/**\n" - + " * Context class\n" - + " */\n" - + "class Context {\n" - + " public static var printFunc:PrintFunc;\n" - + "}", - 0, Format(0, false)), - makeRemoveTestEdit("testcases/classes/StaticUsing.hx", 557, 639), - ]; - checkRefactor(RefactorExtractType, {fileName: "testcases/classes/StaticUsing.hx", posStart: 590, posEnd: 590}, edits, async); - } - - function testExtractTypeNotDocModule(async:Async) { - var edits:Array = [ - makeRemoveTestEdit("testcases/classes/DocModule.hx", 62, 169), - makeReplaceTestEdit("testcases/classes/ForceRenameCrash.hx", "classes.NotDocModule", 86, 116), - makeCreateTestEdit("testcases/classes/NotDocModule.hx"), - makeInsertTestEdit("testcases/classes/NotDocModule.hx", - "/**\n" - + " * file header\n" - + " */\n\n" - + "package classes;\n\n" - + "class NotDocModule {\n" - + " public function new() {}\n\n" - + " public function doSomething() {\n" - + " trace(\"something\");\n" - + " }\n" - + "}", - 0, Format(0, false)), - makeInsertTestEdit("testcases/classes/pack/UseDocModule.hx", "import classes.NotDocModule;\n", 23), - ]; - checkRefactor(RefactorExtractType, {fileName: "testcases/classes/DocModule.hx", posStart: 73, posEnd: 73}, edits, async); - } - - // Extract Interface - - function testFailExtractInterfaceNoClass(async:Async) { - failCanRefactor(RefactorExtractInterface, {fileName: "testcases/classes/ChildClass.hx", posStart: 875, posEnd: 875}, "unsupported"); - failRefactor(RefactorExtractInterface, {fileName: "testcases/classes/ChildClass.hx", posStart: 875, posEnd: 875}, - "failed to collect data for extract interface", async); - } - - function testExtractInterfaceBaseClass(async:Async) { - var edits:Array = [ - makeInsertTestEdit("testcases/classes/BaseClass.hx", " implements IBaseClass", 33), - makeCreateTestEdit("testcases/classes/IBaseClass.hx"), - makeInsertTestEdit("testcases/classes/IBaseClass.hx", - "package classes;\n\n" - + "interface IBaseClass {\n" - + "function doSomething(data:Array):Void;\n" - + "function doSomething3(d:Array):Void;\n" - + "function doSomething4(d:Array):Void;\n" - + "function doSomething5(d:Array):Void;\n" - + "function doSomething6(d:Array):Bool;\n" - + "}", - 0, Format(0, false)), - ]; - addTypeHint("testcases/classes/BaseClass.hx", 131, LibType("Void", "Void", [])); - addTypeHint("testcases/classes/BaseClass.hx", 225, LibType("Void", "Void", [])); - addTypeHint("testcases/classes/BaseClass.hx", 296, LibType("Void", "Void", [])); - addTypeHint("testcases/classes/BaseClass.hx", 384, LibType("Void", "Void", [])); - addTypeHint("testcases/classes/BaseClass.hx", 468, LibType("Bool", "Bool", [])); - checkRefactor(RefactorExtractInterface, {fileName: "testcases/classes/BaseClass.hx", posStart: 27, posEnd: 27}, edits, async); - } - - // Rewrite Finals to Vars - - function testRewriteFinalsToVarsJsonClass(async:Async) { - failCanRefactor(RefactorRewriteVarsToFinals(false), {fileName: "testcases/classes/JsonClass.hx", posStart: 18, posEnd: 696}, "unsupported"); - failRefactor(RefactorRewriteVarsToFinals(false), {fileName: "testcases/classes/JsonClass.hx", posStart: 18, posEnd: 696}, - "failed to collect data for rewrite vars/finals", async); - } - - function testRewriteFinalsToVarsPrinter(async:Async) { - var edits:Array = [ - makeReplaceTestEdit("testcases/classes/Printer.hx", "var", 147, 152, NoFormat), - makeReplaceTestEdit("testcases/classes/Printer.hx", "var", 225, 230, NoFormat), - ]; - checkRefactor(RefactorRewriteVarsToFinals(false), {fileName: "testcases/classes/Printer.hx", posStart: 129, posEnd: 1079}, edits, async); - } - - // Rewrite Vars To Finals - - function testRewriteVarsToFinalsPrinter(async:Async) { - failCanRefactor(RefactorRewriteVarsToFinals(true), {fileName: "testcases/classes/Printer.hx", posStart: 129, posEnd: 1079}, "unsupported"); - failRefactor(RefactorRewriteVarsToFinals(true), {fileName: "testcases/classes/Printer.hx", posStart: 129, posEnd: 1079}, - "failed to collect data for rewrite vars/finals", async); - } - - function testRewriteVarsToFinalsJsonClass(async:Async) { - var edits:Array = [ - makeReplaceTestEdit("testcases/classes/JsonClass.hx", "final", 37, 40, NoFormat), - makeReplaceTestEdit("testcases/classes/JsonClass.hx", "final", 50, 53, NoFormat), - makeReplaceTestEdit("testcases/classes/JsonClass.hx", "final", 68, 71, NoFormat), - makeReplaceTestEdit("testcases/classes/JsonClass.hx", "final", 84, 87, NoFormat), - makeReplaceTestEdit("testcases/classes/JsonClass.hx", "final", 301, 304, NoFormat), - ]; - checkRefactor(RefactorRewriteVarsToFinals(true), {fileName: "testcases/classes/JsonClass.hx", posStart: 18, posEnd: 696}, edits, async); - } - - // Wrap with Try…Catch - - function testFailTryCatchCollectDataEmptyFile(async:Async) { - failCanRefactor(RefactorRewriteWrapWithTryCatch, {fileName: "testcases/classes/BaseClass.hx", posStart: 156, posEnd: 263}, "unsupported"); - failRefactor(RefactorRewriteWrapWithTryCatch, {fileName: "testcases/classes/BaseClass.hx", posStart: 156, posEnd: 263}, - "failed to collect data for rewrite wrap with try catch", async); - } - - function testRewriteWrapTryCatchJsonClass(async:Async) { - var edits:Array = [ - makeReplaceTestEdit("testcases/classes/JsonClass.hx", - "try {\n" - + "var newgroup:Null = new JsonClass(group.id, group.type, group.width);\n" - + " newgroup.id = group.id;\n" - + " newgroup.type = group.type;\n" - + " newgroup.width = group.width;\n" - + " newgroup.maxWidth = group.maxWidth;\n\n" - + " return newgroup;\n" - + "}\n" - + "catch (e:haxe.Exception) {\n" - + "// TODO: handle exception\n" - + "trace (e.details());\n" - + "}", - 301, 527, Format(2, true)), - ]; - checkRefactor(RefactorRewriteWrapWithTryCatch, {fileName: "testcases/classes/JsonClass.hx", posStart: 300, posEnd: 527}, edits, async); - } -} diff --git a/test/refactor/refactor/RefactorExtractInterfaceTest.hx b/test/refactor/refactor/RefactorExtractInterfaceTest.hx new file mode 100644 index 0000000..2d65690 --- /dev/null +++ b/test/refactor/refactor/RefactorExtractInterfaceTest.hx @@ -0,0 +1,36 @@ +package refactor.refactor; + +class RefactorExtractInterfaceTest extends RefactorTestBase { + function setupClass() { + setupTestSources(["testcases/classes"]); + } + + function testFailExtractInterfaceNoClass(async:Async) { + failCanRefactor(RefactorExtractInterface, {fileName: "testcases/classes/ChildClass.hx", posStart: 875, posEnd: 875}, "unsupported"); + failRefactor(RefactorExtractInterface, {fileName: "testcases/classes/ChildClass.hx", posStart: 875, posEnd: 875}, + "failed to collect data for extract interface", async); + } + + function testExtractInterfaceBaseClass(async:Async) { + var edits:Array = [ + makeInsertTestEdit("testcases/classes/BaseClass.hx", " implements IBaseClass", 33), + makeCreateTestEdit("testcases/classes/IBaseClass.hx"), + makeInsertTestEdit("testcases/classes/IBaseClass.hx", + "package classes;\n\n" + + "interface IBaseClass {\n" + + "function doSomething(data:Array):Void;\n" + + "function doSomething3(d:Array):Void;\n" + + "function doSomething4(d:Array):Void;\n" + + "function doSomething5(d:Array):Void;\n" + + "function doSomething6(d:Array):Bool;\n" + + "}", + 0, Format(0, false)), + ]; + addTypeHint("testcases/classes/BaseClass.hx", 131, LibType("Void", "Void", [])); + addTypeHint("testcases/classes/BaseClass.hx", 225, LibType("Void", "Void", [])); + addTypeHint("testcases/classes/BaseClass.hx", 296, LibType("Void", "Void", [])); + addTypeHint("testcases/classes/BaseClass.hx", 384, LibType("Void", "Void", [])); + addTypeHint("testcases/classes/BaseClass.hx", 468, LibType("Bool", "Bool", [])); + checkRefactor(RefactorExtractInterface, {fileName: "testcases/classes/BaseClass.hx", posStart: 27, posEnd: 27}, edits, async); + } +} diff --git a/test/refactor/refactor/RefactorExtractTypeTest.hx b/test/refactor/refactor/RefactorExtractTypeTest.hx new file mode 100644 index 0000000..32be35d --- /dev/null +++ b/test/refactor/refactor/RefactorExtractTypeTest.hx @@ -0,0 +1,112 @@ +package refactor.refactor; + +class RefactorExtractTypeTest extends RefactorTestBase { + function setupClass() { + setupTestSources(["testcases/classes", "testcases/typedefs"]); + } + + function testFailExtractTypChildClass(async:Async) { + failCanRefactor(RefactorExtractType, {fileName: "testcases/classes/ChildClass.hx", posStart: 30, posEnd: 30}, "unsupported"); + failRefactor(RefactorExtractType, {fileName: "testcases/classes/ChildClass.hx", posStart: 30, posEnd: 30}, "failed to collect data for extract type", + async); + } + + function testExtractTypeListOfChilds(async:Async) { + var edits:Array = [ + makeRemoveTestEdit("testcases/classes/ChildClass.hx", 860, 901), + makeCreateTestEdit("testcases/classes/ListOfChilds.hx"), + makeInsertTestEdit("testcases/classes/ListOfChilds.hx", "package classes;\n\ntypedef ListOfChilds = Array;", 0, Format(0, false)), + makeInsertTestEdit("testcases/classes/pack/UseChild.hx", "import classes.ListOfChilds;\n", 23), + ]; + checkRefactor(RefactorExtractType, {fileName: "testcases/classes/ChildClass.hx", posStart: 873, posEnd: 873}, edits, async); + } + + function testExtractTypeTextLoader(async:Async) { + var edits:Array = [ + makeRemoveTestEdit("testcases/classes/Printer.hx", 1262, 1397), + makeCreateTestEdit("testcases/classes/TextLoader.hx"), + makeInsertTestEdit("testcases/classes/TextLoader.hx", + "package classes;\n\n" + + "import js.lib.Promise;\n\n" + + "class TextLoader {\n" + + " public function new() {}\n\n" + + " public function load(text:String):Promise {\n" + + " return Promise.resolve(text);\n" + + " }\n" + + "}", + 0, Format(0, false)), + makeInsertTestEdit("testcases/classes/pack/UsePrinter.hx", "import classes.TextLoader;\n", 23), + ]; + checkRefactor(RefactorExtractType, {fileName: "testcases/classes/Printer.hx", posStart: 1273, posEnd: 1283}, edits, async); + } + + function testExtractTypeContextWithDocComment(async:Async) { + var edits:Array = [ + makeCreateTestEdit("testcases/classes/Context.hx"), + makeInsertTestEdit("testcases/classes/Context.hx", + "package classes;\n\n" + + "using classes.ChildHelper;\n" + + "using classes.pack.SecondChildHelper;\n\n" + + "/**\n" + + " * Context class\n" + + " */\n" + + "class Context {\n" + + " public static var printFunc:PrintFunc;\n" + + "}", + 0, Format(0, false)), + makeRemoveTestEdit("testcases/classes/StaticUsing.hx", 557, 639), + ]; + checkRefactor(RefactorExtractType, {fileName: "testcases/classes/StaticUsing.hx", posStart: 590, posEnd: 590}, edits, async); + } + + function testExtractTypeNotDocModule(async:Async) { + var edits:Array = [ + makeRemoveTestEdit("testcases/classes/DocModule.hx", 62, 169), + makeReplaceTestEdit("testcases/classes/ForceRenameCrash.hx", "classes.NotDocModule", 86, 116), + makeCreateTestEdit("testcases/classes/NotDocModule.hx"), + makeInsertTestEdit("testcases/classes/NotDocModule.hx", + "/**\n" + + " * file header\n" + + " */\n\n" + + "package classes;\n\n" + + "class NotDocModule {\n" + + " public function new() {}\n\n" + + " public function doSomething() {\n" + + " trace(\"something\");\n" + + " }\n" + + "}", + 0, Format(0, false)), + makeInsertTestEdit("testcases/classes/pack/UseDocModule.hx", "import classes.NotDocModule;\n", 23), + ]; + checkRefactor(RefactorExtractType, {fileName: "testcases/classes/DocModule.hx", posStart: 73, posEnd: 73}, edits, async); + } + + function testExtractTypeUserConfig(async:Async) { + var edits:Array = [ + makeRemoveTestEdit("testcases/typedefs/Types.hx", 247, 811), + makeCreateTestEdit("testcases/typedefs/UserConfig.hx"), + makeInsertTestEdit("testcases/typedefs/UserConfig.hx", + "package typedefs;\n\n" + + "import haxe.extern.EitherType;\n\n" + + "typedef UserConfig = {\n" + + " var enableCodeLens:Bool;\n" + + " var enableDiagnostics:Bool;\n" + + " var enableServerView:Bool;\n" + + " var enableSignatureHelpDocumentation:Bool;\n" + + " var diagnosticsPathFilter:String;\n" + + " var displayPort:EitherType;\n" + + " var buildCompletionCache:Bool;\n" + + " var enableCompletionCacheWarning:Bool;\n" + + " var useLegacyCompletion:Bool;\n" + + " var codeGeneration:CodeGenerationConfig;\n" + + " var exclude:Array;\n" + + " var postfixCompletion:PostfixCompletionConfig;\n" + + " var importsSortOrder:ImportsSortOrderConfig;\n" + + " var maxCompletionItems:Int;\n" + + " var renameSourceFolders:Array;\n" + + "}", + 0, Format(0, false)), + ]; + checkRefactor(RefactorExtractType, {fileName: "testcases/typedefs/Types.hx", posStart: 260, posEnd: 260}, edits, async); + } +} diff --git a/test/refactor/refactor/RefactorTypedefTest.hx b/test/refactor/refactor/RefactorTypedefTest.hx deleted file mode 100644 index db91513..0000000 --- a/test/refactor/refactor/RefactorTypedefTest.hx +++ /dev/null @@ -1,56 +0,0 @@ -package refactor.refactor; - -class RefactorTypedefTest extends RefactorTestBase { - function setupClass() { - setupTestSources(["testcases/typedefs"]); - } - - function testExtractTypeListOfChilds(async:Async) { - var edits:Array = [ - makeRemoveTestEdit("testcases/typedefs/Types.hx", 247, 811), - makeCreateTestEdit("testcases/typedefs/UserConfig.hx"), - makeInsertTestEdit("testcases/typedefs/UserConfig.hx", - "package typedefs;\n\n" - + "import haxe.extern.EitherType;\n\n" - + "typedef UserConfig = {\n" - + " var enableCodeLens:Bool;\n" - + " var enableDiagnostics:Bool;\n" - + " var enableServerView:Bool;\n" - + " var enableSignatureHelpDocumentation:Bool;\n" - + " var diagnosticsPathFilter:String;\n" - + " var displayPort:EitherType;\n" - + " var buildCompletionCache:Bool;\n" - + " var enableCompletionCacheWarning:Bool;\n" - + " var useLegacyCompletion:Bool;\n" - + " var codeGeneration:CodeGenerationConfig;\n" - + " var exclude:Array;\n" - + " var postfixCompletion:PostfixCompletionConfig;\n" - + " var importsSortOrder:ImportsSortOrderConfig;\n" - + " var maxCompletionItems:Int;\n" - + " var renameSourceFolders:Array;\n" - + "}", - 0, Format(0, false)), - ]; - checkRefactor(RefactorExtractType, {fileName: "testcases/typedefs/Types.hx", posStart: 260, posEnd: 260}, edits, async); - } - - function testRewriteVarsToFinalsTestFormatterInputData(async:Async) { - var edits:Array = [ - makeReplaceTestEdit("testcases/typedefs/codedata/TestFormatterInputData.hx", "final", 257, 260, NoFormat), - makeReplaceTestEdit("testcases/typedefs/codedata/TestFormatterInputData.hx", "final", 297, 300, NoFormat), - makeReplaceTestEdit("testcases/typedefs/codedata/TestFormatterInputData.hx", "final", 334, 337, NoFormat), - makeReplaceTestEdit("testcases/typedefs/codedata/TestFormatterInputData.hx", "final", 382, 385, NoFormat), - makeReplaceTestEdit("testcases/typedefs/codedata/TestFormatterInputData.hx", "final", 420, 423, NoFormat), - makeReplaceTestEdit("testcases/typedefs/codedata/TestFormatterInputData.hx", "final", 452, 455, NoFormat), - ]; - checkRefactor(RefactorRewriteVarsToFinals(true), {fileName: "testcases/typedefs/codedata/TestFormatterInputData.hx", posStart: 245, posEnd: 473}, - edits, async); - } - - function testRewriteFinalsToVarsTestFormatterInputData(async:Async) { - failCanRefactor(RefactorRewriteVarsToFinals(false), {fileName: "testcases/typedefs/codedata/TestFormatterInputData.hx", posStart: 245, posEnd: 473}, - "unsupported"); - failRefactor(RefactorRewriteVarsToFinals(false), {fileName: "testcases/typedefs/codedata/TestFormatterInputData.hx", posStart: 245, posEnd: 473}, - "failed to collect data for rewrite vars/finals", async); - } -} diff --git a/test/refactor/refactor/RewriteFinalsToVarsTest.hx b/test/refactor/refactor/RewriteFinalsToVarsTest.hx new file mode 100644 index 0000000..d151a41 --- /dev/null +++ b/test/refactor/refactor/RewriteFinalsToVarsTest.hx @@ -0,0 +1,21 @@ +package refactor.refactor; + +class RewriteFinalsToVarsTest extends RefactorTestBase { + function setupClass() { + setupTestSources(["testcases/classes"]); + } + + function testRewriteFinalsToVarsJsonClass(async:Async) { + failCanRefactor(RefactorRewriteVarsToFinals(false), {fileName: "testcases/classes/JsonClass.hx", posStart: 18, posEnd: 696}, "unsupported"); + failRefactor(RefactorRewriteVarsToFinals(false), {fileName: "testcases/classes/JsonClass.hx", posStart: 18, posEnd: 696}, + "failed to collect data for rewrite vars/finals", async); + } + + function testRewriteFinalsToVarsPrinter(async:Async) { + var edits:Array = [ + makeReplaceTestEdit("testcases/classes/Printer.hx", "var", 147, 152, NoFormat), + makeReplaceTestEdit("testcases/classes/Printer.hx", "var", 225, 230, NoFormat), + ]; + checkRefactor(RefactorRewriteVarsToFinals(false), {fileName: "testcases/classes/Printer.hx", posStart: 129, posEnd: 1079}, edits, async); + } +} diff --git a/test/refactor/refactor/RewriteVarsToFinalsTest.hx b/test/refactor/refactor/RewriteVarsToFinalsTest.hx new file mode 100644 index 0000000..1540f43 --- /dev/null +++ b/test/refactor/refactor/RewriteVarsToFinalsTest.hx @@ -0,0 +1,50 @@ +package refactor.refactor; + +class RewriteVarsToFinalsTest extends RefactorTestBase { + function setupClass() { + setupTestSources(["testcases/classes", "testcases/typedefs"]); + } + + function testRewriteVarsToFinalsPrinter(async:Async) { + failCanRefactor(RefactorRewriteVarsToFinals(true), {fileName: "testcases/classes/Printer.hx", posStart: 129, posEnd: 1079}, "unsupported"); + failRefactor(RefactorRewriteVarsToFinals(true), {fileName: "testcases/classes/Printer.hx", posStart: 129, posEnd: 1079}, + "failed to collect data for rewrite vars/finals", async); + } + + function testRewriteVarsToFinalsPrinterReversedPos(async:Async) { + failCanRefactor(RefactorRewriteVarsToFinals(true), {fileName: "testcases/classes/Printer.hx", posStart: 1079, posEnd: 129}, "unsupported"); + failRefactor(RefactorRewriteVarsToFinals(true), {fileName: "testcases/classes/Printer.hx", posStart: 1079, posEnd: 129}, + "failed to collect data for rewrite vars/finals", async); + } + + function testRewriteVarsToFinalsJsonClass(async:Async) { + var edits:Array = [ + makeReplaceTestEdit("testcases/classes/JsonClass.hx", "final", 37, 40, NoFormat), + makeReplaceTestEdit("testcases/classes/JsonClass.hx", "final", 50, 53, NoFormat), + makeReplaceTestEdit("testcases/classes/JsonClass.hx", "final", 68, 71, NoFormat), + makeReplaceTestEdit("testcases/classes/JsonClass.hx", "final", 84, 87, NoFormat), + makeReplaceTestEdit("testcases/classes/JsonClass.hx", "final", 301, 304, NoFormat), + ]; + checkRefactor(RefactorRewriteVarsToFinals(true), {fileName: "testcases/classes/JsonClass.hx", posStart: 18, posEnd: 696}, edits, async); + } + + function testRewriteVarsToFinalsTestFormatterInputData(async:Async) { + var edits:Array = [ + makeReplaceTestEdit("testcases/typedefs/codedata/TestFormatterInputData.hx", "final", 257, 260, NoFormat), + makeReplaceTestEdit("testcases/typedefs/codedata/TestFormatterInputData.hx", "final", 297, 300, NoFormat), + makeReplaceTestEdit("testcases/typedefs/codedata/TestFormatterInputData.hx", "final", 334, 337, NoFormat), + makeReplaceTestEdit("testcases/typedefs/codedata/TestFormatterInputData.hx", "final", 382, 385, NoFormat), + makeReplaceTestEdit("testcases/typedefs/codedata/TestFormatterInputData.hx", "final", 420, 423, NoFormat), + makeReplaceTestEdit("testcases/typedefs/codedata/TestFormatterInputData.hx", "final", 452, 455, NoFormat), + ]; + checkRefactor(RefactorRewriteVarsToFinals(true), {fileName: "testcases/typedefs/codedata/TestFormatterInputData.hx", posStart: 245, posEnd: 473}, + edits, async); + } + + function testRewriteFinalsToVarsTestFormatterInputData(async:Async) { + failCanRefactor(RefactorRewriteVarsToFinals(false), {fileName: "testcases/typedefs/codedata/TestFormatterInputData.hx", posStart: 245, posEnd: 473}, + "unsupported"); + failRefactor(RefactorRewriteVarsToFinals(false), {fileName: "testcases/typedefs/codedata/TestFormatterInputData.hx", posStart: 245, posEnd: 473}, + "failed to collect data for rewrite vars/finals", async); + } +} diff --git a/test/refactor/refactor/RewriteWrapWithTryCatchTest.hx b/test/refactor/refactor/RewriteWrapWithTryCatchTest.hx new file mode 100644 index 0000000..2d912eb --- /dev/null +++ b/test/refactor/refactor/RewriteWrapWithTryCatchTest.hx @@ -0,0 +1,39 @@ +package refactor.refactor; + +class RewriteWrapWithTryCatchTest extends RefactorTestBase { + function setupClass() { + setupTestSources(["testcases/classes"]); + } + + function testFailTryCatchCollectDataEmptyFile(async:Async) { + failCanRefactor(RefactorRewriteWrapWithTryCatch, {fileName: "testcases/classes/BaseClass.hx", posStart: 156, posEnd: 263}, "unsupported"); + failRefactor(RefactorRewriteWrapWithTryCatch, {fileName: "testcases/classes/BaseClass.hx", posStart: 156, posEnd: 263}, + "failed to collect data for rewrite wrap with try catch", async); + } + + function testFailTryCatchCollectDataReversedPos(async:Async) { + failCanRefactor(RefactorRewriteWrapWithTryCatch, {fileName: "testcases/classes/BaseClass.hx", posStart: 263, posEnd: 156}, "unsupported"); + failRefactor(RefactorRewriteWrapWithTryCatch, {fileName: "testcases/classes/BaseClass.hx", posStart: 263, posEnd: 156}, + "failed to collect data for rewrite wrap with try catch", async); + } + + function testRewriteWrapTryCatchJsonClass(async:Async) { + var edits:Array = [ + makeReplaceTestEdit("testcases/classes/JsonClass.hx", + "try {\n" + + "var newgroup:Null = new JsonClass(group.id, group.type, group.width);\n" + + " newgroup.id = group.id;\n" + + " newgroup.type = group.type;\n" + + " newgroup.width = group.width;\n" + + " newgroup.maxWidth = group.maxWidth;\n\n" + + " return newgroup;\n" + + "}\n" + + "catch (e:haxe.Exception) {\n" + + "// TODO: handle exception\n" + + "trace (e.details());\n" + + "}", + 301, 527, Format(2, true)), + ]; + checkRefactor(RefactorRewriteWrapWithTryCatch, {fileName: "testcases/classes/JsonClass.hx", posStart: 300, posEnd: 527}, edits, async); + } +} diff --git a/test/refactor/rename/RenameTypedefTest.hx b/test/refactor/rename/RenameTypedefTest.hx index 8e0c293..3f4878e 100644 --- a/test/refactor/rename/RenameTypedefTest.hx +++ b/test/refactor/rename/RenameTypedefTest.hx @@ -96,4 +96,13 @@ class RenameTypedefTest extends RenameTestBase { ]; checkRename({fileName: "testcases/typedefs/codedata/TestFormatterInputData.hx", toName: "indentationOffset", pos: 462}, edits, async); } + + public function testRenameForceCommandResolveSupport(async:Async) { + var edits:Array = [ + makeReplaceTestEdit("testcases/typedefs/ExperimentalCapabilities.hx", "forceSupport", 324, 350), + makeReplaceTestEdit("testcases/typedefs/ExperimentalCapabilities.hx", "forceSupport", 440, 466), + makeReplaceTestEdit("testcases/typedefs/ExperimentalCapabilities.hx", "forceSupport", 491, 517), + ]; + checkRename({fileName: "testcases/typedefs/ExperimentalCapabilities.hx", toName: "forceSupport", pos: 330}, edits, async); + } } diff --git a/testcases/typedefs/ExperimentalCapabilities.hx b/testcases/typedefs/ExperimentalCapabilities.hx new file mode 100644 index 0000000..b6d6617 --- /dev/null +++ b/testcases/typedefs/ExperimentalCapabilities.hx @@ -0,0 +1,19 @@ +package typedefs; + +typedef ExperimentalCapabilities = { + /** + List of supported commands on client, like `"codeAction.insertSnippet"`, + to generate snippets in code actions, instead of simple text edits + **/ + var ?supportedCommands:Array; + + /** Forces resolve support for code actions `command` property **/ + var ?forceCommandResolveSupport:Bool; +} + +function canRun(capabilities:ExperimentalCapabilities):Bool { + if (capabilities.forceCommandResolveSupport != null && capabilities.forceCommandResolveSupport) { + return true; + } + return false; +} From e73924073263968639aecb3b717527a4dff68ebe Mon Sep 17 00:00:00 2001 From: AlexHaxe Date: Sun, 22 Dec 2024 20:42:17 +0100 Subject: [PATCH 58/59] fixed field rename with null-safe access --- src/refactor/rename/RenameField.hx | 1 + test/refactor/rename/RenameClassTest.hx | 13 ++++++++ testcases/classes/Logger.hx | 44 +++++++++++++++++++++++++ 3 files changed, 58 insertions(+) create mode 100644 testcases/classes/Logger.hx diff --git a/src/refactor/rename/RenameField.hx b/src/refactor/rename/RenameField.hx index 090f5b2..44d8a7a 100644 --- a/src/refactor/rename/RenameField.hx +++ b/src/refactor/rename/RenameField.hx @@ -88,6 +88,7 @@ class RenameField { static function replaceInTypeWithFieldAccess(changelist:Changelist, type:Type, prefix:String, from:String, to:String) { var allUses:Array = type.getIdentifiers(prefix + from); var allAccess:Array = type.getStartsWith('$prefix$from.'); + allAccess = allAccess.concat(type.getStartsWith('$prefix$from?.')); var shadowed:Bool = false; for (access in allAccess) { for (use in allUses) { diff --git a/test/refactor/rename/RenameClassTest.hx b/test/refactor/rename/RenameClassTest.hx index 57f6710..8177575 100644 --- a/test/refactor/rename/RenameClassTest.hx +++ b/test/refactor/rename/RenameClassTest.hx @@ -397,4 +397,17 @@ class RenameClassTest extends RenameTestBase { checkRename({fileName: "testcases/classes/DocModule.hx", toName: "doMore", pos: 128}, edits, async); } + + public function testRenameLogger(async:Async) { + var edits:Array = [ + makeReplaceTestEdit("testcases/classes/Logger.hx", "log", 104, 110), + makeReplaceTestEdit("testcases/classes/Logger.hx", "log", 209, 215), + makeReplaceTestEdit("testcases/classes/Logger.hx", "log", 330, 336), + makeReplaceTestEdit("testcases/classes/Logger.hx", "log", 450, 456), + makeReplaceTestEdit("testcases/classes/Logger.hx", "log", 569, 575), + makeReplaceTestEdit("testcases/classes/Logger.hx", "log", 689, 695), + ]; + + checkRename({fileName: "testcases/classes/Logger.hx", toName: "log", pos: 107}, edits, async); + } } diff --git a/testcases/classes/Logger.hx b/testcases/classes/Logger.hx new file mode 100644 index 0000000..793289f --- /dev/null +++ b/testcases/classes/Logger.hx @@ -0,0 +1,44 @@ +package classes; + +import haxe.Exception; +import haxe.PosInfos; + +class Log { + @inject private static var logger:ILogger; + + public static inline function fatal(text:String, ?e:Exception, ?pos:PosInfos):Void { + logger?.fatal(text, e, pos); + } + + public static inline function error(text:String, ?e:Exception, ?pos:PosInfos):Void { + logger?.error(text, e, pos); + } + + public static inline function warn(text:String, ?e:Exception, ?pos:PosInfos):Void { + logger?.warn(text, e, pos); + } + + public static inline function info(text:String, ?e:Exception, ?pos:PosInfos):Void { + logger?.info(text, e, pos); + } + + public static inline function debug(text:String, ?e:Exception, ?pos:PosInfos):Void { + logger?.debug(text, e, pos); + } + + public static inline function logVersion():Void { + debug("Version: 1.0"); + } +} + +interface ILogger { + function fatal(text:String, ?e:Exception, ?pos:PosInfos):Void; + + function error(text:String, ?e:Exception, ?pos:PosInfos):Void; + + function warn(text:String, ?e:Exception, ?pos:PosInfos):Void; + + function info(text:String, ?e:Exception, ?pos:PosInfos):Void; + + function debug(text:String, ?e:Exception, ?pos:PosInfos):Void; +} From a26c0ea885d14be46bdc7cf66c226ec0b5478be4 Mon Sep 17 00:00:00 2001 From: AlexHaxe Date: Mon, 23 Dec 2024 15:14:16 +0100 Subject: [PATCH 59/59] prepare release --- CHANGELOG.md | 23 +++++++++++++++++++---- haxelib.json | 4 ++-- package-lock.json | 19 ++++++++++--------- package.json | 4 ++-- 4 files changed, 33 insertions(+), 17 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 314c4cd..9f636b1 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,19 +1,34 @@ # Version history -## dev branch / next version (2.x.x) +## dev branch / next version (3.x.x) -## 2.4.0 (2024-) +## 3.0.0 (2024-12-23) -- added invalidateFile / removeFile to allow rescan in case of file changes or deletion -- added support for create and delete file operations as edits - added ExtractType refactor module - added ExtractInterface refactor module - added ExtractMethod refactor module +- added Extract Constructor Params refactor module as vars or finals +- added Rewrite Vars to Finals and Rewrite Finals to Vars refactor module +- added Wrap with Try…Catch refactor module +- added invalidateFile / removeFile to allow rescan in case of file changes or deletion +- added support for create and delete file operations as edits +- added support for local function extraction +- added indentation options for snippet formatting - changed Refactor class to Rename - changed getFullModulName call to fullModuleName property +- changed order of type resolution to built-in then external typer - fixed type name renaming not changing in use locations - fixed discovery of arrow functions as type hints +- fixed extracting from functions with type parameters +- fixed discovery of identifiers in if conditions +- fixed code gen for empty return or throw as last expressions of selection +- fixed parameter collection from string interpolation +- fixed importInsertPos for files with comment headers +- fixed enum field rename eating dot separator +- fixed abstract enum type resolution +- fixed rename field with null-safe access operator - refactored typehint data structure +- refactored identifier discovery ## 2.3.1 (2024-11-01) diff --git a/haxelib.json b/haxelib.json index c7176db..00b5c07 100644 --- a/haxelib.json +++ b/haxelib.json @@ -8,8 +8,8 @@ "refactor" ], "description": "A code renaming tool for Haxe", - "version": "2.3.1", - "releasenote": "fixed discovery of vars in pattern extraction - see CHANGELOG", + "version": "3.0.0", + "releasenote": "added ExtractType, ExtractInterface and ExtracMethod refactoring and more + bugfixes - see CHANGELOG", "contributors": [ "AlexHaxe" ], diff --git a/package-lock.json b/package-lock.json index b538352..8c4d0fd 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,26 +1,27 @@ { "name": "@haxecheckstyle/haxe-rename", - "version": "2.3.1", + "version": "3.0.0", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "@haxecheckstyle/haxe-rename", - "version": "2.3.1", + "version": "3.0.0", "license": "MIT", "bin": { "haxe-rename": "bin/rename.js" }, "devDependencies": { - "lix": "^15.12.0" + "lix": "^15.12.4" } }, "node_modules/lix": { - "version": "15.12.0", - "resolved": "https://registry.npmjs.org/lix/-/lix-15.12.0.tgz", - "integrity": "sha512-FA36oCl+M+3Of8L4eErXw7tAHGOjqEC4IgEvH6oPDsiYd4yN6XpzZGcbLuIyu4PiztjOrr1TKJnpwi32qb2ddw==", + "version": "15.12.4", + "resolved": "https://registry.npmjs.org/lix/-/lix-15.12.4.tgz", + "integrity": "sha512-xfwGQrXXNba9BM7VcP2u9WvElBYgGJW+1gviaFqJH5OUFPAhwv8TZNk5f+Yew4pyKOmMqshdUYgk9+d3U6P6kw==", "dev": true, "hasInstallScript": true, + "license": "MIT", "bin": { "haxe": "bin/haxeshim.js", "haxelib": "bin/haxelibshim.js", @@ -31,9 +32,9 @@ }, "dependencies": { "lix": { - "version": "15.12.0", - "resolved": "https://registry.npmjs.org/lix/-/lix-15.12.0.tgz", - "integrity": "sha512-FA36oCl+M+3Of8L4eErXw7tAHGOjqEC4IgEvH6oPDsiYd4yN6XpzZGcbLuIyu4PiztjOrr1TKJnpwi32qb2ddw==", + "version": "15.12.4", + "resolved": "https://registry.npmjs.org/lix/-/lix-15.12.4.tgz", + "integrity": "sha512-xfwGQrXXNba9BM7VcP2u9WvElBYgGJW+1gviaFqJH5OUFPAhwv8TZNk5f+Yew4pyKOmMqshdUYgk9+d3U6P6kw==", "dev": true } } diff --git a/package.json b/package.json index ad0449c..2708519 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@haxecheckstyle/haxe-rename", - "version": "2.3.1", + "version": "3.0.0", "description": "Renaming tool for Haxe", "repository": { "type": "git", @@ -16,7 +16,7 @@ "email": "Alexander.Blum@gmail.com" }, "devDependencies": { - "lix": "^15.12.0" + "lix": "^15.12.4" }, "bin": { "haxe-rename": "bin/rename.js"