checkstyle checks
java checkstyle

feat: disallow generic exception types check

kokirigla.de 4e28e8fa 358cee7d

verified
+206 -1
+1 -1
build.gradle.kts
··· 3 3 } 4 4 5 5 group = "com.nayrid" 6 - version = "1.0.0" 6 + version = "1.1.0-SNAPSHOT" 7 7 8 8 dependencies { 9 9 api(libs.jspecify)
+108
src/main/java/com/nayrid/checks/NoGenericExceptionCheck.java
··· 1 + /* 2 + * This file is part of checks, licensed under the MIT License. 3 + * 4 + * Copyright (c) 2026 nayrid 5 + * 6 + * Permission is hereby granted, free of charge, to any person obtaining a copy 7 + * of this software and associated documentation files (the "Software"), to deal 8 + * in the Software without restriction, including without limitation the rights 9 + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 + * copies of the Software, and to permit persons to whom the Software is 11 + * furnished to do so, subject to the following conditions: 12 + * 13 + * The above copyright notice and this permission notice shall be included in all 14 + * copies or substantial portions of the Software. 15 + * 16 + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 22 + * SOFTWARE. 23 + */ 24 + package com.nayrid.checks; 25 + 26 + import com.puppycrawl.tools.checkstyle.api.AbstractCheck; 27 + import com.puppycrawl.tools.checkstyle.api.DetailAST; 28 + import com.puppycrawl.tools.checkstyle.api.TokenTypes; 29 + import org.jspecify.annotations.Nullable; 30 + 31 + import java.util.Arrays; 32 + import java.util.HashSet; 33 + import java.util.Set; 34 + 35 + /** 36 + * Forbids throwing generic exception types. 37 + * 38 + * @since 1.0.0 39 + */ 40 + public final class NoGenericExceptionCheck extends AbstractCheck { 41 + 42 + private static final Set<String> DEFAULT_FORBIDDEN = new HashSet<>(Arrays.asList( 43 + "Exception", 44 + "Throwable", 45 + "RuntimeException", 46 + "Error" 47 + )); 48 + 49 + private static final String MSG_KEY = "genericExceptionForbidden"; 50 + 51 + private Set<String> forbidden = DEFAULT_FORBIDDEN; 52 + 53 + /** 54 + * Sets the {@code forbidden} exception types. 55 + * 56 + * @param forbiddenTypes new types 57 + * @since 1.0.0 58 + */ 59 + @SuppressWarnings("MethodName") 60 + public void setForbidden(final String[] forbiddenTypes) { 61 + this.forbidden = new HashSet<>(Arrays.asList(forbiddenTypes)); 62 + } 63 + 64 + @Override 65 + public int[] getDefaultTokens() { 66 + return new int[] { TokenTypes.METHOD_DEF }; 67 + } 68 + 69 + @Override 70 + public int[] getAcceptableTokens() { 71 + return this.getDefaultTokens(); 72 + } 73 + 74 + @Override 75 + public int[] getRequiredTokens() { 76 + return new int[0]; 77 + } 78 + 79 + @Override 80 + public void visitToken(final DetailAST ast) { 81 + final DetailAST modifiers = ast.findFirstToken(TokenTypes.MODIFIERS); 82 + 83 + if (!isPublicOrProtected(modifiers)) { 84 + return; 85 + } 86 + 87 + final DetailAST throwsClause = ast.findFirstToken(TokenTypes.LITERAL_THROWS); 88 + if (throwsClause == null) { 89 + return; 90 + } 91 + 92 + DetailAST child = throwsClause.getFirstChild(); 93 + while (child != null) { 94 + if (child.getType() == TokenTypes.IDENT) { 95 + final String exceptionName = child.getText(); 96 + if (forbidden.contains(exceptionName)) { 97 + log(child, MSG_KEY, exceptionName); 98 + } 99 + } 100 + child = child.getNextSibling(); 101 + } 102 + } 103 + 104 + private boolean isPublicOrProtected(final @Nullable DetailAST modifiers) { 105 + return modifiers != null && (modifiers.findFirstToken(TokenTypes.LITERAL_PUBLIC) != null 106 + || modifiers.findFirstToken(TokenTypes.LITERAL_PROTECTED) != null); 107 + } 108 + }
+1
src/main/resources/com/nayrid/checks/messages.properties
··· 1 1 missingSince=Javadoc must contain a `@since` tag. 2 + genericExceptionForbidden=Public API must not throw generic exception type: {0}
+78
src/test/java/com/nayrid/checks/NoGenericExceptionCheckTest.java
··· 1 + /* 2 + * This file is part of checks, licensed under the MIT License. 3 + * 4 + * Copyright (c) 2026 nayrid 5 + * 6 + * Permission is hereby granted, free of charge, to any person obtaining a copy 7 + * of this software and associated documentation files (the "Software"), to deal 8 + * in the Software without restriction, including without limitation the rights 9 + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 + * copies of the Software, and to permit persons to whom the Software is 11 + * furnished to do so, subject to the following conditions: 12 + * 13 + * The above copyright notice and this permission notice shall be included in all 14 + * copies or substantial portions of the Software. 15 + * 16 + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 22 + * SOFTWARE. 23 + */ 24 + package com.nayrid.checks; 25 + 26 + import com.puppycrawl.tools.checkstyle.AbstractModuleTestSupport; 27 + import com.puppycrawl.tools.checkstyle.DefaultConfiguration; 28 + import org.junit.jupiter.api.Test; 29 + 30 + import static com.puppycrawl.tools.checkstyle.utils.CommonUtil.EMPTY_STRING_ARRAY; 31 + 32 + public class NoGenericExceptionCheckTest extends AbstractModuleTestSupport { 33 + 34 + @Override 35 + public String getPackageLocation() { 36 + return "com/nayrid/checks/nogenericexception"; 37 + } 38 + 39 + @Test 40 + public void testGenericException() throws Exception { 41 + final String[] expected = { 42 + "5:30: Public API must not throw generic exception type: Exception", 43 + }; 44 + 45 + verify( 46 + createModuleConfig(NoGenericExceptionCheck.class), 47 + getPath("HasGenericException.java"), 48 + expected 49 + ); 50 + } 51 + 52 + @Test 53 + public void testNonGenericException() throws Exception { 54 + verify( 55 + createModuleConfig(NoGenericExceptionCheck.class), 56 + getPath("HasNonGenericException.java"), 57 + EMPTY_STRING_ARRAY 58 + ); 59 + } 60 + 61 + 62 + @Test 63 + public void testNonGenericExceptionConfigured() throws Exception { 64 + final DefaultConfiguration checkConfig = createModuleConfig(NoGenericExceptionCheck.class); 65 + 66 + checkConfig.addProperty("forbidden", "NullPointerException"); 67 + 68 + final String[] expected = { 69 + "5:30: Public API must not throw generic exception type: NullPointerException", 70 + }; 71 + 72 + verify( 73 + checkConfig, 74 + getPath("HasNonGenericException.java"), 75 + expected 76 + ); 77 + } 78 + }
+9
src/test/resources/com/nayrid/checks/nogenericexception/HasGenericException.java
··· 1 + package com.nayrid.checks.requiresince; 2 + 3 + public class HasGenericException { 4 + 5 + public void foo() throws Exception { 6 + throw new Exception("foo"); 7 + } 8 + 9 + }
+9
src/test/resources/com/nayrid/checks/nogenericexception/HasNonGenericException.java
··· 1 + package com.nayrid.checks.requiresince; 2 + 3 + public class HasNonGenericException { 4 + 5 + public void foo() throws NullPointerException { 6 + throw new NullPointerException("foo"); 7 + } 8 + 9 + }