diff --git a/config/checkstyle-checks.xml b/config/checkstyle-checks.xml index 3f1f64fb371..86aaa4c973e 100644 --- a/config/checkstyle-checks.xml +++ b/config/checkstyle-checks.xml @@ -577,7 +577,7 @@ SL_ASSIGN, SR_ASSIGN, STAR_ASSIGN, LAMBDA, TEXT_BLOCK_LITERAL_BEGIN, LAND, LOR, LITERAL_INSTANCEOF, GT, LT, GE, LE, EQUAL, NOT_EQUAL, UNARY_MINUS, UNARY_PLUS, INC, DEC, LNOT, BNOT, POST_INC, POST_DEC, BOR, BXOR, BAND, - QUESTION"/> + QUESTION, TYPECAST"/> diff --git a/src/main/java/com/puppycrawl/tools/checkstyle/checks/coding/UnnecessaryParenthesesCheck.java b/src/main/java/com/puppycrawl/tools/checkstyle/checks/coding/UnnecessaryParenthesesCheck.java index 39d6e8b1ac9..36233dd75e4 100644 --- a/src/main/java/com/puppycrawl/tools/checkstyle/checks/coding/UnnecessaryParenthesesCheck.java +++ b/src/main/java/com/puppycrawl/tools/checkstyle/checks/coding/UnnecessaryParenthesesCheck.java @@ -182,7 +182,9 @@ * * POST_INC, * - * POST_DEC. + * POST_DEC, + * + * TYPECAST. * * * @@ -394,6 +396,7 @@ public int[] getDefaultTokens() { TokenTypes.BNOT, TokenTypes.POST_INC, TokenTypes.POST_DEC, + TokenTypes.TYPECAST, }; } @@ -445,6 +448,7 @@ public int[] getAcceptableTokens() { TokenTypes.BOR, TokenTypes.BAND, TokenTypes.QUESTION, + TokenTypes.TYPECAST, }; } @@ -467,42 +471,86 @@ else if (ast.getType() == TokenTypes.QUESTION) { .forEach(unnecessaryChild -> log(unnecessaryChild, MSG_EXPR)); } else if (parent.getType() != TokenTypes.ANNOTATION_MEMBER_VALUE_PAIR) { - final int type = ast.getType(); - final boolean surrounded = isSurrounded(ast); - // An identifier surrounded by parentheses. - if (surrounded && type == TokenTypes.IDENT) { - parentToSkip = ast.getParent(); - log(ast, MSG_IDENT, ast.getText()); - } - // A literal (numeric or string) surrounded by parentheses. - else if (surrounded && TokenUtil.isOfType(type, LITERALS)) { - parentToSkip = ast.getParent(); - if (type == TokenTypes.STRING_LITERAL) { - log(ast, MSG_STRING, - chopString(ast.getText())); - } - else if (type == TokenTypes.TEXT_BLOCK_LITERAL_BEGIN) { - // Strip newline control characters to keep message as single-line, add - // quotes to make string consistent with STRING_LITERAL - final String logString = QUOTE - + NEWLINE.matcher( - ast.getFirstChild().getText()).replaceAll("\\\\n") - + QUOTE; - log(ast, MSG_STRING, chopString(logString)); - } - else { - log(ast, MSG_LITERAL, ast.getText()); - } - } - // The rhs of an assignment surrounded by parentheses. - else if (TokenUtil.isOfType(type, ASSIGNMENTS)) { - assignDepth++; - final DetailAST last = ast.getLastChild(); - if (last.getType() == TokenTypes.RPAREN) { - log(ast, MSG_ASSIGN); - } + findUnnecessaryParenthesesInAst(ast); + } + } + + /** + * Finds and logs if {@code LITERAL},{@code IDENT}, {@code ASSIGNMENT} + * and {@code TYPECAST} are surrounded by parentheses. + * + * @param ast the {@code DetailAST} to check. + */ + private void findUnnecessaryParenthesesInAst(DetailAST ast) { + final int type = ast.getType(); + final boolean surrounded = isSurrounded(ast); + // An identifier surrounded by parentheses. + if (surrounded && type == TokenTypes.IDENT) { + parentToSkip = ast.getParent(); + log(ast, MSG_IDENT, ast.getText()); + } + // A literal (numeric or string) surrounded by parentheses. + else if (surrounded && TokenUtil.isOfType(type, LITERALS)) { + findUnnecessaryParenthesesAroundLiterals(ast); + } + // The rhs of an assignment surrounded by parentheses. + else if (TokenUtil.isOfType(type, ASSIGNMENTS)) { + assignDepth++; + final DetailAST last = ast.getLastChild(); + if (last.getType() == TokenTypes.RPAREN) { + log(ast, MSG_ASSIGN); } } + // A typecast surrounded by parentheses. + else if (surrounded && type == TokenTypes.TYPECAST + && isTypeCastSurrounded(ast)) { + log(ast, MSG_EXPR); + } + } + + /** + * Checks and logs if the given {@code LITERAL} is surrounded by parentheses. + * + * @param ast the {@code DetailAST} to check. + */ + private void findUnnecessaryParenthesesAroundLiterals(DetailAST ast) { + final int type = ast.getType(); + parentToSkip = ast.getParent(); + if (type == TokenTypes.STRING_LITERAL) { + log(ast, MSG_STRING, + chopString(ast.getText())); + } + else if (type == TokenTypes.TEXT_BLOCK_LITERAL_BEGIN) { + // Strip newline control characters to keep message as single-line, add + // quotes to make string consistent with STRING_LITERAL + final String logString = QUOTE + + NEWLINE.matcher( + ast.getFirstChild().getText()).replaceAll("\\\\n") + + QUOTE; + log(ast, MSG_STRING, chopString(logString)); + } + else { + log(ast, MSG_LITERAL, ast.getText()); + } + } + + /** + * Checks if the given {@code TYPECAST} is surrounded by parentheses. + * + * @param ast the {@code DetailAST} to check if it is surrounded by + * parentheses. + * @return {@code true} if is surrounded by + * parentheses. + */ + private static boolean isTypeCastSurrounded(DetailAST ast) { + final DetailAST parent = ast.getParent(); + final int parentType = parent.getType(); + return parentType != TokenTypes.DOT + && parentType != TokenTypes.EXPR + && parentType != TokenTypes.INDEX_OP + && parentType != TokenTypes.METHOD_REF + && parentType != TokenTypes.QUESTION + && !TokenUtil.isOfType(parentType, ASSIGNMENTS); } @Override diff --git a/src/main/resources/com/puppycrawl/tools/checkstyle/meta/checks/coding/UnnecessaryParenthesesCheck.xml b/src/main/resources/com/puppycrawl/tools/checkstyle/meta/checks/coding/UnnecessaryParenthesesCheck.xml index 14cb05d78e6..5081364fb73 100644 --- a/src/main/resources/com/puppycrawl/tools/checkstyle/meta/checks/coding/UnnecessaryParenthesesCheck.xml +++ b/src/main/resources/com/puppycrawl/tools/checkstyle/meta/checks/coding/UnnecessaryParenthesesCheck.xml @@ -68,7 +68,7 @@ } </pre> - diff --git a/src/test/java/com/puppycrawl/tools/checkstyle/checks/coding/UnnecessaryParenthesesCheckTest.java b/src/test/java/com/puppycrawl/tools/checkstyle/checks/coding/UnnecessaryParenthesesCheckTest.java index 4fbb0405fa3..86c1ef2a04b 100644 --- a/src/test/java/com/puppycrawl/tools/checkstyle/checks/coding/UnnecessaryParenthesesCheckTest.java +++ b/src/test/java/com/puppycrawl/tools/checkstyle/checks/coding/UnnecessaryParenthesesCheckTest.java @@ -100,6 +100,61 @@ public void testDefault() throws Exception { getPath("InputUnnecessaryParenthesesOperatorsAndCasts.java"), expected); } + @Test + public void testCasts1() throws Exception { + final String[] expected = { + "20:18: " + getCheckMessage(MSG_EXPR), + "20:43: " + getCheckMessage(MSG_EXPR), + "30:11: " + getCheckMessage(MSG_ASSIGN), + "33:11: " + getCheckMessage(MSG_ASSIGN), + "33:22: " + getCheckMessage(MSG_EXPR), + "48:19: " + getCheckMessage(MSG_EXPR), + "65:15: " + getCheckMessage(MSG_EXPR), + "74:31: " + getCheckMessage(MSG_EXPR), + "83:40: " + getCheckMessage(MSG_EXPR), + "93:20: " + getCheckMessage(MSG_EXPR), + "96:22: " + getCheckMessage(MSG_EXPR), + "100:30: " + getCheckMessage(MSG_EXPR), + "104:38: " + getCheckMessage(MSG_EXPR), + "108:52: " + getCheckMessage(MSG_EXPR), + "114:18: " + getCheckMessage(MSG_EXPR), + }; + verifyWithInlineConfigParser( + getPath("InputUnnecessaryParenthesesCasts1.java"), expected); + } + + @Test + public void testCasts2() throws Exception { + final String[] expected = { + "31:28: " + getCheckMessage(MSG_EXPR), + "44:15: " + getCheckMessage(MSG_EXPR), + "54:32: " + getCheckMessage(MSG_EXPR), + "62:30: " + getCheckMessage(MSG_EXPR), + "72:14: " + getCheckMessage(MSG_EXPR), + "85:23: " + getCheckMessage(MSG_EXPR), + "95:23: " + getCheckMessage(MSG_EXPR), + "99:28: " + getCheckMessage(MSG_EXPR), + "103:38: " + getCheckMessage(MSG_EXPR), + }; + verifyWithInlineConfigParser( + getPath("InputUnnecessaryParenthesesCasts2.java"), expected); + } + + @Test + public void testCasts3() throws Exception { + final String[] expected = { + "31:49: " + getCheckMessage(MSG_EXPR), + "41:18: " + getCheckMessage(MSG_EXPR), + "46:34: " + getCheckMessage(MSG_EXPR), + "50:26: " + getCheckMessage(MSG_EXPR), + "55:50: " + getCheckMessage(MSG_EXPR), + "61:29: " + getCheckMessage(MSG_EXPR), + "66:13: " + getCheckMessage(MSG_ASSIGN), + }; + verifyWithInlineConfigParser( + getPath("InputUnnecessaryParenthesesCasts3.java"), expected); + } + @Test public void test15Extensions() throws Exception { final String[] expected = { diff --git a/src/test/resources/com/puppycrawl/tools/checkstyle/checks/coding/unnecessaryparentheses/InputUnnecessaryParenthesesCasts1.java b/src/test/resources/com/puppycrawl/tools/checkstyle/checks/coding/unnecessaryparentheses/InputUnnecessaryParenthesesCasts1.java new file mode 100644 index 00000000000..d0339cd1a12 --- /dev/null +++ b/src/test/resources/com/puppycrawl/tools/checkstyle/checks/coding/unnecessaryparentheses/InputUnnecessaryParenthesesCasts1.java @@ -0,0 +1,119 @@ +/* +UnnecessaryParentheses +tokens = (default)EXPR, IDENT, NUM_DOUBLE, NUM_FLOAT, NUM_INT, NUM_LONG, \ + STRING_LITERAL, LITERAL_NULL, LITERAL_FALSE, LITERAL_TRUE, ASSIGN, \ + BAND_ASSIGN, BOR_ASSIGN, BSR_ASSIGN, BXOR_ASSIGN, DIV_ASSIGN, \ + MINUS_ASSIGN, MOD_ASSIGN, PLUS_ASSIGN, SL_ASSIGN, SR_ASSIGN, STAR_ASSIGN, \ + LAMBDA, TEXT_BLOCK_LITERAL_BEGIN, LAND, LITERAL_INSTANCEOF, GT, LT, GE, \ + LE, EQUAL, NOT_EQUAL, UNARY_MINUS, UNARY_PLUS, INC, DEC, LNOT, BNOT, \ + POST_INC, POST_DEC, TYPECAST + + +*/ +package com.puppycrawl.tools.checkstyle.checks.coding.unnecessaryparentheses; +public class InputUnnecessaryParenthesesCasts1 { + public void valid1() { + int x = 23; + int y = 44; + float k = 12f; + + int d = ((int) 100f) + 100 * 2 / ((int) 12.5) + (int) 90f; + // 2 violations above: + // 'Unnecessary parentheses around expression' + // 'Unnecessary parentheses around expression' + int p = (int) 110f + 10 * 2 / (int) 10f + (int) 32.2; + + y = (int) (22.2 * 2) / ((int) 8f + 5); + + double arg2 = 23.2; + int i = (int) arg2; + i = ((int) arg2); // violation 'Unnecessary parentheses around assignment right-hand side' + p = (int) arg2; + + x = (2 * 2 /((int) k)); + // 2 violations above: + // 'Unnecessary parentheses around assignment right-hand side' + // 'Unnecessary parentheses around expression' + x = 2 * 2 / (int) k; + + int par = ((int)2f * 2) / 4; + y = ((Integer) x).hashCode(); + + int py = 12; + float xy = 40f; + int yp = 0; + boolean finished = true; + boolean result = false; + + if(py >= ((int)xy) || (yp ==1 | py >=1)) { + // violation above 'Unnecessary parentheses around expression.' + xy--; + } + else if(yp >= (int)xy || (py ==1 | py >=1)) { + xy++; + } + + if (!((int) xy > yp) && py < 20) { + py++; + } + + if (35 + (int)'a' == 100) { + py++; + } + + boolean checkone = true; + if (!((boolean) checkone)) { + // violation above 'Unnecessary parentheses around expression.' + checkone = false; + } + else if ((boolean) checkone) { + checkone = !checkone; + } + + double limit = 3.2; + for (int j = 0; j >= ((int) limit); j++) { + // violation above 'Unnecessary parentheses around expression.' + yp +=1; + } + for (int j =0; j >= (int) limit; j++) { + py--; + break; + } + + for(int j = 10; !finished && !((boolean) (j > 5)) ; j++){ + // violation above 'Unnecessary parentheses around expression.' + break; + } + for(int jp = 9; !finished || !(boolean) (jp >5); jp++){ + checkone = false; + break; + } + + // violation below 'Unnecessary parentheses around expression.' + long p1 = ((long) ((20 >> 24 ) & 0xFF)) << 24; + long p2 = (long) ((20 >> 24 ) & 0xFF) << 24; + + float f4 = -((float) 42); // violation 'Unnecessary parentheses around expression.' + float f5 = -(float) 90; + + // violation below 'Unnecessary parentheses around expression.' + long shiftedbytwo = ((long)900) << 2; + long shiftedbythree = (long)899 << 3; + + // violation below 'Unnecessary parentheses around expression.' + short complement = (short) ~((short) 87777); + short bcomplement = (short) ~(short) 90122; + + // violation below 'Unnecessary parentheses around expression.' + int numSlices1 = (int) Math.max(Math.ceil(((double) 20) / 10), 1); + int numSlices2 = (int) Math.max(Math.ceil((double) 20 / 10), 1); + } + + private long getLong1(int start, int end) { + // violation below 'Unnecessary parentheses around expression.' + return (((long) start) << 32) | 0xFFFFFFFFL & end; + } + private long getLong2(int start, int end) { + return ((long) start << 32) | 0xFFFFFFFFL & end; + } +} diff --git a/src/test/resources/com/puppycrawl/tools/checkstyle/checks/coding/unnecessaryparentheses/InputUnnecessaryParenthesesCasts2.java b/src/test/resources/com/puppycrawl/tools/checkstyle/checks/coding/unnecessaryparentheses/InputUnnecessaryParenthesesCasts2.java new file mode 100644 index 00000000000..16cd5bb8637 --- /dev/null +++ b/src/test/resources/com/puppycrawl/tools/checkstyle/checks/coding/unnecessaryparentheses/InputUnnecessaryParenthesesCasts2.java @@ -0,0 +1,116 @@ +/* +UnnecessaryParentheses +tokens = (default)EXPR, IDENT, NUM_DOUBLE, NUM_FLOAT, NUM_INT, NUM_LONG, \ + STRING_LITERAL, LITERAL_NULL, LITERAL_FALSE, LITERAL_TRUE, ASSIGN, \ + BAND_ASSIGN, BOR_ASSIGN, BSR_ASSIGN, BXOR_ASSIGN, DIV_ASSIGN, \ + MINUS_ASSIGN, MOD_ASSIGN, PLUS_ASSIGN, SL_ASSIGN, SR_ASSIGN, STAR_ASSIGN, \ + LAMBDA, TEXT_BLOCK_LITERAL_BEGIN, LAND, LITERAL_INSTANCEOF, GT, LT, GE, \ + LE, EQUAL, NOT_EQUAL, UNARY_MINUS, UNARY_PLUS, INC, DEC, LNOT, BNOT, \ + POST_INC, POST_DEC, TYPECAST + + +*/ + +package com.puppycrawl.tools.checkstyle.checks.coding.unnecessaryparentheses; + +import static org.junit.Assert.assertEquals; +import static java.lang.Math.abs; +import java.util.Arrays; +import java.util.HashSet; +import java.util.function.ToIntFunction; + +public class InputUnnecessaryParenthesesCasts2 { + public void fooConditionals(T element) { + int x = 23; + int y = 44; + float k = 12f; + boolean finished = true; + boolean result = false; + + String filevalue = "FILEVALUE"; + if (!finished || !((boolean) filevalue.contains("O"))) { + // violation above 'Unnecessary parentheses around expression.' + filevalue += "F"; + } + else if (finished || !(boolean) filevalue.contains("O")) { + filevalue += "G"; + } + + if (result && finished || ((int) 23.1 + 21) == 32) { + y--; + } + + // violation below 'Unnecessary parentheses around expression.' + if (!((boolean) filevalue.contains("G")) || finished) { + x++; + } + else if (!(boolean) filevalue.contains("P") || finished) { + filevalue += "p"; + } + + String[] a = {"s", "a", "1", "2", "3"}; + String[] abr = {"18", "z", "w", "30", "u", "vel"}; + Arrays.stream(a) + .filter(s -> !((boolean) s.isEmpty())) + // violation above 'Unnecessary parentheses around expression.' + .toArray(String[]::new); + + Arrays.stream(abr).filter(s -> !(boolean) s.isEmpty()) + .toArray(String[]::new); + + Arrays.stream(a) // violation below 'Unnecessary parentheses around expression.' + .filter(s -> ((boolean) s.isEmpty())) + .toArray(String[]::new); + Arrays.stream(abr) + .filter(s -> (boolean) s.isEmpty()) + .toArray(String[]::new); + + new HashSet().stream() + .filter(f -> f > ((int) 1.1 + 200)); + + // violation below 'Unnecessary parentheses around expression.' + if (((double) abs(10 - 2)) / 2 <= 0.01) { + y += 10; + } + else if ((double) abs(10 - 2) / 2 >= 0.02) { + x += 2; + } + + final ToIntFunction comparison1 = ((Comparable) element)::compareTo; // no violation + + Bean bean = new Bean(); + assertEquals(1, ((int[]) bean.array)[0]); // no violation + + // violation below 'Unnecessary parentheses around expression.' + float rest = ((float) (50 - System.currentTimeMillis())) / 1000; + float stop = (float) (50 - System.currentTimeMillis()) / 1000; + + // no violation below as parentheses detection in ternary(?) operators + // is handled by different logic and is dependent on QUESION token + float pin = false ? ((float) 21) : ((float) 31); + + Object obj = "hello"; // no violation below + String result1 = (obj instanceof String) ? ((String) obj) : "Default"; + + String op1 = ((String) obj) + ((Boolean) finished).toString(); + // violation above 'Unnecessary parentheses around expression.' + String op2 = (String) obj + ((Boolean) finished).toString(); + + filevalue = "'" + ((char) 32) + "'"; + // violation above 'Unnecessary parentheses around expression.' + filevalue = "'" + (char) 31 + "'"; // no violation + + ck("name", (long) k, (long) ((byte) 0x22)); + // violation above 'Unnecessary parentheses around expression.' + ck("check", (long) k, (long) (byte) 0x24); + } + + public void ck(String byt, long a, long c) {} + static class T {} + public class Bean { + public Object array; + public Bean() { + array = new int[]{1, 2, 3}; + } + } +} diff --git a/src/test/resources/com/puppycrawl/tools/checkstyle/checks/coding/unnecessaryparentheses/InputUnnecessaryParenthesesCasts3.java b/src/test/resources/com/puppycrawl/tools/checkstyle/checks/coding/unnecessaryparentheses/InputUnnecessaryParenthesesCasts3.java new file mode 100644 index 00000000000..877868f85c0 --- /dev/null +++ b/src/test/resources/com/puppycrawl/tools/checkstyle/checks/coding/unnecessaryparentheses/InputUnnecessaryParenthesesCasts3.java @@ -0,0 +1,77 @@ +/* +UnnecessaryParentheses +tokens = (default)EXPR, IDENT, NUM_DOUBLE, NUM_FLOAT, NUM_INT, NUM_LONG, \ + STRING_LITERAL, LITERAL_NULL, LITERAL_FALSE, LITERAL_TRUE, ASSIGN, \ + BAND_ASSIGN, BOR_ASSIGN, BSR_ASSIGN, BXOR_ASSIGN, DIV_ASSIGN, \ + MINUS_ASSIGN, MOD_ASSIGN, PLUS_ASSIGN, SL_ASSIGN, SR_ASSIGN, STAR_ASSIGN, \ + LAMBDA, TEXT_BLOCK_LITERAL_BEGIN, LAND, LITERAL_INSTANCEOF, GT, LT, GE, \ + LE, EQUAL, NOT_EQUAL, UNARY_MINUS, UNARY_PLUS, INC, DEC, LNOT, BNOT, \ + POST_INC, POST_DEC, TYPECAST + + +*/ + +package com.puppycrawl.tools.checkstyle.checks.coding.unnecessaryparentheses; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +public class InputUnnecessaryParenthesesCasts3 { + private static final double MDP_MULT = 2.2; + + public void fooUnnecessaryParentheses(String hexa) { + Object b = new Object(); + int unm = 30; + boolean Eval = false; + List siZ = new ArrayList<>(); + Map record = new HashMap<>(); + if (hexa.contains("Z")) { + throw new IllegalArgumentException(((char) b) + " is not a hexadecimal digit"); + // violation above 'Unnecessary parentheses around expression.' + } + if (hexa.contains("Y")) { + throw new IllegalArgumentException((char) b + " is not a hexadecimal digit"); + } + + Attrs le = new Attrs(); + String fileName = "log.txt"; + setFileLastModified(fileName, + ((long) le.getMTime()) * 1000); + // violation above 'Unnecessary parentheses around expression.' + setFileLastModified(fileName, + (long) le.getMTime() * 1000); + + if (!Eval && unm > 0 && ((int) (12f * unm - 1)) > unm+20) {} + // violation above 'Unnecessary parentheses around expression.' + if (!Eval && unm > 0 && (int) (12f * unm - 1) > unm+20) {} + + final long l8 = ((long) record.get(unm)) & 0xffL; + // violation above 'Unnecessary parentheses around expression.' + final long l9 = (long) record.get(unm) & 0xffL; + + int maxSize = 21; + double used = maxSize == 0 ? 0 : 100 * (((double) unm) / maxSize); + // violation above 'Unnecessary parentheses around expression.' + double used1 = maxSize == 0 ? 0 : 100 * ((double) unm / maxSize); + + int dX = 90; + int dY = 2; + int stepx = (int) (((double)dX) * MDP_MULT) / dY; + // violation above 'Unnecessary parentheses around expression.' + int stepy = (int) ((double)dX * MDP_MULT) / dY; + + Object layerOffset; + unm += ((int) (Math.sqrt(unm) * 1.5)); + // violation above 'Unnecessary parentheses around assignment right-hand side' + unm += (int) (Math.sqrt(unm) * 1.5); + } + + public static void setFileLastModified(String fileName, long lastModified) {} + public static class Attrs { + int getMTime() { + return 123; + } + } +} diff --git a/src/xdocs/checks/coding/unnecessaryparentheses.xml b/src/xdocs/checks/coding/unnecessaryparentheses.xml index c380ee723a4..df1df3f31b0 100644 --- a/src/xdocs/checks/coding/unnecessaryparentheses.xml +++ b/src/xdocs/checks/coding/unnecessaryparentheses.xml @@ -179,6 +179,8 @@ if ((++f) > g && a) { // violation, unnecessary paren BAND , QUESTION + , + TYPECAST . @@ -264,6 +266,8 @@ if ((++f) > g && a) { // violation, unnecessary paren POST_INC , POST_DEC + , + TYPECAST . 3.4