···11+/*
22+ * This file is part of checks, licensed under the MIT License.
33+ *
44+ * Copyright (c) 2026 nayrid
55+ *
66+ * Permission is hereby granted, free of charge, to any person obtaining a copy
77+ * of this software and associated documentation files (the "Software"), to deal
88+ * in the Software without restriction, including without limitation the rights
99+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
1010+ * copies of the Software, and to permit persons to whom the Software is
1111+ * furnished to do so, subject to the following conditions:
1212+ *
1313+ * The above copyright notice and this permission notice shall be included in all
1414+ * copies or substantial portions of the Software.
1515+ *
1616+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
1717+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
1818+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
1919+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
2020+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
2121+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
2222+ * SOFTWARE.
2323+ */
2424+package com.nayrid.checks;
2525+2626+import com.puppycrawl.tools.checkstyle.api.AbstractCheck;
2727+import com.puppycrawl.tools.checkstyle.api.DetailAST;
2828+import com.puppycrawl.tools.checkstyle.api.TokenTypes;
2929+import org.jspecify.annotations.Nullable;
3030+3131+import java.util.Arrays;
3232+import java.util.HashSet;
3333+import java.util.Set;
3434+3535+/**
3636+ * Forbids throwing generic exception types.
3737+ *
3838+ * @since 1.0.0
3939+ */
4040+public final class NoGenericExceptionCheck extends AbstractCheck {
4141+4242+ private static final Set<String> DEFAULT_FORBIDDEN = new HashSet<>(Arrays.asList(
4343+ "Exception",
4444+ "Throwable",
4545+ "RuntimeException",
4646+ "Error"
4747+ ));
4848+4949+ private static final String MSG_KEY = "genericExceptionForbidden";
5050+5151+ private Set<String> forbidden = DEFAULT_FORBIDDEN;
5252+5353+ /**
5454+ * Sets the {@code forbidden} exception types.
5555+ *
5656+ * @param forbiddenTypes new types
5757+ * @since 1.0.0
5858+ */
5959+ @SuppressWarnings("MethodName")
6060+ public void setForbidden(final String[] forbiddenTypes) {
6161+ this.forbidden = new HashSet<>(Arrays.asList(forbiddenTypes));
6262+ }
6363+6464+ @Override
6565+ public int[] getDefaultTokens() {
6666+ return new int[] { TokenTypes.METHOD_DEF };
6767+ }
6868+6969+ @Override
7070+ public int[] getAcceptableTokens() {
7171+ return this.getDefaultTokens();
7272+ }
7373+7474+ @Override
7575+ public int[] getRequiredTokens() {
7676+ return new int[0];
7777+ }
7878+7979+ @Override
8080+ public void visitToken(final DetailAST ast) {
8181+ final DetailAST modifiers = ast.findFirstToken(TokenTypes.MODIFIERS);
8282+8383+ if (!isPublicOrProtected(modifiers)) {
8484+ return;
8585+ }
8686+8787+ final DetailAST throwsClause = ast.findFirstToken(TokenTypes.LITERAL_THROWS);
8888+ if (throwsClause == null) {
8989+ return;
9090+ }
9191+9292+ DetailAST child = throwsClause.getFirstChild();
9393+ while (child != null) {
9494+ if (child.getType() == TokenTypes.IDENT) {
9595+ final String exceptionName = child.getText();
9696+ if (forbidden.contains(exceptionName)) {
9797+ log(child, MSG_KEY, exceptionName);
9898+ }
9999+ }
100100+ child = child.getNextSibling();
101101+ }
102102+ }
103103+104104+ private boolean isPublicOrProtected(final @Nullable DetailAST modifiers) {
105105+ return modifiers != null && (modifiers.findFirstToken(TokenTypes.LITERAL_PUBLIC) != null
106106+ || modifiers.findFirstToken(TokenTypes.LITERAL_PROTECTED) != null);
107107+ }
108108+}
···11+/*
22+ * This file is part of checks, licensed under the MIT License.
33+ *
44+ * Copyright (c) 2026 nayrid
55+ *
66+ * Permission is hereby granted, free of charge, to any person obtaining a copy
77+ * of this software and associated documentation files (the "Software"), to deal
88+ * in the Software without restriction, including without limitation the rights
99+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
1010+ * copies of the Software, and to permit persons to whom the Software is
1111+ * furnished to do so, subject to the following conditions:
1212+ *
1313+ * The above copyright notice and this permission notice shall be included in all
1414+ * copies or substantial portions of the Software.
1515+ *
1616+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
1717+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
1818+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
1919+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
2020+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
2121+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
2222+ * SOFTWARE.
2323+ */
2424+package com.nayrid.checks;
2525+2626+import com.puppycrawl.tools.checkstyle.AbstractModuleTestSupport;
2727+import com.puppycrawl.tools.checkstyle.DefaultConfiguration;
2828+import org.junit.jupiter.api.Test;
2929+3030+import static com.puppycrawl.tools.checkstyle.utils.CommonUtil.EMPTY_STRING_ARRAY;
3131+3232+public class NoGenericExceptionCheckTest extends AbstractModuleTestSupport {
3333+3434+ @Override
3535+ public String getPackageLocation() {
3636+ return "com/nayrid/checks/nogenericexception";
3737+ }
3838+3939+ @Test
4040+ public void testGenericException() throws Exception {
4141+ final String[] expected = {
4242+ "5:30: Public API must not throw generic exception type: Exception",
4343+ };
4444+4545+ verify(
4646+ createModuleConfig(NoGenericExceptionCheck.class),
4747+ getPath("HasGenericException.java"),
4848+ expected
4949+ );
5050+ }
5151+5252+ @Test
5353+ public void testNonGenericException() throws Exception {
5454+ verify(
5555+ createModuleConfig(NoGenericExceptionCheck.class),
5656+ getPath("HasNonGenericException.java"),
5757+ EMPTY_STRING_ARRAY
5858+ );
5959+ }
6060+6161+6262+ @Test
6363+ public void testNonGenericExceptionConfigured() throws Exception {
6464+ final DefaultConfiguration checkConfig = createModuleConfig(NoGenericExceptionCheck.class);
6565+6666+ checkConfig.addProperty("forbidden", "NullPointerException");
6767+6868+ final String[] expected = {
6969+ "5:30: Public API must not throw generic exception type: NullPointerException",
7070+ };
7171+7272+ verify(
7373+ checkConfig,
7474+ getPath("HasNonGenericException.java"),
7575+ expected
7676+ );
7777+ }
7878+}