1
0
mirror of https://github.com/SquidDev-CC/CC-Tweaked synced 2025-01-06 07:20:30 +00:00

Use spotless for enforcing licenses

It's more verbose as the default license plugin doesn't support multiple
license headers. However, it also gives us some other goodies (namely
formatting Kotlin and removing unused imports), so worth doing.
This commit is contained in:
Jonathan Coates 2022-10-22 17:47:39 +01:00
parent 57cf6084e2
commit af5d816798
No known key found for this signature in database
GPG Key ID: B9E431FF07C98D06
19 changed files with 179 additions and 51 deletions

View File

@ -24,6 +24,13 @@ repos:
- repo: local
hooks:
- id: license
name: Spotless
files: ".*\\.(java|kt|kts)$"
language: system
entry: ./gradlew spotlessApply
pass_filenames: false
require_serial: true
- id: checkstyle
name: Check Java codestyle
files: ".*\\.java$"
@ -31,18 +38,11 @@ repos:
entry: ./gradlew checkstyleMain checkstyleTest
pass_filenames: false
require_serial: true
- id: license
name: Check Java license headers
files: ".*\\.java$"
language: system
entry: ./gradlew licenseFormat
pass_filenames: false
require_serial: true
- id: illuaminate
name: Check Lua code
files: ".*\\.(lua|java|md)"
language: system
entry: ./gradlew lintLua -i
entry: ./gradlew lintLua
pass_filenames: false
require_serial: true

View File

@ -2,7 +2,6 @@ plugins {
id "checkstyle"
id "jacoco"
id "maven-publish"
id "org.cadixdev.licenser" version "0.6.1"
id "com.matthewprenger.cursegradle" version "1.4.0"
id "com.github.breadmoirai.github-release" version "2.2.12"
id "org.jetbrains.kotlin.jvm" version "1.7.0"
@ -11,7 +10,8 @@ plugins {
id "org.spongepowered.mixin" version "0.7.+"
id "org.parchmentmc.librarian.forgegradle" version "1.+"
id "com.github.johnrengelman.shadow" version "7.1.2"
id "cc-tweaked.illuaminate"
id("cc-tweaked.illuaminate")
id("cc-tweaked.java-convention")
}
@ -388,23 +388,6 @@ jacocoTestReport {
test.finalizedBy("jacocoTestReport")
license {
header = file('config/license/main.txt')
lineEnding = '\n'
newLine = false
properties {
year = Calendar.getInstance().get(Calendar.YEAR)
}
include("**/*.java") // We could apply to Kotlin, but for now let's not
matching("dan200/computercraft/api/**") {
header = file('config/license/api.txt')
}
}
check.dependsOn("licenseCheck")
def lintLua = tasks.register("lintLua", IlluaminateExec.class) {
group = JavaBasePlugin.VERIFICATION_GROUP
description = "Lint Lua (and Lua docs) with illuaminate"

View File

@ -8,6 +8,10 @@ repositories {
gradlePluginPortal()
}
dependencies {
implementation(libs.spotless)
}
gradlePlugin {
plugins {
register("cc-tweaked.illuaminate") {

View File

@ -0,0 +1,7 @@
dependencyResolutionManagement {
versionCatalogs {
create("libs") {
from(files("../gradle/libs.versions.toml"))
}
}
}

View File

@ -0,0 +1,48 @@
import cc.tweaked.gradle.LicenseHeader
import com.diffplug.gradle.spotless.FormatExtension
import com.diffplug.spotless.LineEnding
import java.nio.charset.StandardCharsets
plugins {
java
jacoco
id("com.diffplug.spotless")
}
spotless {
encoding = StandardCharsets.UTF_8
lineEndings = LineEnding.UNIX
fun FormatExtension.defaults() {
endWithNewline()
trimTrailingWhitespace()
indentWithSpaces(4)
}
val licenser = LicenseHeader.create(
api = file("config/license/api.txt"),
main = file("config/license/main.txt"),
)
java {
defaults()
addStep(licenser)
removeUnusedImports()
}
val ktlintConfig = mapOf(
"disabled_rules" to "no-wildcard-imports",
"ij_kotlin_allow_trailing_comma" to "true",
"ij_kotlin_allow_trailing_comma_on_call_site" to "true",
)
kotlinGradle {
defaults()
ktlint().editorConfigOverride(ktlintConfig)
}
kotlin {
defaults()
ktlint().editorConfigOverride(ktlintConfig)
}
}

View File

@ -0,0 +1,73 @@
package cc.tweaked.gradle
import com.diffplug.spotless.FormatterFunc
import com.diffplug.spotless.FormatterStep
import com.diffplug.spotless.generic.LicenseHeaderStep
import java.io.File
import java.io.Serializable
import java.nio.charset.StandardCharsets
/**
* Similar to [LicenseHeaderStep], but supports multiple licenses.
*/
object LicenseHeader {
/**
* The current year to use in templates. Intentionally not dynamic to avoid failing the build.
*/
private const val YEAR = 2022
private val COMMENT = Regex("""^/\*(.*?)\*/\n?""", RegexOption.DOT_MATCHES_ALL)
fun create(api: File, main: File): FormatterStep = FormatterStep.createLazy(
"license",
{ Licenses(getTemplateText(api), getTemplateText(main)) },
{ state -> FormatterFunc.NeedsFile { contents, file -> formatFile(state, contents, file) } },
)
private fun getTemplateText(file: File): String =
file.readText(StandardCharsets.UTF_8).trim().replace("\${year}", "$YEAR")
private fun formatFile(licenses: Licenses, contents: String, file: File): String {
val license = getLicense(contents)
val expectedLicense = getExpectedLicense(licenses, file.parentFile)
return when {
license == null -> setLicense(expectedLicense, contents)
license.second != expectedLicense -> setLicense(expectedLicense, contents, license.first)
else -> contents
}
}
private fun getExpectedLicense(licenses: Licenses, file: File): String {
var file: File? = file
while (file != null) {
if (file.name == "api" && file.parentFile?.name == "computercraft") return licenses.api
file = file.parentFile
}
return licenses.main
}
private fun getLicense(contents: String): Pair<Int, String>? {
val match = COMMENT.find(contents) ?: return null
val license = match.groups[1]!!.value
.trim().lineSequence()
.map { it.trimStart(' ', '*') }
.joinToString("\n")
return Pair(match.range.last + 1, license)
}
private fun setLicense(license: String, contents: String, start: Int = 0): String {
val out = StringBuilder()
out.append("/*\n")
for (line in license.lineSequence()) out.append(" * ").append(line).append("\n")
out.append(" */\n")
out.append(contents, start, contents.length)
return out.toString()
}
private data class Licenses(val api: String, val main: String) : Serializable {
companion object {
private const val serialVersionUID: Long = 7741106448372435662L
}
}
}

View File

@ -9,6 +9,9 @@ hamcrest = "2.2"
jqwik = "1.7.0"
junit = "5.9.1"
# Build tools
spotless = "6.8.0"
[libraries]
autoService = { module = "com.google.auto.service:auto-service", version.ref = "autoService" }
jetbrainsAnnotations = { module = "org.jetbrains:annotations", version.ref = "jetbrainsAnnotations" }
@ -23,6 +26,9 @@ junit-jupiter-api = { module = "org.junit.jupiter:junit-jupiter-api", version.re
junit-jupiter-engine = { module = "org.junit.jupiter:junit-jupiter-engine", version.ref = "junit" }
junit-jupiter-params = { module = "org.junit.jupiter:junit-jupiter-params", version.ref = "junit" }
# Gradle plugins
spotless = { module = "com.diffplug.spotless:spotless-plugin-gradle", version.ref = "spotless" }
[bundles]
kotlin = ["kotlin-stdlib", "kotlin-coroutines"]

View File

@ -14,4 +14,4 @@ pluginManagement {
}
val mc_version: String by settings
rootProject.name = "cc-tweaked-${mc_version}"
rootProject.name = "cc-tweaked-$mc_version"

View File

@ -19,7 +19,6 @@ import kotlin.time.Duration
import kotlin.time.Duration.Companion.seconds
import kotlin.time.ExperimentalTime
abstract class NullApiEnvironment : IAPIEnvironment {
private val computerEnv = BasicEnvironment()

View File

@ -28,8 +28,8 @@ class TestHttpApi {
ComputerCraft.httpRules = Collections.unmodifiableList(
listOf(
AddressRule.parse("\$private", null, Action.DENY.toPartial()),
AddressRule.parse("*", null, Action.ALLOW.toPartial())
)
AddressRule.parse("*", null, Action.ALLOW.toPartial()),
),
)
}
}

View File

@ -25,7 +25,8 @@ class Computer_Test {
context.assertBlockState(
lamp,
{ !it.getValue(RedstoneLampBlock.LIT) },
{ "Lamp should still not be lit" })
{ "Lamp should still not be lit" },
)
}
}
}

View File

@ -15,10 +15,14 @@ class Modem_Test {
this
.thenComputerOk(marker = "initial")
.thenExecute {
helper.setBlock(position, BlockCable.correctConnections(
helper.level, helper.absolutePos(position),
Registry.ModBlocks.CABLE.get().defaultBlockState().setValue(BlockCable.CABLE, true)
))
helper.setBlock(
position,
BlockCable.correctConnections(
helper.level,
helper.absolutePos(position),
Registry.ModBlocks.CABLE.get().defaultBlockState().setValue(BlockCable.CABLE, true),
),
)
}
.thenComputerOk()
}

View File

@ -24,7 +24,7 @@ class Monitor_Test {
val toSet = BlockStateInput(
Registry.ModBlocks.MONITOR_ADVANCED.get().defaultBlockState(),
Collections.emptySet(),
tag
tag,
)
context.setBlock(pos, Blocks.AIR.defaultBlockState())

View File

@ -2,7 +2,7 @@ package dan200.computercraft.ingame
import dan200.computercraft.ingame.api.*
class PrintoutTest {
class Printout_Test {
@GameTest(batch = "client:Printout_Test.In_frame_at_night", timeoutTicks = Timeouts.CLIENT_TIMEOUT)
fun In_frame_at_night(helper: GameTestHelper) = helper.sequence {
this

View File

@ -86,9 +86,9 @@ class Turtle_Test {
DetailRegistries.ITEM_STACK.addProvider(
object : BasicItemDetailProvider<ItemPrintout>("printout", ItemPrintout::class.java) {
override fun provideDetails(data: MutableMap<in String, Any>, stack: ItemStack, item: ItemPrintout) {
data["type"] = item.type.toString();
data["type"] = item.type.toString()
}
}
},
)
}
.thenComputerOk()

View File

@ -79,7 +79,8 @@ typealias GameTestSequence = TestList
typealias GameTestAssertException = RuntimeException
private fun GameTestSequence.addResult(task: () -> Unit, delay: Long? = null): GameTestSequence {
private fun GameTestSequence.addResult(task: () -> Unit) = addResult(null, task)
private fun GameTestSequence.addResult(delay: Long?, task: () -> Unit): GameTestSequence {
val result = UnsafeHacks.newInstance(TestTickResult::class.java) as TestTickResult
result.assertion = Runnable(task)
result.expectedDelay = delay
@ -88,29 +89,29 @@ private fun GameTestSequence.addResult(task: () -> Unit, delay: Long? = null): G
}
fun GameTestSequence.thenSucceed() {
addResult({
addResult {
if (parent.error != null) return@addResult
parent.finish()
parent.markAsComplete()
})
}
}
fun GameTestSequence.thenWaitUntil(task: () -> Unit) = addResult(task)
fun GameTestSequence.thenExecute(task: () -> Unit) = addResult({
fun GameTestSequence.thenExecute(task: () -> Unit) = addResult {
try {
task()
} catch (e: Exception) {
parent.fail(e)
}
})
}
fun GameTestSequence.thenIdle(delay: Long) = addResult({
fun GameTestSequence.thenIdle(delay: Long) = addResult {
if (parent.tickCount < lastTick + delay) {
throw GameTestAssertException("Waiting")
}
})
}
/**
* Proguard strips out all the "on success" code as it's never called anywhere. This is workable most of the time, but

View File

@ -99,7 +99,7 @@ fun GameTestSequence.thenScreenshot(name: String? = null): GameTestSequence {
val screenshotPath = screenshotsPath.resolve("$fullName.png")
val originalPath = TestMod.sourceDir.resolve("screenshots").resolve("$fullName.png")
if (!Files.exists(originalPath)) throw GameTestAssertException("$fullName does not exist. Use `/cctest promote' to create it.");
if (!Files.exists(originalPath)) throw GameTestAssertException("$fullName does not exist. Use `/cctest promote' to create it.")
val screenshot = ImageIO.read(screenshotPath.toFile())
?: throw GameTestAssertException("Error reading screenshot from $screenshotPath")
@ -147,14 +147,16 @@ fun GameTestHelper.positionAtArmorStand() {
player.connection.teleport(stand.x, stand.y, stand.z, stand.yRot, stand.xRot)
}
class ClientTestHelper {
val minecraft: Minecraft = Minecraft.getInstance()
fun screenshot(name: String, callback: () -> Unit = {}) {
ScreenShotHelper.grab(
minecraft.gameDirectory, name,
minecraft.window.width, minecraft.window.height, minecraft.mainRenderTarget
minecraft.gameDirectory,
name,
minecraft.window.width,
minecraft.window.height,
minecraft.mainRenderTarget,
) {
TestMod.log.info(it.string)
callback()