diff --git a/eclipsecs-sevntu-plugin/src/com/github/sevntu/checkstyle/checks/coding/checkstyle-metadata.properties b/eclipsecs-sevntu-plugin/src/com/github/sevntu/checkstyle/checks/coding/checkstyle-metadata.properties
index b7859c2c4a..cfa739246c 100755
--- a/eclipsecs-sevntu-plugin/src/com/github/sevntu/checkstyle/checks/coding/checkstyle-metadata.properties
+++ b/eclipsecs-sevntu-plugin/src/com/github/sevntu/checkstyle/checks/coding/checkstyle-metadata.properties
@@ -57,6 +57,12 @@ ForbidCertainMethodCheck.desc = Forbids certain method usage.
You can
ForbidCertainMethodCheck.methodName = Regex to match name of the forbidden method. When blank or unspecified, all the methods will be allowed.
ForbidCertainMethodCheck.argumentCount = Number or range to match number of arguments the forbidden method takes. Multiple ranges are separated by comma. When unspecified, only method name will be used for check.
+ForbidFieldAccessCheck.name = Forbid certain field access
+ForbidFieldAccessCheck.desc =
Checks that certain fields are not used. This can be used to enforce that fields like e.g. {@link java.util.Locale#ROOT} are not used.
+ForbidFieldAccessCheck.packageName = The field package name to be forbidden. (default 'java.util') +ForbidFieldAccessCheck.className = The field class name to be forbidden. (default 'Locale') +ForbidFieldAccessCheck.fieldName = The field name to be forbidden. (default 'ROOT') + ForbidInstantiationCheck.name = Forbid Instantiation ForbidInstantiationCheck.desc = Forbids instantiation of certain object types by their full classname.For example:
"java.lang.NullPointerException" will forbid the NPE instantiation.
Note: className should to be full: use "java.lang.NullPointerException" instead of "NullpointerException".
ForbidInstantiationCheck.forbiddenClasses = ClassNames for objects that are forbidden to instantiate. diff --git a/eclipsecs-sevntu-plugin/src/com/github/sevntu/checkstyle/checks/coding/checkstyle-metadata.xml b/eclipsecs-sevntu-plugin/src/com/github/sevntu/checkstyle/checks/coding/checkstyle-metadata.xml index fcba5c0679..94f88bc7ed 100644 --- a/eclipsecs-sevntu-plugin/src/com/github/sevntu/checkstyle/checks/coding/checkstyle-metadata.xml +++ b/eclipsecs-sevntu-plugin/src/com/github/sevntu/checkstyle/checks/coding/checkstyle-metadata.xml @@ -192,6 +192,22 @@+ * Checks that certain fields are not used. This can be used to enforce that fields like e.g. + * {@link java.util.Locale#ROOT}. + *
+ * + *+ * Parameters are: + *
+ * + *+ * Together, these three parameters forms the field fully qualified name. + *
+ * + *+ * Default parameters are: + *
+ * + *+ * which forbids the usage of {@link java.util.Locale#ROOT}. + *
+ * + * @author Yasser Aziza + * @since 1.38.0 + */ +public class ForbidFieldAccessCheck extends AbstractCheck { + + /** + * Warning message key. + */ + public static final String MSG_KEY = "forbid.field.access"; + + /** + * '.' character used as separator between FQN elements. + */ + private static final char DOT = '.'; + + /** + * Package name. + */ + private String packageName = "java.util"; + + /** + * Class name. + */ + private String className = "Locale"; + + /** + * Field name. + */ + private String fieldName = "ROOT"; + + /** + * Whether the field class was imported. + */ + private boolean wasPackageImported; + + /** + * Sets the package name, in which the field is declared. + * @param packageName the field name + */ + public void setPackageName(String packageName) { + this.packageName = packageName; + } + + /** + * Sets the class name, which declares the field. + * @param className the class name + */ + public void setClassName(String className) { + this.className = className; + } + + /** + * Sets the Field name, which should be forbidden to use. + * @param fieldName the field name + */ + public void setFieldName(String fieldName) { + this.fieldName = fieldName; + } + + /** + * Gets the field fully qualified name. + * @return {@link String} containing the field FQN + */ + private String getFieldFullyQualifiedName() { + return packageName + DOT + className + DOT + fieldName; + } + + @Override + public int[] getDefaultTokens() { + return new int[] { + TokenTypes.STATIC_IMPORT, + TokenTypes.IMPORT, + TokenTypes.IDENT, + }; + } + + @Override + public int[] getAcceptableTokens() { + return getDefaultTokens(); + } + + @Override + public int[] getRequiredTokens() { + return getDefaultTokens(); + } + + @Override + public void visitToken(DetailAST ast) { + if (isStaticImported(ast)) { + log(ast, MSG_KEY, getFieldFullyQualifiedName()); + } + else if (isPackageImported(ast)) { + // Mark forbidden field package as imported + wasPackageImported = true; + } + else if (wasPackageImported && TokenTypes.IDENT == ast.getType() && isSameType(ast)) { + log(ast.getPreviousSibling(), MSG_KEY, getFieldFullyQualifiedName()); + } + } + + /** + * Checks whether the field is static imported. + * + * @param ast the {@link TokenTypes#STATIC_IMPORT} node to be checked + * @return {@code true} if the field was static imported, {@code false} otherwise + */ + private boolean isStaticImported(DetailAST ast) { + return TokenTypes.STATIC_IMPORT == ast.getType() + && getFieldFullyQualifiedName().equals(SevntuUtil.getText(ast)); + } + + /** + * Checks whether the field package is imported. + * + * @param ast the {@link TokenTypes#IMPORT} node to be checked + * @return {@code true} if the field was imported, {@code false} otherwise + */ + private boolean isPackageImported(DetailAST ast) { + final String importName = packageName + DOT + className; + + return TokenTypes.IMPORT == ast.getType() + && importName.equals(FullIdent.createFullIdentBelow(ast).getText()); + } + + /** + * Checks if the given {@link TokenTypes#IDENT} node has the same type as the forbidden field. + * + * @param ast the {@link TokenTypes#IDENT} node to be checked + * @return {@code true} if the field has the same FQN, {@code false} otherwise + */ + private boolean isSameType(DetailAST ast) { + return fieldName.equals(ast.getText()) + && ast.getPreviousSibling() != null + && className.equals(ast.getPreviousSibling().getText()); + } + +} diff --git a/sevntu-checks/src/main/resources/com/github/sevntu/checkstyle/checks/coding/messages.properties b/sevntu-checks/src/main/resources/com/github/sevntu/checkstyle/checks/coding/messages.properties index 465f6cf02c..10624070dd 100644 --- a/sevntu-checks/src/main/resources/com/github/sevntu/checkstyle/checks/coding/messages.properties +++ b/sevntu-checks/src/main/resources/com/github/sevntu/checkstyle/checks/coding/messages.properties @@ -21,6 +21,7 @@ finalize.implementation.useless=finalize() method is useless: it does nothing ex forbid.c.comments.in.the.method.body=C-style comments (/*...*/) inside method body are not allowed. forbid.certain.imports=Import ''{1}'' should not match ''{0}'' pattern, it is forbidden. forbid.certain.method=Call to ''{0}'' method (matches pattern ''{1}'') with ''{2}'' arguments (matches pattern ''{3}'') is forbidden. +forbid.field.access=Field access of type ''{0}'' is forbidden forbid.instantiation=Instantiation of ''{0}'' is not allowed. forbid.return.in.finally.block=Finally block should not contain return statements. forbid.throw.anonymous.exception=Avoid throwing anonymous exception. diff --git a/sevntu-checks/src/test/java/com/github/sevntu/checkstyle/checks/coding/ForbidFieldAccessCheckTest.java b/sevntu-checks/src/test/java/com/github/sevntu/checkstyle/checks/coding/ForbidFieldAccessCheckTest.java new file mode 100644 index 0000000000..4bd4a065dc --- /dev/null +++ b/sevntu-checks/src/test/java/com/github/sevntu/checkstyle/checks/coding/ForbidFieldAccessCheckTest.java @@ -0,0 +1,95 @@ +//////////////////////////////////////////////////////////////////////////////// +// checkstyle: Checks Java source code for adherence to a set of rules. +// Copyright (C) 2001-2020 the original author or authors. +// +// This library is free software; you can redistribute it and/or +// modify it under the terms of the GNU Lesser General Public +// License as published by the Free Software Foundation; either +// version 2.1 of the License, or (at your option) any later version. +// +// This library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public +// License along with this library; if not, write to the Free Software +// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +//////////////////////////////////////////////////////////////////////////////// + +package com.github.sevntu.checkstyle.checks.coding; + +import static com.github.sevntu.checkstyle.checks.coding.ForbidFieldAccessCheck.MSG_KEY; + +import org.junit.Test; + +import com.puppycrawl.tools.checkstyle.AbstractModuleTestSupport; +import com.puppycrawl.tools.checkstyle.DefaultConfiguration; + +/** + * @author Yasser Aziza + */ +public class ForbidFieldAccessCheckTest extends AbstractModuleTestSupport { + + @Override + protected String getPackageLocation() { + return "com/github/sevntu/checkstyle/checks/coding"; + } + + @Test + public void testImportDefaultField() throws Exception { + final DefaultConfiguration checkConfig = + createModuleConfig(ForbidFieldAccessCheck.class); + + final String[] expected = { + "12:36: " + getCheckMessage(MSG_KEY, "java.util.Locale.ROOT"), + }; + + verify(checkConfig, getPath("InputForbidFieldAccessCheckTest.java"), expected); + } + + @Test + public void testStaticImportDefaultField() throws Exception { + final DefaultConfiguration checkConfig = + createModuleConfig(ForbidFieldAccessCheck.class); + + final String[] expected = { + "5:1: " + getCheckMessage(MSG_KEY, "java.util.Locale.ROOT"), + }; + + verify(checkConfig, getPath("InputForbidFieldAccessCheckStaticTest.java"), expected); + } + + @Test + public void testImportInteger() throws Exception { + final DefaultConfiguration checkConfig = + createModuleConfig(ForbidFieldAccessCheck.class); + + checkConfig.addAttribute("packageName", "java.lang"); + checkConfig.addAttribute("className", "Integer"); + checkConfig.addAttribute("fieldName", "MAX_VALUE"); + + final String[] expected = { + "17:20: " + getCheckMessage(MSG_KEY, "java.lang.Integer.MAX_VALUE"), + }; + + verify(checkConfig, getPath("InputForbidFieldAccessCheckTest.java"), expected); + } + + @Test + public void testStaticImportInteger() throws Exception { + final DefaultConfiguration checkConfig = + createModuleConfig(ForbidFieldAccessCheck.class); + + checkConfig.addAttribute("packageName", "java.lang"); + checkConfig.addAttribute("className", "Integer"); + checkConfig.addAttribute("fieldName", "MAX_VALUE"); + + final String[] expected = { + "6:1: " + getCheckMessage(MSG_KEY, "java.lang.Integer.MAX_VALUE"), + }; + + verify(checkConfig, getPath("InputForbidFieldAccessCheckStaticTest.java"), expected); + } + +} diff --git a/sevntu-checks/src/test/java/com/github/sevntu/checkstyle/internal/AllChecksTest.java b/sevntu-checks/src/test/java/com/github/sevntu/checkstyle/internal/AllChecksTest.java index f740cc61ad..cd44f499e7 100644 --- a/sevntu-checks/src/test/java/com/github/sevntu/checkstyle/internal/AllChecksTest.java +++ b/sevntu-checks/src/test/java/com/github/sevntu/checkstyle/internal/AllChecksTest.java @@ -29,7 +29,6 @@ import java.util.Arrays; import java.util.HashMap; import java.util.List; -import java.util.Locale; import java.util.Map; import java.util.Map.Entry; import java.util.Properties; @@ -54,7 +53,7 @@ public void testDefaultTokensAreSubsetOfAcceptableTokens() throws Exception { final int[] acceptableTokens = testedCheck.getAcceptableTokens(); if (!isSubset(defaultTokens, acceptableTokens)) { - final String errorMessage = String.format(Locale.ROOT, + final String errorMessage = String.format( "%s's default tokens must be a subset" + " of acceptable tokens.", check.getName()); Assert.fail(errorMessage); @@ -73,7 +72,7 @@ public void testRequiredTokensAreSubsetOfAcceptableTokens() throws Exception { final int[] acceptableTokens = testedCheck.getAcceptableTokens(); if (!isSubset(requiredTokens, acceptableTokens)) { - final String errorMessage = String.format(Locale.ROOT, + final String errorMessage = String.format( "%s's required tokens must be a subset" + " of acceptable tokens.", check.getName()); Assert.fail(errorMessage); @@ -92,7 +91,7 @@ public void testRequiredTokensAreSubsetOfDefaultTokens() throws Exception { final int[] requiredTokens = testedCheck.getRequiredTokens(); if (!isSubset(requiredTokens, defaultTokens)) { - final String errorMessage = String.format(Locale.ROOT, + final String errorMessage = String.format( "%s's required tokens must be a subset" + " of default tokens.", check.getName()); Assert.fail(errorMessage); @@ -108,7 +107,7 @@ public void testAllChecksAreReferencedInConfigFile() throws Exception { checksNames.stream().filter(check -> !checksReferencedInConfig.contains(check)) .forEach(check -> { - final String errorMessage = String.format(Locale.ROOT, + final String errorMessage = String.format( "%s is not referenced in sevntu-checks.xml", check); Assert.fail(errorMessage); }); diff --git a/sevntu-checks/src/test/resources/com/github/sevntu/checkstyle/checks/coding/InputForbidFieldAccessCheckStaticTest.java b/sevntu-checks/src/test/resources/com/github/sevntu/checkstyle/checks/coding/InputForbidFieldAccessCheckStaticTest.java new file mode 100644 index 0000000000..bd904f712a --- /dev/null +++ b/sevntu-checks/src/test/resources/com/github/sevntu/checkstyle/checks/coding/InputForbidFieldAccessCheckStaticTest.java @@ -0,0 +1,18 @@ +package com.github.sevntu.checkstyle.checks.coding; + +import com.puppycrawl.tools.checkstyle.api.LocalizedMessage; + +import static java.util.Locale.ROOT; +import static java.lang.Integer.MAX_VALUE; + +public class InputForbidFieldAccessCheckStaticTest { + + public static void setLocaleToRoot() { + LocalizedMessage.setLocale(ROOT); + } + + public static boolean isInRange(int x) { + return x < MAX_VALUE; + } + +} diff --git a/sevntu-checks/src/test/resources/com/github/sevntu/checkstyle/checks/coding/InputForbidFieldAccessCheckTest.java b/sevntu-checks/src/test/resources/com/github/sevntu/checkstyle/checks/coding/InputForbidFieldAccessCheckTest.java new file mode 100644 index 0000000000..35aeee06ba --- /dev/null +++ b/sevntu-checks/src/test/resources/com/github/sevntu/checkstyle/checks/coding/InputForbidFieldAccessCheckTest.java @@ -0,0 +1,20 @@ +package com.github.sevntu.checkstyle.checks.coding; + +import com.puppycrawl.tools.checkstyle.api.LocalizedMessage; + +import java.util.Locale; +import java.lang.Integer; + +public class InputForbidFieldAccessCheckTest { + private final static String ROOT = "ROOT"; + + public static void setLocaleToRoot() { + LocalizedMessage.setLocale(Locale.ROOT); + System.out.println(ROOT); + } + + public static boolean isInRange(int x) { + return x < Integer.MAX_VALUE; + } + +} diff --git a/sevntu-checkstyle-sonar-plugin/src/main/resources/com/github/sevntu/checkstyle/sonar/checkstyle-extensions.xml b/sevntu-checkstyle-sonar-plugin/src/main/resources/com/github/sevntu/checkstyle/sonar/checkstyle-extensions.xml index 53ef2ae7dd..4ea49944ce 100644 --- a/sevntu-checkstyle-sonar-plugin/src/main/resources/com/github/sevntu/checkstyle/sonar/checkstyle-extensions.xml +++ b/sevntu-checkstyle-sonar-plugin/src/main/resources/com/github/sevntu/checkstyle/sonar/checkstyle-extensions.xml @@ -396,6 +396,26 @@ +