1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20 package com.puppycrawl.tools.checkstyle.checks.imports;
21
22 import java.util.ArrayList;
23 import java.util.HashSet;
24 import java.util.List;
25 import java.util.Set;
26 import java.util.regex.Matcher;
27 import java.util.regex.Pattern;
28
29 import com.puppycrawl.tools.checkstyle.FileStatefulCheck;
30 import com.puppycrawl.tools.checkstyle.api.AbstractCheck;
31 import com.puppycrawl.tools.checkstyle.api.DetailAST;
32 import com.puppycrawl.tools.checkstyle.api.FileContents;
33 import com.puppycrawl.tools.checkstyle.api.FullIdent;
34 import com.puppycrawl.tools.checkstyle.api.TextBlock;
35 import com.puppycrawl.tools.checkstyle.api.TokenTypes;
36 import com.puppycrawl.tools.checkstyle.checks.javadoc.JavadocTag;
37 import com.puppycrawl.tools.checkstyle.utils.CommonUtil;
38 import com.puppycrawl.tools.checkstyle.utils.JavadocUtil;
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53 @FileStatefulCheck
54 public class UnusedImportsCheck extends AbstractCheck {
55
56
57
58
59
60 public static final String MSG_KEY = "import.unused";
61
62
63 private static final Pattern CLASS_NAME = CommonUtil.createPattern(
64 "((:?[\\p{L}_$][\\p{L}\\p{N}_$]*\\.)*[\\p{L}_$][\\p{L}\\p{N}_$]*)");
65
66 private static final Pattern FIRST_CLASS_NAME = CommonUtil.createPattern(
67 "^" + CLASS_NAME);
68
69 private static final Pattern ARGUMENT_NAME = CommonUtil.createPattern(
70 "[(,]\\s*" + CLASS_NAME.pattern());
71
72
73 private static final Pattern JAVA_LANG_PACKAGE_PATTERN =
74 CommonUtil.createPattern("^java\\.lang\\.[a-zA-Z]+$");
75
76
77 private static final String STAR_IMPORT_SUFFIX = ".*";
78
79
80 private final Set<FullIdent> imports = new HashSet<>();
81
82
83 private final Set<String> referenced = new HashSet<>();
84
85
86 private boolean collect;
87
88 private boolean processJavadoc = true;
89
90
91
92
93
94
95 public void setProcessJavadoc(boolean value) {
96 processJavadoc = value;
97 }
98
99 @Override
100 public void beginTree(DetailAST rootAST) {
101 collect = false;
102 imports.clear();
103 referenced.clear();
104 }
105
106 @Override
107 public void finishTree(DetailAST rootAST) {
108
109 imports.stream()
110 .filter(imprt -> isUnusedImport(imprt.getText()))
111 .forEach(imprt -> log(imprt.getDetailAst(),
112 MSG_KEY, imprt.getText()));
113 }
114
115 @Override
116 public int[] getDefaultTokens() {
117 return getRequiredTokens();
118 }
119
120 @Override
121 public int[] getRequiredTokens() {
122 return new int[] {
123 TokenTypes.IDENT,
124 TokenTypes.IMPORT,
125 TokenTypes.STATIC_IMPORT,
126
127 TokenTypes.PACKAGE_DEF,
128 TokenTypes.ANNOTATION_DEF,
129 TokenTypes.ANNOTATION_FIELD_DEF,
130 TokenTypes.ENUM_DEF,
131 TokenTypes.ENUM_CONSTANT_DEF,
132 TokenTypes.CLASS_DEF,
133 TokenTypes.INTERFACE_DEF,
134 TokenTypes.METHOD_DEF,
135 TokenTypes.CTOR_DEF,
136 TokenTypes.VARIABLE_DEF,
137 };
138 }
139
140 @Override
141 public int[] getAcceptableTokens() {
142 return getRequiredTokens();
143 }
144
145 @Override
146 public void visitToken(DetailAST ast) {
147 if (ast.getType() == TokenTypes.IDENT) {
148 if (collect) {
149 processIdent(ast);
150 }
151 }
152 else if (ast.getType() == TokenTypes.IMPORT) {
153 processImport(ast);
154 }
155 else if (ast.getType() == TokenTypes.STATIC_IMPORT) {
156 processStaticImport(ast);
157 }
158 else {
159 collect = true;
160 if (processJavadoc) {
161 collectReferencesFromJavadoc(ast);
162 }
163 }
164 }
165
166
167
168
169
170
171 private boolean isUnusedImport(String imprt) {
172 final Matcher javaLangPackageMatcher = JAVA_LANG_PACKAGE_PATTERN.matcher(imprt);
173 return !referenced.contains(CommonUtil.baseClassName(imprt))
174 || javaLangPackageMatcher.matches();
175 }
176
177
178
179
180
181 private void processIdent(DetailAST ast) {
182 final DetailAST parent = ast.getParent();
183 final int parentType = parent.getType();
184 if (parentType != TokenTypes.DOT
185 && parentType != TokenTypes.METHOD_DEF
186 || parentType == TokenTypes.DOT
187 && ast.getNextSibling() != null) {
188 referenced.add(ast.getText());
189 }
190 }
191
192
193
194
195
196 private void processImport(DetailAST ast) {
197 final FullIdent name = FullIdent.createFullIdentBelow(ast);
198 if (!name.getText().endsWith(STAR_IMPORT_SUFFIX)) {
199 imports.add(name);
200 }
201 }
202
203
204
205
206
207 private void processStaticImport(DetailAST ast) {
208 final FullIdent name =
209 FullIdent.createFullIdent(
210 ast.getFirstChild().getNextSibling());
211 if (!name.getText().endsWith(STAR_IMPORT_SUFFIX)) {
212 imports.add(name);
213 }
214 }
215
216
217
218
219
220 private void collectReferencesFromJavadoc(DetailAST ast) {
221 final FileContents contents = getFileContents();
222 final int lineNo = ast.getLineNo();
223 final TextBlock textBlock = contents.getJavadocBefore(lineNo);
224 if (textBlock != null) {
225 referenced.addAll(collectReferencesFromJavadoc(textBlock));
226 }
227 }
228
229
230
231
232
233
234
235 private static Set<String> collectReferencesFromJavadoc(TextBlock textBlock) {
236 final List<JavadocTag> tags = new ArrayList<>();
237
238
239 tags.addAll(getValidTags(textBlock, JavadocUtil.JavadocTagType.INLINE));
240
241 tags.addAll(getValidTags(textBlock, JavadocUtil.JavadocTagType.BLOCK));
242
243 final Set<String> references = new HashSet<>();
244
245 tags.stream()
246 .filter(JavadocTag::canReferenceImports)
247 .forEach(tag -> references.addAll(processJavadocTag(tag)));
248 return references;
249 }
250
251
252
253
254
255
256
257 private static List<JavadocTag> getValidTags(TextBlock cmt,
258 JavadocUtil.JavadocTagType tagType) {
259 return JavadocUtil.getJavadocTags(cmt, tagType).getValidTags();
260 }
261
262
263
264
265
266
267 private static Set<String> processJavadocTag(JavadocTag tag) {
268 final Set<String> references = new HashSet<>();
269 final String identifier = tag.getFirstArg().trim();
270 for (Pattern pattern : new Pattern[]
271 {FIRST_CLASS_NAME, ARGUMENT_NAME}) {
272 references.addAll(matchPattern(identifier, pattern));
273 }
274 return references;
275 }
276
277
278
279
280
281
282
283
284 private static Set<String> matchPattern(String identifier, Pattern pattern) {
285 final Set<String> references = new HashSet<>();
286 final Matcher matcher = pattern.matcher(identifier);
287 while (matcher.find()) {
288 references.add(topLevelType(matcher.group(1)));
289 }
290 return references;
291 }
292
293
294
295
296
297
298
299
300 private static String topLevelType(String type) {
301 final String topLevelType;
302 final int dotIndex = type.indexOf('.');
303 if (dotIndex == -1) {
304 topLevelType = type;
305 }
306 else {
307 topLevelType = type.substring(0, dotIndex);
308 }
309 return topLevelType;
310 }
311
312 }