1 ///////////////////////////////////////////////////////////////////////////////////////////////
2 // checkstyle: Checks Java source code and other text files for adherence to a set of rules.
3 // Copyright (C) 2001-2026 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.indentation;
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.CommonUtil;
27
28 /**
29 * <div>
30 * Checks that the {@code throws} clause of a wrapped method or constructor declaration
31 * is properly aligned according to the
32 * <a href="https://cr.openjdk.org/~alundblad/styleguide/index-v6.html#toc-wrapping-method-declarations">
33 * OpenJDK Java Style Guide</a>.
34 * </div>
35 *
36 * <p>
37 * This check only applies when the method or constructor declaration is
38 * <em>wrapped</em>, that is, the parameter list spans more than one line.
39 * Single-line declarations are not in scope.
40 * </p>
41 *
42 * <p>
43 * The two rules enforced are:
44 * </p>
45 * <ol>
46 * <li>The {@code throws} keyword must start on a <em>new line</em>, it must not share
47 * the line with the closing {@code )} of the parameter list.</li>
48 * <li>The {@code throws} keyword must <em>stand out</em> from the parameter list by
49 * being indented 8 columns relative to <em>either</em>:
50 * <ul>
51 * <li>the column of the method/constructor declaration (i.e. the indentation of the
52 * line containing the declaration keyword)</li>
53 * <li>the indentation (first non-whitespace column) of the source line immediately
54 * above the line where {@code throws} appears</li>
55 * </ul>
56 * </li>
57 * </ol>
58 *
59 * @since 14.2.0
60 */
61 @StatelessCheck
62 public class OpenjdkMethodThrowsAlignmentCheck extends AbstractCheck {
63
64 /**
65 * A key is pointing to the warning message text in "messages.properties" file.
66 */
67 public static final String MSG_KEY_NOT_ON_NEW_LINE = "openjdk.throws.new.line";
68
69 /**
70 * A key is pointing to the warning message text in "messages.properties" file.
71 */
72 public static final String MSG_KEY_WRONG_INDENTATION = "openjdk.throws.indentation";
73
74 /**
75 * The Indentation the throws clause needs relative to either baseline
76 * (the method declaration column or the previous-line indentation).
77 */
78 private static final int LINE_WRAPPING_INDENTATION = 8;
79
80 /**
81 * Creates a new {@code OpenjdkMethodThrowsAlignmentCheck} instance.
82 */
83 public OpenjdkMethodThrowsAlignmentCheck() {
84 // no code by default
85 }
86
87 @Override
88 public int[] getDefaultTokens() {
89 return getAcceptableTokens();
90 }
91
92 @Override
93 public int[] getAcceptableTokens() {
94 return new int[] {
95 TokenTypes.METHOD_DEF,
96 TokenTypes.CTOR_DEF,
97 };
98 }
99
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 }