001//////////////////////////////////////////////////////////////////////////////// 002// checkstyle: Checks Java source code for adherence to a set of rules. 003// Copyright (C) 2001-2022 the original author or authors. 004// 005// This library is free software; you can redistribute it and/or 006// modify it under the terms of the GNU Lesser General Public 007// License as published by the Free Software Foundation; either 008// version 2.1 of the License, or (at your option) any later version. 009// 010// This library is distributed in the hope that it will be useful, 011// but WITHOUT ANY WARRANTY; without even the implied warranty of 012// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 013// Lesser General Public License for more details. 014// 015// You should have received a copy of the GNU Lesser General Public 016// License along with this library; if not, write to the Free Software 017// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA 018//////////////////////////////////////////////////////////////////////////////// 019 020package com.puppycrawl.tools.checkstyle.checks.coding; 021 022import java.util.List; 023import java.util.stream.Collectors; 024 025import com.puppycrawl.tools.checkstyle.StatelessCheck; 026import com.puppycrawl.tools.checkstyle.api.AbstractCheck; 027import com.puppycrawl.tools.checkstyle.api.DetailAST; 028import com.puppycrawl.tools.checkstyle.utils.CommonUtil; 029import com.puppycrawl.tools.checkstyle.xpath.AbstractNode; 030import com.puppycrawl.tools.checkstyle.xpath.RootNode; 031import net.sf.saxon.Configuration; 032import net.sf.saxon.om.Item; 033import net.sf.saxon.sxpath.XPathDynamicContext; 034import net.sf.saxon.sxpath.XPathEvaluator; 035import net.sf.saxon.sxpath.XPathExpression; 036import net.sf.saxon.trans.XPathException; 037 038/** 039 * <p> 040 * Evaluates Xpath query and report violation on all matching AST nodes. This check allows 041 * user to implement custom checks using Xpath. If Xpath query is not specified explicitly, 042 * then the check does nothing. 043 * </p> 044 * <p> 045 * It is recommended to define custom message for violation to explain what is not allowed and what 046 * to use instead, default message might be too abstract. To customize a message you need to 047 * add {@code message} element with <b>matchxpath.match</b> as {@code key} attribute and 048 * desired message as {@code value} attribute. 049 * </p> 050 * <p> 051 * Please read more about Xpath syntax at 052 * <a href="https://www.saxonica.com/html/documentation10/expressions/">Xpath Syntax</a>. 053 * Information regarding Xpath functions can be found at 054 * <a href="https://www.saxonica.com/html/documentation10/functions/fn/">XSLT/XPath Reference</a>. 055 * Note, that <b>@text</b> attribute can used only with token types that are listed in 056 * <a href="https://github.com/checkstyle/checkstyle/search?q=%22TOKEN_TYPES_WITH_TEXT_ATTRIBUTE+%3D+Arrays.asList%22"> 057 * XpathUtil</a>. 058 * </p> 059 * <ul> 060 * <li> 061 * Property {@code query} - Specify Xpath query. 062 * Type is {@code java.lang.String}. 063 * Default value is {@code ""}. 064 * </li> 065 * </ul> 066 * <p> 067 * Checkstyle provides <a href="https://checkstyle.org/cmdline.html">command line tool</a> 068 * and <a href="https://checkstyle.org/writingchecks.html#The_Checkstyle_SDK_Gui">GUI 069 * application</a> with options to show AST and to ease usage of Xpath queries. 070 * </p> 071 * <p><b>-T</b> option prints AST tree of the checked file.</p> 072 * <pre> 073 * $ java -jar checkstyle-X.XX-all.jar -T Main.java 074 * CLASS_DEF -> CLASS_DEF [1:0] 075 * |--MODIFIERS -> MODIFIERS [1:0] 076 * | `--LITERAL_PUBLIC -> public [1:0] 077 * |--LITERAL_CLASS -> class [1:7] 078 * |--IDENT -> Main [1:13] 079 * `--OBJBLOCK -> OBJBLOCK [1:18] 080 * |--LCURLY -> { [1:18] 081 * |--METHOD_DEF -> METHOD_DEF [2:4] 082 * | |--MODIFIERS -> MODIFIERS [2:4] 083 * | | `--LITERAL_PUBLIC -> public [2:4] 084 * | |--TYPE -> TYPE [2:11] 085 * | | `--IDENT -> String [2:11] 086 * | |--IDENT -> sayHello [2:18] 087 * | |--LPAREN -> ( [2:26] 088 * | |--PARAMETERS -> PARAMETERS [2:27] 089 * | | `--PARAMETER_DEF -> PARAMETER_DEF [2:27] 090 * | | |--MODIFIERS -> MODIFIERS [2:27] 091 * | | |--TYPE -> TYPE [2:27] 092 * | | | `--IDENT -> String [2:27] 093 * | | `--IDENT -> name [2:34] 094 * | |--RPAREN -> ) [2:38] 095 * | `--SLIST -> { [2:40] 096 * | |--LITERAL_RETURN -> return [3:8] 097 * | | |--EXPR -> EXPR [3:25] 098 * | | | `--PLUS -> + [3:25] 099 * | | | |--STRING_LITERAL -> "Hello, " [3:15] 100 * | | | `--IDENT -> name [3:27] 101 * | | `--SEMI -> ; [3:31] 102 * | `--RCURLY -> } [4:4] 103 * `--RCURLY -> } [5:0] 104 * </pre> 105 * <p><b>-b</b> option shows AST nodes that match given Xpath query. This command can be used to 106 * validate accuracy of Xpath query against given file.</p> 107 * <pre> 108 * $ java -jar checkstyle-X.XX-all.jar Main.java -b "//METHOD_DEF[./IDENT[@text='sayHello']]" 109 * CLASS_DEF -> CLASS_DEF [1:0] 110 * `--OBJBLOCK -> OBJBLOCK [1:18] 111 * |--METHOD_DEF -> METHOD_DEF [2:4] 112 * </pre> 113 * <p> 114 * The following example demonstrates validation of methods order, so that public methods should 115 * come before the private ones: 116 * </p> 117 * <pre> 118 * <module name="MatchXpath"> 119 * <property name="query" value="//METHOD_DEF[.//LITERAL_PRIVATE and 120 * following-sibling::METHOD_DEF[.//LITERAL_PUBLIC]]"/> 121 * <message key="matchxpath.match" 122 * value="Private methods must appear after public methods"/> 123 * </module> 124 * </pre> 125 * <p> 126 * Example: 127 * </p> 128 * <pre> 129 * public class Test { 130 * public void method1() { } 131 * private void method2() { } // violation 132 * public void method3() { } 133 * private void method4() { } // violation 134 * public void method5() { } 135 * private void method6() { } // ok 136 * } 137 * </pre> 138 * <p> 139 * To violate if there are any parametrized constructors 140 * </p> 141 * <pre> 142 * <module name="MatchXpath"> 143 * <property name="query" value="//CTOR_DEF[count(./PARAMETERS/*) > 0]"/> 144 * <message key="matchxpath.match" 145 * value="Parameterized constructors are not allowed, there should be only default 146 * ctor"/> 147 * </module> 148 * </pre> 149 * <p> 150 * Example: 151 * </p> 152 * <pre> 153 * public class Test { 154 * public Test(Object c) { } // violation 155 * public Test(int a, HashMap<String, Integer> b) { } // violation 156 * public Test() { } // ok 157 * } 158 * </pre> 159 * <p> 160 * To violate if method name is 'test' or 'foo' 161 * </p> 162 * <pre> 163 * <module name="MatchXpath"> 164 * <property name="query" value="//METHOD_DEF[./IDENT[@text='test' or @text='foo']]"/> 165 * <message key="matchxpath.match" 166 * value="Method name should not be 'test' or 'foo'"/> 167 * </module> 168 * </pre> 169 * <p> 170 * Example: 171 * </p> 172 * <pre> 173 * public class Test { 174 * public void test() {} // violation 175 * public void getName() {} // ok 176 * public void foo() {} // violation 177 * public void sayHello() {} // ok 178 * } 179 * </pre> 180 * <p> 181 * To violate if new instance creation was done without <b>var</b> type 182 * </p> 183 * <pre> 184 * <module name="MatchXpath"> 185 * <property name="query" value="//VARIABLE_DEF[./ASSIGN/EXPR/LITERAL_NEW 186 * and not(./TYPE/IDENT[@text='var'])]"/> 187 * <message key="matchxpath.match" 188 * value="New instances should be created via 'var' keyword to avoid duplication of type 189 * reference in statement"/> 190 * </module> 191 * </pre> 192 * <p> 193 * Example: 194 * </p> 195 * <pre> 196 * public class Test { 197 * public void foo() { 198 * SomeObject a = new SomeObject(); // violation 199 * var b = new SomeObject(); // OK 200 * } 201 * } 202 * </pre> 203 * <p> 204 * Parent is {@code com.puppycrawl.tools.checkstyle.TreeWalker} 205 * </p> 206 * <p> 207 * Violation Message Keys: 208 * </p> 209 * <ul> 210 * <li> 211 * {@code matchxpath.match} 212 * </li> 213 * </ul> 214 * 215 * @since 8.39 216 */ 217@StatelessCheck 218public class MatchXpathCheck extends AbstractCheck { 219 220 /** 221 * A key is pointing to the warning message text provided by user. 222 */ 223 public static final String MSG_KEY = "matchxpath.match"; 224 225 /** Specify Xpath query. */ 226 private String query = ""; 227 228 /** Xpath expression. */ 229 private XPathExpression xpathExpression; 230 231 /** 232 * Setter to specify Xpath query. 233 * 234 * @param query Xpath query. 235 * @throws IllegalStateException if creation of xpath expression fails 236 */ 237 public void setQuery(String query) { 238 this.query = query; 239 if (!query.isEmpty()) { 240 try { 241 final XPathEvaluator xpathEvaluator = 242 new XPathEvaluator(Configuration.newConfiguration()); 243 xpathExpression = xpathEvaluator.createExpression(query); 244 } 245 catch (XPathException ex) { 246 throw new IllegalStateException("Creating Xpath expression failed: " + query, ex); 247 } 248 } 249 } 250 251 @Override 252 public int[] getDefaultTokens() { 253 return getRequiredTokens(); 254 } 255 256 @Override 257 public int[] getAcceptableTokens() { 258 return getRequiredTokens(); 259 } 260 261 @Override 262 public int[] getRequiredTokens() { 263 return CommonUtil.EMPTY_INT_ARRAY; 264 } 265 266 @Override 267 public boolean isCommentNodesRequired() { 268 return true; 269 } 270 271 @Override 272 public void beginTree(DetailAST rootAST) { 273 if (xpathExpression != null) { 274 final List<DetailAST> matchingNodes = findMatchingNodesByXpathQuery(rootAST); 275 matchingNodes.forEach(node -> log(node, MSG_KEY)); 276 } 277 } 278 279 /** 280 * Find nodes that match query. 281 * 282 * @param rootAST root node 283 * @return list of matching nodes 284 * @throws IllegalStateException if evaluation of xpath query fails 285 */ 286 private List<DetailAST> findMatchingNodesByXpathQuery(DetailAST rootAST) { 287 try { 288 final RootNode rootNode = new RootNode(rootAST); 289 final XPathDynamicContext xpathDynamicContext = 290 xpathExpression.createDynamicContext(rootNode); 291 final List<Item> matchingItems = xpathExpression.evaluate(xpathDynamicContext); 292 return matchingItems.stream() 293 .map(item -> ((AbstractNode) item).getUnderlyingNode()) 294 .collect(Collectors.toList()); 295 } 296 catch (XPathException ex) { 297 throw new IllegalStateException("Evaluation of Xpath query failed: " + query, ex); 298 } 299 } 300}