blob: 129d67c08296a11652a4cb79de632640be047af3 [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 static java.nio.charset.StandardCharsets.UTF_8;
import java.beans.PropertyDescriptor;
import java.io.File;
import java.io.IOException;
import java.io.StringReader;
import java.lang.reflect.Field;
import java.net.URI;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Locale;
import java.util.NoSuchElementException;
import java.util.Properties;
import java.util.Set;
import java.util.TreeSet;
import java.util.regex.Pattern;
import org.apache.commons.beanutils.PropertyUtils;
import org.junit.Assert;
import org.junit.Test;
import org.w3c.dom.Document;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
import org.xml.sax.InputSource;
import com.puppycrawl.tools.checkstyle.Checker;
import com.puppycrawl.tools.checkstyle.ConfigurationLoader;
import com.puppycrawl.tools.checkstyle.ModuleFactory;
import com.puppycrawl.tools.checkstyle.PropertiesExpander;
import com.puppycrawl.tools.checkstyle.api.AbstractCheck;
import com.puppycrawl.tools.checkstyle.api.AbstractFileSetCheck;
import com.puppycrawl.tools.checkstyle.api.CheckstyleException;
import com.puppycrawl.tools.checkstyle.api.Configuration;
import com.puppycrawl.tools.checkstyle.api.Scope;
import com.puppycrawl.tools.checkstyle.api.SeverityLevel;
import com.puppycrawl.tools.checkstyle.checks.javadoc.AbstractJavadocCheck;
import com.puppycrawl.tools.checkstyle.checks.naming.AccessModifier;
import com.puppycrawl.tools.checkstyle.internal.utils.CheckUtil;
import com.puppycrawl.tools.checkstyle.internal.utils.TestUtil;
import com.puppycrawl.tools.checkstyle.internal.utils.XdocUtil;
import com.puppycrawl.tools.checkstyle.internal.utils.XmlUtil;
public class XdocsPagesTest {
private static final Path AVAILABLE_CHECKS_PATH = Paths.get("src/xdocs/checks.xml");
private static final String LINK_TEMPLATE =
"(?s).*<a href=\"config_\\w+\\.html#%1$s\">%1$s</a>.*";
private static final Pattern VERSION = Pattern.compile("\\d+\\.\\d+(\\.\\d+)?");
private static final Pattern DESCRIPTION_VERSION = Pattern
.compile("^Since Checkstyle \\d+\\.\\d+(\\.\\d+)?");
private static final List<String> XML_FILESET_LIST = Arrays.asList(
"TreeWalker",
"name=\"Checker\"",
"name=\"Header\"",
"name=\"Translation\"",
"name=\"SeverityMatchFilter\"",
"name=\"SuppressionFilter\"",
"name=\"SuppressWarningsFilter\"",
"name=\"BeforeExecutionExclusionFileFilter\"",
"name=\"RegexpHeader\"",
"name=\"RegexpOnFilename\"",
"name=\"RegexpSingleline\"",
"name=\"RegexpMultiline\"",
"name=\"JavadocPackage\"",
"name=\"NewlineAtEndOfFile\"",
"name=\"UniqueProperties\"",
"name=\"FileLength\"",
"name=\"FileTabCharacter\""
);
private static final Set<String> CHECK_PROPERTIES = getProperties(AbstractCheck.class);
private static final Set<String> JAVADOC_CHECK_PROPERTIES =
getProperties(AbstractJavadocCheck.class);
private static final Set<String> FILESET_PROPERTIES = getProperties(AbstractFileSetCheck.class);
private static final List<String> UNDOCUMENTED_PROPERTIES = Arrays.asList(
"Checker.classLoader",
"Checker.classloader",
"Checker.moduleClassLoader",
"Checker.moduleFactory",
"TreeWalker.classLoader",
"TreeWalker.moduleFactory",
"TreeWalker.cacheFile",
"TreeWalker.upChild",
"SuppressWithNearbyCommentFilter.fileContents",
"SuppressionCommentFilter.fileContents"
);
private static final Set<String> SUN_MODULES = Collections.unmodifiableSet(
new HashSet<>(CheckUtil.getConfigSunStyleModules()));
private static final Set<String> GOOGLE_MODULES = Collections.unmodifiableSet(
new HashSet<>(CheckUtil.getConfigGoogleStyleModules()));
@Test
public void testAllChecksPresentOnAvailableChecksPage() throws Exception {
final String availableChecks = new String(Files.readAllBytes(AVAILABLE_CHECKS_PATH), UTF_8);
CheckUtil.getSimpleNames(CheckUtil.getCheckstyleChecks())
.forEach(checkName -> {
if (!isPresent(availableChecks, checkName)) {
Assert.fail(checkName + " is not correctly listed on Available Checks page"
+ " - add it to " + AVAILABLE_CHECKS_PATH);
}
});
}
private static boolean isPresent(String availableChecks, String checkName) {
final String linkPattern = String.format(Locale.ROOT, LINK_TEMPLATE, checkName);
return availableChecks.matches(linkPattern);
}
@Test
public void testAllXmlExamples() throws Exception {
for (Path path : XdocUtil.getXdocsFilePaths()) {
final String input = new String(Files.readAllBytes(path), UTF_8);
final String fileName = path.getFileName().toString();
final Document document = XmlUtil.getRawXml(fileName, input, input);
final NodeList sources = document.getElementsByTagName("source");
for (int position = 0; position < sources.getLength(); position++) {
final String unserializedSource = sources.item(position).getTextContent()
.replace("...", "").trim();
if (unserializedSource.charAt(0) != '<'
|| unserializedSource.charAt(unserializedSource.length() - 1) != '>'
// no dtd testing yet
|| unserializedSource.contains("<!")) {
continue;
}
final String code = buildXml(unserializedSource);
// validate only
XmlUtil.getRawXml(fileName, code, unserializedSource);
// can't test ant structure, or old and outdated checks
Assert.assertTrue("Xml is invalid, old or has outdated structure",
fileName.startsWith("anttask")
|| fileName.startsWith("releasenotes")
|| isValidCheckstyleXml(fileName, code, unserializedSource));
}
}
}
private static String buildXml(String unserializedSource) throws IOException {
// not all examples come with the full xml structure
String code = unserializedSource
// don't corrupt our own cachefile
.replace("target/cachefile", "target/cachefile-test");
if (!hasFileSetClass(code)) {
code = "<module name=\"TreeWalker\">\n" + code + "\n</module>";
}
if (!code.contains("name=\"Checker\"")) {
code = "<module name=\"Checker\">\n" + code + "\n</module>";
}
if (!code.startsWith("<?xml")) {
final String dtdPath = new File(
"src/main/resources/com/puppycrawl/tools/checkstyle/configuration_1_3.dtd")
.getCanonicalPath();
code = "<?xml version=\"1.0\"?>\n<!DOCTYPE module PUBLIC "
+ "\"-//Puppy Crawl//DTD Check Configuration 1.3//EN\" \"" + dtdPath + "\">\n"
+ code;
}
return code;
}
private static boolean hasFileSetClass(String xml) {
boolean found = false;
for (String find : XML_FILESET_LIST) {
if (xml.contains(find)) {
found = true;
break;
}
}
return found;
}
private static boolean isValidCheckstyleXml(String fileName, String code,
String unserializedSource)
throws IOException, CheckstyleException {
// can't process non-existent examples, or out of context snippets
if (!code.contains("com.mycompany") && !code.contains("checkstyle-packages")
&& !code.contains("MethodLimit") && !code.contains("<suppress ")
&& !code.contains("<import-control ")
&& !unserializedSource.startsWith("<property ")
&& !unserializedSource.startsWith("<taskdef ")) {
// validate checkstyle structure and contents
try {
final Properties properties = new Properties();
properties.setProperty("checkstyle.header.file",
new File("config/java.header").getCanonicalPath());
final PropertiesExpander expander = new PropertiesExpander(properties);
final Configuration config = ConfigurationLoader.loadConfiguration(new InputSource(
new StringReader(code)), expander, false);
final Checker checker = new Checker();
try {
final ClassLoader moduleClassLoader = Checker.class.getClassLoader();
checker.setModuleClassLoader(moduleClassLoader);
checker.configure(config);
}
finally {
checker.destroy();
}
}
catch (CheckstyleException ex) {
throw new CheckstyleException(fileName + " has invalid Checkstyle xml ("
+ ex.getMessage() + "): " + unserializedSource, ex);
}
}
return true;
}
@Test
public void testAllCheckSections() throws Exception {
final ModuleFactory moduleFactory = TestUtil.getPackageObjectFactory();
for (Path path : XdocUtil.getXdocsConfigFilePaths(XdocUtil.getXdocsFilePaths())) {
final String fileName = path.getFileName().toString();
if ("config_reporting.xml".equals(fileName)) {
continue;
}
final String input = new String(Files.readAllBytes(path), UTF_8);
final Document document = XmlUtil.getRawXml(fileName, input, input);
final NodeList sources = document.getElementsByTagName("section");
String lastSectionName = null;
for (int position = 0; position < sources.getLength(); position++) {
final Node section = sources.item(position);
final String sectionName = section.getAttributes().getNamedItem("name")
.getNodeValue();
if ("Content".equals(sectionName) || "Overview".equals(sectionName)) {
Assert.assertNull(fileName + " section '" + sectionName + "' should be first",
lastSectionName);
continue;
}
Assert.assertTrue(fileName + " section '" + sectionName
+ "' shouldn't end with 'Check'", !sectionName.endsWith("Check"));
if (lastSectionName != null) {
Assert.assertTrue(
fileName + " section '" + sectionName
+ "' is out of order compared to '" + lastSectionName + "'",
sectionName.toLowerCase(Locale.ENGLISH).compareTo(
lastSectionName.toLowerCase(Locale.ENGLISH)) >= 0);
}
validateCheckSection(moduleFactory, fileName, sectionName, section);
lastSectionName = sectionName;
}
}
}
/**
* Test contains asserts in callstack, but idea does not see them
* @noinspection JUnitTestMethodWithNoAssertions
*/
@Test
public void testAllCheckSectionsEx() throws Exception {
final ModuleFactory moduleFactory = TestUtil.getPackageObjectFactory();
final Path path = Paths.get(XdocUtil.DIRECTORY_PATH + "/config.xml");
final String fileName = path.getFileName().toString();
final String input = new String(Files.readAllBytes(path), UTF_8);
final Document document = XmlUtil.getRawXml(fileName, input, input);
final NodeList sources = document.getElementsByTagName("section");
for (int position = 0; position < sources.getLength(); position++) {
final Node section = sources.item(position);
final String sectionName = section.getAttributes().getNamedItem("name")
.getNodeValue();
if (!"Checker".equals(sectionName) && !"TreeWalker".equals(sectionName)) {
continue;
}
validateCheckSection(moduleFactory, fileName, sectionName, section);
}
}
private static void validateCheckSection(ModuleFactory moduleFactory, String fileName,
String sectionName, Node section) throws Exception {
final Object instance;
try {
instance = moduleFactory.createModule(sectionName);
}
catch (CheckstyleException ex) {
throw new CheckstyleException(fileName + " couldn't find class: " + sectionName, ex);
}
int subSectionPos = 0;
for (Node subSection : XmlUtil.getChildrenElements(section)) {
final String subSectionName = subSection.getAttributes().getNamedItem("name")
.getNodeValue();
// can be in different orders, and completely optional
if ("Notes".equals(subSectionName)
|| "Rule Description".equals(subSectionName)) {
continue;
}
// optional sections that can be skipped if they have nothing to report
if (subSectionPos == 1 && !"Properties".equals(subSectionName)) {
validatePropertySection(fileName, sectionName, null, instance);
subSectionPos++;
}
if (subSectionPos == 4 && !"Error Messages".equals(subSectionName)) {
validateErrorSection(fileName, sectionName, null, instance);
subSectionPos++;
}
Assert.assertEquals(fileName + " section '" + sectionName
+ "' should be in order", getSubSectionName(subSectionPos),
subSectionName);
switch (subSectionPos) {
case 0:
validateSinceDescriptionSection(fileName, sectionName, subSection);
break;
case 1:
validatePropertySection(fileName, sectionName, subSection, instance);
break;
case 2:
break;
case 3:
validateUsageExample(fileName, sectionName, subSection);
break;
case 4:
validateErrorSection(fileName, sectionName, subSection, instance);
break;
case 5:
validatePackageSection(fileName, sectionName, subSection, instance);
break;
case 6:
validateParentSection(fileName, sectionName, subSection);
break;
default:
break;
}
subSectionPos++;
}
}
private static void validateSinceDescriptionSection(String fileName, String sectionName,
Node subSection) {
Assert.assertTrue(fileName + " section '" + sectionName
+ "' should have a valid version at the start of the description like:\n"
+ DESCRIPTION_VERSION.pattern(),
DESCRIPTION_VERSION.matcher(subSection.getTextContent().trim()).find());
}
private static Object getSubSectionName(int subSectionPos) {
final String result;
switch (subSectionPos) {
case 0:
result = "Description";
break;
case 1:
result = "Properties";
break;
case 2:
result = "Examples";
break;
case 3:
result = "Example of Usage";
break;
case 4:
result = "Error Messages";
break;
case 5:
result = "Package";
break;
case 6:
result = "Parent Module";
break;
default:
result = null;
break;
}
return result;
}
private static void validatePropertySection(String fileName, String sectionName,
Node subSection, Object instance) throws Exception {
final Set<String> properties = getProperties(instance.getClass());
final Class<?> clss = instance.getClass();
fixCapturedProperties(sectionName, instance, clss, properties);
if (subSection != null) {
Assert.assertTrue(fileName + " section '" + sectionName
+ "' should have no properties to show", !properties.isEmpty());
validatePropertySectionProperties(fileName, sectionName, subSection, instance,
properties);
}
Assert.assertTrue(fileName + " section '" + sectionName + "' should show properties: "
+ properties, properties.isEmpty());
}
private static void fixCapturedProperties(String sectionName, Object instance, Class<?> clss,
Set<String> properties) {
// remove global properties that don't need documentation
if (hasParentModule(sectionName)) {
if (AbstractJavadocCheck.class.isAssignableFrom(clss)) {
properties.removeAll(JAVADOC_CHECK_PROPERTIES);
}
else if (AbstractCheck.class.isAssignableFrom(clss)) {
properties.removeAll(CHECK_PROPERTIES);
}
}
if (AbstractFileSetCheck.class.isAssignableFrom(clss)) {
properties.removeAll(FILESET_PROPERTIES);
// override
properties.add("fileExtensions");
}
// remove undocumented properties
new HashSet<>(properties).stream()
.filter(prop -> UNDOCUMENTED_PROPERTIES.contains(clss.getSimpleName() + "." + prop))
.forEach(properties::remove);
if (AbstractCheck.class.isAssignableFrom(clss)) {
final AbstractCheck check = (AbstractCheck) instance;
final int[] acceptableTokens = check.getAcceptableTokens();
Arrays.sort(acceptableTokens);
final int[] defaultTokens = check.getDefaultTokens();
Arrays.sort(defaultTokens);
final int[] requiredTokens = check.getRequiredTokens();
Arrays.sort(requiredTokens);
if (!Arrays.equals(acceptableTokens, defaultTokens)
|| !Arrays.equals(acceptableTokens, requiredTokens)) {
properties.add("tokens");
}
}
if (AbstractJavadocCheck.class.isAssignableFrom(clss)) {
final AbstractJavadocCheck check = (AbstractJavadocCheck) instance;
final int[] acceptableJavadocTokens = check.getAcceptableJavadocTokens();
Arrays.sort(acceptableJavadocTokens);
final int[] defaultJavadocTokens = check.getDefaultJavadocTokens();
Arrays.sort(defaultJavadocTokens);
final int[] requiredJavadocTokens = check.getRequiredJavadocTokens();
Arrays.sort(requiredJavadocTokens);
if (!Arrays.equals(acceptableJavadocTokens, defaultJavadocTokens)
|| !Arrays.equals(acceptableJavadocTokens, requiredJavadocTokens)) {
properties.add("javadocTokens");
}
}
}
private static void validatePropertySectionProperties(String fileName, String sectionName,
Node subSection, Object instance, Set<String> properties) throws Exception {
boolean skip = true;
boolean didJavadocTokens = false;
boolean didTokens = false;
for (Node row : XmlUtil.getChildrenElements(XmlUtil.getFirstChildElement(subSection))) {
final List<Node> columns = new ArrayList<>(XmlUtil.getChildrenElements(row));
Assert.assertEquals(fileName + " section '" + sectionName
+ "' should have the requested columns", 5, columns.size());
if (skip) {
Assert.assertEquals(fileName + " section '" + sectionName
+ "' should have the specific title", "name", columns.get(0)
.getTextContent());
Assert.assertEquals(fileName + " section '" + sectionName
+ "' should have the specific title", "description", columns.get(1)
.getTextContent());
Assert.assertEquals(fileName + " section '" + sectionName
+ "' should have the specific title", "type", columns.get(2)
.getTextContent());
Assert.assertEquals(fileName + " section '" + sectionName
+ "' should have the specific title", "default value", columns.get(3)
.getTextContent());
Assert.assertEquals(fileName + " section '" + sectionName
+ "' should have the specific title", "since", columns.get(4)
.getTextContent());
skip = false;
continue;
}
Assert.assertFalse(fileName + " section '" + sectionName
+ "' should have token properties last", didTokens);
final String propertyName = columns.get(0).getTextContent();
Assert.assertTrue(fileName + " section '" + sectionName
+ "' should not contain the property: " + propertyName,
properties.remove(propertyName));
if ("tokens".equals(propertyName)) {
final AbstractCheck check = (AbstractCheck) instance;
validatePropertySectionPropertyTokens(fileName, sectionName, check, columns);
didTokens = true;
}
else if ("javadocTokens".equals(propertyName)) {
final AbstractJavadocCheck check = (AbstractJavadocCheck) instance;
validatePropertySectionPropertyJavadocTokens(fileName, sectionName, check, columns);
didJavadocTokens = true;
}
else {
Assert.assertFalse(fileName + " section '" + sectionName
+ "' should have javadoc token properties next to last, before tokens",
didJavadocTokens);
validatePropertySectionPropertyEx(fileName, sectionName, instance, columns,
propertyName);
}
Assert.assertFalse(fileName + " section '" + sectionName
+ "' should have a version for " + propertyName, columns.get(4)
.getTextContent().trim().isEmpty());
Assert.assertTrue(fileName + " section '" + sectionName
+ "' should have a valid version for " + propertyName,
VERSION.matcher(columns.get(4).getTextContent().trim()).matches());
}
}
private static void validatePropertySectionPropertyEx(String fileName, String sectionName,
Object instance, List<Node> columns, String propertyName) throws Exception {
Assert.assertFalse(fileName + " section '" + sectionName
+ "' should have a description for " + propertyName, columns.get(1)
.getTextContent().trim().isEmpty());
final String actualTypeName = columns.get(2).getTextContent().replace("\n", "")
.replace("\r", "").replaceAll(" +", " ").trim();
Assert.assertFalse(fileName + " section '" + sectionName + "' should have a type for "
+ propertyName, actualTypeName.isEmpty());
final PropertyDescriptor descriptor = PropertyUtils.getPropertyDescriptor(instance,
propertyName);
final Class<?> clss = descriptor.getPropertyType();
final String expectedTypeName = getModulePropertyExpectedTypeName(clss, instance,
propertyName);
if (expectedTypeName != null) {
final String expectedValue = getModulePropertyExpectedValue(clss, instance,
propertyName);
Assert.assertEquals(fileName + " section '" + sectionName
+ "' should have the type for " + propertyName, expectedTypeName,
actualTypeName);
if (expectedValue != null) {
final String actualValue = columns.get(3).getTextContent().replace("\n", "")
.replace("\r", "").replaceAll(" +", " ").trim();
Assert.assertEquals(fileName + " section '" + sectionName
+ "' should have the value for " + propertyName, expectedValue,
actualValue);
}
}
}
private static void validatePropertySectionPropertyTokens(String fileName, String sectionName,
AbstractCheck check, List<Node> columns) {
Assert.assertEquals(fileName + " section '" + sectionName
+ "' should have the basic token description", "tokens to check", columns.get(1)
.getTextContent());
Assert.assertEquals(
fileName + " section '" + sectionName + "' should have all the acceptable tokens",
"subset of tokens "
+ CheckUtil.getTokenText(check.getAcceptableTokens(),
check.getRequiredTokens()), columns.get(2).getTextContent()
.replaceAll("\\s+", " ").trim());
Assert.assertEquals(fileName + " section '" + sectionName
+ "' should have all the default tokens",
CheckUtil.getTokenText(check.getDefaultTokens(), check.getRequiredTokens()),
columns.get(3).getTextContent().replaceAll("\\s+", " ").trim());
}
private static void validatePropertySectionPropertyJavadocTokens(String fileName,
String sectionName, AbstractJavadocCheck check, List<Node> columns) {
Assert.assertEquals(fileName + " section '" + sectionName
+ "' should have the basic token javadoc description", "javadoc tokens to check",
columns.get(1).getTextContent());
Assert.assertEquals(
fileName + " section '" + sectionName
+ "' should have all the acceptable javadoc tokens",
"subset of javadoc tokens "
+ CheckUtil.getJavadocTokenText(check.getAcceptableJavadocTokens(),
check.getRequiredJavadocTokens()), columns.get(2).getTextContent()
.replaceAll("\\s+", " ").trim());
Assert.assertEquals(
fileName + " section '" + sectionName
+ "' should have all the default javadoc tokens",
CheckUtil.getJavadocTokenText(check.getDefaultJavadocTokens(),
check.getRequiredJavadocTokens()), columns.get(3).getTextContent()
.replaceAll("\\s+", " ").trim());
}
/** @noinspection IfStatementWithTooManyBranches */
private static String getModulePropertyExpectedTypeName(Class<?> clss, Object instance,
String propertyName) {
final String instanceName = instance.getClass().getSimpleName();
String result = null;
if (clss == boolean.class) {
result = "Boolean";
}
else if (clss == int.class) {
result = "Integer";
}
else if (clss == int[].class) {
result = "Integer Set";
}
else if (clss == double[].class) {
result = "Number Set";
}
else if (clss == String[].class) {
if (propertyName.endsWith("Tokens") || propertyName.endsWith("Token")
|| "AtclauseOrderCheck".equals(instanceName) && "target".equals(propertyName)
|| "MultipleStringLiteralsCheck".equals(instanceName)
&& "ignoreOccurrenceContext".equals(propertyName)) {
result = "subset of tokens TokenTypes";
}
else {
result = "String Set";
}
}
else if (clss == URI.class) {
result = "URI";
}
else if (clss == Pattern.class) {
result = "Regular Expression";
}
else if (clss == SeverityLevel.class) {
result = "Severity";
}
else if (clss == Scope.class) {
result = "Scope";
}
else if (clss == AccessModifier[].class) {
result = "Access Modifier Set";
}
else if (clss != String.class) {
Assert.fail("Unknown property type: " + clss.getSimpleName());
}
if ("SuppressWarningsHolder".equals(instanceName)) {
result = result + " in a format of comma separated attribute=value entries. The "
+ "attribute is the fully qualified name of the Check and value is its alias.";
}
return result;
}
private static String getModulePropertyExpectedValue(Class<?> clss, Object instance,
String propertyName) throws Exception {
final Field field = getField(instance.getClass(), propertyName);
String result = null;
if (field != null) {
final Object value = field.get(instance);
if (clss == boolean.class) {
result = value.toString();
}
else if (clss == int.class) {
if (value.equals(Integer.MAX_VALUE)) {
result = "java.lang.Integer.MAX_VALUE";
}
else {
result = value.toString();
}
}
else if (clss == int[].class) {
result = Arrays.toString((int[]) value).replace("[", "").replace("]", "");
if (result.isEmpty()) {
result = "{}";
}
}
else if (clss == double[].class) {
result = Arrays.toString((double[]) value).replace("[", "").replace("]", "")
.replace(".0", "");
if (result.isEmpty()) {
result = "{}";
}
}
else if (clss == URI.class) {
if (value != null) {
result = '"' + value.toString() + '"';
}
}
else if (clss == Pattern.class) {
if (value != null) {
result = '"' + value.toString().replace("\n", "\\n").replace("\t", "\\t")
.replace("\r", "\\r").replace("\f", "\\f") + '"';
}
if ("\"$^\"".equals(result)) {
result += " (empty)";
}
}
else if (value != null && (clss == SeverityLevel.class || clss == Scope.class)) {
result = value.toString().toLowerCase(Locale.ENGLISH);
}
else if (value != null && clss == AccessModifier[].class) {
result = Arrays.toString((Object[]) value).replace("[", "").replace("]", "");
}
if (clss != String.class && clss != String[].class && result == null) {
result = "null";
}
}
return result;
}
private static Field getField(Class<?> clss, String propertyName) {
Field result = null;
if (clss != null) {
try {
result = clss.getDeclaredField(propertyName);
result.setAccessible(true);
}
catch (NoSuchFieldException ignored) {
result = getField(clss.getSuperclass(), propertyName);
}
}
return result;
}
private static void validateErrorSection(String fileName, String sectionName, Node subSection,
Object instance) throws Exception {
final Class<?> clss = instance.getClass();
final Set<Field> fields = CheckUtil.getCheckMessages(clss);
final Set<String> list = new TreeSet<>();
for (Field field : fields) {
// below is required for package/private classes
if (!field.isAccessible()) {
field.setAccessible(true);
}
list.add(field.get(null).toString());
}
final StringBuilder expectedText = new StringBuilder(120);
for (String s : list) {
expectedText.append(s);
expectedText.append('\n');
}
if (expectedText.length() > 0) {
expectedText.append("All messages can be customized if the default message doesn't "
+ "suit you.\nPlease see the documentation to learn how to.");
}
if (subSection == null) {
Assert.assertEquals(fileName + " section '" + sectionName
+ "' should have the expected error keys", "", expectedText.toString());
}
else {
Assert.assertEquals(fileName + " section '" + sectionName
+ "' should have the expected error keys", expectedText.toString().trim(),
subSection.getTextContent().replaceAll("\n\\s+", "\n").trim());
for (Node node : XmlUtil.findChildElementsByTag(subSection, "a")) {
final String url = node.getAttributes().getNamedItem("href").getTextContent();
final String linkText = node.getTextContent().trim();
final String expectedUrl;
if ("see the documentation".equals(linkText)) {
expectedUrl = "config.html#Custom_messages";
}
else {
expectedUrl = "https://github.com/search?q="
+ "path%3Asrc%2Fmain%2Fresources%2F"
+ clss.getPackage().getName().replace(".", "%2F")
+ "+filename%3Amessages*.properties+repo%3Acheckstyle%2Fcheckstyle+%22"
+ linkText + "%22";
}
Assert.assertEquals(fileName + " section '" + sectionName
+ "' should have matching url for '" + linkText + "'", expectedUrl, url);
}
}
}
private static void validateUsageExample(String fileName, String sectionName, Node subSection) {
final String text = subSection.getTextContent().replace("Checkstyle Style", "")
.replace("Google Style", "").replace("Sun Style", "").trim();
Assert.assertTrue(fileName + " section '" + sectionName
+ "' has unknown text in 'Example of Usage': " + text, text.isEmpty());
boolean hasCheckstyle = false;
boolean hasGoogle = false;
boolean hasSun = false;
for (Node node : XmlUtil.findChildElementsByTag(subSection, "a")) {
final String url = node.getAttributes().getNamedItem("href").getTextContent();
final String linkText = node.getTextContent().trim();
String expectedUrl = null;
if ("Checkstyle Style".equals(linkText)) {
hasCheckstyle = true;
expectedUrl = "https://github.com/search?q="
+ "path%3Aconfig+filename%3Acheckstyle_checks.xml+"
+ "repo%3Acheckstyle%2Fcheckstyle+" + sectionName;
}
else if ("Google Style".equals(linkText)) {
hasGoogle = true;
expectedUrl = "https://github.com/search?q="
+ "path%3Asrc%2Fmain%2Fresources+filename%3Agoogle_checks.xml+"
+ "repo%3Acheckstyle%2Fcheckstyle+"
+ sectionName;
Assert.assertTrue(fileName + " section '" + sectionName
+ "' should be in google_checks.xml or not reference 'Google Style'",
GOOGLE_MODULES.contains(sectionName));
}
else if ("Sun Style".equals(linkText)) {
hasSun = true;
expectedUrl = "https://github.com/search?q="
+ "path%3Asrc%2Fmain%2Fresources+filename%3Asun_checks.xml+"
+ "repo%3Acheckstyle%2Fcheckstyle+"
+ sectionName;
Assert.assertTrue(fileName + " section '" + sectionName
+ "' should be in sun_checks.xml or not reference 'Sun Style'",
SUN_MODULES.contains(sectionName));
}
Assert.assertEquals(fileName + " section '" + sectionName
+ "' should have matching url", expectedUrl, url);
}
Assert.assertTrue(fileName + " section '" + sectionName
+ "' should have a checkstyle section", hasCheckstyle);
Assert.assertTrue(fileName + " section '" + sectionName
+ "' should have a google section since it is in it's config", hasGoogle
|| !GOOGLE_MODULES.contains(sectionName));
Assert.assertTrue(fileName + " section '" + sectionName
+ "' should have a sun section since it is in it's config",
hasSun || !SUN_MODULES.contains(sectionName));
}
private static void validatePackageSection(String fileName, String sectionName,
Node subSection, Object instance) {
Assert.assertEquals(fileName + " section '" + sectionName
+ "' should have matching package", instance.getClass().getPackage().getName(),
subSection.getTextContent().trim());
}
private static void validateParentSection(String fileName, String sectionName,
Node subSection) {
final String expected;
if (hasParentModule(sectionName)) {
expected = "TreeWalker";
}
else {
expected = "Checker";
}
Assert.assertEquals(
fileName + " section '" + sectionName + "' should have matching parent",
expected, subSection
.getTextContent().trim());
}
private static boolean hasParentModule(String sectionName) {
final String search = "\"" + sectionName + "\"";
boolean result = true;
for (String find : XML_FILESET_LIST) {
if (find.contains(search)) {
result = false;
break;
}
}
return result;
}
private static Set<String> getProperties(Class<?> clss) {
final Set<String> result = new TreeSet<>();
final PropertyDescriptor[] map = PropertyUtils.getPropertyDescriptors(clss);
for (PropertyDescriptor p : map) {
if (p.getWriteMethod() != null) {
result.add(p.getName());
}
}
return result;
}
@Test
public void testAllStyleRules() throws Exception {
for (Path path : XdocUtil.getXdocsStyleFilePaths(XdocUtil.getXdocsFilePaths())) {
final String fileName = path.getFileName().toString();
final String input = new String(Files.readAllBytes(path), UTF_8);
final Document document = XmlUtil.getRawXml(fileName, input, input);
final NodeList sources = document.getElementsByTagName("tr");
Set<String> styleChecks = null;
if (path.toFile().getName().contains("google")) {
styleChecks = new HashSet<>(GOOGLE_MODULES);
}
else if (path.toFile().getName().contains("sun")) {
styleChecks = new HashSet<>();
}
String lastRuleName = null;
for (int position = 0; position < sources.getLength(); position++) {
final Node row = sources.item(position);
final List<Node> columns = new ArrayList<>(
XmlUtil.findChildElementsByTag(row, "td"));
if (columns.isEmpty()) {
continue;
}
final String ruleName = columns.get(1).getTextContent().trim();
if (lastRuleName != null) {
Assert.assertTrue(
fileName + " rule '" + ruleName + "' is out of order compared to '"
+ lastRuleName + "'",
ruleName.toLowerCase(Locale.ENGLISH).compareTo(
lastRuleName.toLowerCase(Locale.ENGLISH)) >= 0);
}
if (!"--".equals(ruleName)) {
validateStyleAnchors(XmlUtil.findChildElementsByTag(columns.get(0), "a"),
fileName, ruleName);
}
validateStyleModules(XmlUtil.findChildElementsByTag(columns.get(2), "a"),
XmlUtil.findChildElementsByTag(columns.get(3), "a"), styleChecks, fileName,
ruleName);
lastRuleName = ruleName;
}
// these modules aren't documented, but are added to the config
styleChecks.remove("TreeWalker");
styleChecks.remove("Checker");
Assert.assertTrue(fileName + " requires the following check(s) to appear: "
+ styleChecks, styleChecks.isEmpty());
}
}
private static void validateStyleAnchors(Set<Node> anchors, String fileName, String ruleName) {
Assert.assertEquals(fileName + " rule '" + ruleName + "' must have two row anchors", 2,
anchors.size());
final int space = ruleName.indexOf(' ');
Assert.assertTrue(fileName + " rule '" + ruleName
+ "' must have have a space between the rule's number and the rule's name",
space != -1);
final String ruleNumber = ruleName.substring(0, space);
int position = 1;
for (Node anchor : anchors) {
final String actualUrl;
final String expectedUrl;
if (position == 1) {
actualUrl = anchor.getAttributes().getNamedItem("name").getTextContent();
expectedUrl = ruleNumber;
}
else {
actualUrl = anchor.getAttributes().getNamedItem("href").getTextContent();
expectedUrl = "#" + ruleNumber;
}
Assert.assertEquals(fileName + " rule '" + ruleName + "' anchor " + position
+ " should have matching name/url", expectedUrl, actualUrl);
position++;
}
}
private static void validateStyleModules(Set<Node> checks, Set<Node> configs,
Set<String> styleChecks, String fileName, String ruleName) {
final Iterator<Node> itrChecks = checks.iterator();
final Iterator<Node> itrConfigs = configs.iterator();
while (itrChecks.hasNext()) {
final Node module = itrChecks.next();
final String moduleName = module.getTextContent().trim();
if (!module.getAttributes().getNamedItem("href").getTextContent()
.startsWith("config_")) {
continue;
}
Assert.assertTrue(fileName + " rule '" + ruleName + "' module '" + moduleName
+ "' shouldn't end with 'Check'", !moduleName.endsWith("Check"));
styleChecks.remove(moduleName);
for (String configName : new String[] {"config", "test"}) {
Node config = null;
try {
config = itrConfigs.next();
}
catch (NoSuchElementException ignore) {
Assert.fail(fileName + " rule '" + ruleName + "' module '" + moduleName
+ "' is missing the config link: " + configName);
}
Assert.assertEquals(fileName + " rule '" + ruleName + "' module '" + moduleName
+ "' has mismatched config/test links", configName, config.getTextContent()
.trim());
final String configUrl = config.getAttributes().getNamedItem("href")
.getTextContent();
if ("config".equals(configName)) {
final String expectedUrl = "https://github.com/search?q="
+ "path%3Asrc%2Fmain%2Fresources+filename%3Agoogle_checks.xml+"
+ "repo%3Acheckstyle%2Fcheckstyle+" + moduleName;
Assert.assertEquals(fileName + " rule '" + ruleName + "' module '" + moduleName
+ "' should have matching " + configName + " url", expectedUrl,
configUrl);
}
else if ("test".equals(configName)) {
Assert.assertTrue(fileName + " rule '" + ruleName + "' module '" + moduleName
+ "' should have matching " + configName + " url",
configUrl.startsWith("https://github.com/checkstyle/checkstyle/"
+ "blob/master/src/it/java/com/google/checkstyle/test/"));
Assert.assertTrue(fileName + " rule '" + ruleName + "' module '" + moduleName
+ "' should have matching " + configName + " url",
configUrl.endsWith("/" + moduleName + "Test.java"));
Assert.assertTrue(fileName + " rule '" + ruleName + "' module '" + moduleName
+ "' should have a test that exists", new File(configUrl.substring(53)
.replace('/', File.separatorChar)).exists());
}
}
}
Assert.assertFalse(fileName + " rule '" + ruleName + "' has too many configs",
itrConfigs.hasNext());
}
}