
Сплющите структуру упаковки Kotlin с одним рецептом Openrewrite
12 июня 2025 г.Я некоторое время смотрел на Openrewrite, но у меня еще не было времени играть с этим. Если вы никогда не слышали о OpenRewrite, Openrewrite позаботится о рефакторинге вашей кодовой базы на более новые версии, структуры и парадигмы.
OpenRewrite-это автоматизированная экосистема с открытым исходным кодом для исходного кода, позволяющая разработчикам эффективно устранить технические долги в своих репозиториях.
Он состоит из двигателя автоматического повторного факторирования, который запускает предварительные рецепты рефакторирования с открытым исходным кодом для общих фреймворков, исправлений безопасности и стилистических задач согласованности-сокращение ваших усилий по кодированию с часов или дней до минут. Строительными плагинами инструментов, такие как плагин OpenWrite Gradle и плагин Openrewrite Maven, помогут запустить эти рецепты на одном хранилище за раз.
В то время как первоначальное внимание уделялось языку Java, сообщество Openrewrite постоянно расширяет языковые и структуру. Тысячи великих людей и команд работают вместе, чтобы программное обеспечение было бесшовным для обновления и постоянно безопасного.
-Введение в Openrewrite
Openrewrite имеет два компонента: рецепты и двигатель, который их запускает.
Рецепт представляет собой группу операций поиска и рефакторинга, которые могут быть применены к семантическому дереву без потерь. Рецепт может представлять собой одну отдельную работу или может быть связана вместе с другими рецептами для достижения более крупной цели, такой как миграция структуры.
-Рецепты
Использование Openrewrite довольно просто. Он уже предоставляет большой корпус существующих рецептов, некоторые из которых являются бесплатными. То, что я нахожу удивительно мощным, - это способность создавать новые рецепты. Я решил узнать об этом и написать свой собственный.
Мой вариант использования
Мой вариант использования - структура пакета Kotlin. В Java класс вch.frankel.blog
Пакет должен соблюдать жесткую структуру папок: от корня,ch
Вfrankel
, а потомblog
Полем В Kotlin вы можете поместить один и тот же класс в то же пакет в корне. Официальная документация по котлин имеет рекомендации по структуре источника:
В проектах Pure Kotlin рекомендуемая структура каталогов следует за структурой пакета с общим корневым пакетом опущены. Например, если весь код в проекте находится в
org.example.kotlin
пакет и его подпакинги, файлы сorg.example.kotlin
пакет должен быть размещен непосредственно под корнем исходного и файла вorg.example.kotlin.network.socket
Должен быть в подкаталоге сети/сокета исходного корня.-Структура каталога
Рецепт будет перенести исходные файлы ближе к корневым пакетам согласно вышеуказанной рекомендации. Мы могли бы достичь того же с такими инструментами Sysadmin, какmv
Вsed
, или Ides. Хотя можно было бы реализовать мою идею с этими инструментами, Openrewrite имеет несколько преимуществ:
- Тестируемый: Дизайн API тестируется, поэтому вы можете убедиться, что не испортите свою кодовую базу
- Масштабируемый: Это работает на огромных кодовых базах
- Композитный: Это позволяет обеспечить более одного рецепта и запускать их за один проход
Перед погружением в код мы должны немного узнать об API.
Основы рецепта
Openrewrite реализует шаблон посетителей. Вот сокращенная диаграмма класса для исходного кода Kotlin, которую я буду использовать позже.
Вот отрывK
класс, чтобы дополнить предыдущую диаграмму:
Сделать все это вместе
Теперь, когда у нас есть более четкое представление об API, пришло время подумать о коде.
Я требую, чтобы пользователь вручную настраивал корневой пакет в этой версии прототипа. Я хотел бы вычислить корневой пакет с кодовой базы в будущей версии. Например, с исходными файлами с соответствующими объявлениями пакетовch.frankel.blog
Вch.frankel.blog.foo
, иch.frankel.blog.bar
, корневой пакет вычисляется какch.frankel.blog
Полем Кроме того, я предполагаю, что исходные файлы находятся подsrc/main/resources
Полем
Процесс должен, для каждого файла:
- Получить параметризованное имя пакета
- Если имя пакета является корневым пакетом, немедленно верните вычислительный блок
- Если нет, вычислите новое местоположение вычислительного устройства
- Установите место для нового файла на вычислительном блоке
- Вернуть обновленную вычислительную единицу
Вот реализация вышеизложенного:
class FlattenStructure(private val rootPackage: String) : Recipe() { //1
override fun getDisplayName(): String = "Flatten Kotlin package directory structure" //2
override fun getDescription(): String = //2
"Move Kotlin files to match idiomatic layout by omitting the root package according to the official recommendation."
override fun getVisitor(): TreeVisitor<*, ExecutionContext> {
return object : KotlinIsoVisitor<ExecutionContext>() {
override fun visitCompilationUnit(cu: K.CompilationUnit, ctx: ExecutionContext): K.CompilationUnit {
val packageName = cu.packageDeclaration?.packageName ?: return cu //3
if (!packageName.startsWith(rootPackage)) return cu //4
val relativePath = packageName.removePrefix(rootPackage)
.removePrefix(".")
.replace('.', '/') //5
val filename = cu.sourcePath.fileName.toString()
val newPath: Path = Paths.get("src/main/kotlin")
.resolve(relativePath)
.resolve(filename) //6
return cu.withSourcePath(newPath) //7
}
}
}
}
- Параметризуйте рецепт с помощью корневого пакета
- Атрибуты для целей документации
- Если мы не можем вычислить имя пакета, верните
- Если блок компиляции уже находится в корневом пакете, верните
- Вычислить относительный путь из корневого пакета и имени пакета устройства
- Вычислить новый путь
- Верните компиляционный блок с новым путем
Тестирование рецепта
API Openrewrite поддается тестированию. Он предлагает анализаторы на разных языках, которые он поддерживает, и в контексте выполнения в памяти. Вот отрыв из того, что я использовал для проверки вышеуказанного рецепта:
Я решил использовать параметризованный тест JUNIT, но это не имеет значения:
class FlattenStructureTest {
@ParameterizedTest
@MethodSource("testData")
fun `should flatten accordingly`(
sourceCode: String,
originalPath: String,
configuredRootPackage: String,
expectedPath: String
) {
// Given
val parser = KotlinParser.builder().build() //1
val cu = parser.parse( //2
InMemoryExecutionContext(), //3
sourceCode
).findFirst() //4
.orElseThrow { IllegalStateException("Failed to parse Kotlin file") } //4
val originalPath = Paths.get(originalPath)
val modifiedCu = (cu as K.CompilationUnit).withSourcePath(originalPath) //5
// When
val recipe = FlattenStructure(configuredRootPackage)
val result = recipe.visitor.visit(modifiedCu, InMemoryExecutionContext()) //6
// Then
val expectedPath = Paths.get(expectedPath)
assertEquals(expectedPath, (result as SourceFile).sourcePath) //7
}
// Provide the file content, the path, the root, and the expected path
}
- Парсеры предлагают API шаблона застройщика
- Сделайте анализ
- Поскольку мы не используем контекст для передачи сообщений по рецептам, мы можем создать экземпляр в памяти для каждого теста
parse
возвращает потокSourceFile
Полем Несмотря на то, что мы знаем, что он содержит один, нам нужно обрабатывать поток и возможное исключение.- Из -за отсутствия дженериков нам нужно разыграть возвращенных
SourceFile
кK.CompilationUnit
- Посетите исходный код вручную - в отличие от двигателя, который делает это для нас
- Ожидайте, что файл был перемещен (практически)
Теперь мы можем использовать рецепт:
---
type: specs.openrewrite.org/v1beta/recipe
name: ch.frankel.MyMigration
recipeList:
- ch.frankel.openrewrite.kotlin.FlattenStructure:
rootPackage: com.acme
Потенциальные будущие работы
Я намеренно исключил требование рецепта: рецептДОЛЖЕНБудьте json-serializable. Ни Градл, ни Мавен не используют эту функцию, но другие инструменты в экосистеме делают.
Кроме того, рецепт требует установления корневого пакета вручную. Мы можем перечислить все доступные исходные файлы и их соответствующий пакет и вычислить корень во многих случаях. Это требует немного более специализированный рецепт. Я намеренно оставил его для другого потенциального поста.
Заключение
В этом посте я показываю, насколько легко авторизацию простого и протестированного рецепта Openrewrite. Возможно, я могу вычислить корневой пакет в будущем посте.
Openrewrite легко расширяется; Это подтверждает мое предыдущее убеждение, что это инструмент для миграции вашей кодовой базы.
Полный исходный код для этого поста можно найти на GitHub:
https://github.com/ajavageek/flatten-kotlin-recipe?embedable=true
Идти дальше:
- Введение в Openrewrite
- Написание рецепта рефакторинга Java
Первоначально опубликовано наJava Geek8 июня 2025 года
Оригинал