1
0
mirror of https://github.com/SquidDev-CC/CC-Tweaked synced 2025-09-06 12:27:56 +00:00

Switch to JSpecify annotations

Now, hear me out, what if instead of having three @Nullable annotations,
we had *four*?

I've been wanting to switch away from javax.annoations for a while. The
library has been deprecated for ever and, unlike other @Nullable
annotations, the annotation is attached to the parameter/function
itself, rather than the type.

We use JSpecify rather than one of the alternatives (JetBrains,
CheckerFramework) mostly because it's what NullAway recommends. We keep
CheckerFramework around for @DefaultQualifier, and JB's for @Contract.

There are some ugly changes here — for instance, `@Nullable byte[]` is
replace by `byte @Nullable`, and `@Nullable ILuaMachine.Factory` is
`ILuaMachine.@Nullable Factory`. Ughr, I understand why, but it does not
spark joy :).
This commit is contained in:
Jonathan Coates
2025-02-16 18:09:15 +00:00
parent 12a44fed6f
commit 0998acaa82
326 changed files with 542 additions and 540 deletions

View File

@@ -0,0 +1,56 @@
// SPDX-FileCopyrightText: 2025 The CC: Tweaked Developers
//
// SPDX-License-Identifier: MPL-2.0
@file:Suppress("JAVA_MODULE_DOES_NOT_EXPORT_PACKAGE")
package cc.tweaked.linter
import com.google.errorprone.BugPattern
import com.google.errorprone.VisitorState
import com.google.errorprone.bugpatterns.BugChecker
import com.google.errorprone.fixes.SuggestedFix
import com.google.errorprone.matchers.Description
import com.google.errorprone.util.ASTHelpers
import com.sun.source.tree.ImportTree
import com.sun.source.tree.Tree
@BugPattern(
summary = "Checks for forbidden imports.",
severity = BugPattern.SeverityLevel.ERROR,
tags = [BugPattern.StandardTags.LIKELY_ERROR],
)
class ForbiddenImport : BugChecker(), BugChecker.ImportTreeMatcher {
override fun matchImport(tree: ImportTree, state: VisitorState): Description {
if (tree.isStatic || tree.qualifiedIdentifier.kind != Tree.Kind.MEMBER_SELECT) return Description.NO_MATCH
val sym = ASTHelpers.getSymbol(tree.qualifiedIdentifier) ?: return Description.NO_MATCH
val importedName = sym.qualifiedName.toString()
if (!IMPORTS.contains(importedName)) return Description.NO_MATCH
val message = buildDescription(tree.qualifiedIdentifier).setMessage("Cannot import this symbol")
val replacement = ALTERNATIVE_IMPORTS[importedName]
if (replacement != null) message.addFix(SuggestedFix.replace(tree.qualifiedIdentifier, replacement))
return message.build()
}
companion object {
private val ALTERNATIVE_IMPORTS = mapOf(
// Ban JSR 305 and JetBrains @Nullable, and prefer the JSpecify one.
"org.javax.annotation.Nullable" to "org.jspecify.annotations.Nullable",
"org.jetbrains.annotations.Nullable" to "org.jspecify.annotations.Nullable",
// Prefer ErrorProne annotations over JSR ones.
"javax.annotation.CheckReturnValue" to "com.google.errorprone.annotations.CheckReturnValue",
"javax.annotation.OverridingMethodsMustInvokeSuper" to "com.google.errorprone.annotations.OverridingMethodsMustInvokeSuper",
"javax.annotation.concurrent.GuardedBy" to "com.google.errorprone.annotations.concurrent.GuardedBy",
)
private val IMPORTS: Set<String> = setOf(
// We ban all @Nonnull annotations, because that should be default already.
"javax.annotation.Nonnull",
"org.jetbrains.annotations.NotNull",
"org.jspecify.annotations.NonNull",
) + ALTERNATIVE_IMPORTS.keys
}
}

View File

@@ -2,6 +2,7 @@
#
# SPDX-License-Identifier: MPL-2.0
cc.tweaked.linter.ExtraMustCallSuper
cc.tweaked.linter.ForbiddenImport
cc.tweaked.linter.LoaderOverride
cc.tweaked.linter.MissingLoaderOverride
cc.tweaked.linter.SideChecker

View File

@@ -0,0 +1,39 @@
// SPDX-FileCopyrightText: 2022 The CC: Tweaked Developers
//
// SPDX-License-Identifier: MPL-2.0
package cc.tweaked.linter;
import com.google.common.base.Predicates;
import com.google.errorprone.CompilationTestHelper;
import org.junit.jupiter.api.Test;
public class TestForbiddenImport {
private final CompilationTestHelper compilationHelper = CompilationTestHelper.newInstance(ForbiddenImport.class, getClass());
@Test
public void testForbiddenImport() {
compilationHelper
.addSourceLines("Import.java", """
// BUG: Diagnostic matches: X
import org.jspecify.annotations.NonNull;
class X {
}
""")
.expectErrorMessage("X", Predicates.containsPattern("Cannot import this symbol"))
.doTest();
}
@Test
public void testForbiddenImportSuggestion() {
compilationHelper
.addSourceLines("Import.java", """
// BUG: Diagnostic matches: X
import javax.annotation.concurrent.GuardedBy;
class X {
}
""")
.expectErrorMessage("X", Predicates.containsPattern("Did you mean 'import com.google.errorprone.annotations.concurrent.GuardedBy;'"))
.doTest();
}
}