InnerAssignmentCheck.java

1
////////////////////////////////////////////////////////////////////////////////
2
// checkstyle: Checks Java source code for adherence to a set of rules.
3
// Copyright (C) 2001-2018 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.coding;
21
22
import java.util.Arrays;
23
24
import antlr.collections.AST;
25
import com.puppycrawl.tools.checkstyle.StatelessCheck;
26
import com.puppycrawl.tools.checkstyle.api.AbstractCheck;
27
import com.puppycrawl.tools.checkstyle.api.DetailAST;
28
import com.puppycrawl.tools.checkstyle.api.TokenTypes;
29
30
/**
31
 * <p>
32
 * Checks for assignments in subexpressions, such as in
33
 * {@code String s = Integer.toString(i = 2);}.
34
 * </p>
35
 * <p>
36
 * Rationale: With the exception of {@code for} iterators, all assignments
37
 * should occur in their own top-level statement to increase readability.
38
 * With inner assignments like the above it is difficult to see all places
39
 * where a variable is set.
40
 * </p>
41
 *
42
 * @author lkuehne
43
 */
44
@StatelessCheck
45
public class InnerAssignmentCheck
46
        extends AbstractCheck {
47
48
    /**
49
     * A key is pointing to the warning message text in "messages.properties"
50
     * file.
51
     */
52
    public static final String MSG_KEY = "assignment.inner.avoid";
53
54
    /**
55
     * List of allowed AST types from an assignment AST node
56
     * towards the root.
57
     */
58
    private static final int[][] ALLOWED_ASSIGNMENT_CONTEXT = {
59
        {TokenTypes.EXPR, TokenTypes.SLIST},
60
        {TokenTypes.VARIABLE_DEF},
61
        {TokenTypes.EXPR, TokenTypes.ELIST, TokenTypes.FOR_INIT},
62
        {TokenTypes.EXPR, TokenTypes.ELIST, TokenTypes.FOR_ITERATOR},
63
        {TokenTypes.ANNOTATION_MEMBER_VALUE_PAIR}, {
64
            TokenTypes.RESOURCE,
65
            TokenTypes.RESOURCES,
66
            TokenTypes.RESOURCE_SPECIFICATION,
67
        },
68
        {TokenTypes.EXPR, TokenTypes.LAMBDA},
69
    };
70
71
    /**
72
     * List of allowed AST types from an assignment AST node
73
     * towards the root.
74
     */
75
    private static final int[][] CONTROL_CONTEXT = {
76
        {TokenTypes.EXPR, TokenTypes.LITERAL_DO},
77
        {TokenTypes.EXPR, TokenTypes.LITERAL_FOR},
78
        {TokenTypes.EXPR, TokenTypes.LITERAL_WHILE},
79
        {TokenTypes.EXPR, TokenTypes.LITERAL_IF},
80
        {TokenTypes.EXPR, TokenTypes.LITERAL_ELSE},
81
    };
82
83
    /**
84
     * List of allowed AST types from a comparison node (above an assignment)
85
     * towards the root.
86
     */
87
    private static final int[][] ALLOWED_ASSIGNMENT_IN_COMPARISON_CONTEXT = {
88
        {TokenTypes.EXPR, TokenTypes.LITERAL_WHILE, },
89
    };
90
91
    /**
92
     * The token types that identify comparison operators.
93
     */
94
    private static final int[] COMPARISON_TYPES = {
95
        TokenTypes.EQUAL,
96
        TokenTypes.GE,
97
        TokenTypes.GT,
98
        TokenTypes.LE,
99
        TokenTypes.LT,
100
        TokenTypes.NOT_EQUAL,
101
    };
102
103
    static {
104
        Arrays.sort(COMPARISON_TYPES);
105
    }
106
107
    @Override
108
    public int[] getDefaultTokens() {
109 1 1. getDefaultTokens : mutated return of Object value for com/puppycrawl/tools/checkstyle/checks/coding/InnerAssignmentCheck::getDefaultTokens to ( if (x != null) null else throw new RuntimeException ) → KILLED
        return getRequiredTokens();
110
    }
111
112
    @Override
113
    public int[] getAcceptableTokens() {
114 1 1. getAcceptableTokens : mutated return of Object value for com/puppycrawl/tools/checkstyle/checks/coding/InnerAssignmentCheck::getAcceptableTokens to ( if (x != null) null else throw new RuntimeException ) → KILLED
        return getRequiredTokens();
115
    }
116
117
    @Override
118
    public int[] getRequiredTokens() {
119 1 1. getRequiredTokens : mutated return of Object value for com/puppycrawl/tools/checkstyle/checks/coding/InnerAssignmentCheck::getRequiredTokens to ( if (x != null) null else throw new RuntimeException ) → KILLED
        return new int[] {
120
            TokenTypes.ASSIGN,            // '='
121
            TokenTypes.DIV_ASSIGN,        // "/="
122
            TokenTypes.PLUS_ASSIGN,       // "+="
123
            TokenTypes.MINUS_ASSIGN,      //"-="
124
            TokenTypes.STAR_ASSIGN,       // "*="
125
            TokenTypes.MOD_ASSIGN,        // "%="
126
            TokenTypes.SR_ASSIGN,         // ">>="
127
            TokenTypes.BSR_ASSIGN,        // ">>>="
128
            TokenTypes.SL_ASSIGN,         // "<<="
129
            TokenTypes.BXOR_ASSIGN,       // "^="
130
            TokenTypes.BOR_ASSIGN,        // "|="
131
            TokenTypes.BAND_ASSIGN,       // "&="
132
        };
133
    }
134
135
    @Override
136
    public void visitToken(DetailAST ast) {
137 1 1. visitToken : negated conditional → KILLED
        if (!isInContext(ast, ALLOWED_ASSIGNMENT_CONTEXT)
138 1 1. visitToken : negated conditional → KILLED
                && !isInNoBraceControlStatement(ast)
139 1 1. visitToken : negated conditional → KILLED
                && !isInWhileIdiom(ast)) {
140 1 1. visitToken : removed call to com/puppycrawl/tools/checkstyle/checks/coding/InnerAssignmentCheck::log → KILLED
            log(ast.getLineNo(), ast.getColumnNo(), MSG_KEY);
141
        }
142
    }
143
144
    /**
145
     * Determines if ast is in the body of a flow control statement without
146
     * braces. An example of such a statement would be
147
     * <p>
148
     * <pre>
149
     * if (y < 0)
150
     *     x = y;
151
     * </pre>
152
     * </p>
153
     * <p>
154
     * This leads to the following AST structure:
155
     * </p>
156
     * <p>
157
     * <pre>
158
     * LITERAL_IF
159
     *     LPAREN
160
     *     EXPR // test
161
     *     RPAREN
162
     *     EXPR // body
163
     *     SEMI
164
     * </pre>
165
     * </p>
166
     * <p>
167
     * We need to ensure that ast is in the body and not in the test.
168
     * </p>
169
     *
170
     * @param ast an assignment operator AST
171
     * @return whether ast is in the body of a flow control statement
172
     */
173
    private static boolean isInNoBraceControlStatement(DetailAST ast) {
174
        boolean result = false;
175 1 1. isInNoBraceControlStatement : negated conditional → KILLED
        if (isInContext(ast, CONTROL_CONTEXT)) {
176
            final DetailAST expr = ast.getParent();
177
            final AST exprNext = expr.getNextSibling();
178 1 1. isInNoBraceControlStatement : negated conditional → KILLED
            result = exprNext.getType() == TokenTypes.SEMI;
179
        }
180 1 1. isInNoBraceControlStatement : replaced return of integer sized value with (x == 0 ? 1 : 0) → KILLED
        return result;
181
    }
182
183
    /**
184
     * Tests whether the given AST is used in the "assignment in while" idiom.
185
     * <pre>
186
     * String line;
187
     * while ((line = bufferedReader.readLine()) != null) {
188
     *    // process the line
189
     * }
190
     * </pre>
191
     * Assignment inside a condition is not a problem here, as the assignment is surrounded by an
192
     * extra pair of parentheses. The comparison is {@code != null} and there is no chance that
193
     * intention was to write {@code line == reader.readLine()}.
194
     *
195
     * @param ast assignment AST
196
     * @return whether the context of the assignment AST indicates the idiom
197
     */
198
    private static boolean isInWhileIdiom(DetailAST ast) {
199
        boolean result = false;
200 1 1. isInWhileIdiom : negated conditional → KILLED
        if (isComparison(ast.getParent())) {
201
            result = isInContext(
202
                    ast.getParent(), ALLOWED_ASSIGNMENT_IN_COMPARISON_CONTEXT);
203
        }
204 1 1. isInWhileIdiom : replaced return of integer sized value with (x == 0 ? 1 : 0) → KILLED
        return result;
205
    }
206
207
    /**
208
     * Checks if an AST is a comparison operator.
209
     * @param ast the AST to check
210
     * @return true iff ast is a comparison operator.
211
     */
212
    private static boolean isComparison(DetailAST ast) {
213
        final int astType = ast.getType();
214 3 1. isComparison : changed conditional boundary → KILLED
2. isComparison : negated conditional → KILLED
3. isComparison : replaced return of integer sized value with (x == 0 ? 1 : 0) → KILLED
        return Arrays.binarySearch(COMPARISON_TYPES, astType) >= 0;
215
    }
216
217
    /**
218
     * Tests whether the provided AST is in
219
     * one of the given contexts.
220
     *
221
     * @param ast the AST from which to start walking towards root
222
     * @param contextSet the contexts to test against.
223
     *
224
     * @return whether the parents nodes of ast match one of the allowed type paths.
225
     */
226
    private static boolean isInContext(DetailAST ast, int[]... contextSet) {
227
        boolean found = false;
228 1 1. isInContext : Changed increment from 1 to -1 → KILLED
        for (int[] element : contextSet) {
229
            DetailAST current = ast;
230
            for (int anElement : element) {
231
                current = current.getParent();
232 1 1. isInContext : negated conditional → KILLED
                if (current.getType() == anElement) {
233
                    found = true;
234
                }
235
                else {
236
                    found = false;
237
                    break;
238
                }
239
            }
240
241 1 1. isInContext : negated conditional → KILLED
            if (found) {
242
                break;
243
            }
244
        }
245 1 1. isInContext : replaced return of integer sized value with (x == 0 ? 1 : 0) → KILLED
        return found;
246
    }
247
248
}

Mutations

109

1.1
Location : getDefaultTokens
Killed by : com.puppycrawl.tools.checkstyle.checks.coding.InnerAssignmentCheckTest.testTokensNotNull(com.puppycrawl.tools.checkstyle.checks.coding.InnerAssignmentCheckTest)
mutated return of Object value for com/puppycrawl/tools/checkstyle/checks/coding/InnerAssignmentCheck::getDefaultTokens to ( if (x != null) null else throw new RuntimeException ) → KILLED

114

1.1
Location : getAcceptableTokens
Killed by : com.puppycrawl.tools.checkstyle.checks.coding.InnerAssignmentCheckTest.testTokensNotNull(com.puppycrawl.tools.checkstyle.checks.coding.InnerAssignmentCheckTest)
mutated return of Object value for com/puppycrawl/tools/checkstyle/checks/coding/InnerAssignmentCheck::getAcceptableTokens to ( if (x != null) null else throw new RuntimeException ) → KILLED

119

1.1
Location : getRequiredTokens
Killed by : com.puppycrawl.tools.checkstyle.checks.coding.InnerAssignmentCheckTest.testTokensNotNull(com.puppycrawl.tools.checkstyle.checks.coding.InnerAssignmentCheckTest)
mutated return of Object value for com/puppycrawl/tools/checkstyle/checks/coding/InnerAssignmentCheck::getRequiredTokens to ( if (x != null) null else throw new RuntimeException ) → KILLED

137

1.1
Location : visitToken
Killed by : com.puppycrawl.tools.checkstyle.checks.coding.InnerAssignmentCheckTest.testLambdaExpression(com.puppycrawl.tools.checkstyle.checks.coding.InnerAssignmentCheckTest)
negated conditional → KILLED

138

1.1
Location : visitToken
Killed by : com.puppycrawl.tools.checkstyle.checks.coding.InnerAssignmentCheckTest.testIt(com.puppycrawl.tools.checkstyle.checks.coding.InnerAssignmentCheckTest)
negated conditional → KILLED

139

1.1
Location : visitToken
Killed by : com.puppycrawl.tools.checkstyle.checks.coding.InnerAssignmentCheckTest.testIt(com.puppycrawl.tools.checkstyle.checks.coding.InnerAssignmentCheckTest)
negated conditional → KILLED

140

1.1
Location : visitToken
Killed by : com.puppycrawl.tools.checkstyle.checks.coding.InnerAssignmentCheckTest.testIt(com.puppycrawl.tools.checkstyle.checks.coding.InnerAssignmentCheckTest)
removed call to com/puppycrawl/tools/checkstyle/checks/coding/InnerAssignmentCheck::log → KILLED

175

1.1
Location : isInNoBraceControlStatement
Killed by : com.puppycrawl.tools.checkstyle.checks.coding.InnerAssignmentCheckTest.testIt(com.puppycrawl.tools.checkstyle.checks.coding.InnerAssignmentCheckTest)
negated conditional → KILLED

178

1.1
Location : isInNoBraceControlStatement
Killed by : com.puppycrawl.tools.checkstyle.checks.coding.InnerAssignmentCheckTest.testIt(com.puppycrawl.tools.checkstyle.checks.coding.InnerAssignmentCheckTest)
negated conditional → KILLED

180

1.1
Location : isInNoBraceControlStatement
Killed by : com.puppycrawl.tools.checkstyle.checks.coding.InnerAssignmentCheckTest.testIt(com.puppycrawl.tools.checkstyle.checks.coding.InnerAssignmentCheckTest)
replaced return of integer sized value with (x == 0 ? 1 : 0) → KILLED

200

1.1
Location : isInWhileIdiom
Killed by : com.puppycrawl.tools.checkstyle.checks.coding.InnerAssignmentCheckTest.testIt(com.puppycrawl.tools.checkstyle.checks.coding.InnerAssignmentCheckTest)
negated conditional → KILLED

204

1.1
Location : isInWhileIdiom
Killed by : com.puppycrawl.tools.checkstyle.checks.coding.InnerAssignmentCheckTest.testIt(com.puppycrawl.tools.checkstyle.checks.coding.InnerAssignmentCheckTest)
replaced return of integer sized value with (x == 0 ? 1 : 0) → KILLED

214

1.1
Location : isComparison
Killed by : com.puppycrawl.tools.checkstyle.checks.coding.InnerAssignmentCheckTest.testIt(com.puppycrawl.tools.checkstyle.checks.coding.InnerAssignmentCheckTest)
changed conditional boundary → KILLED

2.2
Location : isComparison
Killed by : com.puppycrawl.tools.checkstyle.checks.coding.InnerAssignmentCheckTest.testIt(com.puppycrawl.tools.checkstyle.checks.coding.InnerAssignmentCheckTest)
negated conditional → KILLED

3.3
Location : isComparison
Killed by : com.puppycrawl.tools.checkstyle.checks.coding.InnerAssignmentCheckTest.testIt(com.puppycrawl.tools.checkstyle.checks.coding.InnerAssignmentCheckTest)
replaced return of integer sized value with (x == 0 ? 1 : 0) → KILLED

228

1.1
Location : isInContext
Killed by : com.puppycrawl.tools.checkstyle.checks.coding.InnerAssignmentCheckTest.testLambdaExpression(com.puppycrawl.tools.checkstyle.checks.coding.InnerAssignmentCheckTest)
Changed increment from 1 to -1 → KILLED

232

1.1
Location : isInContext
Killed by : com.puppycrawl.tools.checkstyle.checks.coding.InnerAssignmentCheckTest.testIt(com.puppycrawl.tools.checkstyle.checks.coding.InnerAssignmentCheckTest)
negated conditional → KILLED

241

1.1
Location : isInContext
Killed by : com.puppycrawl.tools.checkstyle.checks.coding.InnerAssignmentCheckTest.testLambdaExpression(com.puppycrawl.tools.checkstyle.checks.coding.InnerAssignmentCheckTest)
negated conditional → KILLED

245

1.1
Location : isInContext
Killed by : com.puppycrawl.tools.checkstyle.checks.coding.InnerAssignmentCheckTest.testIt(com.puppycrawl.tools.checkstyle.checks.coding.InnerAssignmentCheckTest)
replaced return of integer sized value with (x == 0 ? 1 : 0) → KILLED

Active mutators

Tests examined


Report generated by PIT 1.3.1