blob: 38c24c739f004b6b28537d20be8d4b73a334856b [file] [log] [blame]
////////////////////////////////////////////////////////////////////////////////
// checkstyle: Checks Java source code for adherence to a set of rules.
// Copyright (C) 2001-2017 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.puppycrawl.tools.checkstyle.internal;
import java.lang.reflect.Field;
import java.lang.reflect.Modifier;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Properties;
import java.util.Set;
import java.util.TreeMap;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import org.junit.Assert;
import org.junit.Test;
import com.puppycrawl.tools.checkstyle.AbstractModuleTestSupport;
import com.puppycrawl.tools.checkstyle.Checker;
import com.puppycrawl.tools.checkstyle.DefaultConfiguration;
import com.puppycrawl.tools.checkstyle.ModuleFactory;
import com.puppycrawl.tools.checkstyle.api.AbstractCheck;
import com.puppycrawl.tools.checkstyle.api.CheckstyleException;
import com.puppycrawl.tools.checkstyle.api.Configuration;
import com.puppycrawl.tools.checkstyle.checks.imports.ImportControlCheck;
import com.puppycrawl.tools.checkstyle.internal.utils.CheckUtil;
import com.puppycrawl.tools.checkstyle.internal.utils.ConfigurationUtil;
import com.puppycrawl.tools.checkstyle.internal.utils.TestUtil;
import com.puppycrawl.tools.checkstyle.internal.utils.XdocUtil;
import com.puppycrawl.tools.checkstyle.utils.CommonUtils;
import com.puppycrawl.tools.checkstyle.utils.ModuleReflectionUtils;
public class AllChecksTest extends AbstractModuleTestSupport {
private static final Locale[] ALL_LOCALES = {
Locale.GERMAN,
new Locale("es"),
new Locale("fi"),
Locale.FRENCH,
Locale.JAPANESE,
new Locale("pt"),
new Locale("tr"),
Locale.CHINESE,
Locale.ENGLISH,
};
private static final Map<String, Set<String>> CHECKSTYLE_TOKENS_IN_CONFIG_TO_IGNORE =
new HashMap<>();
private static final Map<String, Set<String>> GOOGLE_TOKENS_IN_CONFIG_TO_IGNORE =
new HashMap<>();
static {
// checkstyle
CHECKSTYLE_TOKENS_IN_CONFIG_TO_IGNORE.put("NoWhitespaceBefore", Stream.of(
// we use GenericWhitespace for this behavior
"GENERIC_START", "GENERIC_END").collect(Collectors.toSet()));
CHECKSTYLE_TOKENS_IN_CONFIG_TO_IGNORE.put("AbbreviationAsWordInName", Stream.of(
// enum values should be uppercase, we use EnumValueNameCheck instead
"ENUM_CONSTANT_DEF").collect(Collectors.toSet()));
CHECKSTYLE_TOKENS_IN_CONFIG_TO_IGNORE.put("FinalLocalVariable", Stream.of(
// we prefer all parameters be effectively final as to not damage readability
// we use ParameterAssignmentCheck to enforce this
"PARAMETER_DEF").collect(Collectors.toSet()));
// we have no need to block these specific tokens
CHECKSTYLE_TOKENS_IN_CONFIG_TO_IGNORE.put("IllegalToken",
Stream.of("LITERAL_SUPER", "LITERAL_ASSERT", "ENUM_CONSTANT_DEF",
"TYPE_PARAMETERS", "TYPE_UPPER_BOUNDS", "NUM_DOUBLE", "LITERAL_SWITCH",
"ANNOTATIONS", "LITERAL_SHORT", "LITERAL_PROTECTED", "FOR_CONDITION",
"FOR_INIT", "LITERAL_LONG", "MINUS", "OBJBLOCK", "LITERAL_NULL",
"ANNOTATION", "LITERAL_TRUE", "COMMENT_CONTENT", "LITERAL_CHAR",
"PARAMETER_DEF", "POST_DEC", "ANNOTATION_FIELD_DEF", "BLOCK_COMMENT_END",
"TYPE", "LITERAL_INT", "BSR", "ENUM", "ANNOTATION_MEMBER_VALUE_PAIR",
"TYPECAST", "LITERAL_SYNCHRONIZED", "PLUS_ASSIGN", "DOT", "LPAREN",
"LITERAL_IF", "LITERAL_CATCH", "BAND", "INTERFACE_DEF", "LOR", "BNOT",
"METHOD_CALL", "AT", "ELLIPSIS", "ARRAY_INIT", "FOR_EACH_CLAUSE",
"LITERAL_THROWS", "CHAR_LITERAL", "CASE_GROUP", "POST_INC", "SEMI",
"LITERAL_FINALLY", "ASSIGN", "RESOURCE_SPECIFICATION", "STATIC_IMPORT",
"GENERIC_START", "IMPORT", "SL", "VARIABLE_DEF", "LITERAL_DOUBLE",
"RCURLY", "RESOURCE", "SR", "COMMA", "BAND_ASSIGN", "METHOD_DEF",
"LITERAL_VOID", "NUM_LONG", "LITERAL_TRANSIENT", "LITERAL_THIS", "LCURLY",
"MINUS_ASSIGN", "TYPE_LOWER_BOUNDS", "TYPE_ARGUMENT", "LITERAL_CLASS",
"INSTANCE_INIT", "DIV", "STAR", "UNARY_MINUS", "FOR_ITERATOR", "NOT_EQUAL",
"LE", "LITERAL_INTERFACE", "LITERAL_FLOAT", "LITERAL_INSTANCEOF",
"BOR_ASSIGN", "LT", "SL_ASSIGN", "ELIST", "ANNOTATION_ARRAY_INIT",
"MODIFIERS", "LITERAL_BREAK", "EXTENDS_CLAUSE", "TYPE_PARAMETER",
"LITERAL_DEFAULT", "STATIC_INIT", "BSR_ASSIGN", "TYPE_EXTENSION_AND",
"BOR", "LITERAL_PRIVATE", "LITERAL_THROW", "LITERAL_BYTE", "BXOR",
"WILDCARD_TYPE", "FINAL", "PARAMETERS", "RPAREN", "SR_ASSIGN",
"UNARY_PLUS", "EMPTY_STAT", "LITERAL_STATIC", "LITERAL_CONTINUE",
"STAR_ASSIGN", "LAMBDA", "RBRACK", "BXOR_ASSIGN", "CTOR_CALL",
"LITERAL_FALSE", "DO_WHILE", "LITERAL_PUBLIC", "LITERAL_WHILE", "PLUS",
"INC", "CTOR_DEF", "GENERIC_END", "DIV_ASSIGN", "SLIST", "LNOT", "LAND",
"LITERAL_ELSE", "ABSTRACT", "STRICTFP", "QUESTION", "LITERAL_NEW",
"LITERAL_RETURN", "SINGLE_LINE_COMMENT", "INDEX_OP", "EXPR",
"BLOCK_COMMENT_BEGIN", "PACKAGE_DEF", "IMPLEMENTS_CLAUSE", "NUM_FLOAT",
"LITERAL_DO", "EOF", "GE", "RESOURCES", "MOD", "DEC", "EQUAL",
"LITERAL_BOOLEAN", "CLASS_DEF", "COLON", "LITERAL_TRY", "ENUM_DEF", "GT",
"NUM_INT", "ANNOTATION_DEF", "METHOD_REF", "TYPE_ARGUMENTS",
"DOUBLE_COLON", "IDENT", "MOD_ASSIGN", "LITERAL_FOR", "SUPER_CTOR_CALL",
"STRING_LITERAL", "ARRAY_DECLARATOR", "LITERAL_CASE").collect(
Collectors.toSet()));
// we have no need to block specific token text
CHECKSTYLE_TOKENS_IN_CONFIG_TO_IGNORE.put("IllegalTokenText",
Stream.of("NUM_DOUBLE", "NUM_FLOAT", "NUM_INT", "NUM_LONG", "IDENT",
"COMMENT_CONTENT", "STRING_LITERAL", "CHAR_LITERAL")
.collect(Collectors.toSet()));
// we do not use this check as it is deprecated
CHECKSTYLE_TOKENS_IN_CONFIG_TO_IGNORE.put("WriteTag",
Stream.of("ENUM_CONSTANT_DEF", "METHOD_DEF", "CTOR_DEF", "ANNOTATION_FIELD_DEF")
.collect(Collectors.toSet()));
// state of the configuration when test was made until reason found in
// https://github.com/checkstyle/checkstyle/issues/3730
CHECKSTYLE_TOKENS_IN_CONFIG_TO_IGNORE.put("AnnotationLocation",
Stream.of("TYPECAST", "DOT", "TYPE_ARGUMENT", "LITERAL_NEW", "LITERAL_THROWS",
"IMPLEMENTS_CLAUSE", "CLASS_DEF", "CTOR_DEF", "ENUM_DEF", "INTERFACE_DEF",
"METHOD_DEF", "VARIABLE_DEF").collect(Collectors.toSet()));
CHECKSTYLE_TOKENS_IN_CONFIG_TO_IGNORE.put("NoLineWrap", Stream.of(
// method declaration could be long due to "parameters/exceptions", it is ok to
// be not strict there
"METHOD_DEF", "CTOR_DEF",
// type declaration could be long due to "extends/implements", it is ok to
// be not strict there
"CLASS_DEF", "ENUM_DEF", "INTERFACE_DEF")
.collect(Collectors.toSet()));
CHECKSTYLE_TOKENS_IN_CONFIG_TO_IGNORE.put("NoWhitespaceAfter", Stream.of(
// whitespace after is preferred
"TYPECAST", "LITERAL_SYNCHRONIZED").collect(Collectors.toSet()));
CHECKSTYLE_TOKENS_IN_CONFIG_TO_IGNORE.put("SeparatorWrap", Stream.of(
// needs context to decide what type of parentheses should be separated or not
// which this check does not provide
"LPAREN", "RPAREN").collect(Collectors.toSet()));
CHECKSTYLE_TOKENS_IN_CONFIG_TO_IGNORE.put("NeedBraces", Stream.of(
// we prefer no braces here as it looks unusual even though they help avoid sharing
// scope of variables
"LITERAL_DEFAULT", "LITERAL_CASE").collect(Collectors.toSet()));
CHECKSTYLE_TOKENS_IN_CONFIG_TO_IGNORE.put("FinalParameters", Stream.of(
// we prefer these to be effectively final as to not damage readability
"FOR_EACH_CLAUSE", "LITERAL_CATCH").collect(Collectors.toSet()));
CHECKSTYLE_TOKENS_IN_CONFIG_TO_IGNORE.put("WhitespaceAround", Stream.of(
// we prefer no spaces on one side or both for these tokens
"ARRAY_INIT",
"ELLIPSIS",
// these are covered by GenericWhitespaceCheck
"WILDCARD_TYPE", "GENERIC_END", "GENERIC_START").collect(Collectors.toSet()));
CHECKSTYLE_TOKENS_IN_CONFIG_TO_IGNORE.put("RightCurly", Stream.of(
// Until https://github.com/checkstyle/checkstyle/issues/4177
"LAMBDA").collect(Collectors.toSet()));
// google
GOOGLE_TOKENS_IN_CONFIG_TO_IGNORE.put("AnnotationLocation", Stream.of(
// state of the configuration when test was made until reason found in
// https://github.com/checkstyle/checkstyle/issues/3730
"TYPECAST", "DOT", "TYPE_ARGUMENT", "ANNOTATION_DEF", "LITERAL_NEW",
"LITERAL_THROWS", "PARAMETER_DEF", "IMPLEMENTS_CLAUSE", "ANNOTATION_FIELD_DEF")
.collect(Collectors.toSet()));
GOOGLE_TOKENS_IN_CONFIG_TO_IGNORE.put("AbbreviationAsWordInName", Stream.of(
// enum values should be uppercase
"ENUM_CONSTANT_DEF").collect(Collectors.toSet()));
GOOGLE_TOKENS_IN_CONFIG_TO_IGNORE.put("NoLineWrap", Stream.of(
// method declaration could be long due to "parameters/exceptions", it is ok to
// be not strict there
"METHOD_DEF", "CTOR_DEF", "CLASS_DEF", "ENUM_DEF", "INTERFACE_DEF")
.collect(Collectors.toSet()));
GOOGLE_TOKENS_IN_CONFIG_TO_IGNORE.put("SeparatorWrap", Stream.of(
// location could be any to allow writing expressions for indexes evaluation
// on new line, see https://github.com/checkstyle/checkstyle/issues/3752
"RBRACK",
// for some targets annotations can be used without wrapping, as described
// in https://google.github.io/styleguide/javaguide.html#s4.8.5-annotations
"AT",
// location could be any to allow using for line separation in enum values,
// see https://github.com/checkstyle/checkstyle/issues/3752
"SEMI",
// needs context to decide what type of parentheses should be separated or not
// which this check does not provide
"LPAREN", "RPAREN").collect(Collectors.toSet()));
GOOGLE_TOKENS_IN_CONFIG_TO_IGNORE.put("NeedBraces", Stream.of(
// google doesn't require or prevent braces on these
"LAMBDA", "LITERAL_DEFAULT", "LITERAL_CASE").collect(Collectors.toSet()));
GOOGLE_TOKENS_IN_CONFIG_TO_IGNORE.put("EmptyBlock", Stream.of(
// google doesn't specifically mention empty braces at the start of a case/default
"LITERAL_DEFAULT", "LITERAL_CASE",
// can be empty for special cases via '6.2 Caught exceptions: not ignored'
"LITERAL_CATCH",
// specifically allowed via '5.2.4 Constant names'
"ARRAY_INIT",
// state of the configuration when test was made until
// https://github.com/checkstyle/checkstyle/issues/4121
"INSTANCE_INIT", "LITERAL_DO", "LITERAL_FOR", "LITERAL_SYNCHRONIZED",
"LITERAL_WHILE", "STATIC_INIT").collect(Collectors.toSet()));
GOOGLE_TOKENS_IN_CONFIG_TO_IGNORE.put("WhitespaceAround", Stream.of(
// allowed via '4.8.3 Arrays'
"ARRAY_INIT",
// '...' is almost same as '[]' by meaning
"ELLIPSIS",
// google prefers no spaces on one side or both for these tokens
"GENERIC_START", "GENERIC_END", "WILDCARD_TYPE")
.collect(Collectors.toSet()));
GOOGLE_TOKENS_IN_CONFIG_TO_IGNORE.put("IllegalTokenText", Stream.of(
// all other java tokens and text are allowed
"NUM_DOUBLE", "NUM_FLOAT", "NUM_INT", "NUM_LONG", "IDENT",
"COMMENT_CONTENT", "STRING_LITERAL", "CHAR_LITERAL")
.collect(Collectors.toSet()));
GOOGLE_TOKENS_IN_CONFIG_TO_IGNORE.put("OperatorWrap", Stream.of(
// specifically allowed via '4.5.1 Where to break' because the following are
// assignment operators and they are allowed to break before or after the symbol
"DIV_ASSIGN", "BOR_ASSIGN", "SL_ASSIGN", "ASSIGN", "BSR_ASSIGN", "BAND_ASSIGN",
"PLUS_ASSIGN", "MINUS_ASSIGN", "SR_ASSIGN", "STAR_ASSIGN", "BXOR_ASSIGN",
"MOD_ASSIGN",
// state of the configuration when test was made until
// https://github.com/checkstyle/checkstyle/issues/4122
"COLON", "TYPE_EXTENSION_AND").collect(Collectors.toSet()));
GOOGLE_TOKENS_IN_CONFIG_TO_IGNORE.put("RightCurly", Stream.of(
// Until https://github.com/checkstyle/checkstyle/issues/4178
"LAMBDA").collect(Collectors.toSet()));
GOOGLE_TOKENS_IN_CONFIG_TO_IGNORE.put("NoWhitespaceBefore", Stream.of(
// google uses GenericWhitespace for this behavior
"GENERIC_START", "GENERIC_END").collect(Collectors.toSet()));
}
@Override
protected String getPackageLocation() {
return "com/puppycrawl/tools/checkstyle/internal/allchecks";
}
@Test
public void testAllModulesWithDefaultConfiguration() throws Exception {
final String inputFilePath = getPath("InputAllChecksDefaultConfig.java");
final String[] expected = CommonUtils.EMPTY_STRING_ARRAY;
for (Class<?> module : CheckUtil.getCheckstyleModules()) {
if (ModuleReflectionUtils.isRootModule(module)) {
continue;
}
final DefaultConfiguration moduleConfig = createModuleConfig(module);
final Checker checker;
if (module.equals(ImportControlCheck.class)) {
// ImportControlCheck must have the import control configuration file to avoid
// violation.
moduleConfig.addAttribute("file", getPath(
"InputAllChecksImportControl.xml"));
}
checker = createChecker(moduleConfig);
verify(checker, inputFilePath, expected);
}
}
@Test
public void testDefaultTokensAreSubsetOfAcceptableTokens() throws Exception {
for (Class<?> check : CheckUtil.getCheckstyleChecks()) {
if (AbstractCheck.class.isAssignableFrom(check)) {
final AbstractCheck testedCheck = (AbstractCheck) check.getDeclaredConstructor()
.newInstance();
final int[] defaultTokens = testedCheck.getDefaultTokens();
final int[] acceptableTokens = testedCheck.getAcceptableTokens();
if (!isSubset(defaultTokens, acceptableTokens)) {
final String errorMessage = String.format(Locale.ROOT,
"%s's default tokens must be a subset"
+ " of acceptable tokens.", check.getName());
Assert.fail(errorMessage);
}
}
}
}
@Test
public void testRequiredTokensAreSubsetOfAcceptableTokens() throws Exception {
for (Class<?> check : CheckUtil.getCheckstyleChecks()) {
if (AbstractCheck.class.isAssignableFrom(check)) {
final AbstractCheck testedCheck = (AbstractCheck) check.getDeclaredConstructor()
.newInstance();
final int[] requiredTokens = testedCheck.getRequiredTokens();
final int[] acceptableTokens = testedCheck.getAcceptableTokens();
if (!isSubset(requiredTokens, acceptableTokens)) {
final String errorMessage = String.format(Locale.ROOT,
"%s's required tokens must be a subset"
+ " of acceptable tokens.", check.getName());
Assert.fail(errorMessage);
}
}
}
}
@Test
public void testRequiredTokensAreSubsetOfDefaultTokens() throws Exception {
for (Class<?> check : CheckUtil.getCheckstyleChecks()) {
if (AbstractCheck.class.isAssignableFrom(check)) {
final AbstractCheck testedCheck = (AbstractCheck) check.getDeclaredConstructor()
.newInstance();
final int[] defaultTokens = testedCheck.getDefaultTokens();
final int[] requiredTokens = testedCheck.getRequiredTokens();
if (!isSubset(requiredTokens, defaultTokens)) {
final String errorMessage = String.format(Locale.ROOT,
"%s's required tokens must be a subset"
+ " of default tokens.", check.getName());
Assert.fail(errorMessage);
}
}
}
}
@Test
public void testAllModulesAreReferencedInConfigFile() throws Exception {
final Set<String> modulesReferencedInConfig = CheckUtil.getConfigCheckStyleModules();
final Set<String> moduleNames = CheckUtil.getSimpleNames(CheckUtil.getCheckstyleModules());
//Issue: https://github.com/checkstyle/checkstyle/issues/4421
moduleNames.remove("SuppressionXpathFilter");
moduleNames.stream().filter(check -> !modulesReferencedInConfig.contains(check))
.forEach(check -> {
final String errorMessage = String.format(Locale.ROOT,
"%s is not referenced in checkstyle_checks.xml", check);
Assert.fail(errorMessage);
});
}
@Test
public void testAllCheckTokensAreReferencedInCheckstyleConfigFile() throws Exception {
final Configuration configuration = ConfigurationUtil
.loadConfiguration("config/checkstyle_checks.xml");
validateAllCheckTokensAreReferencedInConfigFile("checkstyle", configuration,
CHECKSTYLE_TOKENS_IN_CONFIG_TO_IGNORE);
}
@Test
public void testAllCheckTokensAreReferencedInGoogleConfigFile() throws Exception {
final Configuration configuration = ConfigurationUtil
.loadConfiguration("src/main/resources/google_checks.xml");
validateAllCheckTokensAreReferencedInConfigFile("google", configuration,
GOOGLE_TOKENS_IN_CONFIG_TO_IGNORE);
}
private static void validateAllCheckTokensAreReferencedInConfigFile(String configName,
Configuration configuration, Map<String, Set<String>> tokensToIgnore) throws Exception {
final ModuleFactory moduleFactory = TestUtil.getPackageObjectFactory();
final Set<Configuration> configChecks = ConfigurationUtil.getChecks(configuration);
final Map<String, Set<String>> configCheckTokens = new HashMap<>();
final Map<String, Set<String>> checkTokens = new HashMap<>();
for (Configuration checkConfig : configChecks) {
final String checkName = checkConfig.getName();
final Object instance;
try {
instance = moduleFactory.createModule(checkName);
}
catch (CheckstyleException ex) {
throw new CheckstyleException("Couldn't find check: " + checkName, ex);
}
if (instance instanceof AbstractCheck) {
final AbstractCheck check = (AbstractCheck) instance;
Set<String> configTokens = configCheckTokens.get(checkName);
if (configTokens == null) {
configTokens = new HashSet<>();
configCheckTokens.put(checkName, configTokens);
// add all overridden tokens
final Set<String> overrideTokens = tokensToIgnore.get(checkName);
if (overrideTokens != null) {
configTokens.addAll(overrideTokens);
}
configTokens.addAll(CheckUtil.getTokenNameSet(check.getRequiredTokens()));
checkTokens.put(checkName,
CheckUtil.getTokenNameSet(check.getAcceptableTokens()));
}
try {
configTokens.addAll(Arrays.asList(checkConfig.getAttribute("tokens").trim()
.split(",\\s*")));
}
catch (CheckstyleException ex) {
// no tokens defined, so it is using default
configTokens.addAll(CheckUtil.getTokenNameSet(check.getDefaultTokens()));
}
}
}
for (Entry<String, Set<String>> entry : checkTokens.entrySet()) {
Assert.assertEquals("'" + entry.getKey()
+ "' should have all acceptable tokens from check in " + configName
+ " config or specify an override to ignore the specific tokens",
entry.getValue(), configCheckTokens.get(entry.getKey()));
}
}
@Test
public void testAllCheckstyleModulesHaveXdocDocumentation() throws Exception {
final Set<String> checkstyleModulesNames = CheckUtil.getSimpleNames(CheckUtil
.getCheckstyleModules());
final Set<String> modulesNamesWhichHaveXdocs = XdocUtil.getModulesNamesWhichHaveXdoc();
// these are documented on non-'config_' pages
checkstyleModulesNames.remove("TreeWalker");
checkstyleModulesNames.remove("Checker");
//Issue: https://github.com/checkstyle/checkstyle/issues/4421
checkstyleModulesNames.remove("SuppressionXpathFilter");
checkstyleModulesNames.stream()
.filter(moduleName -> !modulesNamesWhichHaveXdocs.contains(moduleName))
.forEach(moduleName -> {
final String missingModuleMessage = String.format(Locale.ROOT,
"Module %s does not have xdoc documentation.",
moduleName);
Assert.fail(missingModuleMessage);
});
}
@Test
public void testAllCheckstyleModulesInCheckstyleConfig() throws Exception {
final Set<String> configChecks = CheckUtil.getConfigCheckStyleModules();
final Set<String> moduleNames = CheckUtil.getSimpleNames(CheckUtil.getCheckstyleModules());
//Issue: https://github.com/checkstyle/checkstyle/issues/4421
moduleNames.remove("SuppressionXpathFilter");
for (String moduleName : moduleNames) {
Assert.assertTrue("checkstyle_checks.xml is missing module: " + moduleName,
configChecks.contains(moduleName));
}
}
@Test
public void testAllCheckstyleChecksHaveMessage() throws Exception {
for (Class<?> module : CheckUtil.getCheckstyleChecks()) {
final String name = module.getSimpleName();
Assert.assertFalse(name
+ " should have at least one 'MSG_*' field for error messages", CheckUtil
.getCheckMessages(module).isEmpty());
}
}
@Test
public void testAllCheckstyleMessages() throws Exception {
final Map<String, List<String>> usedMessages = new TreeMap<>();
// test validity of messages from modules
for (Class<?> module : CheckUtil.getCheckstyleModules()) {
for (Field message : CheckUtil.getCheckMessages(module)) {
Assert.assertEquals(module.getSimpleName() + "." + message.getName()
+ " should be 'public static final'", Modifier.PUBLIC | Modifier.STATIC
| Modifier.FINAL, message.getModifiers());
// below is required for package/private classes
if (!message.isAccessible()) {
message.setAccessible(true);
}
verifyCheckstyleMessage(usedMessages, module, message);
}
}
// test properties for messages not used by checks
for (Entry<String, List<String>> entry : usedMessages.entrySet()) {
final Properties pr = new Properties();
pr.load(AllChecksTest.class.getResourceAsStream(
"/" + entry.getKey().replace('.', '/') + "/messages.properties"));
for (Object key : pr.keySet()) {
// hidden exception messages
if ("translation.wrongLanguageCode".equals(key)) {
continue;
}
Assert.assertTrue("property '" + key + "' isn't used by any check in package '"
+ entry.getKey() + "'", entry.getValue().contains(key.toString()));
}
}
}
private static void verifyCheckstyleMessage(Map<String, List<String>> usedMessages,
Class<?> module, Field message) throws Exception {
final String messageString = message.get(null).toString();
final String packageName = module.getPackage().getName();
List<String> packageMessages = usedMessages.get(packageName);
if (packageMessages == null) {
packageMessages = new ArrayList<>();
usedMessages.put(packageName, packageMessages);
}
packageMessages.add(messageString);
for (Locale locale : ALL_LOCALES) {
String result = null;
try {
result = CheckUtil.getCheckMessage(module, locale, messageString);
}
catch (IllegalArgumentException ex) {
Assert.fail(module.getSimpleName() + " with the message '" + messageString
+ "' in locale '" + locale.getLanguage() + "' failed with: "
+ ex.getClass().getSimpleName() + " - " + ex.getMessage());
}
Assert.assertNotNull(
module.getSimpleName() + " should have text for the message '"
+ messageString + "' in locale " + locale.getLanguage() + "'",
result);
Assert.assertFalse(
module.getSimpleName() + " should have non-empty text for the message '"
+ messageString + "' in locale '" + locale.getLanguage() + "'",
result.trim().isEmpty());
Assert.assertFalse(
module.getSimpleName() + " should have non-TODO text for the message '"
+ messageString + "' in locale " + locale.getLanguage() + "'",
!"todo.match".equals(messageString)
&& result.trim().startsWith("TODO"));
}
}
/**
* Checks that an array is a subset of other array.
* @param array to check whether it is a subset.
* @param arrayToCheckIn array to check in.
*/
private static boolean isSubset(int[] array, int... arrayToCheckIn) {
Arrays.sort(arrayToCheckIn);
boolean result = true;
for (final int element : array) {
if (Arrays.binarySearch(arrayToCheckIn, element) < 0) {
result = false;
break;
}
}
return result;
}
}