Skip to content

Commit

Permalink
Issue #79: Processing UT changes to get possible property values
Browse files Browse the repository at this point in the history
  • Loading branch information
Luolc committed Aug 24, 2017
1 parent f92362c commit f66cd92
Show file tree
Hide file tree
Showing 9 changed files with 594 additions and 1 deletion.
4 changes: 4 additions & 0 deletions config/import-control.xml
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,10 @@
<allow pkg="org.xml"/>
</subpackage>

<subpackage name="customcheck">
<allow pkg="com.puppycrawl.tools.checkstyle"/>
</subpackage>

<subpackage name="data">
<!-- it is only allowed until https://github.com/checkstyle/checkstyle/issues/3492 -->
<allow pkg="org.immutables.gson"/>
Expand Down
6 changes: 5 additions & 1 deletion config/pmd.xml
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,11 @@
<!-- we use class comments as source for xdoc files, so content is big and that is by design -->
<exclude name="CommentSize"/>
</rule>
<rule ref="rulesets/java/comments.xml/CommentRequired" />
<rule ref="rulesets/java/comments.xml/CommentRequired">
<properties>
<property name="publicMethodCommentRequirement" value="Ignored"/>
</properties>
</rule>
<rule ref="rulesets/java/comments.xml/CommentSize">
<properties>
<!-- we use class comments as source for xdoc files, so content is big and that is by design -->
Expand Down
5 changes: 5 additions & 0 deletions pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -393,6 +393,11 @@
<branchRate>0</branchRate>
<lineRate>0</lineRate>
</regex>
<regex>
<pattern>com.github.checkstyle.regression.customcheck.UnitTestProcessorCheck</pattern>
<branchRate>76</branchRate>
<lineRate>92</lineRate>
</regex>
<regex>
<pattern>com.github.checkstyle.regression.report.ReportGenerator</pattern>
<branchRate>0</branchRate>
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
////////////////////////////////////////////////////////////////////////////////
// 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.github.checkstyle.regression.customcheck;

import java.io.File;
import java.util.Collections;
import java.util.List;

import com.puppycrawl.tools.checkstyle.Checker;
import com.puppycrawl.tools.checkstyle.DefaultConfiguration;
import com.puppycrawl.tools.checkstyle.TreeWalker;
import com.puppycrawl.tools.checkstyle.api.CheckstyleException;
import com.puppycrawl.tools.checkstyle.api.Configuration;

/**
* Processes the specific file using our custom check.
*
* <p>This utility class would run a custom check on the file with the given path and return
* the collected property info.</p>
* @author LuoLiangchen
*/
public final class CustomCheckProcessor {
/** Prevents instantiation. */
private CustomCheckProcessor() {
}

/**
* Processes the file with the given path using the given custom check.
* @param path the path of the file
* @param checkClass the class of the custom check
* @throws CheckstyleException failure when running the check
*/
public static void process(String path, Class<?> checkClass) throws CheckstyleException {
final DefaultConfiguration moduleConfig = createModuleConfig(checkClass);
final Configuration dc = createTreeWalkerConfig(moduleConfig);
final Checker checker = new Checker();
checker.setModuleClassLoader(Thread.currentThread().getContextClassLoader());
checker.configure(dc);
final List<File> processedFiles = Collections.singletonList(new File(path));
checker.process(processedFiles);
}

/**
* Creates {@link DefaultConfiguration} for the {@link TreeWalker}
* based on the given {@link Configuration} instance.
* @param config {@link Configuration} instance.
* @return {@link DefaultConfiguration} for the {@link TreeWalker}
* based on the given {@link Configuration} instance.
*/
private static DefaultConfiguration createTreeWalkerConfig(Configuration config) {
final DefaultConfiguration dc =
new DefaultConfiguration("configuration");
final DefaultConfiguration twConf = createModuleConfig(TreeWalker.class);
// make sure that the tests always run with this charset
dc.addAttribute("charset", "UTF-8");
dc.addChild(twConf);
twConf.addChild(config);
return dc;
}

/**
* Creates {@link DefaultConfiguration} for the given class.
* @param clazz the class of module
* @return the {@link DefaultConfiguration} of the module class
*/
private static DefaultConfiguration createModuleConfig(Class<?> clazz) {
return new DefaultConfiguration(clazz.getName());
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,220 @@
////////////////////////////////////////////////////////////////////////////////
// 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.github.checkstyle.regression.customcheck;

import java.util.Collections;
import java.util.LinkedHashMap;
import java.util.LinkedHashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.Set;

import com.github.checkstyle.regression.data.ImmutableProperty;
import com.github.checkstyle.regression.data.ModuleInfo;
import com.puppycrawl.tools.checkstyle.DefaultConfiguration;
import com.puppycrawl.tools.checkstyle.api.AbstractCheck;
import com.puppycrawl.tools.checkstyle.api.DetailAST;
import com.puppycrawl.tools.checkstyle.api.TokenTypes;

/**
* The custom check which processes the unit test class of a checkstyle module and grab the
* possible properties that could be used for generating config.
*
* <p>This check would walk through the {@code @Test} annotation, find variable definition
* like {@code final DefaultConfiguration checkConfig = createModuleConfig(FooCheck.class)}
* and grab the property info from {@link DefaultConfiguration#addAttribute(String, String)}
* method call.</p>
* @author LuoLiangchen
*/
public class UnitTestProcessorCheck extends AbstractCheck {
/** The map of unit test method name to properties. */
private static final Map<String, Set<ModuleInfo.Property>> UNIT_TEST_TO_PROPERTIES =
new LinkedHashMap<>();

@Override
public int[] getDefaultTokens() {
return new int[] {
TokenTypes.ANNOTATION,
};
}

@Override
public int[] getRequiredTokens() {
return new int[] {
TokenTypes.ANNOTATION,
};
}

@Override
public int[] getAcceptableTokens() {
return new int[] {
TokenTypes.ANNOTATION,
};
}

@Override
public void visitToken(DetailAST ast) {
if ("Test".equals(ast.findFirstToken(TokenTypes.IDENT).getText())) {
final DetailAST methodDef = ast.getParent().getParent();
final DetailAST methodBlock = methodDef.findFirstToken(TokenTypes.SLIST);
final Optional<String> configVariableName =
getModuleConfigVariableName(methodBlock);
if (configVariableName.isPresent()) {
final Set<ModuleInfo.Property> properties = new LinkedHashSet<>();

for (DetailAST expr : getAllChildrenWithToken(methodBlock, TokenTypes.EXPR)) {
if (isAddAttributeMethodCall(expr.getFirstChild(), configVariableName.get())) {
final DetailAST elist =
expr.getFirstChild().findFirstToken(TokenTypes.ELIST);
final String key = convertExprToText(elist.getFirstChild());
final String value = convertExprToText(elist.getLastChild());
properties.add(ImmutableProperty.builder().name(key).value(value).build());
}
}

if (!UNIT_TEST_TO_PROPERTIES.containsValue(properties)) {
final String methodName = methodDef.findFirstToken(TokenTypes.IDENT).getText();
UNIT_TEST_TO_PROPERTIES.put(methodName, properties);
}
}
}
}

/**
* Clears the map of unit test method name to properties.
*/
public static void clearUnitTestToPropertiesMap() {
UNIT_TEST_TO_PROPERTIES.clear();
}

/**
* Gets the map of unit test method name to properties.
* @return the map of unit test method name to properties
*/
public static Map<String, Set<ModuleInfo.Property>> getUnitTestToPropertiesMap() {
return Collections.unmodifiableMap(UNIT_TEST_TO_PROPERTIES);
}

/**
* Gets the module config variable name, if it exists.
* @param methodBlock the UT method block ast, which should have a type {@link TokenTypes#SLIST}
* @return the optional variable name, if it exists
*/
private static Optional<String> getModuleConfigVariableName(DetailAST methodBlock) {
Optional<String> returnValue = Optional.empty();

for (DetailAST ast = methodBlock.getFirstChild(); ast != null; ast = ast.getNextSibling()) {
if (ast.getType() == TokenTypes.VARIABLE_DEF) {
final DetailAST type = ast.findFirstToken(TokenTypes.TYPE);
final DetailAST assign = ast.findFirstToken(TokenTypes.ASSIGN);
if (isDefaultConfigurationType(type) && isCreateModuleConfigAssign(assign)) {
returnValue = Optional.of(type.getNextSibling().getText());
break;
}
}
}

return returnValue;
}

/**
* Checks whether this {@link TokenTypes#TYPE} ast is {@link DefaultConfiguration}.
* @param ast the {@link TokenTypes#TYPE} ast
* @return true if the type is {@link DefaultConfiguration}
*/
private static boolean isDefaultConfigurationType(DetailAST ast) {
return "DefaultConfiguration".equals(ast.getFirstChild().getText());
}

/**
* Checks whether this {@link TokenTypes#ASSIGN} ast contains
* a {@code createModuleConfig} method call.
* @param ast the {@link TokenTypes#ASSIGN} ast
* @return true if the assignment contains a {@code createModuleConfig} method call
*/
private static boolean isCreateModuleConfigAssign(DetailAST ast) {
final boolean result;

if (ast == null) {
result = false;
}
else {
final DetailAST exprChild = ast.getFirstChild().getFirstChild();
result = exprChild.getType() == TokenTypes.METHOD_CALL
&& exprChild.getFirstChild().getType() == TokenTypes.IDENT
&& "createModuleConfig".equals(exprChild.getFirstChild().getText());
}

return result;
}

/**
* Gets all children of a ast with the given tokens type.
* @param parent the parent ast
* @param type the given tokens type
* @return the children with the given tokens type
*/
private static List<DetailAST> getAllChildrenWithToken(DetailAST parent, int type) {
final List<DetailAST> returnValue = new LinkedList<>();

for (DetailAST ast = parent.getFirstChild(); ast != null; ast = ast.getNextSibling()) {
if (ast.getType() == type) {
returnValue.add(ast);
}
}

return returnValue;
}

/**
* Checks whether this expression is an {@code addAttribute} method call on an instance with
* the given variable name.
* @param ast the ast to check
* @param variableName the given variable name of the module config instance
* @return true if the expression is a valid {@code addAttribute} method call
*/
private static boolean isAddAttributeMethodCall(DetailAST ast, String variableName) {
final boolean result;

if (ast.getType() == TokenTypes.METHOD_CALL
&& ast.getFirstChild().getType() == TokenTypes.DOT) {
final DetailAST dot = ast.getFirstChild();
result = variableName.equals(dot.getFirstChild().getText())
&& "addAttribute".equals(dot.getLastChild().getText());
}
else {
result = false;
}

return result;
}

/**
* Converts an expression to raw text.
* @param ast the expression ast to convert
* @return the converted raw text
*/
private static String convertExprToText(DetailAST ast) {
final String original = ast.getFirstChild().getText();
return original.substring(1, original.length() - 1);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
////////////////////////////////////////////////////////////////////////////////
// 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
////////////////////////////////////////////////////////////////////////////////

/**
* Contains custom checks and utility classes to process files in checkstyle repository.
*/
package com.github.checkstyle.regression.customcheck;
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
////////////////////////////////////////////////////////////////////////////////
// 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.github.checkstyle.regression.customcheck;

import static com.github.checkstyle.regression.internal.TestUtils.assertUtilsClassHasPrivateConstructor;

import org.junit.Test;

public class CustomCheckProcessorTest {
@Test
public void testIsProperUtilsClass() throws Exception {
assertUtilsClassHasPrivateConstructor(CustomCheckProcessor.class);
}
}
Loading

0 comments on commit f66cd92

Please sign in to comment.