-
Notifications
You must be signed in to change notification settings - Fork 6
Issue #79: Processing UT changes to get possible property values #85
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: master
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,88 @@ | ||
| //////////////////////////////////////////////////////////////////////////////// | ||
| // 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. | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Update documentation that check needs a public/static way to retrieve it's own results using this method. |
||
| * | ||
| * <p>The custom check needs a public/static function to retrieve the processing result.</p> | ||
| * @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,268 @@ | ||
| //////////////////////////////////////////////////////////////////////////////// | ||
| // 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 java.util.regex.Matcher; | ||
| import java.util.regex.Pattern; | ||
|
|
||
| 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>The 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> | ||
| * | ||
| * <p>The check also support to detect module config which defined as a class field. This kind | ||
| * of config is detected by the assignment in the unit test method, like {@code checkConfig = | ||
| * createModuleConfig(FooCheck.class)}, where {@code checkConfig} could be a class field.</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); | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
I don't see an update or any tests for this. |
||
| 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 = | ||
| convertExpressionToText(elist.getFirstChild().getFirstChild()); | ||
| final String value = | ||
| convertExpressionToText(elist.getLastChild().getFirstChild()); | ||
| 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()); | ||
| } | ||
| } | ||
| else if (ast.getType() == TokenTypes.EXPR | ||
| && ast.getFirstChild().getType() == TokenTypes.ASSIGN) { | ||
| final DetailAST exprChild = ast.getFirstChild(); | ||
| if (isCreateModuleConfigAssign(exprChild)) { | ||
| returnValue = Optional.of(exprChild.getFirstChild().getText()); | ||
| } | ||
| } | ||
| if (returnValue.isPresent()) { | ||
| 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 methodCall; | ||
| if (ast.findFirstToken(TokenTypes.METHOD_CALL) == null) { | ||
| methodCall = ast.getFirstChild().getFirstChild(); | ||
| } | ||
| else { | ||
| methodCall = ast.findFirstToken(TokenTypes.METHOD_CALL); | ||
| } | ||
| result = methodCall.getType() == TokenTypes.METHOD_CALL | ||
| && methodCall.getFirstChild().getType() == TokenTypes.IDENT | ||
| && "createModuleConfig".equals(methodCall.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 content to raw text. | ||
| * @param ast the first child of expression ast to convert | ||
| * @return the converted raw text | ||
| */ | ||
| private String convertExpressionToText(DetailAST ast) { | ||
| String result = null; | ||
| if (ast.getType() == TokenTypes.STRING_LITERAL) { | ||
| final String original = ast.getText(); | ||
| result = original.substring(1, original.length() - 1); | ||
| } | ||
| else if (ast.getType() == TokenTypes.PLUS) { | ||
| result = convertExpressionToText(ast.getFirstChild()) | ||
| + convertExpressionToText(ast.getLastChild()); | ||
| } | ||
| else if (ast.getType() == TokenTypes.LITERAL_NULL) { | ||
| result = ast.getText(); | ||
| } | ||
| else if (ast.getType() == TokenTypes.METHOD_CALL) { | ||
| final String line = getFileContents().getLine(ast.getLineNo() - 1); | ||
| final Pattern pattern = Pattern.compile( | ||
| "\\.addAttribute\\(.+, (?:.+\\.)*(.+)\\.toString\\(\\)\\);"); | ||
| final Matcher matcher = pattern.matcher(line); | ||
| if (matcher.find()) { | ||
| result = matcher.group(1); | ||
| } | ||
| } | ||
|
|
||
| if (result == null) { | ||
| throw new IllegalStateException("the processor cannot support this ast: " + ast); | ||
| } | ||
|
|
||
| return result; | ||
| } | ||
| } | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Please explain this change.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Without this pmd would force us to add javadoc on
@Overridemethod. I copied this settings from checkstyle repo.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ok, thanks.
This can be ignored.