View Javadoc
1   ///////////////////////////////////////////////////////////////////////////////////////////////
2   // checkstyle: Checks Java source code and other text files for adherence to a set of rules.
3   // Copyright (C) 2001-2024 the original author or authors.
4   //
5   // This library is free software; you can redistribute it and/or
6   // modify it under the terms of the GNU Lesser General Public
7   // License as published by the Free Software Foundation; either
8   // version 2.1 of the License, or (at your option) any later version.
9   //
10  // This library is distributed in the hope that it will be useful,
11  // but WITHOUT ANY WARRANTY; without even the implied warranty of
12  // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
13  // Lesser General Public License for more details.
14  //
15  // You should have received a copy of the GNU Lesser General Public
16  // License along with this library; if not, write to the Free Software
17  // Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
18  ///////////////////////////////////////////////////////////////////////////////////////////////
19  
20  package com.puppycrawl.tools.checkstyle.checks.whitespace;
21  
22  import com.puppycrawl.tools.checkstyle.StatelessCheck;
23  import com.puppycrawl.tools.checkstyle.api.AbstractCheck;
24  import com.puppycrawl.tools.checkstyle.api.DetailAST;
25  import com.puppycrawl.tools.checkstyle.api.TokenTypes;
26  import com.puppycrawl.tools.checkstyle.utils.TokenUtil;
27  
28  /**
29   * <div>
30   * Checks that there is no whitespace before the colon in a switch block.
31   * </div>
32   *
33   * <p>
34   * Parent is {@code com.puppycrawl.tools.checkstyle.TreeWalker}
35   * </p>
36   *
37   * <p>
38   * Violation Message Keys:
39   * </p>
40   * <ul>
41   * <li>
42   * {@code ws.preceded}
43   * </li>
44   * </ul>
45   *
46   * @since 8.45
47   */
48  @StatelessCheck
49  public class NoWhitespaceBeforeCaseDefaultColonCheck
50      extends AbstractCheck {
51  
52      /**
53       * A key is pointing to the warning message text in "messages.properties"
54       * file.
55       */
56      public static final String MSG_KEY = "ws.preceded";
57  
58      @Override
59      public int[] getDefaultTokens() {
60          return getRequiredTokens();
61      }
62  
63      @Override
64      public int[] getAcceptableTokens() {
65          return getRequiredTokens();
66      }
67  
68      @Override
69      public int[] getRequiredTokens() {
70          return new int[] {TokenTypes.COLON};
71      }
72  
73      @Override
74      public boolean isCommentNodesRequired() {
75          return true;
76      }
77  
78      @Override
79      public void visitToken(DetailAST ast) {
80          if (isInSwitch(ast) && isWhiteSpaceBeforeColon(ast)) {
81              log(ast, MSG_KEY, ast.getText());
82          }
83      }
84  
85      /**
86       * Checks if the colon is inside a switch block.
87       *
88       * @param colonAst DetailAST to check.
89       * @return true, if colon case is inside a switch block.
90       */
91      private static boolean isInSwitch(DetailAST colonAst) {
92          return TokenUtil.isOfType(colonAst.getParent(), TokenTypes.LITERAL_CASE,
93                  TokenTypes.LITERAL_DEFAULT);
94      }
95  
96      /**
97       * Checks if there is a whitespace before the colon of a switch case or switch default.
98       *
99       * @param colonAst DetailAST to check.
100      * @return true, if there is whitespace preceding colonAst.
101      */
102     private static boolean isWhiteSpaceBeforeColon(DetailAST colonAst) {
103         final DetailAST parent = colonAst.getParent();
104         final boolean result;
105         if (isOnDifferentLineWithPreviousToken(colonAst)) {
106             result = true;
107         }
108         else if (parent.getType() == TokenTypes.LITERAL_CASE) {
109             result = isWhitespaceBeforeColonOfCase(colonAst);
110         }
111         else {
112             result = isWhitespaceBeforeColonOfDefault(colonAst);
113         }
114         return result;
115     }
116 
117     /**
118      * Checks if there is a whitespace before the colon of a switch case.
119      *
120      * @param colonAst DetailAST to check.
121      * @return true, if there is whitespace preceding colonAst.
122      */
123     private static boolean isWhitespaceBeforeColonOfCase(DetailAST colonAst) {
124         final DetailAST previousSibling = colonAst.getPreviousSibling();
125         int offset = 0;
126         if (previousSibling.getType() == TokenTypes.BLOCK_COMMENT_BEGIN) {
127             offset = 1;
128         }
129         return colonAst.getColumnNo() != getLastColumnNumberOf(previousSibling) + offset;
130     }
131 
132     /**
133      * Checks if there is a whitespace before the colon of a switch default.
134      *
135      * @param colonAst DetailAST to check.
136      * @return true, if there is whitespace preceding colonAst.
137      */
138     private static boolean isWhitespaceBeforeColonOfDefault(DetailAST colonAst) {
139         final boolean result;
140         final DetailAST previousSibling = colonAst.getPreviousSibling();
141         if (previousSibling == null) {
142             final DetailAST literalDefault = colonAst.getParent();
143             result = colonAst.getColumnNo()
144                     != literalDefault.getColumnNo() + literalDefault.getText().length();
145         }
146         else {
147             result =
148                     colonAst.getColumnNo() != getLastColumnNumberOf(previousSibling) + 1;
149         }
150         return result;
151     }
152 
153     /**
154      * Checks if the colon is on same line as of case or default.
155      *
156      * @param colonAst DetailAST to check.
157      * @return true, if colon case is in different line as of case or default.
158      */
159     private static boolean isOnDifferentLineWithPreviousToken(DetailAST colonAst) {
160         final DetailAST previousSibling;
161         final DetailAST parent = colonAst.getParent();
162         if (parent.getType() == TokenTypes.LITERAL_CASE) {
163             previousSibling = colonAst.getPreviousSibling();
164         }
165         else {
166             previousSibling = colonAst.getParent();
167         }
168         return !TokenUtil.areOnSameLine(previousSibling, colonAst);
169     }
170 
171     /**
172      * Returns the last column number of an ast.
173      *
174      * @param ast DetailAST to check.
175      * @return ast's last column number.
176      */
177     private static int getLastColumnNumberOf(DetailAST ast) {
178         DetailAST lastChild = ast;
179         while (lastChild.hasChildren()) {
180             lastChild = lastChild.getLastChild();
181         }
182         return lastChild.getColumnNo() + lastChild.getText().length();
183     }
184 
185 }