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}