diff --git a/.checkstyle/checkstyle_base.xml b/.checkstyle/checkstyle_base.xml
new file mode 100644
index 0000000000..de2a8f13b3
--- /dev/null
+++ b/.checkstyle/checkstyle_base.xml
@@ -0,0 +1,203 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/.checkstyle/suppressions.xml b/.checkstyle/suppressions.xml
new file mode 100644
index 0000000000..2b0d20aa39
--- /dev/null
+++ b/.checkstyle/suppressions.xml
@@ -0,0 +1,7 @@
+
+
+
+
+
diff --git a/.checkstyle/type_use_annotations.txt b/.checkstyle/type_use_annotations.txt
new file mode 100644
index 0000000000..4ac51acc7c
--- /dev/null
+++ b/.checkstyle/type_use_annotations.txt
@@ -0,0 +1,10 @@
+NonNull
+NotNull
+Nullable
+UnknownNullability
+Unmodifiable
+UnmodifiableView
+Range
+Positive
+NonNegative
+IntRange
diff --git a/.checkstyle/xpath-suppressions.xml b/.checkstyle/xpath-suppressions.xml
new file mode 100644
index 0000000000..b0ba3f631e
--- /dev/null
+++ b/.checkstyle/xpath-suppressions.xml
@@ -0,0 +1,17 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/.editorconfig b/.editorconfig
index 600eac0fb9..4911a3847a 100644
--- a/.editorconfig
+++ b/.editorconfig
@@ -10,6 +10,7 @@ ij_any_block_comment_add_space = false
ij_any_block_comment_at_first_column = false
ij_any_line_comment_at_first_column = false
ij_any_line_comment_add_space = true
+trim_trailing_whitespace = true
[*.tiny]
indent_style = tab
@@ -20,7 +21,7 @@ end_of_line = crlf
[*.yml]
indent_size = 2
-[*.patch]
+[{*.patch,*.md}]
trim_trailing_whitespace = false
[*.java]
@@ -29,12 +30,26 @@ ij_java_class_count_to_use_import_on_demand = 999999
ij_java_insert_inner_class_imports = false
ij_java_names_count_to_use_import_on_demand = 999999
ij_java_imports_layout = *, |, $*
+ij_java_layout_static_imports_separately = true
ij_java_generate_final_locals = true
ij_java_generate_final_parameters = true
ij_java_method_parameters_new_line_after_left_paren = true
ij_java_method_parameters_right_paren_on_new_line = true
ij_java_use_fq_class_names = false
ij_java_class_names_in_javadoc = 1
+# javadoc
+ij_java_doc_add_blank_line_after_description = true
+ij_java_doc_add_blank_line_after_param_comments = false
+ij_java_doc_add_blank_line_after_return = false
+ij_java_doc_add_p_tag_on_empty_lines = true
+ij_java_doc_align_exception_comments = true
+ij_java_doc_align_param_comments = true
+ij_java_doc_do_not_wrap_if_one_line = false
+ij_java_doc_enable_formatting = true
+ij_java_doc_enable_leading_asterisks = true
+ij_java_doc_indent_on_continuation = true
+ij_java_doc_param_description_on_new_line = false
+ij_java_doc_use_throws_not_exception_tag = true
[paper-server/src/minecraft/java/**/*.java]
ij_java_use_fq_class_names = true
diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml
index 181664ec91..4004e163e0 100644
--- a/.github/workflows/build.yml
+++ b/.github/workflows/build.yml
@@ -111,6 +111,13 @@ jobs:
path: |
**/build/test-results/test/TEST-*.xml
+ - name: Publish Checkstyle Report
+ uses: Juuxel/publish-checkstyle-report@v3
+ if: failure() || success()
+ with:
+ reports: |
+ */build/reports/checkstyle/*.xml
+
- name: Create Paperclip Jar
if: fromJSON(steps.determine.outputs.result).action == 'paperclip'
run: ./gradlew createPaperclipJar --stacktrace
diff --git a/build.gradle.kts b/build.gradle.kts
index 513b96803a..64f8f0aad6 100644
--- a/build.gradle.kts
+++ b/build.gradle.kts
@@ -1,3 +1,5 @@
+import io.papermc.paperweight.checkstyle.PaperCheckstyleExt
+import io.papermc.paperweight.checkstyle.tasks.PaperCheckstyleTask
import org.gradle.api.tasks.testing.logging.TestExceptionFormat
import org.gradle.api.tasks.testing.logging.TestLogEvent
@@ -6,14 +8,39 @@ plugins {
}
subprojects {
- apply(plugin = "java-library")
- apply(plugin = "maven-publish")
+ apply {
+ plugin("java-library")
+ plugin("maven-publish")
+ }
extensions.configure {
toolchain {
languageVersion = JavaLanguageVersion.of(25)
}
}
+
+ val tempDisabled = setOf("paper-server", "paper-generator", "test-plugin")
+
+ if (name !in tempDisabled) {
+ apply { plugin("io.papermc.paperweight.paper-checkstyle") }
+ extensions.configure {
+ typeUseAnnotationsFile.set(rootProject.layout.projectDirectory.file(".checkstyle/type_use_annotations.txt"))
+ }
+
+ /*tasks.withType().configureEach {
+ configDirectory = rootProject.layout.projectDirectory.dir(".checkstyle")
+ // configFile = layout.projectDirectory.file(".checkstyle/checkstyle.xml").asFile // use the base file if not overwritten
+ maxHeapSize = "2g"
+ reports {
+ xml.required = true
+ html.required = true
+ }
+ }*/
+
+ dependencies {
+ "checkstyle"(project(":paper-checkstyle"))
+ }
+ }
}
val paperMavenPublicUrl = "https://repo.papermc.io/repository/maven-public/"
diff --git a/paper-api/.checkstyle/checkstyle.xml b/paper-api/.checkstyle/checkstyle.xml
new file mode 100644
index 0000000000..f755c2af00
--- /dev/null
+++ b/paper-api/.checkstyle/checkstyle.xml
@@ -0,0 +1,26 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/paper-api/.checkstyle/ignored_directories.txt b/paper-api/.checkstyle/ignored_directories.txt
new file mode 100644
index 0000000000..2d76afe894
--- /dev/null
+++ b/paper-api/.checkstyle/ignored_directories.txt
@@ -0,0 +1,201 @@
+paper-api/src/generated/java/com/destroystokyo/paper/entity/ai/
+paper-api/src/generated/java/io/papermc/paper/registry/keys/
+paper-api/src/generated/java/io/papermc/paper/registry/keys/tags/
+paper-api/src/main/java/co/aikar/timings/
+paper-api/src/main/java/co/aikar/util/
+paper-api/src/main/java/com/destroystokyo/paper/
+paper-api/src/main/java/com/destroystokyo/paper/block/
+paper-api/src/main/java/com/destroystokyo/paper/brigadier/
+paper-api/src/main/java/com/destroystokyo/paper/entity/
+paper-api/src/main/java/com/destroystokyo/paper/entity/ai/
+paper-api/src/main/java/com/destroystokyo/paper/entity/villager/
+paper-api/src/main/java/com/destroystokyo/paper/event/block/
+paper-api/src/main/java/com/destroystokyo/paper/event/brigadier/
+paper-api/src/main/java/com/destroystokyo/paper/event/entity/
+paper-api/src/main/java/com/destroystokyo/paper/event/inventory/
+paper-api/src/main/java/com/destroystokyo/paper/event/player/
+paper-api/src/main/java/com/destroystokyo/paper/event/profile/
+paper-api/src/main/java/com/destroystokyo/paper/event/server/
+paper-api/src/main/java/com/destroystokyo/paper/exception/
+paper-api/src/main/java/com/destroystokyo/paper/inventory/meta/
+paper-api/src/main/java/com/destroystokyo/paper/loottable/
+paper-api/src/main/java/com/destroystokyo/paper/network/
+paper-api/src/main/java/com/destroystokyo/paper/profile/
+paper-api/src/main/java/com/destroystokyo/paper/util/
+paper-api/src/main/java/com/destroystokyo/paper/utils/
+paper-api/src/main/java/io/papermc/paper/advancement/
+paper-api/src/main/java/io/papermc/paper/annotation/
+paper-api/src/main/java/io/papermc/paper/ban/
+paper-api/src/main/java/io/papermc/paper/block/
+paper-api/src/main/java/io/papermc/paper/block/bed/
+paper-api/src/main/java/io/papermc/paper/block/fluid/
+paper-api/src/main/java/io/papermc/paper/block/fluid/type/
+paper-api/src/main/java/io/papermc/paper/brigadier/
+paper-api/src/main/java/io/papermc/paper/chat/
+paper-api/src/main/java/io/papermc/paper/command/
+paper-api/src/main/java/io/papermc/paper/command/brigadier/
+paper-api/src/main/java/io/papermc/paper/command/brigadier/argument/
+paper-api/src/main/java/io/papermc/paper/command/brigadier/argument/position/
+paper-api/src/main/java/io/papermc/paper/command/brigadier/argument/predicate/
+paper-api/src/main/java/io/papermc/paper/command/brigadier/argument/range/
+paper-api/src/main/java/io/papermc/paper/command/brigadier/argument/resolvers/
+paper-api/src/main/java/io/papermc/paper/command/brigadier/argument/resolvers/selector/
+paper-api/src/main/java/io/papermc/paper/configuration/
+paper-api/src/main/java/io/papermc/paper/connection/
+paper-api/src/main/java/io/papermc/paper/datacomponent/
+paper-api/src/main/java/io/papermc/paper/datacomponent/item/
+paper-api/src/main/java/io/papermc/paper/datacomponent/item/attribute/
+paper-api/src/main/java/io/papermc/paper/datacomponent/item/blocksattacks/
+paper-api/src/main/java/io/papermc/paper/datacomponent/item/consumable/
+paper-api/src/main/java/io/papermc/paper/datapack/
+paper-api/src/main/java/io/papermc/paper/dialog/
+paper-api/src/main/java/io/papermc/paper/enchantments/
+paper-api/src/main/java/io/papermc/paper/entity/
+paper-api/src/main/java/io/papermc/paper/entity/poi/
+paper-api/src/main/java/io/papermc/paper/event/block/
+paper-api/src/main/java/io/papermc/paper/event/connection/
+paper-api/src/main/java/io/papermc/paper/event/connection/configuration/
+paper-api/src/main/java/io/papermc/paper/event/entity/
+paper-api/src/main/java/io/papermc/paper/event/executor/
+paper-api/src/main/java/io/papermc/paper/event/inventory/
+paper-api/src/main/java/io/papermc/paper/event/packet/
+paper-api/src/main/java/io/papermc/paper/event/player/
+paper-api/src/main/java/io/papermc/paper/event/server/
+paper-api/src/main/java/io/papermc/paper/event/world/
+paper-api/src/main/java/io/papermc/paper/event/world/border/
+paper-api/src/main/java/io/papermc/paper/generated/
+paper-api/src/main/java/io/papermc/paper/inventory/
+paper-api/src/main/java/io/papermc/paper/inventory/tooltip/
+paper-api/src/main/java/io/papermc/paper/item/
+paper-api/src/main/java/io/papermc/paper/persistence/
+paper-api/src/main/java/io/papermc/paper/plugin/
+paper-api/src/main/java/io/papermc/paper/plugin/bootstrap/
+paper-api/src/main/java/io/papermc/paper/plugin/configuration/
+paper-api/src/main/java/io/papermc/paper/plugin/lifecycle/event/
+paper-api/src/main/java/io/papermc/paper/plugin/lifecycle/event/handler/
+paper-api/src/main/java/io/papermc/paper/plugin/lifecycle/event/handler/configuration/
+paper-api/src/main/java/io/papermc/paper/plugin/lifecycle/event/registrar/
+paper-api/src/main/java/io/papermc/paper/plugin/lifecycle/event/types/
+paper-api/src/main/java/io/papermc/paper/plugin/loader/
+paper-api/src/main/java/io/papermc/paper/plugin/loader/library/
+paper-api/src/main/java/io/papermc/paper/plugin/loader/library/impl/
+paper-api/src/main/java/io/papermc/paper/plugin/provider/classloader/
+paper-api/src/main/java/io/papermc/paper/plugin/provider/entrypoint/
+paper-api/src/main/java/io/papermc/paper/plugin/provider/util/
+paper-api/src/main/java/io/papermc/paper/potion/
+paper-api/src/main/java/io/papermc/paper/raytracing/
+paper-api/src/main/java/io/papermc/paper/registry/
+paper-api/src/main/java/io/papermc/paper/registry/data/
+paper-api/src/main/java/io/papermc/paper/registry/data/client/
+paper-api/src/main/java/io/papermc/paper/registry/data/dialog/
+paper-api/src/main/java/io/papermc/paper/registry/data/dialog/action/
+paper-api/src/main/java/io/papermc/paper/registry/data/dialog/body/
+paper-api/src/main/java/io/papermc/paper/registry/data/dialog/input/
+paper-api/src/main/java/io/papermc/paper/registry/data/dialog/type/
+paper-api/src/main/java/io/papermc/paper/registry/event/
+paper-api/src/main/java/io/papermc/paper/registry/event/type/
+paper-api/src/main/java/io/papermc/paper/registry/holder/
+paper-api/src/main/java/io/papermc/paper/registry/set/
+paper-api/src/main/java/io/papermc/paper/registry/tag/
+paper-api/src/main/java/io/papermc/paper/scoreboard/numbers/
+paper-api/src/main/java/io/papermc/paper/tag/
+paper-api/src/main/java/io/papermc/paper/text/
+paper-api/src/main/java/io/papermc/paper/threadedregions/
+paper-api/src/main/java/io/papermc/paper/threadedregions/scheduler/
+paper-api/src/main/java/io/papermc/paper/util/
+paper-api/src/main/java/io/papermc/paper/world/
+paper-api/src/main/java/io/papermc/paper/world/damagesource/
+paper-api/src/main/java/io/papermc/paper/world/flag/
+paper-api/src/main/java/org/bukkit/
+paper-api/src/main/java/org/bukkit/advancement/
+paper-api/src/main/java/org/bukkit/attribute/
+paper-api/src/main/java/org/bukkit/ban/
+paper-api/src/main/java/org/bukkit/block/
+paper-api/src/main/java/org/bukkit/block/banner/
+paper-api/src/main/java/org/bukkit/block/data/
+paper-api/src/main/java/org/bukkit/block/data/type/
+paper-api/src/main/java/org/bukkit/block/sign/
+paper-api/src/main/java/org/bukkit/block/spawner/
+paper-api/src/main/java/org/bukkit/block/structure/
+paper-api/src/main/java/org/bukkit/boss/
+paper-api/src/main/java/org/bukkit/command/
+paper-api/src/main/java/org/bukkit/command/defaults/
+paper-api/src/main/java/org/bukkit/configuration/
+paper-api/src/main/java/org/bukkit/configuration/file/
+paper-api/src/main/java/org/bukkit/configuration/serialization/
+paper-api/src/main/java/org/bukkit/conversations/
+paper-api/src/main/java/org/bukkit/damage/
+paper-api/src/main/java/org/bukkit/enchantments/
+paper-api/src/main/java/org/bukkit/entity/
+paper-api/src/main/java/org/bukkit/entity/boat/
+paper-api/src/main/java/org/bukkit/entity/memory/
+paper-api/src/main/java/org/bukkit/entity/minecart/
+paper-api/src/main/java/org/bukkit/event/
+paper-api/src/main/java/org/bukkit/event/block/
+paper-api/src/main/java/org/bukkit/event/command/
+paper-api/src/main/java/org/bukkit/event/enchantment/
+paper-api/src/main/java/org/bukkit/event/entity/
+paper-api/src/main/java/org/bukkit/event/hanging/
+paper-api/src/main/java/org/bukkit/event/inventory/
+paper-api/src/main/java/org/bukkit/event/player/
+paper-api/src/main/java/org/bukkit/event/raid/
+paper-api/src/main/java/org/bukkit/event/server/
+paper-api/src/main/java/org/bukkit/event/vehicle/
+paper-api/src/main/java/org/bukkit/event/weather/
+paper-api/src/main/java/org/bukkit/event/world/
+paper-api/src/main/java/org/bukkit/generator/
+paper-api/src/main/java/org/bukkit/generator/structure/
+paper-api/src/main/java/org/bukkit/help/
+paper-api/src/main/java/org/bukkit/inventory/
+paper-api/src/main/java/org/bukkit/inventory/meta/
+paper-api/src/main/java/org/bukkit/inventory/meta/components/
+paper-api/src/main/java/org/bukkit/inventory/meta/tags/
+paper-api/src/main/java/org/bukkit/inventory/meta/trim/
+paper-api/src/main/java/org/bukkit/inventory/recipe/
+paper-api/src/main/java/org/bukkit/inventory/view/
+paper-api/src/main/java/org/bukkit/inventory/view/builder/
+paper-api/src/main/java/org/bukkit/loot/
+paper-api/src/main/java/org/bukkit/map/
+paper-api/src/main/java/org/bukkit/material/
+paper-api/src/main/java/org/bukkit/material/types/
+paper-api/src/main/java/org/bukkit/metadata/
+paper-api/src/main/java/org/bukkit/packs/
+paper-api/src/main/java/org/bukkit/permissions/
+paper-api/src/main/java/org/bukkit/persistence/
+paper-api/src/main/java/org/bukkit/plugin/
+paper-api/src/main/java/org/bukkit/plugin/java/
+paper-api/src/main/java/org/bukkit/plugin/messaging/
+paper-api/src/main/java/org/bukkit/potion/
+paper-api/src/main/java/org/bukkit/profile/
+paper-api/src/main/java/org/bukkit/projectiles/
+paper-api/src/main/java/org/bukkit/scheduler/
+paper-api/src/main/java/org/bukkit/scoreboard/
+paper-api/src/main/java/org/bukkit/spawner/
+paper-api/src/main/java/org/bukkit/structure/
+paper-api/src/main/java/org/bukkit/tag/
+paper-api/src/main/java/org/bukkit/util/
+paper-api/src/main/java/org/bukkit/util/io/
+paper-api/src/main/java/org/bukkit/util/noise/
+paper-api/src/main/java/org/bukkit/util/permissions/
+paper-api/src/main/java/org/spigotmc/
+paper-api/src/main/java/org/spigotmc/event/player/
+paper-api/src/test/java/com/destroystokyo/paper/
+paper-api/src/test/java/io/papermc/paper/adventure/
+paper-api/src/test/java/io/papermc/paper/registry/
+paper-api/src/test/java/io/papermc/paper/testing/
+paper-api/src/test/java/io/papermc/paper/util/
+paper-api/src/test/java/org/bukkit/
+paper-api/src/test/java/org/bukkit/configuration/
+paper-api/src/test/java/org/bukkit/configuration/file/
+paper-api/src/test/java/org/bukkit/conversations/
+paper-api/src/test/java/org/bukkit/event/
+paper-api/src/test/java/org/bukkit/materials/
+paper-api/src/test/java/org/bukkit/metadata/
+paper-api/src/test/java/org/bukkit/plugin/
+paper-api/src/test/java/org/bukkit/plugin/messaging/
+paper-api/src/test/java/org/bukkit/scoreboard/
+paper-api/src/test/java/org/bukkit/support/
+paper-api/src/test/java/org/bukkit/support/provider/
+paper-api/src/test/java/org/bukkit/support/test/
+paper-api/src/test/java/org/bukkit/util/
+paper-api/src/test/java/org/bukkit/util/io/
diff --git a/paper-api/build.gradle.kts b/paper-api/build.gradle.kts
index 987d61382b..c034f861f5 100644
--- a/paper-api/build.gradle.kts
+++ b/paper-api/build.gradle.kts
@@ -1,9 +1,11 @@
+import io.papermc.paperweight.checkstyle.JavadocTag
import paper.libs.com.google.gson.Gson
plugins {
`java-library`
`maven-publish`
idea
+ id("io.papermc.paperweight.paper-checkstyle")
}
java {
@@ -11,6 +13,15 @@ java {
withJavadocJar()
}
+val projectCustomJavadocTags = setOf(
+ JavadocTag("apiNote", "a", "API Note:"),
+)
+
+paperCheckstyle {
+ customJavadocTags = projectCustomJavadocTags
+ directoriesToSkipFile = layout.projectDirectory.file(".checkstyle/ignored_directories.txt")
+}
+
val annotationsVersion = "26.0.2"
val adventureVersion = "5.2.0"
val bungeeCordChatVersion = "1.21-R0.2-deprecated+build.21"
@@ -92,6 +103,9 @@ dependencies {
testImplementation("org.ow2.asm:asm-tree:9.9.1")
mockitoAgent("org.mockito:mockito-core:5.22.0") { isTransitive = false } // configure mockito agent that is needed in newer java versions
testRuntimeOnly("org.junit.platform:junit-platform-launcher:6.0.3")
+
+ // checkstyle
+ checkstyle(project(":paper-checkstyle"))
}
val generatedDir: java.nio.file.Path = layout.projectDirectory.dir("src/generated/java").asFile.toPath()
@@ -201,7 +215,7 @@ tasks.withType().configureEach {
"https://logging.apache.org/log4j/2.x/javadoc/log4j-api/",
"https://www.javadocs.dev/org.apache.maven.resolver/maven-resolver-api/1.7.3",
)
- options.tags("apiNote:a:API Note:")
+ options.tags(projectCustomJavadocTags.map { it.toOptionString() })
inputs.files(javadocSourcepath).ignoreEmptyDirectories().withPropertyName(javadocSourcepath.name + "-configuration")
val javadocSourcepathElements = javadocSourcepath.elements
diff --git a/paper-api/src/main/java/io/papermc/paper/InternalAPIBridge.java b/paper-api/src/main/java/io/papermc/paper/InternalAPIBridge.java
index 1590367ec2..64306b5745 100644
--- a/paper-api/src/main/java/io/papermc/paper/InternalAPIBridge.java
+++ b/paper-api/src/main/java/io/papermc/paper/InternalAPIBridge.java
@@ -35,7 +35,6 @@ import org.bukkit.plugin.Plugin;
import org.bukkit.plugin.java.JavaPlugin;
import org.jetbrains.annotations.ApiStatus;
import org.jetbrains.annotations.Contract;
-import org.jspecify.annotations.NullMarked;
import org.jspecify.annotations.Nullable;
/**
@@ -45,7 +44,6 @@ import org.jspecify.annotations.Nullable;
* cause issues when called under unexpected circumstances.
*/
@ApiStatus.Internal
-@NullMarked
public interface InternalAPIBridge {
/**
@@ -55,6 +53,7 @@ public interface InternalAPIBridge {
*/
static InternalAPIBridge get() {
class Holder {
+
public static final InternalAPIBridge INSTANCE = Services.service(InternalAPIBridge.class).orElseThrow();
}
diff --git a/paper-api/src/main/java/io/papermc/paper/ServerBuildInfo.java b/paper-api/src/main/java/io/papermc/paper/ServerBuildInfo.java
index 56a5877181..4e099b584c 100644
--- a/paper-api/src/main/java/io/papermc/paper/ServerBuildInfo.java
+++ b/paper-api/src/main/java/io/papermc/paper/ServerBuildInfo.java
@@ -6,12 +6,10 @@ import java.util.OptionalInt;
import net.kyori.adventure.key.Key;
import net.kyori.adventure.util.Services;
import org.jetbrains.annotations.ApiStatus;
-import org.jspecify.annotations.NullMarked;
/**
* Information about the current server build.
*/
-@NullMarked
@ApiStatus.NonExtendable
public interface ServerBuildInfo {
/**
@@ -30,6 +28,7 @@ public interface ServerBuildInfo {
static final Optional INSTANCE = Services.service(ServerBuildInfo.class);
}
//
+
return Holder.INSTANCE.orElseThrow();
}
@@ -46,7 +45,7 @@ public interface ServerBuildInfo {
* @param brandId the brand to check (e.g. "papermc:folia")
* @return {@code true} if the server supports the specified brand
*/
- boolean isBrandCompatible(final Key brandId);
+ boolean isBrandCompatible(Key brandId);
/**
* Gets the brand name of the server.
@@ -103,7 +102,7 @@ public interface ServerBuildInfo {
* @param representation the type of representation
* @return a string
*/
- String asString(final StringRepresentation representation);
+ String asString(StringRepresentation representation);
/**
* String representation types.
diff --git a/paper-api/src/main/java/io/papermc/paper/math/Angle.java b/paper-api/src/main/java/io/papermc/paper/math/Angle.java
index 5181f84935..1083ca4a8f 100644
--- a/paper-api/src/main/java/io/papermc/paper/math/Angle.java
+++ b/paper-api/src/main/java/io/papermc/paper/math/Angle.java
@@ -1,11 +1,8 @@
package io.papermc.paper.math;
-import org.jspecify.annotations.NullMarked;
-
/**
* Represents an angle that can be relative or absolute.
*/
-@NullMarked
public sealed interface Angle permits AngleImpl {
/**
@@ -15,7 +12,7 @@ public sealed interface Angle permits AngleImpl {
* @param degrees the angle value, measured in degrees
* @return a new {@code Angle} instance with the specified value
*/
- static Angle absolute(float degrees) {
+ static Angle absolute(final float degrees) {
return new AngleImpl(degrees, false);
}
@@ -26,7 +23,7 @@ public sealed interface Angle permits AngleImpl {
* @param degrees the angle value, measured in degrees
* @return a new {@code Angle} instance with the specified value
*/
- static Angle relative(float degrees) {
+ static Angle relative(final float degrees) {
return new AngleImpl(degrees, true);
}
diff --git a/paper-api/src/main/java/io/papermc/paper/math/BlockPosition.java b/paper-api/src/main/java/io/papermc/paper/math/BlockPosition.java
index c358bfdefc..c247572422 100644
--- a/paper-api/src/main/java/io/papermc/paper/math/BlockPosition.java
+++ b/paper-api/src/main/java/io/papermc/paper/math/BlockPosition.java
@@ -4,7 +4,6 @@ import org.bukkit.Axis;
import org.bukkit.block.BlockFace;
import org.jetbrains.annotations.ApiStatus;
import org.jetbrains.annotations.Contract;
-import org.jspecify.annotations.NullMarked;
/**
* A position represented with integers.
@@ -14,7 +13,6 @@ import org.jspecify.annotations.NullMarked;
* @see FinePosition
*/
@ApiStatus.Experimental
-@NullMarked
public interface BlockPosition extends Position {
@Override
@@ -73,7 +71,7 @@ public interface BlockPosition extends Position {
* multiplied by the amount.
*
* @param blockFace the block face to offset towards
- * @param amount the number of times to move in that direction
+ * @param amount the number of times to move in that direction
* @return the offset block position
*/
@Contract(pure = true)
@@ -85,7 +83,7 @@ public interface BlockPosition extends Position {
* Returns a block position offset by the amount along
* the specified axis.
*
- * @param axis the axis to offset along
+ * @param axis the axis to offset along
* @param amount the amount to offset along that axis
* @return the offset block position
*/
diff --git a/paper-api/src/main/java/io/papermc/paper/math/FinePosition.java b/paper-api/src/main/java/io/papermc/paper/math/FinePosition.java
index b9c0065d8a..91cbed4117 100644
--- a/paper-api/src/main/java/io/papermc/paper/math/FinePosition.java
+++ b/paper-api/src/main/java/io/papermc/paper/math/FinePosition.java
@@ -2,7 +2,6 @@ package io.papermc.paper.math;
import org.bukkit.util.NumberConversions;
import org.jetbrains.annotations.ApiStatus;
-import org.jspecify.annotations.NullMarked;
/**
* A position represented with doubles.
@@ -12,7 +11,6 @@ import org.jspecify.annotations.NullMarked;
* @see BlockPosition
*/
@ApiStatus.Experimental
-@NullMarked
public interface FinePosition extends Position {
@Override
diff --git a/paper-api/src/main/java/io/papermc/paper/math/Position.java b/paper-api/src/main/java/io/papermc/paper/math/Position.java
index 2fad6604a0..0e9d8ab38e 100644
--- a/paper-api/src/main/java/io/papermc/paper/math/Position.java
+++ b/paper-api/src/main/java/io/papermc/paper/math/Position.java
@@ -5,7 +5,6 @@ import org.bukkit.World;
import org.bukkit.util.Vector;
import org.jetbrains.annotations.ApiStatus;
import org.jetbrains.annotations.Contract;
-import org.jspecify.annotations.NullMarked;
/**
* Common interface for {@link FinePosition} and {@link BlockPosition}.
@@ -13,63 +12,68 @@ import org.jspecify.annotations.NullMarked;
* May see breaking changes until Experimental annotation is removed.
*/
@ApiStatus.Experimental
-@NullMarked
public interface Position {
+ /**
+ * A fine position at the origin (0, 0, 0).
+ */
FinePosition FINE_ZERO = new FinePositionImpl(0, 0, 0);
+ /**
+ * A block position at the origin (0, 0, 0).
+ */
BlockPosition BLOCK_ZERO = new BlockPositionImpl(0, 0, 0);
/**
- * Gets the block x value for this position
+ * Gets the block x value for this position.
*
* @return the block x value
*/
int blockX();
/**
- * Gets the block y value for this position
+ * Gets the block y value for this position.
*
* @return the block y value
*/
int blockY();
/**
- * Gets the block z value for this position
+ * Gets the block z value for this position.
*
* @return the block z value
*/
int blockZ();
/**
- * Gets the x value for this position
+ * Gets the x value for this position.
*
* @return the x value
*/
double x();
/**
- * Gets the y value for this position
+ * Gets the y value for this position.
*
* @return the y value
*/
double y();
/**
- * Gets the z value for this position
+ * Gets the z value for this position.
*
* @return the z value
*/
double z();
/**
- * Checks of this position represents a {@link BlockPosition}
+ * Checks of this position represents a {@link BlockPosition}.
*
* @return true if block
*/
boolean isBlock();
/**
- * Checks if this position represents a {@link FinePosition}
+ * Checks if this position represents a {@link FinePosition}.
*
* @return true if fine
*/
@@ -77,6 +81,8 @@ public interface Position {
/**
* Checks if each component of this position is finite.
+ *
+ * @return true if each component of this position is finite
*/
default boolean isFinite() {
return Double.isFinite(this.x()) && Double.isFinite(this.y()) && Double.isFinite(this.z());
@@ -103,7 +109,7 @@ public interface Position {
FinePosition offset(double x, double y, double z);
/**
- * Returns a new position at the center of the block position this represents
+ * Returns a new position at the center of the block position this represents.
*
* @return a new center position
*/
@@ -114,7 +120,7 @@ public interface Position {
/**
* Returns the block position of this position
- * or itself if it already is a block position
+ * or itself if it already is a block position.
*
* @return the block position
*/
@@ -122,7 +128,7 @@ public interface Position {
BlockPosition toBlock();
/**
- * Converts this position to a vector
+ * Converts this position to a vector.
*
* @return a new vector
*/
@@ -132,7 +138,7 @@ public interface Position {
}
/**
- * Creates a new location object at this position with the specified world
+ * Creates a new location object at this position with the specified world.
*
* @param world the world for the location object
* @return a new location
@@ -143,7 +149,7 @@ public interface Position {
}
/**
- * Creates a position at the coordinates
+ * Creates a position at the coordinates.
*
* @param x x coord
* @param y y coord
@@ -167,7 +173,7 @@ public interface Position {
}
/**
- * Creates a position at the coordinates
+ * Creates a position at the coordinates.
*
* @param x x coord
* @param y y coord
diff --git a/paper-api/src/main/java/io/papermc/paper/math/Rotation.java b/paper-api/src/main/java/io/papermc/paper/math/Rotation.java
index 73add399e4..e150572213 100644
--- a/paper-api/src/main/java/io/papermc/paper/math/Rotation.java
+++ b/paper-api/src/main/java/io/papermc/paper/math/Rotation.java
@@ -1,11 +1,8 @@
package io.papermc.paper.math;
-import org.jspecify.annotations.NullMarked;
-
/**
* Represents a rotation with specified pitch and yaw values.
*/
-@NullMarked
public interface Rotation {
/**
* Creates a new rotation with the specified yaw and pitch values.
diff --git a/paper-api/src/main/java/io/papermc/paper/math/Rotations.java b/paper-api/src/main/java/io/papermc/paper/math/Rotations.java
index 2a8e1aa29a..565628de3c 100644
--- a/paper-api/src/main/java/io/papermc/paper/math/Rotations.java
+++ b/paper-api/src/main/java/io/papermc/paper/math/Rotations.java
@@ -1,21 +1,18 @@
package io.papermc.paper.math;
-import org.jspecify.annotations.NullMarked;
-
/**
* Rotations is an immutable object that stores rotations
* in degrees on each axis (X, Y, Z).
*/
-@NullMarked
public interface Rotations {
/**
- * Rotations instance with every axis set to 0
+ * Rotations instance with every axis set to 0.
*/
Rotations ZERO = ofDegrees(0, 0, 0);
/**
- * Creates a new Rotations instance holding the provided rotations
+ * Creates a new Rotations instance holding the provided rotations.
*
* @param x the angle for the X axis in degrees
* @param y the angle for the Y axis in degrees
@@ -27,21 +24,21 @@ public interface Rotations {
}
/**
- * Returns the angle on the X axis in degrees
+ * Returns the angle on the X axis in degrees.
*
* @return the angle in degrees
*/
double x();
/**
- * Returns the angle on the Y axis in degrees
+ * Returns the angle on the Y axis in degrees.
*
* @return the angle in degrees
*/
double y();
/**
- * Returns the angle on the Z axis in degrees
+ * Returns the angle on the Z axis in degrees.
*
* @return the angle in degrees
*/
@@ -49,7 +46,7 @@ public interface Rotations {
/**
* Returns a new Rotations instance which is the result
- * of changing the X axis to the passed angle
+ * of changing the X axis to the passed angle.
*
* @param x the angle in degrees
* @return the resultant Rotations
@@ -58,7 +55,7 @@ public interface Rotations {
/**
* Returns a new Rotations instance which is the result
- * of changing the Y axis to the passed angle
+ * of changing the Y axis to the passed angle.
*
* @param y the angle in degrees
* @return the resultant Rotations
@@ -67,7 +64,7 @@ public interface Rotations {
/**
* Returns a new Rotations instance which is the result
- * of changing the Z axis to the passed angle
+ * of changing the Z axis to the passed angle.
*
* @param z the angle in degrees
* @return the resultant Rotations
@@ -76,7 +73,7 @@ public interface Rotations {
/**
* Returns a new Rotations instance which is the result of adding
- * the x, y, z components to this Rotations
+ * the x, y, z components to this Rotations.
*
* @param x the angle to add to the X axis in degrees
* @param y the angle to add to the Y axis in degrees
@@ -87,7 +84,7 @@ public interface Rotations {
/**
* Returns a new Rotations instance which is the result of subtracting
- * the x, y, z components from this Rotations
+ * the x, y, z components from this Rotations.
*
* @param x the angle to subtract from the X axis in degrees
* @param y the angle to subtract from the Y axis in degrees
diff --git a/paper-api/src/main/java/io/papermc/paper/math/RotationsImpl.java b/paper-api/src/main/java/io/papermc/paper/math/RotationsImpl.java
index 35b493b870..fd80eaa764 100644
--- a/paper-api/src/main/java/io/papermc/paper/math/RotationsImpl.java
+++ b/paper-api/src/main/java/io/papermc/paper/math/RotationsImpl.java
@@ -1,8 +1,5 @@
package io.papermc.paper.math;
-import org.jspecify.annotations.NullMarked;
-
-@NullMarked
record RotationsImpl(double x, double y, double z) implements Rotations {
@Override
diff --git a/paper-api/src/main/java/io/papermc/paper/math/package-info.java b/paper-api/src/main/java/io/papermc/paper/math/package-info.java
new file mode 100644
index 0000000000..b0116269d9
--- /dev/null
+++ b/paper-api/src/main/java/io/papermc/paper/math/package-info.java
@@ -0,0 +1,7 @@
+/**
+ * Package containing maths-related API, including position API.
+ */
+@NullMarked
+package io.papermc.paper.math;
+
+import org.jspecify.annotations.NullMarked;
diff --git a/paper-api/src/main/java/io/papermc/paper/package-info.java b/paper-api/src/main/java/io/papermc/paper/package-info.java
new file mode 100644
index 0000000000..c374169c56
--- /dev/null
+++ b/paper-api/src/main/java/io/papermc/paper/package-info.java
@@ -0,0 +1,7 @@
+/**
+ * Root package for the Paper API.
+ */
+@NullMarked
+package io.papermc.paper;
+
+import org.jspecify.annotations.NullMarked;
diff --git a/paper-api/src/test/java/io/papermc/paper/TestServerBuildInfo.java b/paper-api/src/test/java/io/papermc/paper/TestServerBuildInfo.java
index 17be27a869..43b8382917 100644
--- a/paper-api/src/test/java/io/papermc/paper/TestServerBuildInfo.java
+++ b/paper-api/src/test/java/io/papermc/paper/TestServerBuildInfo.java
@@ -4,56 +4,57 @@ import java.time.Instant;
import java.util.Optional;
import java.util.OptionalInt;
import net.kyori.adventure.key.Key;
-import org.jetbrains.annotations.NotNull;
+import org.jspecify.annotations.NullMarked;
+@NullMarked
public class TestServerBuildInfo implements ServerBuildInfo {
@Override
- public @NotNull Key brandId() {
+ public Key brandId() {
throw new UnsupportedOperationException();
}
@Override
- public boolean isBrandCompatible(final @NotNull Key brandId) {
+ public boolean isBrandCompatible(final Key brandId) {
throw new UnsupportedOperationException();
}
@Override
- public @NotNull String brandName() {
+ public String brandName() {
throw new UnsupportedOperationException();
}
@Override
- public @NotNull String minecraftVersionId() {
+ public String minecraftVersionId() {
throw new UnsupportedOperationException();
}
@Override
- public @NotNull String minecraftVersionName() {
+ public String minecraftVersionName() {
throw new UnsupportedOperationException();
}
@Override
- public @NotNull OptionalInt buildNumber() {
+ public OptionalInt buildNumber() {
throw new UnsupportedOperationException();
}
@Override
- public @NotNull Instant buildTime() {
+ public Instant buildTime() {
throw new UnsupportedOperationException();
}
@Override
- public @NotNull Optional gitBranch() {
+ public Optional gitBranch() {
throw new UnsupportedOperationException();
}
@Override
- public @NotNull Optional gitCommit() {
+ public Optional gitCommit() {
throw new UnsupportedOperationException();
}
@Override
- public @NotNull String asString(final @NotNull StringRepresentation representation) {
+ public String asString(final StringRepresentation representation) {
return "";
}
}
diff --git a/paper-checkstyle/.checkstyle/checkstyle.xml b/paper-checkstyle/.checkstyle/checkstyle.xml
new file mode 100644
index 0000000000..9e4ff22f88
--- /dev/null
+++ b/paper-checkstyle/.checkstyle/checkstyle.xml
@@ -0,0 +1,9 @@
+
+
+
+
+
+
+
diff --git a/paper-checkstyle/build.gradle.kts b/paper-checkstyle/build.gradle.kts
new file mode 100644
index 0000000000..bac77afee2
--- /dev/null
+++ b/paper-checkstyle/build.gradle.kts
@@ -0,0 +1,26 @@
+plugins {
+ java
+ id("io.papermc.paperweight.paper-checkstyle")
+}
+
+val testData = sourceSets.create("testData")
+
+dependencies {
+ implementation("com.puppycrawl.tools:checkstyle:13.8.0")
+ implementation("org.jspecify:jspecify:1.0.0")
+
+ testCompileOnly("org.jetbrains:annotations:26.0.2")
+ testImplementation(testData.output)
+
+ testImplementation("org.junit.jupiter:junit-jupiter:5.13.3")
+ testRuntimeOnly("org.junit.platform:junit-platform-launcher")
+ testData.implementationConfigurationName("org.jspecify:jspecify:1.0.0")
+
+ checkstyle(project(":paper-checkstyle"))
+}
+
+tasks {
+ test {
+ useJUnitPlatform()
+ }
+}
diff --git a/paper-checkstyle/src/main/java/io/papermc/checkstyle/Util.java b/paper-checkstyle/src/main/java/io/papermc/checkstyle/Util.java
new file mode 100644
index 0000000000..0fd6bb463e
--- /dev/null
+++ b/paper-checkstyle/src/main/java/io/papermc/checkstyle/Util.java
@@ -0,0 +1,185 @@
+package io.papermc.checkstyle;
+
+import com.puppycrawl.tools.checkstyle.JavaParser;
+import com.puppycrawl.tools.checkstyle.api.CheckstyleException;
+import com.puppycrawl.tools.checkstyle.api.DetailAST;
+import com.puppycrawl.tools.checkstyle.api.DetailNode;
+import com.puppycrawl.tools.checkstyle.api.TokenTypes;
+import com.puppycrawl.tools.checkstyle.utils.TokenUtil;
+import java.io.IOException;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.util.ArrayList;
+import java.util.Iterator;
+import java.util.List;
+import java.util.NoSuchElementException;
+import java.util.Set;
+import java.util.function.Predicate;
+import org.apache.commons.lang3.tuple.Pair;
+import org.jspecify.annotations.Nullable;
+
+import static java.util.Objects.requireNonNull;
+
+/**
+ * Utility class containing utility methods for custom checkstyle checks.
+ */
+public final class Util {
+
+ public static final Set PRIMITIVE_TYPES = Set.of(
+ TokenTypes.LITERAL_VOID,
+ TokenTypes.LITERAL_BOOLEAN,
+ TokenTypes.LITERAL_BYTE,
+ TokenTypes.LITERAL_CHAR,
+ TokenTypes.LITERAL_SHORT,
+ TokenTypes.LITERAL_INT,
+ TokenTypes.LITERAL_LONG,
+ TokenTypes.LITERAL_FLOAT,
+ TokenTypes.LITERAL_DOUBLE
+ );
+
+ private Util() {
+ }
+
+
+ /**
+ * Gets the previous sibling of the given node with the given type.
+ *
+ * @param node the node
+ * @param type the type
+ * @return the previous sibling with the given type, or {@code null} if not found
+ */
+ public static @Nullable DetailNode getPreviousSibling(final DetailNode node, final int type) {
+ DetailNode sibling = node.getPreviousSibling();
+ while (sibling != null && sibling.getType() != type) {
+ sibling = sibling.getPreviousSibling();
+ }
+ return sibling;
+ }
+
+ /**
+ * Gets the next sibling of the given node with the given type.
+ *
+ * @param node the node
+ * @param type the type
+ * @return the next sibling with the given type, or {@code null} if not found
+ */
+ public static @Nullable DetailAST getNextSibling(final DetailAST node, final int type) {
+ DetailAST sibling = node.getNextSibling();
+ while (sibling != null && sibling.getType() != type) {
+ sibling = sibling.getNextSibling();
+ }
+ return sibling;
+ }
+
+ /**
+ * Gets the last child of the given node with the given type.
+ *
+ * @param node the node
+ * @param type the type
+ * @return the last child with the given type or {@code null} if not found
+ */
+ public static @Nullable DetailAST getLastChild(final DetailAST node, final int type) {
+ DetailAST child = node.getLastChild();
+ while (child != null && child.getType() != type) {
+ child = child.getPreviousSibling();
+ }
+ return child;
+ }
+
+ /**
+ * Gets the enclosing type declaration of the given node.
+ *
+ * @param node the node
+ * @return the enclosing type declaration, or {@code null} if not found
+ */
+ public static @Nullable DetailAST getEnclosingTypeDeclaration(final DetailAST node) {
+ DetailAST parent = node.getParent();
+ while (parent != null && !TokenUtil.isTypeDeclaration(parent.getType())) {
+ parent = parent.getParent();
+ }
+ return parent;
+ }
+
+ /**
+ * Gets an iterator over the children of the given node with the given type.
+ *
+ * @param ast the node
+ * @param type the type
+ * @return the iterator
+ */
+ public static Iterable childrenIterator(final DetailAST ast, final int type) {
+ return () -> new Iterator<>() {
+ private @Nullable DetailAST current = TokenUtil.findFirstTokenByPredicate(ast, child -> child.getType() == type).orElse(null);
+
+ @Override
+ public boolean hasNext() {
+ return this.current != null;
+ }
+
+ @Override
+ public DetailAST next() {
+ if (this.current == null) {
+ throw new NoSuchElementException();
+ }
+ final DetailAST result = this.current;
+ this.current = getNextSibling(this.current, type);
+ return result;
+ }
+ };
+ }
+
+ public static @Nullable DetailAST findPackageInfoFor(final Path filePath) {
+ final Path packageInfo = filePath.getParent().resolve("package-info.java");
+ if (Files.notExists(packageInfo)) {
+ return null;
+ }
+ final DetailAST packageInfoAst;
+ try {
+ packageInfoAst = JavaParser.parseFile(packageInfo.toFile(), JavaParser.Options.WITHOUT_COMMENTS);
+ } catch (final IOException | CheckstyleException e) {
+ throw new RuntimeException(e);
+ }
+ return packageInfoAst;
+ }
+
+ public static boolean isPackageInfoAnnotated(final Path filePath, final Predicate super DetailAST> annotationPredicate) {
+ final DetailAST packageInfoAst = Util.findPackageInfoFor(filePath);
+ if (packageInfoAst == null) {
+ return false;
+ }
+ final DetailAST firstToken = packageInfoAst.findFirstToken(TokenTypes.PACKAGE_DEF);
+ if (firstToken == null) {
+ return false;
+ }
+ final DetailAST annotations = firstToken.findFirstToken(TokenTypes.ANNOTATIONS);
+ if (annotations == null) {
+ return false;
+ }
+ for (final DetailAST annotation : Util.childrenIterator(annotations, TokenTypes.ANNOTATION)) {
+ if (annotationPredicate.test(annotation)) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ public static Pair extractFullIdent(final DetailAST lastDot) {
+ final List parts = new ArrayList<>();
+ DetailAST dot = lastDot;
+ while (dot.getChildCount(TokenTypes.DOT) > 0) {
+ parts.addFirst(requireNonNull(dot.findFirstToken(TokenTypes.IDENT)).getText());
+ dot = requireNonNull(dot.findFirstToken(TokenTypes.DOT));
+ }
+ if (dot.getChildCount(TokenTypes.IDENT) > 2) {
+ throw new IllegalArgumentException("Invalid AST structure, expected <= 2 IDENTs " + dot.getChildCount(TokenTypes.IDENT) + " " + dot.getLineNo() + " " + dot.getColumnNo());
+ }
+ final DetailAST ident = requireNonNull(dot.findFirstToken(TokenTypes.IDENT));
+ final DetailAST secondIdent = getNextSibling(ident, TokenTypes.IDENT);
+ if (secondIdent != null) {
+ parts.addFirst(secondIdent.getText());
+ }
+ parts.addFirst(ident.getText());
+
+ return Pair.of(String.join(".", parts), ident);
+ }
+}
diff --git a/paper-checkstyle/src/main/java/io/papermc/checkstyle/checks/JavadocAlignParameterDescriptionCheck.java b/paper-checkstyle/src/main/java/io/papermc/checkstyle/checks/JavadocAlignParameterDescriptionCheck.java
new file mode 100644
index 0000000000..ef47c063da
--- /dev/null
+++ b/paper-checkstyle/src/main/java/io/papermc/checkstyle/checks/JavadocAlignParameterDescriptionCheck.java
@@ -0,0 +1,69 @@
+package io.papermc.checkstyle.checks;
+
+import com.puppycrawl.tools.checkstyle.api.DetailNode;
+import com.puppycrawl.tools.checkstyle.api.JavadocCommentsTokenTypes;
+import com.puppycrawl.tools.checkstyle.checks.javadoc.AbstractJavadocCheck;
+import com.puppycrawl.tools.checkstyle.utils.JavadocUtil;
+import io.papermc.checkstyle.Util;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+/**
+ * Checks that parameter descriptions in Javadoc are aligned.
+ */
+public final class JavadocAlignParameterDescriptionCheck extends AbstractJavadocCheck {
+
+ static final Pattern SPACE_PREFIX = Pattern.compile("^\\s*");
+
+ @Override
+ public int[] getDefaultJavadocTokens() {
+ return new int[]{JavadocCommentsTokenTypes.JAVADOC_CONTENT};
+ }
+
+ @Override
+ public void visitJavadocToken(final DetailNode detailNode) {
+ record ParamDesc(DetailNode node, int startCol) {
+ }
+
+ final List params = new ArrayList<>();
+ int maxColumn = -1;
+ DetailNode child = detailNode.getFirstChild();
+ for (final DetailNode javadocBlock : JavadocUtil.getAllNodesOfType(detailNode, JavadocCommentsTokenTypes.JAVADOC_BLOCK_TAG)) {
+ final DetailNode paramBlock = JavadocUtil.findFirstToken(javadocBlock, JavadocCommentsTokenTypes.PARAM_BLOCK_TAG);
+ if (paramBlock == null) {
+ continue;
+ }
+ final DetailNode paramDescription = JavadocUtil.findFirstToken(paramBlock, JavadocCommentsTokenTypes.DESCRIPTION);
+ if (paramDescription == null) {
+ continue;
+ }
+ // iterate over all text nodes (multiline)
+ for (final DetailNode textNode : JavadocUtil.getAllNodesOfType(paramDescription, JavadocCommentsTokenTypes.TEXT)) {
+ final Matcher matcher = SPACE_PREFIX.matcher(textNode.getText());
+ int paramDescColNum = textNode.getColumnNumber();
+ if (matcher.find()) {
+ paramDescColNum += matcher.group().length();
+ }
+ maxColumn = Math.max(maxColumn, paramDescColNum);
+ params.add(new ParamDesc(textNode, paramDescColNum));
+ }
+ child = child.getNextSibling();
+ }
+
+ for (final ParamDesc param : params) {
+ if (param.startCol() != maxColumn) {
+ final DetailNode paramNameNode = Util.getPreviousSibling(param.node().getParent(), JavadocCommentsTokenTypes.PARAMETER_NAME);
+ if (paramNameNode == null) {
+ continue;
+ }
+ this.log(
+ param.node().getLineNumber(),
+ param.startCol(),
+ "Param description for %s should start at column %d".formatted(paramNameNode.getText(), maxColumn + 1)
+ );
+ }
+ }
+ }
+}
diff --git a/paper-checkstyle/src/main/java/io/papermc/checkstyle/checks/NullabilityAnnotationsCheck.java b/paper-checkstyle/src/main/java/io/papermc/checkstyle/checks/NullabilityAnnotationsCheck.java
new file mode 100644
index 0000000000..b32a288b72
--- /dev/null
+++ b/paper-checkstyle/src/main/java/io/papermc/checkstyle/checks/NullabilityAnnotationsCheck.java
@@ -0,0 +1,120 @@
+package io.papermc.checkstyle.checks;
+
+import com.puppycrawl.tools.checkstyle.api.AbstractCheck;
+import com.puppycrawl.tools.checkstyle.api.DetailAST;
+import com.puppycrawl.tools.checkstyle.api.TokenTypes;
+import com.puppycrawl.tools.checkstyle.utils.TokenUtil;
+import io.papermc.checkstyle.Util;
+import java.nio.file.Path;
+import java.util.Set;
+import org.jspecify.annotations.Nullable;
+
+/**
+ * Checks that nullability annotations are present where required.
+ */
+public final class NullabilityAnnotationsCheck extends AbstractCheck {
+
+ private static final Set NULLABILITY_ANNOTATIONS = Set.of("Nullable", "NonNull");
+
+ @Override
+ public int[] getDefaultTokens() {
+ return this.getRequiredTokens();
+ }
+
+ @Override
+ public int[] getAcceptableTokens() {
+ return this.getRequiredTokens();
+ }
+
+ @Override
+ public int[] getRequiredTokens() {
+ return new int[]{
+ TokenTypes.METHOD_DEF,
+ TokenTypes.PARAMETER_DEF,
+ TokenTypes.ANNOTATION_FIELD_DEF,
+ TokenTypes.RECORD_COMPONENT_DEF, // annotations are in ANNOTATIONS token block
+ };
+ }
+
+ private static boolean hasNoNullabilityAnnotationChildren(final @Nullable DetailAST ast) {
+ if (ast == null) {
+ return true;
+ }
+ for (final DetailAST annotation : Util.childrenIterator(ast, TokenTypes.ANNOTATION)) {
+ if (annotation.getChildCount(TokenTypes.IDENT) != 1) {
+ // skip `.` annotations like ApiStatus.Internal as these aren't nullability annotations
+ continue;
+ }
+ final String ident = annotation.findFirstToken(TokenTypes.IDENT).getText();
+ if (NULLABILITY_ANNOTATIONS.contains(ident)) {
+ return false;
+ }
+ }
+ return true;
+ }
+
+ private void visitMethodDefOrParamDef(final DetailAST holderDef, final int baseAnnotationHolderType) {
+ final DetailAST type = holderDef.findFirstToken(TokenTypes.TYPE);
+ final DetailAST arrayTypeStart = type.findFirstToken(TokenTypes.ARRAY_DECLARATOR);
+ if (arrayTypeStart != null) {
+ final DetailAST arrayAnnotations = type.findFirstToken(TokenTypes.ANNOTATIONS);
+ if (hasNoNullabilityAnnotationChildren(arrayAnnotations)) {
+ this.log(arrayTypeStart.getLineNo(), arrayTypeStart.getColumnNo() - 1, "Array is missing nullability annotation");
+ }
+ }
+ if (TokenUtil.findFirstTokenByPredicate(type, t -> Util.PRIMITIVE_TYPES.contains(t.getType())).isPresent()) {
+ // skip primitive types
+ return;
+ }
+ final DetailAST dot = type.findFirstToken(TokenTypes.DOT);
+ final DetailAST annotationHolder;
+ final DetailAST identLoc;
+ if (dot != null) {
+ annotationHolder = dot.findFirstToken(TokenTypes.ANNOTATIONS);
+ identLoc = Util.getLastChild(dot, TokenTypes.IDENT);
+ } else {
+ annotationHolder = holderDef.findFirstToken(baseAnnotationHolderType);
+ identLoc = type;
+ }
+ if (hasNoNullabilityAnnotationChildren(annotationHolder)) {
+ this.log(identLoc.getLineNo(), identLoc.getColumnNo(), "Missing nullability annotation for '" + holderDef.findFirstToken(TokenTypes.IDENT).getText() + "'");
+ }
+ }
+
+ public static boolean isNullMarkedAnnotation(final DetailAST annotation) {
+ if (annotation.getChildCount(TokenTypes.IDENT) != 1) {
+ return false;
+ }
+ final String ident = annotation.findFirstToken(TokenTypes.IDENT).getText();
+ return "NullMarked".equals(ident);
+ }
+
+ public static @Nullable DetailAST getNullMarkedAnnotation(final DetailAST typeDeclaration) {
+ final DetailAST modifiers = typeDeclaration.findFirstToken(TokenTypes.MODIFIERS);
+ if (modifiers == null) {
+ return null;
+ }
+ for (final DetailAST annotation : Util.childrenIterator(modifiers, TokenTypes.ANNOTATION)) {
+ if (isNullMarkedAnnotation(annotation)) {
+ return annotation;
+ }
+ }
+ return null;
+ }
+
+ @Override
+ public void visitToken(final DetailAST ast) {
+ if (Util.isPackageInfoAnnotated(Path.of(this.getFilePath()), NullabilityAnnotationsCheck::isNullMarkedAnnotation)) {
+ return;
+ }
+ for (DetailAST parentDef = Util.getEnclosingTypeDeclaration(ast); parentDef != null; parentDef = Util.getEnclosingTypeDeclaration(parentDef)) {
+ if (getNullMarkedAnnotation(parentDef) != null) {
+ return;
+ }
+ }
+ switch (ast.getType()) {
+ case TokenTypes.METHOD_DEF, TokenTypes.PARAMETER_DEF, TokenTypes.ANNOTATION_FIELD_DEF -> this.visitMethodDefOrParamDef(ast, TokenTypes.MODIFIERS);
+ case TokenTypes.RECORD_COMPONENT_DEF -> this.visitMethodDefOrParamDef(ast, TokenTypes.ANNOTATIONS);
+ }
+ }
+}
diff --git a/paper-checkstyle/src/main/java/io/papermc/checkstyle/checks/RedundantNullabilityCheck.java b/paper-checkstyle/src/main/java/io/papermc/checkstyle/checks/RedundantNullabilityCheck.java
new file mode 100644
index 0000000000..1ed6c69551
--- /dev/null
+++ b/paper-checkstyle/src/main/java/io/papermc/checkstyle/checks/RedundantNullabilityCheck.java
@@ -0,0 +1,40 @@
+package io.papermc.checkstyle.checks;
+
+import com.puppycrawl.tools.checkstyle.api.AbstractCheck;
+import com.puppycrawl.tools.checkstyle.api.DetailAST;
+import com.puppycrawl.tools.checkstyle.api.TokenTypes;
+import io.papermc.checkstyle.Util;
+import java.nio.file.Path;
+
+public class RedundantNullabilityCheck extends AbstractCheck {
+
+ @Override
+ public int[] getDefaultTokens() {
+ return this.getRequiredTokens();
+ }
+
+ @Override
+ public int[] getAcceptableTokens() {
+ return this.getRequiredTokens();
+ }
+
+ @Override
+ public int[] getRequiredTokens() {
+ return new int[]{
+ TokenTypes.CLASS_DEF,
+ TokenTypes.INTERFACE_DEF,
+ TokenTypes.ANNOTATION_DEF,
+ TokenTypes.RECORD_DEF,
+ TokenTypes.ENUM_DEF,
+ };
+ }
+
+ @Override
+ public void visitToken(final DetailAST ast) {
+ final boolean pkgIsNullMarked = Util.isPackageInfoAnnotated(Path.of(this.getFilePath()), NullabilityAnnotationsCheck::isNullMarkedAnnotation);
+ final DetailAST nullMarkedAnnotation = NullabilityAnnotationsCheck.getNullMarkedAnnotation(ast);
+ if (pkgIsNullMarked && nullMarkedAnnotation != null) {
+ this.log(nullMarkedAnnotation.getLineNo(), ast.getColumnNo() - 1, "Redundant NullMarked annotation");
+ }
+ }
+}
diff --git a/paper-checkstyle/src/main/java/io/papermc/checkstyle/checks/UnnecessaryFullyQualifiedImportCheck.java b/paper-checkstyle/src/main/java/io/papermc/checkstyle/checks/UnnecessaryFullyQualifiedImportCheck.java
new file mode 100644
index 0000000000..a3037f3ba3
--- /dev/null
+++ b/paper-checkstyle/src/main/java/io/papermc/checkstyle/checks/UnnecessaryFullyQualifiedImportCheck.java
@@ -0,0 +1,112 @@
+package io.papermc.checkstyle.checks;
+
+import com.puppycrawl.tools.checkstyle.api.DetailAST;
+import com.puppycrawl.tools.checkstyle.api.TokenTypes;
+import io.papermc.checkstyle.Util;
+import io.papermc.checkstyle.checks.util.ImportAwareAbstractCheck;
+import org.apache.commons.lang3.tuple.Pair;
+import org.jspecify.annotations.Nullable;
+
+/**
+ * Requires that the codebase be set up to follow standard naming conventions.
+ *
+ * - packages are always all lowercase
+ * - class names start with a capital letter
+ *
+ */
+public class UnnecessaryFullyQualifiedImportCheck extends ImportAwareAbstractCheck {
+
+ @Override
+ public int[] getDefaultTokens() {
+ return this.getRequiredTokens();
+ }
+
+ @Override
+ public int[] getAcceptableTokens() {
+ return this.getRequiredTokens();
+ }
+
+ @Override
+ public int[] getRequiredTokens() {
+ return new int[]{
+ TokenTypes.TYPE,
+ TokenTypes.TYPE_ARGUMENT,
+ TokenTypes.TYPE_PARAMETER,
+ TokenTypes.TYPE_UPPER_BOUNDS,
+ TokenTypes.TYPE_LOWER_BOUNDS,
+ TokenTypes.EXTENDS_CLAUSE,
+ TokenTypes.PERMITS_CLAUSE,
+ TokenTypes.IMPLEMENTS_CLAUSE,
+ TokenTypes.LITERAL_NEW,
+ TokenTypes.ANNOTATION,
+ TokenTypes.METHOD_CALL,
+ TokenTypes.LITERAL_CLASS,
+ };
+ }
+
+ private static @Nullable DetailAST fromMethodCall(final DetailAST methodCall) {
+ final DetailAST dot = methodCall.findFirstToken(TokenTypes.DOT);
+ if (dot == null) {
+ return null;
+ }
+ final DetailAST nextDot = dot.findFirstToken(TokenTypes.DOT);
+ if (nextDot == null) {
+ return null;
+ }
+ final DetailAST ident = nextDot.findFirstToken(TokenTypes.IDENT);
+ if (ident == null || Character.isLowerCase(ident.getText().charAt(0))) {
+ // method call not on a type
+ return null;
+ }
+ return dot;
+ }
+
+ private static @Nullable DetailAST fromLiteralClass(final DetailAST literalClass) {
+ final DetailAST dot = literalClass.getParent();
+ if (dot.getType() != TokenTypes.DOT) {
+ return null;
+ }
+ return dot;
+ }
+
+ @Override
+ public void visitToken(final DetailAST ast) {
+ final DetailAST actualAst = switch (ast.getType()) {
+ case TokenTypes.METHOD_CALL -> fromMethodCall(ast);
+ case TokenTypes.LITERAL_CLASS -> fromLiteralClass(ast);
+ default -> ast;
+ };
+ if (actualAst == null) {
+ return;
+ }
+ final DetailAST dot = actualAst.findFirstToken(TokenTypes.DOT);
+ if (dot == null) {
+ return;
+ }
+ final Pair fullIdent = Util.extractFullIdent(dot);
+ final DetailAST startNode = fullIdent.getRight();
+ final String fullIdentName = fullIdent.getLeft();
+ if (Character.isUpperCase(fullIdentName.charAt(0))) {
+ // not a package
+ return;
+ }
+ String previousTypeName;
+ String typeName = fullIdentName;
+ int lastDotIdx = typeName.lastIndexOf('.');
+ do {
+ // Check if we have an import for this exact type
+ if (this.hasImportFor(typeName)) {
+ this.log(startNode.getLineNo(), startNode.getColumnNo(), "Fully qualified import used: " + fullIdentName);
+ return;
+ }
+ previousTypeName = typeName;
+ typeName = typeName.substring(0, lastDotIdx);
+ } while ((lastDotIdx = typeName.lastIndexOf('.')) != -1 && Character.isUpperCase(typeName.substring(0, lastDotIdx + 2).charAt(lastDotIdx + 1)));
+
+ final String outerSimpleName = previousTypeName.substring(previousTypeName.lastIndexOf('.') + 1);
+ final String fullImportBySimple = this.getFullImportBySimple(outerSimpleName);
+ if (fullImportBySimple == null) {
+ this.log(startNode.getLineNo(), startNode.getColumnNo(), "Unnecessary fully qualified import (no conflict): " + fullIdentName);
+ }
+ }
+}
diff --git a/paper-checkstyle/src/main/java/io/papermc/checkstyle/checks/package-info.java b/paper-checkstyle/src/main/java/io/papermc/checkstyle/checks/package-info.java
new file mode 100644
index 0000000000..f33ebfddfa
--- /dev/null
+++ b/paper-checkstyle/src/main/java/io/papermc/checkstyle/checks/package-info.java
@@ -0,0 +1,8 @@
+/**
+ * Custom checkstyle checks for PaperMC projects.
+ */
+@NullMarked
+@SuppressWarnings("unused")
+package io.papermc.checkstyle.checks;
+
+import org.jspecify.annotations.NullMarked;
diff --git a/paper-checkstyle/src/main/java/io/papermc/checkstyle/checks/util/ImportAwareAbstractCheck.java b/paper-checkstyle/src/main/java/io/papermc/checkstyle/checks/util/ImportAwareAbstractCheck.java
new file mode 100644
index 0000000000..f0b1494229
--- /dev/null
+++ b/paper-checkstyle/src/main/java/io/papermc/checkstyle/checks/util/ImportAwareAbstractCheck.java
@@ -0,0 +1,55 @@
+package io.papermc.checkstyle.checks.util;
+
+import com.puppycrawl.tools.checkstyle.api.AbstractCheck;
+import com.puppycrawl.tools.checkstyle.api.DetailAST;
+import com.puppycrawl.tools.checkstyle.api.FullIdent;
+import com.puppycrawl.tools.checkstyle.api.TokenTypes;
+import io.papermc.checkstyle.Util;
+import java.util.LinkedHashMap;
+import java.util.LinkedHashSet;
+import java.util.Map;
+import java.util.Objects;
+import java.util.Set;
+import org.jspecify.annotations.Nullable;
+
+public abstract class ImportAwareAbstractCheck extends AbstractCheck {
+
+ private final ThreadLocal> imports = ThreadLocal.withInitial(LinkedHashSet::new);
+ private final ThreadLocal