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 }