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.blocks;
21  
22  import java.util.Arrays;
23  import java.util.Locale;
24  import java.util.Optional;
25  
26  import com.puppycrawl.tools.checkstyle.StatelessCheck;
27  import com.puppycrawl.tools.checkstyle.api.AbstractCheck;
28  import com.puppycrawl.tools.checkstyle.api.DetailAST;
29  import com.puppycrawl.tools.checkstyle.api.TokenTypes;
30  import com.puppycrawl.tools.checkstyle.utils.CodePointUtil;
31  import com.puppycrawl.tools.checkstyle.utils.CommonUtil;
32  
33  
34  
35  
36  
37  
38  
39  
40  
41  
42  
43  
44  
45  
46  
47  
48  
49  @StatelessCheck
50  public class EmptyBlockCheck
51      extends AbstractCheck {
52  
53      
54  
55  
56  
57      public static final String MSG_KEY_BLOCK_NO_STATEMENT = "block.noStatement";
58  
59      
60  
61  
62  
63      public static final String MSG_KEY_BLOCK_EMPTY = "block.empty";
64  
65      
66      private BlockOption option = BlockOption.STATEMENT;
67  
68      
69  
70  
71  
72  
73  
74  
75      public void setOption(String optionStr) {
76          option = BlockOption.valueOf(optionStr.trim().toUpperCase(Locale.ENGLISH));
77      }
78  
79      @Override
80      public int[] getDefaultTokens() {
81          return new int[] {
82              TokenTypes.LITERAL_WHILE,
83              TokenTypes.LITERAL_TRY,
84              TokenTypes.LITERAL_FINALLY,
85              TokenTypes.LITERAL_DO,
86              TokenTypes.LITERAL_IF,
87              TokenTypes.LITERAL_ELSE,
88              TokenTypes.LITERAL_FOR,
89              TokenTypes.INSTANCE_INIT,
90              TokenTypes.STATIC_INIT,
91              TokenTypes.LITERAL_SWITCH,
92              TokenTypes.LITERAL_SYNCHRONIZED,
93          };
94      }
95  
96      @Override
97      public int[] getAcceptableTokens() {
98          return new int[] {
99              TokenTypes.LITERAL_WHILE,
100             TokenTypes.LITERAL_TRY,
101             TokenTypes.LITERAL_CATCH,
102             TokenTypes.LITERAL_FINALLY,
103             TokenTypes.LITERAL_DO,
104             TokenTypes.LITERAL_IF,
105             TokenTypes.LITERAL_ELSE,
106             TokenTypes.LITERAL_FOR,
107             TokenTypes.INSTANCE_INIT,
108             TokenTypes.STATIC_INIT,
109             TokenTypes.LITERAL_SWITCH,
110             TokenTypes.LITERAL_SYNCHRONIZED,
111             TokenTypes.LITERAL_CASE,
112             TokenTypes.LITERAL_DEFAULT,
113             TokenTypes.ARRAY_INIT,
114         };
115     }
116 
117     @Override
118     public int[] getRequiredTokens() {
119         return CommonUtil.EMPTY_INT_ARRAY;
120     }
121 
122     @Override
123     public void visitToken(DetailAST ast) {
124         final Optional<DetailAST> leftCurly = getLeftCurly(ast);
125         if (leftCurly.isPresent()) {
126             final DetailAST leftCurlyAST = leftCurly.orElseThrow();
127             if (option == BlockOption.STATEMENT) {
128                 final boolean emptyBlock;
129                 if (leftCurlyAST.getType() == TokenTypes.LCURLY) {
130                     final DetailAST nextSibling = leftCurlyAST.getNextSibling();
131                     emptyBlock = nextSibling.getType() != TokenTypes.CASE_GROUP
132                             && nextSibling.getType() != TokenTypes.SWITCH_RULE;
133                 }
134                 else {
135                     emptyBlock = leftCurlyAST.getChildCount() <= 1;
136                 }
137                 if (emptyBlock) {
138                     log(leftCurlyAST,
139                         MSG_KEY_BLOCK_NO_STATEMENT);
140                 }
141             }
142             else if (!hasText(leftCurlyAST)) {
143                 log(leftCurlyAST,
144                     MSG_KEY_BLOCK_EMPTY,
145                     ast.getText());
146             }
147         }
148     }
149 
150     
151 
152 
153 
154 
155 
156     private boolean hasText(final DetailAST slistAST) {
157         final DetailAST rightCurly = slistAST.findFirstToken(TokenTypes.RCURLY);
158         final DetailAST rcurlyAST;
159 
160         if (rightCurly == null) {
161             rcurlyAST = slistAST.getParent().findFirstToken(TokenTypes.RCURLY);
162         }
163         else {
164             rcurlyAST = rightCurly;
165         }
166         final int slistLineNo = slistAST.getLineNo();
167         final int slistColNo = slistAST.getColumnNo();
168         final int rcurlyLineNo = rcurlyAST.getLineNo();
169         final int rcurlyColNo = rcurlyAST.getColumnNo();
170         boolean returnValue = false;
171         if (slistLineNo == rcurlyLineNo) {
172             
173             final int[] txt = Arrays.copyOfRange(getLineCodePoints(slistLineNo - 1),
174                     slistColNo + 1, rcurlyColNo);
175 
176             if (!CodePointUtil.isBlank(txt)) {
177                 returnValue = true;
178             }
179         }
180         else {
181             final int[] codePointsFirstLine = getLineCodePoints(slistLineNo - 1);
182             final int[] firstLine = Arrays.copyOfRange(codePointsFirstLine,
183                     slistColNo + 1, codePointsFirstLine.length);
184             final int[] codePointsLastLine = getLineCodePoints(rcurlyLineNo - 1);
185             final int[] lastLine = Arrays.copyOfRange(codePointsLastLine, 0, rcurlyColNo);
186             
187             returnValue = !(CodePointUtil.isBlank(firstLine) && CodePointUtil.isBlank(lastLine))
188                     || !checkIsAllLinesAreWhitespace(slistLineNo, rcurlyLineNo);
189         }
190         return returnValue;
191     }
192 
193     
194 
195 
196 
197 
198 
199 
200 
201 
202 
203     private boolean checkIsAllLinesAreWhitespace(int lineFrom, int lineTo) {
204         boolean result = true;
205         for (int i = lineFrom; i < lineTo - 1; i++) {
206             if (!CodePointUtil.isBlank(getLineCodePoints(i))) {
207                 result = false;
208                 break;
209             }
210         }
211         return result;
212     }
213 
214     
215 
216 
217 
218 
219 
220     private static Optional<DetailAST> getLeftCurly(DetailAST ast) {
221         final DetailAST parent = ast.getParent();
222         final int parentType = parent.getType();
223         final Optional<DetailAST> leftCurly;
224 
225         if (parentType == TokenTypes.SWITCH_RULE) {
226             
227             leftCurly = Optional.ofNullable(parent.findFirstToken(TokenTypes.SLIST));
228         }
229         else if (parentType == TokenTypes.CASE_GROUP) {
230             
231             leftCurly = Optional.ofNullable(ast.getNextSibling())
232                          .map(DetailAST::getFirstChild)
233                          .filter(node -> node.getType() == TokenTypes.SLIST);
234         }
235         else if (ast.findFirstToken(TokenTypes.SLIST) != null) {
236             
237             leftCurly = Optional.of(ast.findFirstToken(TokenTypes.SLIST));
238         }
239         else {
240             
241             leftCurly = Optional.ofNullable(ast.findFirstToken(TokenTypes.LCURLY));
242         }
243         return leftCurly;
244     }
245 
246 }