001/////////////////////////////////////////////////////////////////////////////////////////////// 002// checkstyle: Checks Java source code and other text files for adherence to a set of rules. 003// Copyright (C) 2001-2026 the original author or authors. 004// 005// This library is free software; you can redistribute it and/or 006// modify it under the terms of the GNU Lesser General Public 007// License as published by the Free Software Foundation; either 008// version 2.1 of the License, or (at your option) any later version. 009// 010// This library is distributed in the hope that it will be useful, 011// but WITHOUT ANY WARRANTY; without even the implied warranty of 012// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 013// Lesser General Public License for more details. 014// 015// You should have received a copy of the GNU Lesser General Public 016// License along with this library; if not, write to the Free Software 017// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA 018/////////////////////////////////////////////////////////////////////////////////////////////// 019 020package com.puppycrawl.tools.checkstyle.checks.modules; 021 022import java.util.HashSet; 023import java.util.List; 024import java.util.Set; 025 026import com.puppycrawl.tools.checkstyle.StatelessCheck; 027import com.puppycrawl.tools.checkstyle.api.AbstractCheck; 028import com.puppycrawl.tools.checkstyle.api.DetailAST; 029import com.puppycrawl.tools.checkstyle.api.TokenTypes; 030import com.puppycrawl.tools.checkstyle.internal.annotation.PreserveOrder; 031import com.puppycrawl.tools.checkstyle.utils.CommonUtil; 032 033/** 034 * <div> 035 * Checks the ordering, grouping and separation of directives in a module 036 * declaration. Directives of each kind must form a single block, the blocks 037 * must appear in a configurable order, and each block must be separated from 038 * the previous one by exactly one blank line. 039 * </div> 040 * 041 * <p> 042 * The default configuration enforces 043 * <a href="https://google.github.io/styleguide/javaguide.html#s3.5-module-declaration"> 044 * Google Java Style Guide, Section 3.5.1</a>: all {@code requires} directives 045 * first, then {@code exports}, {@code opens}, {@code uses} and {@code provides}, 046 * each kind in a single block, with a single blank line between blocks. Blank 047 * lines are what delimit blocks, so blank lines between directives of the same 048 * kind are also violations. 049 * </p> 050 * 051 * <p> 052 * All forms of {@code requires} (plain, {@code transitive}, {@code static}) 053 * belong to a single block, and the order of directives inside a block is not validated. 054 * </p> 055 * 056 * <p> 057 * Directive kinds that are not listed in the {@code order} property are not 058 * validated. 059 * </p> 060 * 061 * @since 14.1.0 062 */ 063@StatelessCheck 064public class ModuleDirectiveOrderCheck extends AbstractCheck { 065 066 /** 067 * A key pointing to the warning message text in "messages.properties" file. 068 * Emitted when a directive block appears after a block that it should precede. 069 */ 070 public static final String MSG_ORDER = "module.directive.order"; 071 072 /** 073 * A key pointing to the warning message text in "messages.properties" file. 074 * Emitted when directives of one kind are interleaved with directives of another kind. 075 */ 076 public static final String MSG_GROUPING = "module.directive.grouping"; 077 078 /** 079 * A key pointing to the warning message text in "messages.properties" file. 080 * Emitted when directives of the same kind are separated by blank lines. 081 */ 082 public static final String MSG_SEPARATED_INTERNALLY = 083 "module.directive.separated.internally"; 084 085 /** 086 * A key pointing to the warning message text in "messages.properties" file. 087 * Emitted when a directive block is not separated from the previous block 088 * by exactly one blank line. 089 */ 090 public static final String MSG_SEPARATION = "module.directive.separation"; 091 092 /** Default order of directive kinds. */ 093 private static final List<String> DEFAULT_ORDER = List.of( 094 "requires", 095 "exports", 096 "opens", 097 "uses", 098 "provides" 099 ); 100 101 /** Valid values for entries of the {@code order} property. */ 102 private static final Set<String> VALID_KINDS = Set.copyOf(DEFAULT_ORDER); 103 104 /** 105 * Specify directive kinds in the order their blocks must appear inside the module declaration. 106 */ 107 @PreserveOrder 108 private List<String> order = DEFAULT_ORDER; 109 110 /** 111 * Control whether blank line separation is validated: exactly one blank 112 * line between directive blocks and no blank lines inside a block. 113 */ 114 private boolean validateBlockSeparation = true; 115 116 /** 117 * Creates a new {@code ModuleDirectiveOrderCheck} instance. 118 */ 119 public ModuleDirectiveOrderCheck() { 120 // no code by default 121 } 122 123 /** 124 * Setter to specify directive kinds in the order their blocks must appear 125 * inside the module declaration. 126 * 127 * @param order the order of directive kinds. 128 * @throws IllegalArgumentException when an element of order is not a directive kind. 129 * @since 14.1.0 130 */ 131 public void setOrder(String... order) { 132 for (final String kind : order) { 133 if (!VALID_KINDS.contains(kind)) { 134 throw new IllegalArgumentException("unable to parse " + kind); 135 } 136 } 137 this.order = List.of(order); 138 } 139 140 /** 141 * Setter to control whether blank line separation is validated: exactly 142 * one blank line between directive blocks and no blank lines inside a block. 143 * 144 * @param validateBlockSeparation the value to set. 145 * @since 14.1.0 146 */ 147 public void setValidateBlockSeparation(boolean validateBlockSeparation) { 148 this.validateBlockSeparation = validateBlockSeparation; 149 } 150 151 @Override 152 public int[] getDefaultTokens() { 153 return getRequiredTokens(); 154 } 155 156 @Override 157 public int[] getAcceptableTokens() { 158 return getRequiredTokens(); 159 } 160 161 @Override 162 public int[] getRequiredTokens() { 163 return new int[] {TokenTypes.MODULE_DEF}; 164 } 165 166 @Override 167 public void visitToken(DetailAST ast) { 168 final DetailAST directiveBlock = ast.findFirstToken(TokenTypes.DIRECTIVE_BLOCK); 169 final Set<String> seenKinds = new HashSet<>(); 170 DetailAST previous = null; 171 for (DetailAST child = directiveBlock.getFirstChild(); child != null; 172 child = child.getNextSibling()) { 173 if (order.contains(child.getText())) { 174 if (previous != null) { 175 validateDirectivePlacement(child, previous, seenKinds); 176 } 177 seenKinds.add(child.getText()); 178 previous = child; 179 } 180 } 181 } 182 183 /** 184 * Validates the placement of a directive relative to the previous directive of the module. 185 * 186 * <p> 187 * A directive of the same kind as the previous one continues the current 188 * block and must not be separated from it by blank lines. Otherwise the 189 * directive starts a new block, which must not repeat an earlier kind, 190 * must not belong before the previous block, and must be separated from 191 * it by exactly one blank line. Blank line requirements are validated 192 * only when {@code validateBlockSeparation} is enabled. 193 * </p> 194 * 195 * @param directive the directive to validate. 196 * @param previous the directive before the given one. 197 * @param seenKinds kinds of all directives seen before the given one. 198 */ 199 private void validateDirectivePlacement(DetailAST directive, DetailAST previous, 200 Set<String> seenKinds) { 201 final String kind = directive.getText(); 202 final String previousKind = previous.getText(); 203 final int blankLines = countBlankLinesBetweenDirectives(previous, directive); 204 if (kind.equals(previousKind)) { 205 if (validateBlockSeparation && blankLines > 0) { 206 log(directive, MSG_SEPARATED_INTERNALLY, kind); 207 } 208 } 209 else if (seenKinds.contains(kind)) { 210 log(directive, MSG_GROUPING, kind); 211 } 212 else if (precedesInOrder(kind, previousKind)) { 213 log(directive, MSG_ORDER, kind, previousKind); 214 } 215 else if (validateBlockSeparation && blankLines != 1) { 216 log(directive, MSG_SEPARATION, kind); 217 } 218 } 219 220 /** 221 * Checks whether the given kind precedes the other kind in the {@code order} property. 222 * 223 * @param kind the kind of the directive being validated. 224 * @param previousKind the kind of the previous directive. 225 * @return true if {@code kind} precedes {@code previousKind} in the configured order. 226 */ 227 private boolean precedesInOrder(String kind, String previousKind) { 228 return order.stream() 229 .takeWhile(entry -> !entry.equals(previousKind)) 230 .anyMatch(kind::equals); 231 } 232 233 /** 234 * Counts the blank lines between the end of the previous directive and 235 * the start of the given directive. 236 * 237 * @param previous the directive before the given one. 238 * @param directive the directive to count blank lines before. 239 * @return the number of blank lines between the two directives. 240 */ 241 private int countBlankLinesBetweenDirectives(DetailAST previous, DetailAST directive) { 242 final int previousEnd = previous.getLastChild().getLineNo(); 243 final int directiveStart = directive.getLineNo(); 244 int result = 0; 245 for (int lineIndex = previousEnd; lineIndex <= directiveStart - 2; lineIndex++) { 246 if (CommonUtil.isBlank(getLine(lineIndex))) { 247 result++; 248 } 249 } 250 return result; 251 } 252 253}