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.indentation; 021 022import com.puppycrawl.tools.checkstyle.StatelessCheck; 023import com.puppycrawl.tools.checkstyle.api.AbstractCheck; 024import com.puppycrawl.tools.checkstyle.api.DetailAST; 025import com.puppycrawl.tools.checkstyle.api.TokenTypes; 026import com.puppycrawl.tools.checkstyle.utils.CommonUtil; 027 028/** 029 * <div> 030 * Checks that the {@code throws} clause of a wrapped method or constructor declaration 031 * is properly aligned according to the 032 * <a href="https://cr.openjdk.org/~alundblad/styleguide/index-v6.html#toc-wrapping-method-declarations"> 033 * OpenJDK Java Style Guide</a>. 034 * </div> 035 * 036 * <p> 037 * This check only applies when the method or constructor declaration is 038 * <em>wrapped</em>, that is, the parameter list spans more than one line. 039 * Single-line declarations are not in scope. 040 * </p> 041 * 042 * <p> 043 * The two rules enforced are: 044 * </p> 045 * <ol> 046 * <li>The {@code throws} keyword must start on a <em>new line</em>, it must not share 047 * the line with the closing {@code )} of the parameter list.</li> 048 * <li>The {@code throws} keyword must <em>stand out</em> from the parameter list by 049 * being indented 8 columns relative to <em>either</em>: 050 * <ul> 051 * <li>the column of the method/constructor declaration (i.e. the indentation of the 052 * line containing the declaration keyword)</li> 053 * <li>the indentation (first non-whitespace column) of the source line immediately 054 * above the line where {@code throws} appears</li> 055 * </ul> 056 * </li> 057 * </ol> 058 * 059 * @since 14.2.0 060 */ 061@StatelessCheck 062public class OpenjdkMethodThrowsAlignmentCheck extends AbstractCheck { 063 064 /** 065 * A key is pointing to the warning message text in "messages.properties" file. 066 */ 067 public static final String MSG_KEY_NOT_ON_NEW_LINE = "openjdk.throws.new.line"; 068 069 /** 070 * A key is pointing to the warning message text in "messages.properties" file. 071 */ 072 public static final String MSG_KEY_WRONG_INDENTATION = "openjdk.throws.indentation"; 073 074 /** 075 * The Indentation the throws clause needs relative to either baseline 076 * (the method declaration column or the previous-line indentation). 077 */ 078 private static final int LINE_WRAPPING_INDENTATION = 8; 079 080 /** 081 * Creates a new {@code OpenjdkMethodThrowsAlignmentCheck} instance. 082 */ 083 public OpenjdkMethodThrowsAlignmentCheck() { 084 // no code by default 085 } 086 087 @Override 088 public int[] getDefaultTokens() { 089 return getAcceptableTokens(); 090 } 091 092 @Override 093 public int[] getAcceptableTokens() { 094 return new int[] { 095 TokenTypes.METHOD_DEF, 096 TokenTypes.CTOR_DEF, 097 }; 098 } 099 100 @Override 101 public int[] getRequiredTokens() { 102 return getAcceptableTokens(); 103 } 104 105 @Override 106 public void visitToken(DetailAST ast) { 107 final DetailAST throwsAst = ast.findFirstToken(TokenTypes.LITERAL_THROWS); 108 if (throwsAst != null) { 109 final int lparenLineNo = ast.findFirstToken(TokenTypes.LPAREN).getLineNo(); 110 final int rparenLineNo = ast.findFirstToken(TokenTypes.RPAREN).getLineNo(); 111 112 if (lparenLineNo != rparenLineNo) { 113 final int throwsLineNo = throwsAst.getLineNo(); 114 115 if (throwsLineNo == rparenLineNo) { 116 log(throwsAst, MSG_KEY_NOT_ON_NEW_LINE); 117 } 118 else { 119 final int throwsCol = throwsAst.getColumnNo(); 120 final int declCol = ast.getColumnNo(); 121 final int prevLineIndent = getIndentOfLine(throwsLineNo - 1); 122 final boolean indentedFromDecl = 123 throwsCol - declCol == LINE_WRAPPING_INDENTATION; 124 final boolean indentedFromPrev = 125 throwsCol - prevLineIndent == LINE_WRAPPING_INDENTATION; 126 127 if (throwsCol == prevLineIndent 128 || !indentedFromDecl && !indentedFromPrev) { 129 log(throwsAst, MSG_KEY_WRONG_INDENTATION); 130 } 131 } 132 } 133 } 134 } 135 136 /** 137 * Returns the indentation (column of the first non-whitespace character) 138 * of the given 1-indexed source line number. 139 * 140 * @param lineNo 1-indexed line number of the source line to inspect. 141 * @return 0-indexed column of the first non-whitespace character on that line, 142 * or the full line length if the line is blank. 143 */ 144 private int getIndentOfLine(int lineNo) { 145 final String line = getLines()[lineNo - 1]; 146 return CommonUtil.indexOfNonWhitespace(line); 147 } 148 149}