001////////////////////////////////////////////////////////////////////////////////
002// checkstyle: Checks Java source code for adherence to a set of rules.
003// Copyright (C) 2001-2020 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.naming;
021
022import com.puppycrawl.tools.checkstyle.api.DetailAST;
023import com.puppycrawl.tools.checkstyle.api.TokenTypes;
024import com.puppycrawl.tools.checkstyle.utils.ScopeUtil;
025
026/**
027 * <p>
028 * Checks that local, non-{@code final} variable names conform to a specified pattern.
029 * A catch parameter is considered to be
030 * a local variable.
031 * </p>
032 * <ul>
033 * <li>
034 * Property {@code format} - Specifies valid identifiers.
035 * Type is {@code java.util.regex.Pattern}.
036 * Default value is {@code "^[a-z][a-zA-Z0-9]*$"}.
037 * </li>
038 * <li>
039 * Property {@code allowOneCharVarInForLoop} - Allow one character variable name in
040 * <a href="https://docs.oracle.com/javase/tutorial/java/nutsandbolts/for.html">
041 * initialization expressions</a>
042 * in FOR loop if one char variable name is prohibited by {@code format} regexp.
043 * Type is {@code boolean}.
044 * Default value is {@code false}.
045 * </li>
046 * </ul>
047 * <p>
048 * To configure the check:
049 * </p>
050 * <pre>
051 * &lt;module name="LocalVariableName"/&gt;
052 * </pre>
053 * <p>Code Example:</p>
054 * <pre>
055 * class MyClass {
056 *   void MyMethod() {
057 *     for (int var = 1; var &lt; 10; var++) {} // OK
058 *     for (int VAR = 1; VAR &lt; 10; VAR++) {} // violation, name 'VAR' must match
059 *                                           // pattern '^[a-z][a-zA-Z0-9]*$'
060 *     for (int i = 1; i &lt; 10; i++) {} // OK
061 *     for (int var_1 = 0; var_1 &lt; 10; var_1++) {} // violation, name 'var_1' must match
062 *                                                    // pattern '^[a-z][a-zA-Z0-9]*$'
063 *   }
064 * }
065 * </pre>
066 * <p>
067 * An example of how to configure the check for names that begin with a lower
068 * case letter, followed by letters, digits, and underscores is:
069 * </p>
070 * <pre>
071 * &lt;module name="LocalVariableName"&gt;
072 *   &lt;property name="format" value="^[a-z](_?[a-zA-Z0-9]+)*$"/&gt;
073 * &lt;/module&gt;
074 * </pre>
075 * <p>Code Example:</p>
076 * <pre>
077 * class MyClass {
078 *   void MyMethod() {
079 *     for (int var = 1; var &lt; 10; var++) {} // OK
080 *     for (int VAR = 1; VAR &lt; 10; VAR++) {} // violation, name 'VAR' must match
081 *                                              // pattern '^[a-z](_?[a-zA-Z0-9]+)*$'
082 *     for (int i = 1; i &lt; 10; i++) {} // OK
083 *     for (int var_1 = 0; var_1 &lt; 10; var_1++) {} // OK
084 *   }
085 * }
086 * </pre>
087 * <p>
088 * An example of one character variable name in
089 * initialization expression(like "i") in FOR loop:
090 * </p>
091 * <pre>
092 * for(int i = 1; i &lt; 10; i++) {}
093 * for(int K = 1; K &lt; 10; K++) {}
094 * List list = new ArrayList();
095 * for (Object o : list) {}
096 * for (Object O : list) {}
097 * </pre>
098 * <p>
099 * An example of how to configure the check to allow one character variable name in
100 * <a href="https://docs.oracle.com/javase/tutorial/java/nutsandbolts/for.html">
101 * initialization expressions</a> in FOR loop, where regexp allows 2 or more chars:
102 * </p>
103 * <pre>
104 * &lt;module name="LocalVariableName"&gt;
105 *   &lt;property name="format" value="^[a-z][_a-zA-Z0-9]+$"/&gt;
106 *   &lt;property name="allowOneCharVarInForLoop" value="true"/&gt;
107 * &lt;/module&gt;
108 * </pre>
109 * <p>Code Example:</p>
110 * <pre>
111 * class MyClass {
112 *   void MyMethod() {
113 *     int good = 1;
114 *     int g = 0; // violation
115 *     for (int v = 1; v &lt; 10; v++) { // OK
116 *         int a = 1; // violation
117 *     }
118 *     for (int V = 1; V &lt; 10; V++) { // OK
119 *         int I = 1; // violation
120 *     }
121 *     List list = new ArrayList();
122 *     for (Object o : list) { // OK
123 *         String a = ""; // violation
124 *     }
125 *     for (Object O : list) { // OK
126 *         String A = ""; // violation
127 *     }
128 *   }
129 * }
130 * </pre>
131 * <p>
132 * An example of how to configure the check to that all variables have 3 or more chars in name:
133 * </p>
134 * <pre>
135 * &lt;module name="LocalVariableName"&gt;
136 *   &lt;property name="format" value="^[a-z][_a-zA-Z0-9]{2,}$"/&gt;
137 * &lt;/module&gt;
138 * </pre>
139 * <p>Code Example:</p>
140 * <pre>
141 * class MyClass {
142 *   void MyMethod() {
143 *     int goodName = 0;
144 *     int i = 1; // violation
145 *     for (int var = 1; var &lt; 10; var++) { //OK
146 *       int j = 1; // violation
147 *     }
148 *   }
149 * }
150 * </pre>
151 * <p>
152 * Parent is {@code com.puppycrawl.tools.checkstyle.TreeWalker}
153 * </p>
154 * <p>
155 * Violation Message Keys:
156 * </p>
157 * <ul>
158 * <li>
159 * {@code name.invalidPattern}
160 * </li>
161 * </ul>
162 *
163 * @since 3.0
164 */
165public class LocalVariableNameCheck
166    extends AbstractNameCheck {
167
168    /**
169     * Allow one character variable name in
170     * <a href="https://docs.oracle.com/javase/tutorial/java/nutsandbolts/for.html">
171     * initialization expressions</a>
172     * in FOR loop if one char variable name is prohibited by {@code format} regexp.
173     */
174    private boolean allowOneCharVarInForLoop;
175
176    /** Creates a new {@code LocalVariableNameCheck} instance. */
177    public LocalVariableNameCheck() {
178        super("^[a-z][a-zA-Z0-9]*$");
179    }
180
181    /**
182     * Setter to allow one character variable name in
183     * <a href="https://docs.oracle.com/javase/tutorial/java/nutsandbolts/for.html">
184     * initialization expressions</a>
185     * in FOR loop if one char variable name is prohibited by {@code format} regexp.
186     *
187     * @param allow Flag for allowing or not one character name in FOR loop.
188     */
189    public final void setAllowOneCharVarInForLoop(boolean allow) {
190        allowOneCharVarInForLoop = allow;
191    }
192
193    @Override
194    public int[] getDefaultTokens() {
195        return getRequiredTokens();
196    }
197
198    @Override
199    public int[] getAcceptableTokens() {
200        return getRequiredTokens();
201    }
202
203    @Override
204    public int[] getRequiredTokens() {
205        return new int[] {
206            TokenTypes.VARIABLE_DEF,
207        };
208    }
209
210    @Override
211    protected final boolean mustCheckName(DetailAST ast) {
212        final boolean result;
213        if (allowOneCharVarInForLoop && isForLoopVariable(ast)) {
214            final String variableName = ast.findFirstToken(TokenTypes.IDENT).getText();
215            result = variableName.length() != 1;
216        }
217        else {
218            final DetailAST modifiersAST = ast.findFirstToken(TokenTypes.MODIFIERS);
219            final boolean isFinal = modifiersAST.findFirstToken(TokenTypes.FINAL) != null;
220            result = !isFinal && ScopeUtil.isLocalVariableDef(ast);
221        }
222        return result;
223    }
224
225    /**
226     * Checks if a variable is the loop's one.
227     *
228     * @param variableDef variable definition.
229     * @return true if a variable is the loop's one.
230     */
231    private static boolean isForLoopVariable(DetailAST variableDef) {
232        final int parentType = variableDef.getParent().getType();
233        return parentType == TokenTypes.FOR_INIT
234                || parentType == TokenTypes.FOR_EACH_CLAUSE;
235    }
236
237}