Сегодня у меня на работе была небольшая задача - нужно было исправить названия строковых ресурсов в нашем проекте - избавиться от точек.
Было:
profile.expandСтало:
profile_expandЗачем это было нужно: стандартная инспекция
Find Usages в Android Studio не умеет понимать что использование R.string.profile_expand в коде это на самом деле использование ресурса profile.expand. Она работает только с названиями ресурсов у которых есть только андерскоры: profile_expand.Соответственно, задача стояла в том чтобы перелопатить полторы тысячи ресурсов, делать это руками конечно же никакого желания и времени нету.
Также нужно не забывать что нужно не только переименовать ресурсы, но и их использование в
xml по всему проекту.На ум пришла давняя идея - сделать плагин для
Intellij который позволит компилировать произвольный код для выполнения каких-то единичных операций прямо в IDE.И тут я с удивлением обнаружил что такой инструмент давно существует в IDE!
Называется он
IDE Scripting Console и получить к нему доступ можно через Cmd + Shift + A и поиск по строке IDE Scripting Console. Можете увидеть пример на скриншоте в первом комментарии к этому посту.Внутри этого скрипта можно пользоваться всеми апи платформы Intellij которые обычно доступны при разработке плагинов для IDE. Также из скрипта можно получить значение последнего выполненного выражения, его будет видно в логе запуска скрипта.
Таким образом, задача упростилась донельзя:
1. Берём текущий открытый проект:
val project = ProjectManager.getInstance().openProjects.first()
2. Ищем все XML файлы в проекте (по пути отфильтровываем сгенерированные):
val baseDir = VfsUtil.findFile(Paths.get(project.basePath), true)!!
val psiManager = PsiManager.getInstance(project)
val xmlFiles =
VfsUtil
.collectChildrenRecursively(baseDir)
.asSequence()
.filter { it.extension == "xml" }
.filterNot { it.path.contains("build") }
.mapNotNull { psiManager.findFile(it) as? XmlFile }
3. Переименовываем строковые ресурсы:
xmlFiles
.filter { it.virtualFile.path.contains("/res/values/") }
.mapNotNull { xmlFile -> xmlFile.rootTag?.findSubTags("string")?.asSequence() }
.flatten()
.mapNotNull { stringTag -> stringTag.getAttribute("name") }
.filter { nameAttribute -> nameAttribute.value?.contains('.') == true }
.forEach {
val oldName = it.value!!
val newName = oldName.replace('.', '_')
WriteCommandAction.runWriteCommandAction(project) {
it.setValue(newName)
}
}
4. Ищем все XML атрибуты в проекте в которых есть значение начинающееся с
@string и в котором присутствуют точки:fun XmlFile.getAllAttributesRecursively() =
generateSequence(
seed = listOf(element = rootTag!!),
nextFunction = {
val subTags = it.flatMap { it.subTags.toList() }
subTags.ifEmpty { null }
},
)
.flatMap { it.asSequence() }
.flatMap { it.attributes.asSequence() }
val allXmlAttributes = xmlFiles.flatMap { xmlFile ->
xmlFile
.getAllAttributesRecursively()
.filter { it.value!!.startsWith("@string/") }
.filter { it.value!!.contains('.') }
}
5. Заменяем точки в этих атрибутах на underscore:
allXmlAttributes.forEach {
WriteCommandAction.runWriteCommandAction(project) {
it.setValue(
it.value!!.replace('.', '_')
)
}
}Вот таким нехитрым способом я смог безопасно выполнить нетривиальный рефакторинг в проекте.
Пользуйтесь!