1919
2020package com .puppycrawl .tools .checkstyle ;
2121
22+ import java .beans .PropertyDescriptor ;
2223import java .io .File ;
23- import java .io .IOException ;
2424import java .nio .charset .Charset ;
2525import java .nio .file .Files ;
2626import java .nio .file .StandardOpenOption ;
2727import java .util .ArrayList ;
2828import java .util .Arrays ;
2929import java .util .Comparator ;
3030import java .util .List ;
31+ import java .util .Set ;
32+ import java .util .TreeSet ;
3133
34+ import org .apache .commons .beanutils .PropertyUtils ;
3235import org .junit .Test ;
3336
37+ import com .puppycrawl .tools .checkstyle .api .AbstractCheck ;
38+ import com .puppycrawl .tools .checkstyle .api .AbstractFileSetCheck ;
39+ import com .puppycrawl .tools .checkstyle .checks .javadoc .AbstractJavadocCheck ;
3440import com .puppycrawl .tools .checkstyle .internal .CheckUtil ;
41+ import com .puppycrawl .tools .checkstyle .internal .TestUtils ;
3542import com .puppycrawl .tools .checkstyle .utils .ModuleReflectionUtils ;
3643
3744/**
4047 * @author LuoLiangchen
4148 */
4249public class ExtractInfoGeneratorTest {
50+ /** Modules which do not have global properties to drop. */
51+ private static final List <String > XML_FILESET_LIST = Arrays .asList (
52+ "TreeWalker" ,
53+ "Checker" ,
54+ "Header" ,
55+ "Translation" ,
56+ "SeverityMatchFilter" ,
57+ "SuppressionFilter" ,
58+ "SuppressWarningsFilter" ,
59+ "BeforeExecutionExclusionFileFilter" ,
60+ "RegexpHeader" ,
61+ "RegexpOnFilename" ,
62+ "RegexpSingleline" ,
63+ "RegexpMultiline" ,
64+ "JavadocPackage" ,
65+ "NewlineAtEndOfFile" ,
66+ "UniqueProperties" ,
67+ "FileLength" ,
68+ "FileTabCharacter"
69+ );
70+
71+ /** Properties of abstract check. */
72+ private static final Set <String > CHECK_PROPERTIES = getProperties (AbstractCheck .class );
73+
74+ /** Properties of abstract Javadoc check. */
75+ private static final Set <String > JAVADOC_CHECK_PROPERTIES =
76+ getProperties (AbstractJavadocCheck .class );
77+
78+ /** Properties of abstract file-set check. */
79+ private static final Set <String > FILESET_PROPERTIES = getProperties (AbstractFileSetCheck .class );
80+
81+ /** Properties without document. */
82+ private static final List <String > UNDOCUMENTED_PROPERTIES = Arrays .asList (
83+ "Checker.classLoader" ,
84+ "Checker.classloader" ,
85+ "Checker.moduleClassLoader" ,
86+ "Checker.moduleFactory" ,
87+ "TreeWalker.classLoader" ,
88+ "TreeWalker.moduleFactory" ,
89+ "TreeWalker.cacheFile" ,
90+ "TreeWalker.upChild" ,
91+ "SuppressWithNearbyCommentFilter.fileContents" ,
92+ "SuppressionCommentFilter.fileContents"
93+ );
94+
4395 /**
4496 * Generates the extract info file named as "checkstyle_modules.json".
45- * @throws IOException failure when generating the file
97+ * @throws Exception failure when generating the file
4698 */
4799 @ Test
48- public void generateExtractInfoFile () throws IOException {
100+ public void generateExtractInfoFile () throws Exception {
49101 final List <Class <?>> modules = new ArrayList <>(CheckUtil .getCheckstyleModules ());
50102 modules .sort (Comparator .comparing (Class ::getSimpleName ));
51103 final JsonUtil .JsonArray moduleJsonArray = new JsonUtil .JsonArray ();
@@ -62,8 +114,10 @@ public void generateExtractInfoFile() throws IOException {
62114 * Creates Json object for a module from the module class.
63115 * @param clazz the given module class
64116 * @return the Json object describing the extract info of the module
117+ * @throws Exception failure when creating Json object
65118 */
66- private static JsonUtil .JsonObject createJsonObjectFromModuleClass (Class <?> clazz ) {
119+ private static JsonUtil .JsonObject createJsonObjectFromModuleClass (Class <?> clazz )
120+ throws Exception {
67121 final JsonUtil .JsonObject object = new JsonUtil .JsonObject ();
68122
69123 final String name = clazz .getSimpleName ();
@@ -94,6 +148,116 @@ else if (ModuleReflectionUtils.isRootModule(clazz)) {
94148 object .add ("interfaces" , interfaces );
95149 object .add ("hierarchies" , hierarchies );
96150
151+ final JsonUtil .JsonArray properties = new JsonUtil .JsonArray ();
152+ for (String propertyName : getNecessaryProperties (clazz )) {
153+ final JsonUtil .JsonObject property = new JsonUtil .JsonObject ();
154+ property .addProperty ("name" , propertyName );
155+ Arrays .stream (PropertyUtils .getPropertyDescriptors (clazz ))
156+ .filter (p -> p .getName ().equals (propertyName ))
157+ .map (PropertyDescriptor ::getPropertyType )
158+ .map (Class ::getSimpleName )
159+ .findAny ()
160+ .ifPresent (type -> property .addProperty ("type" , type ));
161+ properties .add (property );
162+ }
163+ object .add ("properties" , properties );
164+
97165 return object ;
98166 }
167+
168+ /**
169+ * Gets the necessary properties of a checkstyle module.
170+ * Global properties and undocumented properties are not necessary for us.
171+ * @param clazz the class instance of the given module
172+ * @return a set of the necessary properties of the module
173+ * @throws Exception failure when getting properties
174+ */
175+ // -@cs[CyclomaticComplexity] many different kinds of module
176+ private static Set <String > getNecessaryProperties (Class <?> clazz )
177+ throws Exception {
178+ final Set <String > properties = getProperties (clazz );
179+ if (hasParentModule (clazz .getSimpleName ())) {
180+ if (AbstractJavadocCheck .class .isAssignableFrom (clazz )) {
181+ properties .removeAll (JAVADOC_CHECK_PROPERTIES );
182+ }
183+ else if (ModuleReflectionUtils .isCheckstyleCheck (clazz )) {
184+ properties .removeAll (CHECK_PROPERTIES );
185+ }
186+ }
187+ if (ModuleReflectionUtils .isFileSetModule (clazz )) {
188+ properties .removeAll (FILESET_PROPERTIES );
189+
190+ // override
191+ properties .add ("fileExtensions" );
192+ }
193+
194+ // undocumented properties are not necessary
195+ properties .removeIf (prop -> UNDOCUMENTED_PROPERTIES .contains (
196+ clazz .getSimpleName () + "." + prop ));
197+
198+ final PackageObjectFactory factory = TestUtils .getPackageObjectFactory ();
199+ final Object instance = factory .createModule (clazz .getSimpleName ());
200+
201+ if (ModuleReflectionUtils .isCheckstyleCheck (clazz )) {
202+ final AbstractCheck check = (AbstractCheck ) instance ;
203+
204+ final int [] acceptableTokens = check .getAcceptableTokens ();
205+ Arrays .sort (acceptableTokens );
206+ final int [] defaultTokens = check .getDefaultTokens ();
207+ Arrays .sort (defaultTokens );
208+ final int [] requiredTokens = check .getRequiredTokens ();
209+ Arrays .sort (requiredTokens );
210+
211+ if (!Arrays .equals (acceptableTokens , defaultTokens )
212+ || !Arrays .equals (acceptableTokens , requiredTokens )) {
213+ properties .add ("tokens" );
214+ }
215+ }
216+
217+ if (AbstractJavadocCheck .class .isAssignableFrom (clazz )) {
218+ final AbstractJavadocCheck check = (AbstractJavadocCheck ) instance ;
219+
220+ final int [] acceptableJavadocTokens = check .getAcceptableJavadocTokens ();
221+ Arrays .sort (acceptableJavadocTokens );
222+ final int [] defaultJavadocTokens = check .getDefaultJavadocTokens ();
223+ Arrays .sort (defaultJavadocTokens );
224+ final int [] requiredJavadocTokens = check .getRequiredJavadocTokens ();
225+ Arrays .sort (requiredJavadocTokens );
226+
227+ if (!Arrays .equals (acceptableJavadocTokens , defaultJavadocTokens )
228+ || !Arrays .equals (acceptableJavadocTokens , requiredJavadocTokens )) {
229+ properties .add ("javadocTokens" );
230+ }
231+ }
232+
233+ return properties ;
234+ }
235+
236+ /**
237+ * Gets the properties of a checkstyle module.
238+ * @param clazz the class instance of the given module
239+ * @return a set of the properties of the module
240+ */
241+ private static Set <String > getProperties (Class <?> clazz ) {
242+ final Set <String > result = new TreeSet <>();
243+ final PropertyDescriptor [] map = PropertyUtils .getPropertyDescriptors (clazz );
244+
245+ for (PropertyDescriptor p : map ) {
246+ if (p .getWriteMethod () != null ) {
247+ result .add (p .getName ());
248+ }
249+ }
250+
251+ return result ;
252+ }
253+
254+ /**
255+ * Checks whether a module has a parent that may contains global properties.
256+ * @param className the class name of given module
257+ * @return true if the module has a parent
258+ */
259+ private static boolean hasParentModule (String className ) {
260+ return !XML_FILESET_LIST .contains (className ) && XML_FILESET_LIST .stream ()
261+ .map (name -> name + "Check" ).noneMatch (name -> name .equals (className ));
262+ }
99263}
0 commit comments