View Javadoc
1   /*
2    * Copyright (c) 2011-2024 Qulice.com
3    *
4    * All rights reserved.
5    *
6    * Redistribution and use in source and binary forms, with or without
7    * modification, are permitted provided that the following conditions
8    * are met: 1) Redistributions of source code must retain the above
9    * copyright notice, this list of conditions and the following
10   * disclaimer. 2) Redistributions in binary form must reproduce the above
11   * copyright notice, this list of conditions and the following
12   * disclaimer in the documentation and/or other materials provided
13   * with the distribution. 3) Neither the name of the Qulice.com nor
14   * the names of its contributors may be used to endorse or promote
15   * products derived from this software without specific prior written
16   * permission.
17   *
18   * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
19   * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT
20   * NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
21   * FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL
22   * THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
23   * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
24   * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
25   * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
26   * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
27   * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
28   * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
29   * OF THE POSSIBILITY OF SUCH DAMAGE.
30   */
31  package com.qulice.checkstyle;
32  
33  import com.google.common.collect.Lists;
34  import com.puppycrawl.tools.checkstyle.api.AbstractCheck;
35  import com.puppycrawl.tools.checkstyle.api.DetailAST;
36  import com.puppycrawl.tools.checkstyle.api.TokenTypes;
37  import java.util.List;
38  
39  /**
40   * Checks node/closing curly brackets to be the last symbols on the line.
41   *
42   * <p>This is how a correct curly bracket structure should look like:
43   *
44   * <pre>
45   * String[] array = new String[] {
46   *      "first",
47   *      "second"
48   * };
49   * </pre>
50   *
51   * or
52   *
53   * <pre>
54   * String[] array = new String[] {"first", "second"};
55   * </pre>
56   *
57   * <p>The motivation for such formatting is simple - we want to see the entire
58   * block as fast as possible. When you look at a block of code you should be
59   * able to see where it starts and where it ends.
60   *
61   * @since 0.6
62   */
63  public final class CurlyBracketsStructureCheck extends AbstractCheck {
64  
65      @Override
66      public int[] getDefaultTokens() {
67          return new int[] {
68              TokenTypes.ARRAY_INIT,
69          };
70      }
71  
72      @Override
73      public int[] getAcceptableTokens() {
74          return this.getDefaultTokens();
75      }
76  
77      @Override
78      public int[] getRequiredTokens() {
79          return this.getDefaultTokens();
80      }
81  
82      @Override
83      public void visitToken(final DetailAST ast) {
84          if (ast.getType() == TokenTypes.ARRAY_INIT) {
85              this.checkParams(ast);
86          }
87      }
88  
89      /**
90       * Checks params statement to satisfy the rule.
91       * @param node Tree node, containing containing array init statement.
92       */
93      private void checkParams(final DetailAST node) {
94          final DetailAST closing = node.findFirstToken(TokenTypes.RCURLY);
95          if (closing != null) {
96              this.checkLines(node, node.getLineNo(), closing.getLineNo());
97          }
98      }
99  
100     /**
101      * Checks params statement to satisfy the rule.
102      * @param node Tree node, containing array init statement.
103      * @param start First line
104      * @param end Final line
105      */
106     private void checkLines(final DetailAST node, final int start,
107         final int end) {
108         if (start != end) {
109             this.checkExpressions(
110                 CurlyBracketsStructureCheck.findAllChildren(
111                     node,
112                     TokenTypes.EXPR
113                 ),
114                 start,
115                 end
116             );
117         }
118     }
119 
120     /**
121      * Checks that all EXPR nodes satisfy the rule.
122      * @param exprs Iterable of EXPR nodes
123      * @param start First line of ARRAY_INIT node
124      * @param end Final line ARRAY_INIT node (corresponds to RCURLY)
125      */
126     private void checkExpressions(final Iterable<DetailAST> exprs,
127         final int start,
128         final int end
129     ) {
130         for (final DetailAST expr : exprs) {
131             final int pline = expr.getLineNo();
132             if (pline == start) {
133                 this.log(pline, "Parameters should start on a new line");
134             }
135             final DetailAST last = expr.getLastChild();
136             final int lline = last.getLineNo();
137             if (lline == end) {
138                 this.log(lline, "Closing bracket should be on a new line");
139             }
140         }
141     }
142 
143     /**
144      * Search for all children of given type.
145      * @param base Parent node to start from
146      * @param type Node type
147      * @return Iterable
148      */
149     private static Iterable<DetailAST> findAllChildren(final DetailAST base,
150         final int type) {
151         final List<DetailAST> children = Lists.newArrayList();
152         DetailAST child = base.getFirstChild();
153         while (child != null) {
154             if (child.getType() == type) {
155                 children.add(child);
156             }
157             child = child.getNextSibling();
158         }
159         return children;
160     }
161 }