mirror of
https://github.com/cashapp/redwood.git
synced 2026-03-09 21:58:16 +08:00
Remove unused lint support (#2834)
If we want it at some point, we can revert this commit.
This commit is contained in:
@@ -33,12 +33,6 @@ public final class app/cash/redwood/gradle/RedwoodGeneratorPlugin$Strategy : jav
|
||||
public static fun values ()[Lapp/cash/redwood/gradle/RedwoodGeneratorPlugin$Strategy;
|
||||
}
|
||||
|
||||
public final class app/cash/redwood/gradle/RedwoodLintPlugin : org/gradle/api/Plugin {
|
||||
public fun <init> ()V
|
||||
public synthetic fun apply (Ljava/lang/Object;)V
|
||||
public fun apply (Lorg/gradle/api/Project;)V
|
||||
}
|
||||
|
||||
public final class app/cash/redwood/gradle/RedwoodModifiersGeneratorPlugin : app/cash/redwood/gradle/RedwoodGeneratorPlugin {
|
||||
public fun <init> ()V
|
||||
}
|
||||
|
||||
@@ -46,12 +46,6 @@ dependencies {
|
||||
|
||||
gradlePlugin {
|
||||
plugins {
|
||||
redwoodLint {
|
||||
id = "app.cash.redwood.lint"
|
||||
displayName = "Redwood lint"
|
||||
description = "Redwood lint Gradle plugin"
|
||||
implementationClass = "app.cash.redwood.gradle.RedwoodLintPlugin"
|
||||
}
|
||||
redwoodSchema {
|
||||
id = "app.cash.redwood.schema"
|
||||
displayName = "Redwood schema"
|
||||
@@ -125,7 +119,6 @@ test {
|
||||
dependsOn(':redwood-runtime:publishAllPublicationsToLocalMavenRepository')
|
||||
dependsOn(':redwood-schema:publishAllPublicationsToLocalMavenRepository')
|
||||
dependsOn(':redwood-tooling-codegen:publishAllPublicationsToLocalMavenRepository')
|
||||
dependsOn(':redwood-tooling-lint:publishAllPublicationsToLocalMavenRepository')
|
||||
dependsOn(':redwood-tooling-schema:publishAllPublicationsToLocalMavenRepository')
|
||||
dependsOn(':redwood-ui-core-api:publishAllPublicationsToLocalMavenRepository')
|
||||
dependsOn(':redwood-ui-core-modifiers:publishAllPublicationsToLocalMavenRepository')
|
||||
|
||||
@@ -1,235 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2022 Square, Inc.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package app.cash.redwood.gradle
|
||||
|
||||
import com.android.build.gradle.AppExtension
|
||||
import com.android.build.gradle.LibraryExtension
|
||||
import java.io.File
|
||||
import java.util.Locale.ROOT
|
||||
import org.gradle.api.NamedDomainObjectProvider
|
||||
import org.gradle.api.Plugin
|
||||
import org.gradle.api.Project
|
||||
import org.gradle.api.Task
|
||||
import org.gradle.api.artifacts.Configuration
|
||||
import org.gradle.api.attributes.Usage
|
||||
import org.gradle.api.attributes.Usage.JAVA_RUNTIME
|
||||
import org.gradle.api.attributes.Usage.USAGE_ATTRIBUTE
|
||||
import org.gradle.api.tasks.TaskProvider
|
||||
import org.gradle.language.base.plugins.LifecycleBasePlugin.CHECK_TASK_NAME
|
||||
import org.gradle.language.base.plugins.LifecycleBasePlugin.VERIFICATION_GROUP
|
||||
import org.jetbrains.kotlin.gradle.dsl.KotlinJvmProjectExtension
|
||||
import org.jetbrains.kotlin.gradle.dsl.KotlinMultiplatformExtension
|
||||
import org.jetbrains.kotlin.gradle.plugin.KotlinCompilation.Companion.MAIN_COMPILATION_NAME
|
||||
import org.jetbrains.kotlin.gradle.plugin.KotlinPlatformType.androidJvm
|
||||
import org.jetbrains.kotlin.gradle.plugin.KotlinPlatformType.common
|
||||
import org.jetbrains.kotlin.gradle.plugin.KotlinTarget
|
||||
|
||||
private const val BASE_TASK_NAME = "redwoodLint"
|
||||
|
||||
@Suppress("unused") // Invoked reflectively by Gradle.
|
||||
public class RedwoodLintPlugin : Plugin<Project> {
|
||||
override fun apply(project: Project) {
|
||||
val configuration = project.configurations.register("redwoodToolingLint")
|
||||
|
||||
project.afterEvaluate {
|
||||
val androidPlugin = if (project.plugins.hasPlugin("com.android.application")) {
|
||||
AndroidPlugin.Application
|
||||
} else if (project.plugins.hasPlugin("com.android.library")) {
|
||||
AndroidPlugin.Library
|
||||
} else {
|
||||
null
|
||||
}
|
||||
|
||||
val task = if (project.plugins.hasPlugin("org.jetbrains.kotlin.multiplatform")) {
|
||||
val rootTask = project.tasks.register(BASE_TASK_NAME) {
|
||||
it.group = VERIFICATION_GROUP
|
||||
it.description = taskDescription("all Kotlin targets")
|
||||
}
|
||||
if (androidPlugin != null) {
|
||||
configureKotlinMultiplatformTargets(project, configuration, rootTask, skipAndroid = true)
|
||||
configureKotlinAndroidVariants(project, configuration, rootTask, androidPlugin, prefix = true)
|
||||
} else {
|
||||
configureKotlinMultiplatformTargets(project, configuration, rootTask)
|
||||
}
|
||||
rootTask
|
||||
} else if (project.plugins.hasPlugin("org.jetbrains.kotlin.jvm")) {
|
||||
configureKotlinJvmProject(project, configuration)
|
||||
} else if (project.plugins.hasPlugin("org.jetbrains.kotlin.android")) {
|
||||
checkNotNull(androidPlugin) {
|
||||
"Kotlin Android plugin requires either Android application or library plugin"
|
||||
}
|
||||
val rootTask = project.tasks.register(BASE_TASK_NAME) {
|
||||
it.group = VERIFICATION_GROUP
|
||||
it.description = taskDescription("all Kotlin targets")
|
||||
}
|
||||
configureKotlinAndroidVariants(
|
||||
project,
|
||||
configuration,
|
||||
rootTask,
|
||||
androidPlugin,
|
||||
prefix = false,
|
||||
)
|
||||
rootTask
|
||||
} else {
|
||||
val name = if (project.path == ":") {
|
||||
"root project"
|
||||
} else {
|
||||
"project ${project.path}"
|
||||
}
|
||||
throw IllegalStateException(
|
||||
"'app.cash.redwood.lint' requires a compatible Kotlin plugin to be applied ($name)",
|
||||
)
|
||||
}
|
||||
|
||||
project.tasks.named(CHECK_TASK_NAME).configure {
|
||||
it.dependsOn(task)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private enum class AndroidPlugin {
|
||||
Application,
|
||||
Library,
|
||||
}
|
||||
|
||||
private fun configureKotlinAndroidVariants(
|
||||
project: Project,
|
||||
configuration: NamedDomainObjectProvider<Configuration>,
|
||||
rootTask: TaskProvider<Task>,
|
||||
android: AndroidPlugin,
|
||||
prefix: Boolean,
|
||||
) {
|
||||
val extensions = project.extensions
|
||||
val variants = when (android) {
|
||||
AndroidPlugin.Application -> extensions.getByType(AppExtension::class.java).applicationVariants
|
||||
AndroidPlugin.Library -> extensions.getByType(LibraryExtension::class.java).libraryVariants
|
||||
}
|
||||
variants.configureEach { variant ->
|
||||
val taskName = buildString {
|
||||
append(BASE_TASK_NAME)
|
||||
if (prefix) {
|
||||
append("Android")
|
||||
}
|
||||
append(variant.name.replaceFirstChar { it.titlecase(ROOT) })
|
||||
}
|
||||
val task = project.createRedwoodLintTask(
|
||||
configuration,
|
||||
taskName,
|
||||
"Kotlin Android ${variant.name} variant",
|
||||
sourceDirs = { variant.sourceSets.flatMap { it.kotlinDirectories } },
|
||||
classpath = { variant.compileConfiguration },
|
||||
)
|
||||
rootTask.configure {
|
||||
it.dependsOn(task)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun configureKotlinMultiplatformTargets(
|
||||
project: Project,
|
||||
configuration: NamedDomainObjectProvider<Configuration>,
|
||||
rootTask: TaskProvider<Task>,
|
||||
skipAndroid: Boolean = false,
|
||||
) {
|
||||
val kotlin = project.extensions.getByType(KotlinMultiplatformExtension::class.java)
|
||||
kotlin.targets.configureEach { target ->
|
||||
if (target.platformType == common) {
|
||||
return@configureEach // All code ends up in platform targets.
|
||||
}
|
||||
if (target.platformType == androidJvm) {
|
||||
if (skipAndroid) return@configureEach
|
||||
throw AssertionError("Found Android Kotlin target but no Android plugin was detected")
|
||||
}
|
||||
|
||||
val task = createKotlinTargetRedwoodLintTask(
|
||||
project,
|
||||
configuration,
|
||||
target,
|
||||
taskName = BASE_TASK_NAME + target.name.replaceFirstChar { it.titlecase(ROOT) },
|
||||
)
|
||||
rootTask.configure {
|
||||
it.dependsOn(task)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun configureKotlinJvmProject(
|
||||
project: Project,
|
||||
configuration: NamedDomainObjectProvider<Configuration>,
|
||||
): TaskProvider<out Task> {
|
||||
val kotlin = project.extensions.getByType(KotlinJvmProjectExtension::class.java)
|
||||
return createKotlinTargetRedwoodLintTask(
|
||||
project,
|
||||
configuration,
|
||||
kotlin.target,
|
||||
BASE_TASK_NAME,
|
||||
)
|
||||
}
|
||||
|
||||
private fun createKotlinTargetRedwoodLintTask(
|
||||
project: Project,
|
||||
configuration: NamedDomainObjectProvider<Configuration>,
|
||||
target: KotlinTarget,
|
||||
taskName: String,
|
||||
): TaskProvider<out Task> {
|
||||
val compilation = target.compilations.getByName(MAIN_COMPILATION_NAME)
|
||||
return project.createRedwoodLintTask(
|
||||
configuration,
|
||||
taskName,
|
||||
"Kotlin ${target.name} target",
|
||||
sourceDirs = {
|
||||
compilation.allKotlinSourceSets.flatMap { it.kotlin.sourceDirectories.files }
|
||||
},
|
||||
classpath = {
|
||||
project.configurations.getByName(compilation.compileDependencyConfigurationName)
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
private fun Project.createRedwoodLintTask(
|
||||
configuration: NamedDomainObjectProvider<Configuration>,
|
||||
name: String,
|
||||
descriptionTarget: String? = null,
|
||||
sourceDirs: () -> Collection<File>,
|
||||
classpath: () -> Configuration,
|
||||
): TaskProvider<out Task> {
|
||||
dependencies.add(configuration.name, project.redwoodDependency("redwood-tooling-lint"))
|
||||
|
||||
return tasks.register(name, RedwoodLintTask::class.java) { task ->
|
||||
task.group = VERIFICATION_GROUP
|
||||
task.description = taskDescription(descriptionTarget)
|
||||
|
||||
task.toolClasspath.setFrom(configuration.get().incoming.artifacts.artifactFiles)
|
||||
task.projectDirectoryPath.set(project.projectDir.absolutePath)
|
||||
task.sourceDirectories.set(sourceDirs())
|
||||
task.classpath.setFrom(
|
||||
classpath().incoming.artifactView {
|
||||
it.attributes {
|
||||
it.attribute(USAGE_ATTRIBUTE, objects.named(Usage::class.java, JAVA_RUNTIME))
|
||||
}
|
||||
}.artifacts.artifactFiles,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
private fun taskDescription(target: String? = null) = buildString {
|
||||
append("Run Redwood's Compose lint checks")
|
||||
if (target != null) {
|
||||
append(" on ")
|
||||
append(target)
|
||||
}
|
||||
}
|
||||
@@ -1,93 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2022 Square, Inc.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package app.cash.redwood.gradle
|
||||
|
||||
import java.io.File
|
||||
import javax.inject.Inject
|
||||
import org.gradle.api.DefaultTask
|
||||
import org.gradle.api.file.ConfigurableFileCollection
|
||||
import org.gradle.api.provider.ListProperty
|
||||
import org.gradle.api.provider.Property
|
||||
import org.gradle.api.tasks.CacheableTask
|
||||
import org.gradle.api.tasks.Classpath
|
||||
import org.gradle.api.tasks.Input
|
||||
import org.gradle.api.tasks.TaskAction
|
||||
import org.gradle.process.ExecOperations
|
||||
import org.gradle.workers.WorkAction
|
||||
import org.gradle.workers.WorkParameters
|
||||
import org.gradle.workers.WorkerExecutor
|
||||
|
||||
@CacheableTask
|
||||
internal abstract class RedwoodLintTask @Inject constructor(
|
||||
private val workerExecutor: WorkerExecutor,
|
||||
) : DefaultTask() {
|
||||
@get:Classpath
|
||||
abstract val toolClasspath: ConfigurableFileCollection
|
||||
|
||||
@get:Input
|
||||
abstract val projectDirectoryPath: Property<String>
|
||||
|
||||
@get:Input
|
||||
abstract val sourceDirectories: ListProperty<File>
|
||||
|
||||
@get:Classpath
|
||||
abstract val classpath: ConfigurableFileCollection
|
||||
|
||||
@TaskAction
|
||||
fun run() {
|
||||
val queue = workerExecutor.noIsolation()
|
||||
queue.submit(RedwoodLintWorker::class.java) {
|
||||
it.toolClasspath.from(toolClasspath)
|
||||
it.projectDirectoryPath.set(projectDirectoryPath)
|
||||
it.sourceDirectories.set(sourceDirectories)
|
||||
it.classpath.setFrom(classpath)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private interface RedwoodLintParameters : WorkParameters {
|
||||
val toolClasspath: ConfigurableFileCollection
|
||||
val projectDirectoryPath: Property<String>
|
||||
val sourceDirectories: ListProperty<File>
|
||||
val classpath: ConfigurableFileCollection
|
||||
}
|
||||
|
||||
private abstract class RedwoodLintWorker @Inject constructor(
|
||||
private val execOperations: ExecOperations,
|
||||
) : WorkAction<RedwoodLintParameters> {
|
||||
override fun execute() {
|
||||
execOperations.javaexec { exec ->
|
||||
exec.classpath = parameters.toolClasspath
|
||||
exec.mainClass.set("app.cash.redwood.tooling.lint.Main")
|
||||
|
||||
exec.args = mutableListOf<String>().apply {
|
||||
add("check")
|
||||
add(parameters.projectDirectoryPath.get())
|
||||
|
||||
for (file in parameters.sourceDirectories.get()) {
|
||||
add("--sources")
|
||||
add(file.toString())
|
||||
}
|
||||
|
||||
val files = parameters.classpath.files
|
||||
if (files.isNotEmpty()) {
|
||||
add("--class-path")
|
||||
add(files.joinToString(File.pathSeparator))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,23 +0,0 @@
|
||||
buildscript {
|
||||
dependencies {
|
||||
classpath "app.cash.redwood:redwood-gradle-plugin:$redwoodVersion"
|
||||
classpath libs.kotlin.gradlePlugin
|
||||
classpath libs.androidGradlePlugin
|
||||
}
|
||||
|
||||
repositories {
|
||||
maven {
|
||||
url "file://${rootDir.absolutePath}/../../../../../build/localMaven"
|
||||
}
|
||||
mavenCentral()
|
||||
google()
|
||||
}
|
||||
}
|
||||
|
||||
apply plugin: 'com.android.library'
|
||||
apply plugin: 'app.cash.redwood.lint'
|
||||
|
||||
android {
|
||||
namespace = 'example.android'
|
||||
compileSdk 33
|
||||
}
|
||||
@@ -1 +0,0 @@
|
||||
kotlin.mpp.stability.nowarn=true
|
||||
@@ -1,7 +0,0 @@
|
||||
dependencyResolutionManagement {
|
||||
versionCatalogs {
|
||||
libs {
|
||||
from(files('../../../../../gradle/libs.versions.toml'))
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,32 +0,0 @@
|
||||
buildscript {
|
||||
dependencies {
|
||||
classpath "app.cash.redwood:redwood-gradle-plugin:$redwoodVersion"
|
||||
classpath libs.kotlin.gradlePlugin
|
||||
classpath libs.androidGradlePlugin
|
||||
}
|
||||
|
||||
repositories {
|
||||
maven {
|
||||
url "file://${rootDir.absolutePath}/../../../../../build/localMaven"
|
||||
}
|
||||
mavenCentral()
|
||||
google()
|
||||
}
|
||||
}
|
||||
|
||||
apply plugin: 'com.android.library'
|
||||
apply plugin: 'org.jetbrains.kotlin.android'
|
||||
apply plugin: 'app.cash.redwood.lint'
|
||||
|
||||
android {
|
||||
namespace = 'example.android'
|
||||
compileSdk 33
|
||||
}
|
||||
|
||||
repositories {
|
||||
maven {
|
||||
url "file://${rootDir.absolutePath}/../../../../../build/localMaven"
|
||||
}
|
||||
mavenCentral()
|
||||
google()
|
||||
}
|
||||
@@ -1 +0,0 @@
|
||||
kotlin.mpp.stability.nowarn=true
|
||||
@@ -1,7 +0,0 @@
|
||||
dependencyResolutionManagement {
|
||||
versionCatalogs {
|
||||
libs {
|
||||
from(files('../../../../../gradle/libs.versions.toml'))
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,24 +0,0 @@
|
||||
buildscript {
|
||||
dependencies {
|
||||
classpath "app.cash.redwood:redwood-gradle-plugin:$redwoodVersion"
|
||||
classpath libs.kotlin.gradlePlugin
|
||||
}
|
||||
|
||||
repositories {
|
||||
maven {
|
||||
url "file://${rootDir.absolutePath}/../../../../../build/localMaven"
|
||||
}
|
||||
mavenCentral()
|
||||
}
|
||||
}
|
||||
|
||||
apply plugin: 'org.jetbrains.kotlin.jvm'
|
||||
apply plugin: 'app.cash.redwood.lint'
|
||||
|
||||
repositories {
|
||||
maven {
|
||||
url "file://${rootDir.absolutePath}/../../../../../build/localMaven"
|
||||
}
|
||||
mavenCentral()
|
||||
google()
|
||||
}
|
||||
@@ -1 +0,0 @@
|
||||
kotlin.mpp.stability.nowarn=true
|
||||
@@ -1,7 +0,0 @@
|
||||
dependencyResolutionManagement {
|
||||
versionCatalogs {
|
||||
libs {
|
||||
from(files('../../../../../gradle/libs.versions.toml'))
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,37 +0,0 @@
|
||||
buildscript {
|
||||
dependencies {
|
||||
classpath "app.cash.redwood:redwood-gradle-plugin:$redwoodVersion"
|
||||
classpath libs.kotlin.gradlePlugin
|
||||
classpath libs.androidGradlePlugin
|
||||
}
|
||||
|
||||
repositories {
|
||||
maven {
|
||||
url "file://${rootDir.absolutePath}/../../../../../build/localMaven"
|
||||
}
|
||||
mavenCentral()
|
||||
google()
|
||||
}
|
||||
}
|
||||
|
||||
apply plugin: 'org.jetbrains.kotlin.multiplatform'
|
||||
apply plugin: 'com.android.library'
|
||||
apply plugin: 'app.cash.redwood.lint'
|
||||
|
||||
kotlin {
|
||||
androidTarget()
|
||||
jvm()
|
||||
}
|
||||
|
||||
android {
|
||||
namespace = 'example.android'
|
||||
compileSdk 33
|
||||
}
|
||||
|
||||
repositories {
|
||||
maven {
|
||||
url "file://${rootDir.absolutePath}/../../../../../build/localMaven"
|
||||
}
|
||||
mavenCentral()
|
||||
google()
|
||||
}
|
||||
@@ -1 +0,0 @@
|
||||
kotlin.mpp.stability.nowarn=true
|
||||
@@ -1,7 +0,0 @@
|
||||
dependencyResolutionManagement {
|
||||
versionCatalogs {
|
||||
libs {
|
||||
from(files('../../../../../gradle/libs.versions.toml'))
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,29 +0,0 @@
|
||||
buildscript {
|
||||
dependencies {
|
||||
classpath "app.cash.redwood:redwood-gradle-plugin:$redwoodVersion"
|
||||
classpath libs.kotlin.gradlePlugin
|
||||
}
|
||||
|
||||
repositories {
|
||||
maven {
|
||||
url "file://${rootDir.absolutePath}/../../../../../build/localMaven"
|
||||
}
|
||||
mavenCentral()
|
||||
}
|
||||
}
|
||||
|
||||
apply plugin: 'org.jetbrains.kotlin.multiplatform'
|
||||
apply plugin: 'app.cash.redwood.lint'
|
||||
|
||||
kotlin {
|
||||
jvm()
|
||||
js()
|
||||
}
|
||||
|
||||
repositories {
|
||||
maven {
|
||||
url "file://${rootDir.absolutePath}/../../../../../build/localMaven"
|
||||
}
|
||||
mavenCentral()
|
||||
google()
|
||||
}
|
||||
@@ -1 +0,0 @@
|
||||
kotlin.mpp.stability.nowarn=true
|
||||
@@ -1,7 +0,0 @@
|
||||
dependencyResolutionManagement {
|
||||
versionCatalogs {
|
||||
libs {
|
||||
from(files('../../../../../gradle/libs.versions.toml'))
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,14 +0,0 @@
|
||||
buildscript {
|
||||
dependencies {
|
||||
classpath "app.cash.redwood:redwood-gradle-plugin:$redwoodVersion"
|
||||
}
|
||||
|
||||
repositories {
|
||||
maven {
|
||||
url "file://${rootDir.absolutePath}/../../../../../build/localMaven"
|
||||
}
|
||||
mavenCentral()
|
||||
}
|
||||
}
|
||||
|
||||
apply plugin: 'app.cash.redwood.lint'
|
||||
@@ -1,7 +0,0 @@
|
||||
dependencyResolutionManagement {
|
||||
versionCatalogs {
|
||||
libs {
|
||||
from(files('../../../../../gradle/libs.versions.toml'))
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -17,7 +17,6 @@ package app.cash.redwood.gradle
|
||||
|
||||
import assertk.assertThat
|
||||
import assertk.assertions.contains
|
||||
import assertk.assertions.containsExactly
|
||||
import assertk.assertions.isEqualTo
|
||||
import assertk.assertions.isNotEmpty
|
||||
import assertk.assertions.prop
|
||||
@@ -104,65 +103,6 @@ class FixtureTest {
|
||||
fixtureGradleRunner(fixtureDir, "redwoodApiCheck").build()
|
||||
}
|
||||
|
||||
@Test fun lintNoKotlinFails() {
|
||||
val fixtureDir = File("src/test/fixture/lint-no-kotlin")
|
||||
val result = fixtureGradleRunner(fixtureDir).buildAndFail()
|
||||
assertThat(result.output).contains(
|
||||
"'app.cash.redwood.lint' requires a compatible Kotlin plugin to be applied (root project)",
|
||||
)
|
||||
}
|
||||
|
||||
@Test fun lintAndroidNoKotlinFails() {
|
||||
val fixtureDir = File("src/test/fixture/lint-android-no-kotlin")
|
||||
val result = fixtureGradleRunner(fixtureDir).buildAndFail()
|
||||
assertThat(result.output).contains(
|
||||
"'app.cash.redwood.lint' requires a compatible Kotlin plugin to be applied (root project)",
|
||||
)
|
||||
}
|
||||
|
||||
@Test fun lintAndroid() {
|
||||
val fixtureDir = File("src/test/fixture/lint-android")
|
||||
val result = fixtureGradleRunner(fixtureDir).build()
|
||||
val lintTasks = result.tasks.map { it.path }.filter { it.startsWith(":redwoodLint") }
|
||||
assertThat(lintTasks).containsExactly(
|
||||
":redwoodLintDebug",
|
||||
":redwoodLintRelease",
|
||||
":redwoodLint",
|
||||
)
|
||||
}
|
||||
|
||||
@Test fun lintJvm() {
|
||||
val fixtureDir = File("src/test/fixture/lint-jvm")
|
||||
val result = fixtureGradleRunner(fixtureDir).build()
|
||||
val lintTasks = result.tasks.map { it.path }.filter { it.startsWith(":redwoodLint") }
|
||||
assertThat(lintTasks).containsExactly(
|
||||
":redwoodLint",
|
||||
)
|
||||
}
|
||||
|
||||
@Test fun lintMppAndroid() {
|
||||
val fixtureDir = File("src/test/fixture/lint-mpp-android")
|
||||
val result = fixtureGradleRunner(fixtureDir).build()
|
||||
val lintTasks = result.tasks.map { it.path }.filter { it.startsWith(":redwoodLint") }
|
||||
assertThat(lintTasks).containsExactly(
|
||||
":redwoodLintAndroidDebug",
|
||||
":redwoodLintAndroidRelease",
|
||||
":redwoodLintJvm",
|
||||
":redwoodLint",
|
||||
)
|
||||
}
|
||||
|
||||
@Test fun lintMppNoAndroid() {
|
||||
val fixtureDir = File("src/test/fixture/lint-mpp-no-android")
|
||||
val result = fixtureGradleRunner(fixtureDir).build()
|
||||
val lintTasks = result.tasks.map { it.path }.filter { it.startsWith(":redwoodLint") }
|
||||
assertThat(lintTasks).containsExactly(
|
||||
":redwoodLintJs",
|
||||
":redwoodLintJvm",
|
||||
":redwoodLint",
|
||||
)
|
||||
}
|
||||
|
||||
@Test fun protocolWithoutModifiers() {
|
||||
// When no layout modifier are present, the serialization codegen contains unused things.
|
||||
// Ensure that it does not produce warnings (which are set to error in this build).
|
||||
|
||||
@@ -1,11 +0,0 @@
|
||||
public final class app/cash/redwood/tooling/lint/ApiMerger {
|
||||
public fun <init> ()V
|
||||
public final fun add (Ljava/lang/String;)Lapp/cash/redwood/tooling/lint/ApiMerger;
|
||||
public final fun merge ()Ljava/lang/String;
|
||||
public final synthetic fun plusAssign (Ljava/lang/String;)V
|
||||
}
|
||||
|
||||
public final class app/cash/redwood/tooling/lint/Main {
|
||||
public static final fun main ([Ljava/lang/String;)V
|
||||
}
|
||||
|
||||
@@ -1,35 +0,0 @@
|
||||
import org.jetbrains.kotlin.gradle.dsl.JvmTarget
|
||||
import org.jetbrains.kotlin.gradle.tasks.KotlinJvmCompile
|
||||
|
||||
import static app.cash.redwood.buildsupport.TargetGroup.Tooling
|
||||
|
||||
redwoodBuild {
|
||||
targets(Tooling)
|
||||
publishing()
|
||||
cliApplication('redwood-lint', 'app.cash.redwood.tooling.lint.Main')
|
||||
}
|
||||
|
||||
apply plugin: 'org.jetbrains.kotlin.plugin.serialization'
|
||||
|
||||
dependencies {
|
||||
implementation libs.clikt
|
||||
implementation libs.kotlinx.serialization.core
|
||||
implementation libs.xmlutil.serialization
|
||||
implementation libs.lint.core
|
||||
|
||||
testImplementation libs.kotlin.test
|
||||
testImplementation libs.junit
|
||||
testImplementation libs.assertk
|
||||
testImplementation libs.jimfs
|
||||
}
|
||||
|
||||
tasks.withType(JavaCompile).configureEach {
|
||||
sourceCompatibility = JavaVersion.VERSION_11.toString()
|
||||
targetCompatibility = JavaVersion.VERSION_11.toString()
|
||||
}
|
||||
|
||||
tasks.withType(KotlinJvmCompile).configureEach {
|
||||
compilerOptions {
|
||||
jvmTarget.set(JvmTarget.JVM_11)
|
||||
}
|
||||
}
|
||||
@@ -1,50 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2022 Square, Inc.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package app.cash.redwood.tooling.lint
|
||||
|
||||
import com.github.ajalt.clikt.core.CliktCommand
|
||||
import com.github.ajalt.clikt.parameters.arguments.argument
|
||||
import com.github.ajalt.clikt.parameters.arguments.help
|
||||
import com.github.ajalt.clikt.parameters.arguments.multiple
|
||||
import com.github.ajalt.clikt.parameters.options.help
|
||||
import com.github.ajalt.clikt.parameters.options.option
|
||||
import com.github.ajalt.clikt.parameters.options.required
|
||||
import com.github.ajalt.clikt.parameters.types.path
|
||||
import java.nio.file.FileSystem
|
||||
import kotlin.io.path.readText
|
||||
import kotlin.io.path.writeText
|
||||
|
||||
internal class ApiMergeCommand(
|
||||
fileSystem: FileSystem,
|
||||
) : CliktCommand(name = "api-merge") {
|
||||
private val inputs by argument("FILE")
|
||||
.help("One or more API definition XML documents")
|
||||
.path(fileSystem = fileSystem)
|
||||
.multiple(required = true)
|
||||
|
||||
private val output by option("-o", "--out", metavar = "FILE")
|
||||
.help("Output file for the merged API definition XML document")
|
||||
.path(fileSystem = fileSystem)
|
||||
.required()
|
||||
|
||||
override fun run() {
|
||||
val merger = ApiMerger()
|
||||
for (input in inputs) {
|
||||
merger += input.readText()
|
||||
}
|
||||
output.writeText(merger.merge())
|
||||
}
|
||||
}
|
||||
@@ -1,112 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2022 Square, Inc.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package app.cash.redwood.tooling.lint
|
||||
|
||||
/**
|
||||
* Merges one or more API definitions into a single, merged API definition.
|
||||
*
|
||||
* Each API definition is an XML document in the following form:
|
||||
* ```xml
|
||||
* <api version="1">
|
||||
* <widget tag="1" since="1">
|
||||
* <trait tag="1" since="1"/>
|
||||
* <trait tag="3" since="1"/>
|
||||
* </widget>
|
||||
* <layout-modifier tag="1" since="1">
|
||||
* <property tag="1" since="1"/>
|
||||
* <property tag="3" since="1"/>
|
||||
* </layout-modifier>
|
||||
* </api>
|
||||
* ```
|
||||
*/
|
||||
public class ApiMerger {
|
||||
private val apis = mutableListOf<RedwoodApi>()
|
||||
|
||||
/** Parse [xml] and add its API definition for merging. */
|
||||
public fun add(xml: String): ApiMerger = apply {
|
||||
val lintApi = RedwoodApi.deserialize(xml)
|
||||
require(lintApi.version == 1U) {
|
||||
"Only Redwood API XML version 1 is supported. Found: ${lintApi.version}"
|
||||
}
|
||||
apis += lintApi
|
||||
}
|
||||
|
||||
@JvmSynthetic // Hide to Java callers.
|
||||
public operator fun plusAssign(xml: String) {
|
||||
add(xml)
|
||||
}
|
||||
|
||||
/** Merge added API definitions into a single API definition XML. */
|
||||
public fun merge(): String {
|
||||
check(apis.isNotEmpty()) {
|
||||
"One or more API definitions must be added in order to merge"
|
||||
}
|
||||
return apis.merge().serialize()
|
||||
}
|
||||
|
||||
private companion object {
|
||||
private fun List<RedwoodApi>.merge(): RedwoodApi {
|
||||
return RedwoodApi(
|
||||
version = 1U,
|
||||
widgets = mergeItems({ it.widgets }, { it.tag }, { it.merge() }),
|
||||
modifier = mergeItems({ it.modifier }, { it.tag }, { it.merge() }),
|
||||
)
|
||||
}
|
||||
|
||||
private fun List<RedwoodWidget>.merge(): RedwoodWidget {
|
||||
return RedwoodWidget(
|
||||
tag = first().tag,
|
||||
since = maxOf { it.since },
|
||||
traits = mergeItems({ it.traits }, { it.tag }, { it.merge() }),
|
||||
)
|
||||
}
|
||||
|
||||
private fun List<RedwoodWidgetTrait>.merge(): RedwoodWidgetTrait {
|
||||
return RedwoodWidgetTrait(
|
||||
tag = first().tag,
|
||||
since = maxOf { it.since },
|
||||
)
|
||||
}
|
||||
|
||||
private fun List<RedwoodModifier>.merge(): RedwoodModifier {
|
||||
return RedwoodModifier(
|
||||
tag = first().tag,
|
||||
since = maxOf { it.since },
|
||||
properties = mergeItems({ it.properties }, { it.tag }, { it.merge() }),
|
||||
)
|
||||
}
|
||||
|
||||
private fun List<RedwoodModifierProperty>.merge(): RedwoodModifierProperty {
|
||||
return RedwoodModifierProperty(
|
||||
tag = first().tag,
|
||||
since = maxOf { it.since },
|
||||
)
|
||||
}
|
||||
|
||||
private fun <T, I, G : Comparable<G>> List<T>.mergeItems(
|
||||
itemSelector: (T) -> List<I>,
|
||||
groupSelector: (I) -> G,
|
||||
itemMerge: (List<I>) -> I,
|
||||
): List<I> {
|
||||
return flatMap(itemSelector)
|
||||
.groupBy(groupSelector)
|
||||
.values
|
||||
.filter { it.size == size }
|
||||
.map(itemMerge)
|
||||
.sortedBy(groupSelector)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,204 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2022 Square, Inc.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
@file:JvmName("Main")
|
||||
@file:Suppress("UnstableApiUsage") // Lint 🙄
|
||||
|
||||
package app.cash.redwood.tooling.lint
|
||||
|
||||
import com.android.tools.lint.LintResourceRepository.Companion.EmptyRepository
|
||||
import com.android.tools.lint.LintStats
|
||||
import com.android.tools.lint.UastEnvironment
|
||||
import com.android.tools.lint.client.api.IssueRegistry
|
||||
import com.android.tools.lint.client.api.LintClient
|
||||
import com.android.tools.lint.client.api.LintDriver
|
||||
import com.android.tools.lint.client.api.LintRequest
|
||||
import com.android.tools.lint.client.api.ResourceRepositoryScope
|
||||
import com.android.tools.lint.detector.api.CURRENT_API
|
||||
import com.android.tools.lint.detector.api.Context
|
||||
import com.android.tools.lint.detector.api.Desugaring
|
||||
import com.android.tools.lint.detector.api.Incident
|
||||
import com.android.tools.lint.detector.api.Issue
|
||||
import com.android.tools.lint.detector.api.JavaContext
|
||||
import com.android.tools.lint.detector.api.Platform
|
||||
import com.android.tools.lint.detector.api.Project
|
||||
import com.android.tools.lint.detector.api.Scope.Companion.JAVA_FILE_SCOPE
|
||||
import com.android.tools.lint.detector.api.Severity
|
||||
import com.android.tools.lint.detector.api.Severity.ERROR
|
||||
import com.android.tools.lint.detector.api.TextFormat
|
||||
import com.android.tools.lint.helpers.DefaultUastParser
|
||||
import com.github.ajalt.clikt.core.CliktCommand
|
||||
import com.github.ajalt.clikt.core.ProgramResult
|
||||
import com.github.ajalt.clikt.parameters.arguments.argument
|
||||
import com.github.ajalt.clikt.parameters.options.default
|
||||
import com.github.ajalt.clikt.parameters.options.multiple
|
||||
import com.github.ajalt.clikt.parameters.options.option
|
||||
import com.github.ajalt.clikt.parameters.options.split
|
||||
import com.github.ajalt.clikt.parameters.types.file
|
||||
import com.intellij.pom.java.LanguageLevel
|
||||
import com.intellij.pom.java.LanguageLevel.JDK_1_7
|
||||
import java.io.File
|
||||
import org.jetbrains.kotlin.cli.jvm.config.addJvmClasspathRoots
|
||||
import org.jetbrains.kotlin.config.JVMConfigurationKeys
|
||||
import org.jetbrains.kotlin.config.LanguageVersionSettings
|
||||
import org.jetbrains.kotlin.config.languageVersionSettings
|
||||
import org.jetbrains.kotlin.utils.PathUtil.getJdkClassesRootsFromCurrentJre
|
||||
|
||||
internal class LintCommand : CliktCommand(name = "check") {
|
||||
private val projectDirectory by argument("PROJECT_DIR")
|
||||
.file()
|
||||
private val sourceDirectories by option("-s", "--sources", metavar = "DIR")
|
||||
.file()
|
||||
.multiple(required = true)
|
||||
private val classpath by option("-cp", "--class-path")
|
||||
.file()
|
||||
.split(File.pathSeparator)
|
||||
.default(emptyList())
|
||||
|
||||
override fun run() {
|
||||
val uastConfig = UastEnvironment.Configuration.create().apply {
|
||||
javaLanguageLevel = JDK_1_7
|
||||
// UAST warns when you give it a directory that does not exist.
|
||||
addSourceRoots(sourceDirectories.filter(File::exists))
|
||||
addClasspathRoots(classpath)
|
||||
kotlinCompilerConfig.addJvmClasspathRoots(getJdkClassesRootsFromCurrentJre())
|
||||
kotlinCompilerConfig.put(JVMConfigurationKeys.NO_JDK, false)
|
||||
}
|
||||
val uastEnvironment = UastEnvironment.create(uastConfig)
|
||||
val ideaProject = uastEnvironment.ideaProject
|
||||
|
||||
val sourceFiles = sourceDirectories.flatMap {
|
||||
it.walk().filter(File::isFile)
|
||||
}
|
||||
|
||||
val client = RedwoodLintClient(ideaProject, uastEnvironment, projectDirectory, sourceFiles)
|
||||
val incidents = client.run()
|
||||
val stats = LintStats.create(incidents, emptyList())
|
||||
|
||||
if (stats.errorCount > 0) {
|
||||
for (incident in incidents) {
|
||||
if (incident.severity.isError) {
|
||||
System.err.println(incident)
|
||||
}
|
||||
}
|
||||
throw ProgramResult(1)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private class RedwoodIssueRegistry : IssueRegistry() {
|
||||
override val api get() = CURRENT_API
|
||||
override val issues get() = emptyList<Issue>()
|
||||
}
|
||||
|
||||
private class RedwoodProject(
|
||||
client: LintClient,
|
||||
projectDir: File,
|
||||
private val sources: List<File>,
|
||||
) : Project(client, projectDir, null) {
|
||||
init {
|
||||
library = true
|
||||
directLibraries = emptyList()
|
||||
}
|
||||
|
||||
override fun initialize() {
|
||||
// Not calling super as it is for Android projects only.
|
||||
}
|
||||
|
||||
override fun getJavaSourceFolders() = sources
|
||||
override fun getSubset() = null // Check whole project.
|
||||
override fun isGradleProject() = false
|
||||
override fun isAndroidProject() = false
|
||||
override fun getDesugaring() = Desugaring.NONE
|
||||
override fun getManifestFiles() = emptyList<File>()
|
||||
override fun getProguardFiles() = emptyList<File>()
|
||||
override fun getResourceFolders() = emptyList<File>()
|
||||
override fun getAssetFolders() = emptyList<File>()
|
||||
override fun getGeneratedSourceFolders() = emptyList<File>()
|
||||
override fun getGeneratedResourceFolders() = emptyList<File>()
|
||||
override fun getTestSourceFolders() = emptyList<File>()
|
||||
override fun getTestLibraries() = emptyList<File>()
|
||||
override fun getTestFixturesSourceFolders() = emptyList<File>()
|
||||
override fun getTestFixturesLibraries() = emptyList<File>()
|
||||
override fun getPropertyFiles() = emptyList<File>()
|
||||
override fun getGradleBuildScripts() = emptyList<File>()
|
||||
override fun getJavaClassFolders() = emptyList<File>()
|
||||
override fun getJavaLibraries(includeProvided: Boolean) = emptyList<File>()
|
||||
}
|
||||
|
||||
private class RedwoodLintClient(
|
||||
private val ideaProject: com.intellij.openapi.project.Project,
|
||||
private val uastEnvironment: UastEnvironment,
|
||||
private val projectDir: File,
|
||||
private val sourceFiles: List<File>,
|
||||
) : LintClient("redwood-lint") {
|
||||
private var run = false
|
||||
private val incidents = mutableListOf<Incident>()
|
||||
|
||||
fun run(): List<Incident> {
|
||||
check(!run) { "run() can only be called once per client instance" }
|
||||
run = true
|
||||
|
||||
val request = LintRequest(this, sourceFiles)
|
||||
request.setProjects(listOf(RedwoodProject(this, projectDir, sourceFiles)))
|
||||
request.setScope(JAVA_FILE_SCOPE)
|
||||
request.setPlatform(Platform.UNSPECIFIED)
|
||||
request.setReleaseMode(false)
|
||||
|
||||
val driver = LintDriver(RedwoodIssueRegistry(), this, request)
|
||||
driver.analyze()
|
||||
|
||||
return incidents
|
||||
}
|
||||
|
||||
override fun readFile(file: File) = file.readText()
|
||||
override val xmlParser get() = throw UnsupportedOperationException()
|
||||
override fun getGradleVisitor() = throw UnsupportedOperationException()
|
||||
override fun getResources(project: Project, scope: ResourceRepositoryScope) = EmptyRepository
|
||||
override fun getUastParser(project: Project?) = object : DefaultUastParser(project, ideaProject) {
|
||||
override fun prepare(
|
||||
contexts: List<JavaContext>,
|
||||
javaLanguageLevel: LanguageLevel?,
|
||||
kotlinLanguageLevel: LanguageVersionSettings?,
|
||||
): Boolean {
|
||||
if (kotlinLanguageLevel != null) {
|
||||
uastEnvironment.kotlinCompilerConfig.languageVersionSettings = kotlinLanguageLevel
|
||||
}
|
||||
|
||||
val kotlinFiles = contexts.map(JavaContext::file).filter { it.path.endsWith(".kt") }
|
||||
uastEnvironment.analyzeFiles(kotlinFiles)
|
||||
|
||||
return super.prepare(contexts, javaLanguageLevel, kotlinLanguageLevel)
|
||||
}
|
||||
}
|
||||
|
||||
override fun report(
|
||||
context: Context,
|
||||
incident: Incident,
|
||||
format: TextFormat,
|
||||
) {
|
||||
incidents += incident
|
||||
}
|
||||
|
||||
override fun log(
|
||||
severity: Severity,
|
||||
exception: Throwable?,
|
||||
format: String?,
|
||||
vararg args: Any,
|
||||
) {
|
||||
val ps = if (severity == ERROR) System.err else System.out
|
||||
ps.println("$severity ${format?.format(*args) ?: ""}")
|
||||
}
|
||||
}
|
||||
@@ -1,97 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2022 Square, Inc.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package app.cash.redwood.tooling.lint
|
||||
|
||||
import kotlinx.serialization.SerialName
|
||||
import kotlinx.serialization.Serializable
|
||||
import nl.adaptivity.xmlutil.serialization.XML
|
||||
|
||||
@Serializable
|
||||
@SerialName("api")
|
||||
internal data class RedwoodApi(
|
||||
val version: UInt,
|
||||
val widgets: List<RedwoodWidget> = emptyList(),
|
||||
val modifier: List<RedwoodModifier> = emptyList(),
|
||||
) {
|
||||
fun serialize() = xml.encodeToString(serializer(), this)
|
||||
|
||||
companion object {
|
||||
private val xml = XML {
|
||||
indent = 2
|
||||
}
|
||||
|
||||
@JvmStatic
|
||||
fun deserialize(string: String) = xml.decodeFromString(serializer(), string)
|
||||
}
|
||||
}
|
||||
|
||||
@Serializable
|
||||
@SerialName("widget")
|
||||
internal data class RedwoodWidget(
|
||||
val tag: Int,
|
||||
val since: UInt,
|
||||
val traits: List<RedwoodWidgetTrait> = emptyList(),
|
||||
) {
|
||||
init {
|
||||
val badTraits = traits.filter { it.since < since }
|
||||
require(badTraits.isEmpty()) {
|
||||
buildString {
|
||||
appendLine("Trait since values must be greater than or equal to widget")
|
||||
appendLine(""" Widget tag="$tag" since="$since"""")
|
||||
for (badTrait in badTraits) {
|
||||
appendLine(""" Trait tag="${badTrait.tag}" since="${badTrait.since}"""")
|
||||
}
|
||||
deleteCharAt(lastIndex) // Remove trailing newline.
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Serializable
|
||||
@SerialName("trait")
|
||||
internal data class RedwoodWidgetTrait(
|
||||
val tag: Int,
|
||||
val since: UInt,
|
||||
)
|
||||
|
||||
@Serializable
|
||||
@SerialName("layout-modifier")
|
||||
internal data class RedwoodModifier(
|
||||
val tag: Int,
|
||||
val since: UInt,
|
||||
val properties: List<RedwoodModifierProperty> = emptyList(),
|
||||
) {
|
||||
init {
|
||||
val badProperties = properties.filter { it.since < since }
|
||||
require(badProperties.isEmpty()) {
|
||||
buildString {
|
||||
appendLine("Property since values must be greater than or equal to layout modifier")
|
||||
appendLine(""" Layout modifier tag="$tag" since="$since"""")
|
||||
for (badProperty in badProperties) {
|
||||
appendLine(""" Property tag="${badProperty.tag}" since="${badProperty.since}"""")
|
||||
}
|
||||
deleteCharAt(lastIndex) // Remove trailing newline.
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Serializable
|
||||
@SerialName("property")
|
||||
internal data class RedwoodModifierProperty(
|
||||
val tag: Int,
|
||||
val since: UInt,
|
||||
)
|
||||
@@ -1,32 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2023 Square, Inc.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
@file:JvmName("Main")
|
||||
|
||||
package app.cash.redwood.tooling.lint
|
||||
|
||||
import com.github.ajalt.clikt.core.NoOpCliktCommand
|
||||
import com.github.ajalt.clikt.core.main
|
||||
import com.github.ajalt.clikt.core.subcommands
|
||||
import java.nio.file.FileSystems
|
||||
|
||||
public fun main(vararg args: String) {
|
||||
NoOpCliktCommand(name = "redwood-lint")
|
||||
.subcommands(
|
||||
ApiMergeCommand(FileSystems.getDefault()),
|
||||
LintCommand(),
|
||||
)
|
||||
.main(args)
|
||||
}
|
||||
@@ -1,71 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2022 Square, Inc.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package app.cash.redwood.tooling.lint
|
||||
|
||||
import assertk.assertThat
|
||||
import assertk.assertions.isEqualTo
|
||||
import com.github.ajalt.clikt.core.main
|
||||
import com.google.common.jimfs.Configuration.unix
|
||||
import com.google.common.jimfs.Jimfs
|
||||
import kotlin.io.path.readText
|
||||
import kotlin.io.path.writeText
|
||||
import org.junit.Test
|
||||
|
||||
class ApiMergeCommandTest {
|
||||
@Test fun mergesInputs() {
|
||||
val fs = Jimfs.newFileSystem(unix())
|
||||
val root = fs.rootDirectories.single()
|
||||
|
||||
val input1 = root.resolve("input1.xml")
|
||||
input1.writeText(
|
||||
"""
|
||||
|<api version="1">
|
||||
| <widget tag="1" since="2">
|
||||
| <trait tag="1" since="2" />
|
||||
| </widget>
|
||||
|</api>
|
||||
|
|
||||
""".trimMargin(),
|
||||
)
|
||||
|
||||
val input2 = root.resolve("input2.xml")
|
||||
input2.writeText(
|
||||
"""
|
||||
|<api version="1">
|
||||
| <widget tag="1" since="2">
|
||||
| <trait tag="1" since="3" />
|
||||
| </widget>
|
||||
|</api>
|
||||
|
|
||||
""".trimMargin(),
|
||||
)
|
||||
|
||||
val output = root.resolve("output.xml")
|
||||
|
||||
val command = ApiMergeCommand(fs)
|
||||
command.main(listOf("--out", output.toString(), input1.toString(), input2.toString()))
|
||||
|
||||
assertThat(output.readText()).isEqualTo(
|
||||
"""
|
||||
|<api version="1">
|
||||
| <widget tag="1" since="2">
|
||||
| <trait tag="1" since="3" />
|
||||
| </widget>
|
||||
|</api>
|
||||
""".trimMargin(),
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -1,297 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2022 Square, Inc.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package app.cash.redwood.tooling.lint
|
||||
|
||||
import assertk.assertThat
|
||||
import assertk.assertions.hasMessage
|
||||
import assertk.assertions.isEqualTo
|
||||
import kotlin.test.assertFailsWith
|
||||
import org.junit.Test
|
||||
|
||||
class ApiMergerTest {
|
||||
@Test fun requiresAtLeastOne() {
|
||||
val merger = ApiMerger()
|
||||
val t = assertFailsWith<IllegalStateException> {
|
||||
merger.merge()
|
||||
}
|
||||
assertThat(t).hasMessage("One or more API definitions must be added in order to merge")
|
||||
}
|
||||
|
||||
@Test fun version1Only() {
|
||||
val merger = ApiMerger()
|
||||
val t = assertFailsWith<IllegalArgumentException> {
|
||||
merger.add("""<api version="2" />""")
|
||||
}
|
||||
assertThat(t).hasMessage("Only Redwood API XML version 1 is supported. Found: 2")
|
||||
}
|
||||
|
||||
@Test fun widgetTakesHigherSince() {
|
||||
val merger = ApiMerger()
|
||||
merger += """
|
||||
|<api version="1">
|
||||
| <widget tag="1" since="2" />
|
||||
|</api>
|
||||
|
|
||||
""".trimMargin()
|
||||
merger += """
|
||||
|<api version="1">
|
||||
| <widget tag="1" since="3" />
|
||||
|</api>
|
||||
|
|
||||
""".trimMargin()
|
||||
val actual = merger.merge()
|
||||
|
||||
assertThat(actual).isEqualTo(
|
||||
"""
|
||||
|<api version="1">
|
||||
| <widget tag="1" since="3" />
|
||||
|</api>
|
||||
""".trimMargin(),
|
||||
)
|
||||
}
|
||||
|
||||
@Test fun widgetRemovedIfNotPresentInAll() {
|
||||
val merger = ApiMerger()
|
||||
merger += """
|
||||
|<api version="1">
|
||||
| <widget tag="1" since="2" />
|
||||
|</api>
|
||||
|
|
||||
""".trimMargin()
|
||||
merger += """
|
||||
|<api version="1" />
|
||||
|
|
||||
""".trimMargin()
|
||||
val actual = merger.merge()
|
||||
|
||||
assertThat(actual).isEqualTo(
|
||||
"""
|
||||
|<api version="1" />
|
||||
""".trimMargin(),
|
||||
)
|
||||
}
|
||||
|
||||
@Test fun widgetTraitTakesHigherSince() {
|
||||
val merger = ApiMerger()
|
||||
merger += """
|
||||
|<api version="1">
|
||||
| <widget tag="1" since="2">
|
||||
| <trait tag="1" since="2" />
|
||||
| </widget>
|
||||
|</api>
|
||||
|
|
||||
""".trimMargin()
|
||||
merger += """
|
||||
|<api version="1">
|
||||
| <widget tag="1" since="2">
|
||||
| <trait tag="1" since="3" />
|
||||
| </widget>
|
||||
|</api>
|
||||
|
|
||||
""".trimMargin()
|
||||
val actual = merger.merge()
|
||||
|
||||
assertThat(actual).isEqualTo(
|
||||
"""
|
||||
|<api version="1">
|
||||
| <widget tag="1" since="2">
|
||||
| <trait tag="1" since="3" />
|
||||
| </widget>
|
||||
|</api>
|
||||
""".trimMargin(),
|
||||
)
|
||||
}
|
||||
|
||||
@Test fun widgetTraitRemovedIfNotPresentInAll() {
|
||||
val merger = ApiMerger()
|
||||
merger += """
|
||||
|<api version="1">
|
||||
| <widget tag="1" since="2">
|
||||
| <trait tag="1" since="2" />
|
||||
| </widget>
|
||||
|</api>
|
||||
|
|
||||
""".trimMargin()
|
||||
merger += """
|
||||
|<api version="1">
|
||||
| <widget tag="1" since="2" />
|
||||
|</api>
|
||||
|
|
||||
""".trimMargin()
|
||||
val actual = merger.merge()
|
||||
|
||||
assertThat(actual).isEqualTo(
|
||||
"""
|
||||
|<api version="1">
|
||||
| <widget tag="1" since="2" />
|
||||
|</api>
|
||||
""".trimMargin(),
|
||||
)
|
||||
}
|
||||
|
||||
@Test fun modifierTakesHigherSince() {
|
||||
val merger = ApiMerger()
|
||||
merger += """
|
||||
|<api version="1">
|
||||
| <layout-modifier tag="1" since="2" />
|
||||
|</api>
|
||||
|
|
||||
""".trimMargin()
|
||||
merger += """
|
||||
|<api version="1">
|
||||
| <layout-modifier tag="1" since="3" />
|
||||
|</api>
|
||||
|
|
||||
""".trimMargin()
|
||||
val actual = merger.merge()
|
||||
|
||||
assertThat(actual).isEqualTo(
|
||||
"""
|
||||
|<api version="1">
|
||||
| <layout-modifier tag="1" since="3" />
|
||||
|</api>
|
||||
""".trimMargin(),
|
||||
)
|
||||
}
|
||||
|
||||
@Test fun modifierRemovedIfNotPresentInAll() {
|
||||
val merger = ApiMerger()
|
||||
merger += """
|
||||
|<api version="1">
|
||||
| <layout-modifier tag="1" since="2" />
|
||||
|</api>
|
||||
|
|
||||
""".trimMargin()
|
||||
merger += """
|
||||
|<api version="1" />
|
||||
|
|
||||
""".trimMargin()
|
||||
val actual = merger.merge()
|
||||
|
||||
assertThat(actual).isEqualTo(
|
||||
"""
|
||||
|<api version="1" />
|
||||
""".trimMargin(),
|
||||
)
|
||||
}
|
||||
|
||||
@Test fun modifierPropertyTakesHigherSince() {
|
||||
val merger = ApiMerger()
|
||||
merger += """
|
||||
|<api version="1">
|
||||
| <layout-modifier tag="1" since="2">
|
||||
| <property tag="1" since="2" />
|
||||
| </layout-modifier>
|
||||
|</api>
|
||||
|
|
||||
""".trimMargin()
|
||||
merger += """
|
||||
|<api version="1">
|
||||
| <layout-modifier tag="1" since="2">
|
||||
| <property tag="1" since="3" />
|
||||
| </layout-modifier>
|
||||
|</api>
|
||||
|
|
||||
""".trimMargin()
|
||||
val actual = merger.merge()
|
||||
|
||||
assertThat(actual).isEqualTo(
|
||||
"""
|
||||
|<api version="1">
|
||||
| <layout-modifier tag="1" since="2">
|
||||
| <property tag="1" since="3" />
|
||||
| </layout-modifier>
|
||||
|</api>
|
||||
""".trimMargin(),
|
||||
)
|
||||
}
|
||||
|
||||
@Test fun modifierRemovedIfNotInAll() {
|
||||
val merger = ApiMerger()
|
||||
merger += """
|
||||
|<api version="1">
|
||||
| <layout-modifier tag="1" since="2">
|
||||
| <property tag="1" since="2" />
|
||||
| </layout-modifier>
|
||||
|</api>
|
||||
|
|
||||
""".trimMargin()
|
||||
merger += """
|
||||
|<api version="1">
|
||||
| <layout-modifier tag="1" since="2" />
|
||||
|</api>
|
||||
|
|
||||
""".trimMargin()
|
||||
val actual = merger.merge()
|
||||
|
||||
assertThat(actual).isEqualTo(
|
||||
"""
|
||||
|<api version="1">
|
||||
| <layout-modifier tag="1" since="2" />
|
||||
|</api>
|
||||
""".trimMargin(),
|
||||
)
|
||||
}
|
||||
|
||||
@Test fun sortedByTag() {
|
||||
val merger = ApiMerger()
|
||||
merger += """
|
||||
|<api version="1">
|
||||
| <widget tag="2" since="1">
|
||||
| <trait tag="4" since="1" />
|
||||
| <trait tag="2" since="1" />
|
||||
| </widget>
|
||||
| <widget tag="1" since="1">
|
||||
| <trait tag="3" since="1" />
|
||||
| <trait tag="1" since="1" />
|
||||
| </widget>
|
||||
| <layout-modifier tag="2" since="1">
|
||||
| <property tag="4" since="1" />
|
||||
| <property tag="2" since="1" />
|
||||
| </layout-modifier>
|
||||
| <layout-modifier tag="1" since="1">
|
||||
| <property tag="3" since="1" />
|
||||
| <property tag="1" since="1" />
|
||||
| </layout-modifier>
|
||||
|</api>
|
||||
|
|
||||
""".trimMargin()
|
||||
val actual = merger.merge()
|
||||
|
||||
assertThat(actual).isEqualTo(
|
||||
"""
|
||||
|<api version="1">
|
||||
| <widget tag="1" since="1">
|
||||
| <trait tag="1" since="1" />
|
||||
| <trait tag="3" since="1" />
|
||||
| </widget>
|
||||
| <widget tag="2" since="1">
|
||||
| <trait tag="2" since="1" />
|
||||
| <trait tag="4" since="1" />
|
||||
| </widget>
|
||||
| <layout-modifier tag="1" since="1">
|
||||
| <property tag="1" since="1" />
|
||||
| <property tag="3" since="1" />
|
||||
| </layout-modifier>
|
||||
| <layout-modifier tag="2" since="1">
|
||||
| <property tag="2" since="1" />
|
||||
| <property tag="4" since="1" />
|
||||
| </layout-modifier>
|
||||
|</api>
|
||||
""".trimMargin(),
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -80,7 +80,6 @@ include ':redwood-schema'
|
||||
include ':redwood-snapshot-testing'
|
||||
include ':redwood-testing'
|
||||
include ':redwood-tooling-codegen'
|
||||
include ':redwood-tooling-lint'
|
||||
include ':redwood-tooling-schema'
|
||||
include ':redwood-treehouse'
|
||||
include ':redwood-treehouse-guest'
|
||||
|
||||
Reference in New Issue
Block a user