View Javadoc
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 }