mirror of
https://github.com/PaperMC/Paper.git
synced 2025-08-01 04:32:11 -07:00
Checkstyle for API
This commit is contained in:
206
paper-checkstyle/.checkstyle/checkstyle.xml
Normal file
206
paper-checkstyle/.checkstyle/checkstyle.xml
Normal file
@@ -0,0 +1,206 @@
|
||||
<!DOCTYPE module PUBLIC
|
||||
"-//Checkstyle//DTD Checkstyle Configuration 1.3//EN"
|
||||
"https://checkstyle.org/dtds/configuration_1_3.dtd">
|
||||
<module name="Checker">
|
||||
<module name="SuppressionFilter">
|
||||
<property name="file" value="${config_loc}/suppressions.xml"/>
|
||||
<property name="optional" value="false"/>
|
||||
</module>
|
||||
|
||||
<!--Single Suppression Filters-->
|
||||
<module name="SuppressionSingleFilter">
|
||||
<!--Suppresses warnings except for common TYPE_USE that should be on same line-->
|
||||
<property name="checks" value="AnnotationOnSameLine"/>
|
||||
<property name="message" value="^Annotation '(?!(${type_use_annotations}))"/>
|
||||
</module>
|
||||
|
||||
<!--Javadoc Comments-->
|
||||
<module name="JavadocPackage"/>
|
||||
<!--Misc-->
|
||||
<module name="NewlineAtEndOfFile"/>
|
||||
<module name="OrderedProperties"/>
|
||||
<!--Whitespace-->
|
||||
<module name="FileTabCharacter"/>
|
||||
|
||||
<module name="TreeWalker">
|
||||
<module name="SuppressionXpathFilter">
|
||||
<property name="file" value="${config_loc}/xpath-suppressions.xml"/>
|
||||
<property name="optional" value="false"/>
|
||||
</module>
|
||||
|
||||
<!--Annotations-->
|
||||
<module name="AnnotationOnSameLine">
|
||||
<!--matches all annotations, but most are suppressed in above-->
|
||||
<property name="tokens" value="METHOD_DEF"/>
|
||||
</module>
|
||||
<module name="AnnotationUseStyle"/>
|
||||
<module name="MissingDeprecated"/>
|
||||
|
||||
<!--Block Checks-->
|
||||
<module name="AvoidNestedBlocks"/>
|
||||
<module name="EmptyBlock"/>
|
||||
<module name="LeftCurly"/>
|
||||
<module name="RightCurly"/>
|
||||
<module name="OneTopLevelClass"/>
|
||||
<module name="SealedShouldHavePermitsList"/>
|
||||
|
||||
<!--Class Design-->
|
||||
<module name="FinalClass"/>
|
||||
<module name="HideUtilityClassConstructor"/>
|
||||
<module name="InterfaceIsType"/>
|
||||
|
||||
<!--Coding-->
|
||||
<module name="ArrayTrailingComma"/>
|
||||
<module name="AvoidDoubleBraceInitialization"/>
|
||||
<module name="AvoidNoArgumentSuperConstructorCall"/>
|
||||
<module name="ConstructorsDeclarationGrouping"/>
|
||||
<module name="CovariantEquals"/>
|
||||
<module name="DeclarationOrder"/>
|
||||
<module name="DefaultComesLast"/>
|
||||
<module name="EmptyStatement"/>
|
||||
<module name="EqualsAvoidNull"/>
|
||||
<module name="EqualsHashCode"/>
|
||||
<module name="FallThrough"/>
|
||||
<module name="FinalLocalVariable">
|
||||
<property name="validateEnhancedForLoopVariable" value="true"/>
|
||||
<property name="validateUnnamedVariables" value="true"/>
|
||||
<property name="tokens" value="PARAMETER_DEF,VARIABLE_DEF"/>
|
||||
</module>
|
||||
<module name="IllegalToken"/> <!--just labels by default-->
|
||||
<module name="IllegalType"/>
|
||||
<module name="PatternVariableAssignment"/>
|
||||
<module name="RequireThis">
|
||||
<property name="validateOnlyOverlapping" value="false"/>
|
||||
</module>
|
||||
<module name="SimplifyBooleanExpression"/>
|
||||
<module name="SimplifyBooleanReturn"/>
|
||||
<module name="StringLiteralEquality"/>
|
||||
<module name="UnnecessaryNullCheckWithInstanceOf"/>
|
||||
<module name="UnnecessarySemicolonAfterOuterTypeDeclaration"/>
|
||||
<module name="UnnecessarySemicolonAfterTypeMemberDeclaration"/>
|
||||
<module name="UnnecessarySemicolonInEnumeration"/>
|
||||
<module name="UnnecessarySemicolonInTryWithResources"/>
|
||||
<module name="UnusedCatchParameterShouldBeUnnamed"/>
|
||||
<module name="UnusedLambdaParameterShouldBeUnnamed"/>
|
||||
<module name="UnusedLocalVariable"/>
|
||||
<module name="WhenShouldBeUsed"/>
|
||||
|
||||
<!--Headers--> <!--N/A-->
|
||||
|
||||
<!--Imports-->
|
||||
<module name="AvoidStarImport"/>
|
||||
<module name="CustomImportOrder">
|
||||
<property name="customImportOrderRules" value="THIRD_PARTY_PACKAGE,STATIC"/>
|
||||
<property name="standardPackageRegExp" value="^$"/>
|
||||
<property name="sortImportsInGroupAlphabetically" value="true"/>
|
||||
</module>
|
||||
<module name="IllegalImport">
|
||||
<property name="regexp" value="true"/>
|
||||
<!--checker-qual nullability-->
|
||||
<property name="illegalClasses" value="org\.checkerframework\.checker\.nullness\.qual\.(Nullable|NonNull|DefaultQualifier)"/>
|
||||
<!--jetbrains nullability-->
|
||||
<property name="illegalClasses" value="org\.jetbrains\.annotations\.(NotNull|Nullable|NotNullByDefault)"/>
|
||||
<!--javax nullability-->
|
||||
</module>
|
||||
<module name="IllegalImport">
|
||||
<property name="regexp" value="true"/>
|
||||
<!--attempts to guard against nested imports (by looking for capital letters in imports)-->
|
||||
<property name="illegalClasses" value="^[^\.A-Z]+(\.[^\.A-Z]+)+?(\.[A-Z][^\.]*)(\.[A-Z][^\.]*)+$"/>
|
||||
<message key="import.illegal" value="Illegal nested import - {0}"/>
|
||||
</module>
|
||||
<module name="RedundantImport"/>
|
||||
<module name="UnusedImports"/>
|
||||
|
||||
<!--Javadoc Comments-->
|
||||
<module name="AtclauseOrder">
|
||||
<property name="tagOrder" value="@param, @return, @throws, @see, @deprecated, @hidden"/>
|
||||
</module>
|
||||
<module name="InvalidJavadocPosition"/>
|
||||
<module name="JavadocBlockTagLocation"/>
|
||||
<module name="JavadocContentLocation"/>
|
||||
<module name="JavadocLeadingAsteriskAlign"/>
|
||||
<module name="JavadocMethod">
|
||||
<!--checks all, but doesn't require. If we have a doc, it should be valid-->
|
||||
<property name="validateThrows" value="true"/>
|
||||
</module>
|
||||
<module name="JavadocMissingLeadingAsterisk"/>
|
||||
<module name="JavadocMissingWhitespaceAfterAsterisk"/>
|
||||
<module name="JavadocStyle"/> <!--checks all, but doesn't require. If we have a doc, it should be valid-->
|
||||
<module name="JavadocTagContinuationIndentation"/>
|
||||
<module name="JavadocType"/> <!--checks all, but doesn't require. If we have a doc, it should be valid-->
|
||||
<module name="NonEmptyAtclauseDescription"/>
|
||||
<module name="RequireEmptyLineBeforeBlockTagGroup"/>
|
||||
|
||||
<!--Metrics--> <!--N/A-->
|
||||
|
||||
<!--Miscellaneous-->
|
||||
<module name="ArrayTypeStyle"/>
|
||||
<module name="AvoidEscapedUnicodeCharacters"/>
|
||||
<module name="CommentsIndentation"/>
|
||||
<module name="FinalParameters">
|
||||
<!-- TODO pattern variable assignment isn't checked yet: PATTERN_VARIABLE_DEF-->
|
||||
<!-- https://github.com/checkstyle/checkstyle/issues/17366 -->
|
||||
<property name="tokens" value="METHOD_DEF,CTOR_DEF,LITERAL_CATCH,FOR_EACH_CLAUSE"/>
|
||||
</module>
|
||||
|
||||
<module name="NoCodeInFile"/>
|
||||
<module name="OuterTypeFilename"/>
|
||||
<module name="UpperEll"/>
|
||||
|
||||
<!--Modifiers-->
|
||||
<module name="ModifierOrder"/>
|
||||
<module name="RedundantModifier"/>
|
||||
|
||||
<!--Naming Conventions-->
|
||||
<module name="AbbreviationAsWordInName">
|
||||
<property name="allowedAbbreviations" value="JSON,UUID"/>
|
||||
<property name="ignoreFinal" value="false"/>
|
||||
</module>
|
||||
<module name="ClassTypeParameterName"/>
|
||||
<module name="ConstantName"/>
|
||||
<module name="IllegalIdentifierName"/>
|
||||
<module name="InterfaceTypeParameterName"/>
|
||||
<module name="LambdaParameterName"/>
|
||||
<module name="LocalFinalVariableName"/>
|
||||
<module name="LocalVariableName"/>
|
||||
<module name="MemberName"/>
|
||||
<module name="MethodName"/>
|
||||
<module name="MethodTypeParameterName"/>
|
||||
<module name="PackageName">
|
||||
<property name="format" value="^[a-z]+(\.[a-z][a-z0-9]*)*$"/>
|
||||
</module>
|
||||
<module name="ParameterName"/>
|
||||
<module name="PatternVariableName"/>
|
||||
<module name="RecordComponentName"/>
|
||||
<module name="RecordTypeParameterName"/>
|
||||
<module name="StaticVariableName"/>
|
||||
<module name="TypeName"/>
|
||||
|
||||
<!--Regexp--> <!--N/A-->
|
||||
<!--Size Violations--> <!--N/A-->
|
||||
|
||||
<!--Whitespace-->
|
||||
<module name="EmptyForInitializerPad"/>
|
||||
<module name="EmptyForIteratorPad"/>
|
||||
<module name="EmptyLineSeparator">
|
||||
<property name="allowNoEmptyLineBetweenFields" value="true"/>
|
||||
<property name="tokens" value="IMPORT,STATIC_IMPORT,CLASS_DEF,INTERFACE_DEF,ENUM_DEF,STATIC_INIT,INSTANCE_INIT,METHOD_DEF,CTOR_DEF,VARIABLE_DEF,RECORD_DEF,COMPACT_CTOR_DEF"/>
|
||||
</module>
|
||||
<module name="GenericWhitespace"/>
|
||||
<module name="MethodParamPad"/>
|
||||
<module name="NoLineWrap"/> <!--just imports and packages-->
|
||||
<module name="NoWhitespaceAfter"/>
|
||||
<module name="NoWhitespaceBefore"/>
|
||||
<module name="NoWhitespaceBeforeCaseDefaultColon"/>
|
||||
<module name="ParenPad"/>
|
||||
<module name="SingleSpaceSeparator"/>
|
||||
<module name="TypecastParenPad"/>
|
||||
<module name="WhitespaceAfter"/>
|
||||
<module name="WhitespaceAround"/>
|
||||
|
||||
<!--Custom-->
|
||||
<module name="JavadocAlignParameterDescription"/>
|
||||
<module name="NullabilityAnnotations"/>
|
||||
<module name="RedundantNullability"/>
|
||||
</module>
|
||||
</module>
|
8
paper-checkstyle/build.gradle.kts
Normal file
8
paper-checkstyle/build.gradle.kts
Normal file
@@ -0,0 +1,8 @@
|
||||
plugins {
|
||||
java
|
||||
}
|
||||
|
||||
dependencies {
|
||||
implementation("com.puppycrawl.tools:checkstyle:10.26.1")
|
||||
implementation("org.jspecify:jspecify:1.0.0")
|
||||
}
|
129
paper-checkstyle/src/main/java/io/papermc/checkstyle/Util.java
Normal file
129
paper-checkstyle/src/main/java/io/papermc/checkstyle/Util.java
Normal file
@@ -0,0 +1,129 @@
|
||||
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.JavadocUtil;
|
||||
import com.puppycrawl.tools.checkstyle.utils.TokenUtil;
|
||||
import java.io.IOException;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.util.Iterator;
|
||||
import java.util.NoSuchElementException;
|
||||
import java.util.function.Predicate;
|
||||
import org.jspecify.annotations.Nullable;
|
||||
|
||||
/**
|
||||
* Utility class containing utility methods for custom checkstyle checks.
|
||||
*/
|
||||
public final class Util {
|
||||
|
||||
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 = JavadocUtil.getPreviousSibling(node);
|
||||
while (sibling != null && sibling.getType() != type) {
|
||||
sibling = JavadocUtil.getPreviousSibling(sibling);
|
||||
}
|
||||
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 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<DetailAST> 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<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);
|
||||
for (final DetailAST annotation : Util.childrenIterator(annotations, TokenTypes.ANNOTATION)) {
|
||||
if (annotationPredicate.test(annotation)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
@@ -0,0 +1,49 @@
|
||||
package io.papermc.checkstyle.checks;
|
||||
|
||||
import com.puppycrawl.tools.checkstyle.api.DetailNode;
|
||||
import com.puppycrawl.tools.checkstyle.api.JavadocTokenTypes;
|
||||
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;
|
||||
|
||||
/**
|
||||
* Checks that parameter descriptions in Javadoc are aligned.
|
||||
*/
|
||||
public final class JavadocAlignParameterDescriptionCheck extends AbstractJavadocCheck {
|
||||
|
||||
@Override
|
||||
public int[] getDefaultJavadocTokens() {
|
||||
return new int[]{JavadocTokenTypes.JAVADOC};
|
||||
}
|
||||
|
||||
@Override
|
||||
public void visitJavadocToken(final DetailNode detailNode) {
|
||||
final List<DetailNode> params = new ArrayList<>();
|
||||
int maxColumn = -1;
|
||||
for (final DetailNode child : detailNode.getChildren()) {
|
||||
final DetailNode paramLiteralNode = JavadocUtil.findFirstToken(child, JavadocTokenTypes.PARAM_LITERAL);
|
||||
if (child.getType() != JavadocTokenTypes.JAVADOC_TAG || paramLiteralNode == null) {
|
||||
continue;
|
||||
}
|
||||
final DetailNode paramDescription = JavadocUtil.getNextSibling(paramLiteralNode, JavadocTokenTypes.DESCRIPTION);
|
||||
maxColumn = Math.max(maxColumn, paramDescription.getColumnNumber());
|
||||
params.add(paramDescription);
|
||||
}
|
||||
|
||||
for (final DetailNode param : params) {
|
||||
if (param.getColumnNumber() != maxColumn) {
|
||||
final DetailNode paramNameNode = Util.getPreviousSibling(param, JavadocTokenTypes.PARAMETER_NAME);
|
||||
if (paramNameNode == null) {
|
||||
continue;
|
||||
}
|
||||
this.log(
|
||||
param.getLineNumber(),
|
||||
param.getColumnNumber() - 1,
|
||||
"Param description for %s should start at column %d".formatted(paramNameNode.getText(), maxColumn)
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,116 @@
|
||||
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;
|
||||
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<String> 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");
|
||||
}
|
||||
}
|
||||
final DetailAST dot = type.findFirstToken(TokenTypes.DOT);
|
||||
final DetailAST annotationHolder;
|
||||
final DetailAST identLoc;
|
||||
if (dot != null) {
|
||||
annotationHolder = dot.findFirstToken(TokenTypes.ANNOTATIONS);
|
||||
identLoc = dot.findFirstToken(TokenTypes.IDENT);
|
||||
} else {
|
||||
annotationHolder = holderDef.findFirstToken(baseAnnotationHolderType);
|
||||
identLoc = type;
|
||||
}
|
||||
if (hasNoNullabilityAnnotationChildren(annotationHolder)) {
|
||||
this.log(identLoc.getLineNo(), identLoc.getColumnNo() - 1, "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);
|
||||
}
|
||||
}
|
||||
}
|
@@ -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");
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,8 @@
|
||||
/**
|
||||
* Custom checkstyle checks for PaperMC projects.
|
||||
*/
|
||||
@NullMarked
|
||||
@SuppressWarnings("unused")
|
||||
package io.papermc.checkstyle.checks;
|
||||
|
||||
import org.jspecify.annotations.NullMarked;
|
@@ -0,0 +1,7 @@
|
||||
/**
|
||||
* This package contains custom checkstyle rules for PaperMC projects.
|
||||
*/
|
||||
@NullMarked
|
||||
package io.papermc.checkstyle;
|
||||
|
||||
import org.jspecify.annotations.NullMarked;
|
@@ -0,0 +1,9 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
|
||||
<!DOCTYPE checkstyle-packages PUBLIC
|
||||
"-//Checkstyle//DTD Package Names Configuration 1.0//EN"
|
||||
"https://checkstyle.org/dtds/packages_1_0.dtd">
|
||||
|
||||
<checkstyle-packages>
|
||||
<package name="io.papermc.checkstyle.checks"/>
|
||||
</checkstyle-packages>
|
Reference in New Issue
Block a user