Test literals

This commit is contained in:
Clément Fournier
2019-02-17 17:13:48 +01:00
committed by Andreas Dangel
parent 56b2e6c420
commit dc56dd0676
11 changed files with 326 additions and 190 deletions

View File

@ -65,7 +65,7 @@ by it was inconsistent, and ultimately that level of nesting was unnecessary.
* As is usual, use the designer to explore the new AST structure
* {% jdoc_old jast::ASTPrimaryPrefix %} and {% jdoc_old jast::ASTPrimarySuffix %} are not nodes anymore.
Subtrees for expressions appear to be left-recursive now. For example,
Subtrees for primary expressions appear to be left-recursive now. For example,
```java
new Foo().bar.foo(1)
@ -120,6 +120,23 @@ give you the information you need quickly.
TODO write a summary of changes in the javadoc of the package, will be more
accessible.
Note: this doesn't affect binary expressions like {% jdoc jast::ASTAdditiveExpression %}.
E.g. `a+b+c` is not parsed as
```
AdditiveExpression
+ AdditiveExpression
+ (a)
+ (b)
+ (c)
```
It's still
```
AdditiveExpression
+ (a)
+ (b)
+ (c)
```
which is easier to navigate, especially from XPath.
## New API support guidelines

View File

@ -5,19 +5,22 @@
package net.sourceforge.pmd.lang.java.ast;
public class ASTBooleanLiteral extends AbstractJavaTypeNode {
public final class ASTBooleanLiteral extends AbstractJavaTypeNode implements ASTLiteral {
private boolean isTrue;
public ASTBooleanLiteral(int id) {
ASTBooleanLiteral(int id) {
super(id);
}
public ASTBooleanLiteral(JavaParser p, int id) {
ASTBooleanLiteral(JavaParser p, int id) {
super(p, id);
}
public void setTrue() {
void setTrue() {
isTrue = true;
}

View File

@ -8,6 +8,11 @@ package net.sourceforge.pmd.lang.java.ast;
import org.apache.commons.lang3.StringEscapeUtils;
/**
* Represents a character literal. The image of this node can be the literal as it appeared
* in the source, but JavaCC performs its own unescaping and some escapes may be lost. At the
* very least it has delimiters. {@link #getUnescapedValue()} allows to recover the actual runtime value.
*/
public final class ASTCharLiteral extends AbstractJavaTypeNode implements ASTLiteral {
@ -21,9 +26,7 @@ public final class ASTCharLiteral extends AbstractJavaTypeNode implements ASTLit
}
/**
* Accept the visitor. *
*/
@Override
public Object jjtAccept(JavaParserVisitor visitor, Object data) {
return visitor.visit(this, data);
@ -39,9 +42,10 @@ public final class ASTCharLiteral extends AbstractJavaTypeNode implements ASTLit
/**
* Gets the char value of this literal.
*/
public char getEscapedValue() {
// TODO
return StringEscapeUtils.unescapeJava(getImage()).charAt(0);
public char getUnescapedValue() {
String image = getImage();
String woDelims = image.substring(1, image.length() - 1);
return StringEscapeUtils.unescapeJava(woDelims).charAt(0);
}
}

View File

@ -15,6 +15,7 @@ package net.sourceforge.pmd.lang.java.ast;
* | {@link ASTCharLiteral CharLiteral}
* | {@link ASTBooleanLiteral BooleanLiteral}
* | {@link ASTNullLiteral NullLiteral}
* | {@link ASTClassLiteral ClassLiteral}
*
* </pre>
*/
@ -44,6 +45,21 @@ public interface ASTLiteral extends ASTPrimaryExpression {
}
/**
* Returns true if this is a {@linkplain ASTNullLiteral class literal}.
*/
default boolean isClassLiteral() {
return this instanceof ASTClassLiteral;
}
/**
* Returns true if this is a {@linkplain ASTBooleanLiteral boolean literal}.
*/
default boolean isBooleanLiteral() {
return this instanceof ASTBooleanLiteral;
}
/**
* Returns true if this is a {@linkplain ASTNumericLiteral numeric literal}
* of any kind.

View File

@ -7,7 +7,6 @@ package net.sourceforge.pmd.lang.java.ast;
import java.math.BigInteger;
import java.util.Locale;
import java.util.regex.Pattern;
/**
@ -17,7 +16,7 @@ public class ASTNumericLiteral extends AbstractJavaTypeNode implements ASTLitera
// by default is double
// TODO this can be done in jjtCloseNodeScope
// TODO all of this can be done in jjtCloseNodeScope
private boolean isInt;
private boolean isFloat;
@ -60,9 +59,7 @@ public class ASTNumericLiteral extends AbstractJavaTypeNode implements ASTLitera
public boolean isIntLiteral() {
String image = getImage();
if (isInt && image != null && image.length() > 0) {
if (!image.endsWith("l") && !image.endsWith("L")) {
return true;
}
return !image.endsWith("l") && !image.endsWith("L");
}
return false;
}
@ -77,9 +74,7 @@ public class ASTNumericLiteral extends AbstractJavaTypeNode implements ASTLitera
public boolean isLongLiteral() {
String image = getImage();
if (isInt && image != null && image.length() > 0) {
if (image.endsWith("l") || image.endsWith("L")) {
return true;
}
return image.endsWith("l") || image.endsWith("L");
}
return false;
}
@ -91,9 +86,7 @@ public class ASTNumericLiteral extends AbstractJavaTypeNode implements ASTLitera
String image = getImage();
if (isFloat && image != null && image.length() > 0) {
char lastChar = image.charAt(image.length() - 1);
if (lastChar == 'f' || lastChar == 'F') {
return true;
}
return lastChar == 'f' || lastChar == 'F';
}
return false;
}
@ -109,9 +102,7 @@ public class ASTNumericLiteral extends AbstractJavaTypeNode implements ASTLitera
String image = getImage();
if (isFloat && image != null && image.length() > 0) {
char lastChar = image.charAt(image.length() - 1);
if (lastChar == 'd' || lastChar == 'D' || Character.isDigit(lastChar) || lastChar == '.') {
return true;
}
return lastChar == 'd' || lastChar == 'D' || Character.isDigit(lastChar) || lastChar == '.';
}
return false;
}
@ -126,7 +117,8 @@ public class ASTNumericLiteral extends AbstractJavaTypeNode implements ASTLitera
image = image.substring(1);
}
if (image.endsWith("l")) {
char last = image.charAt(image.length()-1);
if (last == 'l' || last == 'd' || last == 'f') {
image = image.substring(0, image.length() - 1);
}

View File

@ -9,8 +9,10 @@ import org.apache.commons.lang3.StringEscapeUtils;
/**
* Represents a string literal. The image of this node is the literal as it appeared
* in the source. {@link #getEscapedValue()} allows to recover the actual runtime value.
* Represents a string literal. The image of this node can be the literal as it appeared
* in the source, but JavaCC performs its own unescaping and some escapes may be lost.
* At the very least it has delimiters. {@link #getUnescapedValue()} allows to recover
* the actual runtime value.
*/
public final class ASTStringLiteral extends AbstractJavaTypeNode implements ASTLiteral {
@ -25,6 +27,42 @@ public final class ASTStringLiteral extends AbstractJavaTypeNode implements ASTL
}
private String reconstructedImage = null;
@Override
public String getImage() {
if (reconstructedImage == null) {
reconstructedImage = getEscapedStringLiteral(super.getImage());
}
return reconstructedImage;
}
/**
* Tries to reconstruct the original string literal. If the original length
* is greater than the parsed String literal, then probably some unicode
* escape sequences have been used.
*/
private String getEscapedStringLiteral(String javaccEscaped) {
int fullLength = getEndColumn() - getBeginColumn();
if (fullLength > javaccEscaped.length()) {
StringBuilder result = new StringBuilder(fullLength);
for (int i = 0; i < javaccEscaped.length(); i++) {
char c = javaccEscaped.charAt(i);
if (c < 0x20 || c > 0xff || javaccEscaped.length() == 1) {
String hex = "0000" + Integer.toHexString(c);
result.append("\\u").append(hex.substring(hex.length() - 4));
} else {
result.append(c);
}
}
return result.toString();
}
return javaccEscaped;
}
/**
* Accept the visitor. *
*/
@ -43,8 +81,10 @@ public final class ASTStringLiteral extends AbstractJavaTypeNode implements ASTL
/**
* Returns the value without delimiters and unescaped.
*/
public String getEscapedValue() {
return StringEscapeUtils.unescapeJava(getImage());
public String getUnescapedValue() {
String image = getImage();
String woDelims = image.substring(1, image.length() - 1);
return StringEscapeUtils.unescapeJava(woDelims);
}
}

View File

@ -177,7 +177,7 @@ public class AvoidDuplicateLiteralsRule extends AbstractJavaRule {
if (occurrences.size() >= threshold) {
ASTLiteral first = occurrences.get(0);
if (first instanceof ASTStringLiteral) {
String rawImage = ((ASTStringLiteral) first).getEscapedValue();
String rawImage = ((ASTStringLiteral) first).getUnescapedValue();
Object[] args = {rawImage, occurrences.size(), first.getBeginLine(),};
addViolation(data, first, args);
}

View File

@ -6,7 +6,6 @@ package net.sourceforge.pmd.lang.java.rule.performance;
import net.sourceforge.pmd.lang.java.ast.ASTBlockStatement;
import net.sourceforge.pmd.lang.java.ast.ASTLiteral;
import net.sourceforge.pmd.lang.java.ast.ASTNullLiteral;
import net.sourceforge.pmd.lang.java.ast.ASTStringLiteral;
import net.sourceforge.pmd.lang.java.rule.AbstractJavaRule;
@ -32,7 +31,7 @@ public class AppendCharacterWithCharRule extends AbstractJavaRule {
return data;
}
if (((ASTStringLiteral) node).getEscapedValue().length() == 1) {
if (((ASTStringLiteral) node).getUnescapedValue().length() == 1) {
if (!InefficientStringBufferingRule.isInStringBufferOperation(node, 8, "append")) {
return data;
}

View File

@ -5,7 +5,6 @@
package net.sourceforge.pmd.lang.java.rule.performance;
import net.sourceforge.pmd.lang.ast.Node;
import net.sourceforge.pmd.lang.java.ast.ASTLiteral;
import net.sourceforge.pmd.lang.java.ast.ASTStringLiteral;
import net.sourceforge.pmd.lang.java.rule.AbstractPoorMethodCall;
@ -38,7 +37,7 @@ public class UseIndexOfCharRule extends AbstractPoorMethodCall {
@Override
protected boolean isViolationArgument(Node arg) {
return arg instanceof ASTStringLiteral && ((ASTStringLiteral) arg).getEscapedValue().length() == 1;
return arg instanceof ASTStringLiteral && ((ASTStringLiteral) arg).getUnescapedValue().length() == 1;
}
}

View File

@ -1,155 +0,0 @@
/**
* BSD-style license; for more info see http://pmd.sourceforge.net/license.html
*/
package net.sourceforge.pmd.lang.java.ast;
import static net.sourceforge.pmd.lang.java.ParserTstUtil.getNodes;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
import java.util.Set;
import org.junit.Test;
import net.sourceforge.pmd.PMD;
public class ASTLiteralTest {
@Test
public void testIsStringLiteral() {
Set<ASTLiteral> literals = getNodes(ASTLiteral.class, TEST1);
assertTrue((literals.iterator().next()).isStringLiteral());
}
@Test
public void testIsNotStringLiteral() {
Set<ASTLiteral> literals = getNodes(ASTLiteral.class, TEST2);
assertFalse((literals.iterator().next()).isStringLiteral());
}
@Test
public void testIsIntIntLiteral() {
Set<ASTLiteral> literals = getNodes(ASTLiteral.class, TEST3);
assertTrue((literals.iterator().next()).isIntLiteral());
}
@Test
public void testIsIntLongLiteral() {
Set<ASTLiteral> literals = getNodes(ASTLiteral.class, TEST4);
assertTrue((literals.iterator().next()).isLongLiteral());
}
@Test
public void testIsFloatFloatLiteral() {
Set<ASTLiteral> literals = getNodes(ASTLiteral.class, TEST5);
assertTrue((literals.iterator().next()).isFloatLiteral());
}
@Test
public void testIsFloatDoubleLiteral() {
Set<ASTLiteral> literals = getNodes(ASTLiteral.class, TEST6);
assertTrue((literals.iterator().next()).isDoubleLiteral());
}
@Test
public void testIsCharLiteral() {
Set<ASTLiteral> literals = getNodes(ASTLiteral.class, TEST7);
assertTrue((literals.iterator().next()).isCharLiteral());
}
@Test
public void testIntValueParsing() {
ASTNumericLiteral literal = new ASTNumericLiteral(1);
literal.setIntLiteral();
literal.setImage("1___234");
literal.testingOnlySetBeginColumn(1);
literal.testingOnlySetEndColumn(7);
assertEquals(1___234, literal.getValueAsInt());
}
@Test
public void testIntValueParsingBinary() {
ASTNumericLiteral literal = new ASTNumericLiteral(1);
literal.setIntLiteral();
literal.setImage("0b0000_0010");
literal.testingOnlySetBeginColumn(1);
literal.testingOnlySetEndColumn(7);
assertEquals(0b0000_0010, literal.getValueAsInt());
}
@Test
public void testIntValueParsingNegativeHexa() {
ASTNumericLiteral literal = new ASTNumericLiteral(1);
literal.setIntLiteral();
literal.setImage("-0X0000_000f");
literal.testingOnlySetBeginColumn(1);
literal.testingOnlySetEndColumn(7);
assertEquals(-0X0000_000f, literal.getValueAsInt());
}
@Test
public void testFloatValueParsingNegative() {
ASTNumericLiteral literal = new ASTNumericLiteral(1);
literal.setFloatLiteral();
literal.setImage("-3_456.123_456");
literal.testingOnlySetBeginColumn(1);
literal.testingOnlySetEndColumn(7);
assertEquals(-3_456.123_456f, literal.getValueAsFloat(), 0);
}
@Test
public void testStringUnicodeEscapesNotEscaped() {
ASTStringLiteral literal = new ASTStringLiteral(1);
literal.setImage("abcüabc");
literal.testingOnlySetBeginColumn(1);
literal.testingOnlySetEndColumn(7);
assertEquals("abcüabc", literal.getEscapedValue());
assertEquals("abcüabc", literal.getImage());
}
@Test
public void testStringUnicodeEscapesInvalid() {
ASTStringLiteral literal = new ASTStringLiteral(1);
literal.setImage("abc\\uXYZAabc");
literal.testingOnlySetBeginColumn(1);
literal.testingOnlySetEndColumn(12);
assertEquals("abc\\uXYZAabc", literal.getEscapedValue());
assertEquals("abc\\uXYZAabc", literal.getImage());
}
@Test
public void testStringUnicodeEscapesValid() {
ASTStringLiteral literal = new ASTStringLiteral(1);
literal.setImage("abc\u1234abc");
literal.testingOnlySetBeginColumn(1);
literal.testingOnlySetEndColumn(12);
assertEquals("abc\\u1234abc", literal.getEscapedValue());
assertEquals("abcሴabc", literal.getImage());
}
@Test
public void testCharacterUnicodeEscapesValid() {
ASTCharLiteral literal = new ASTCharLiteral(1);
literal.setImage("\u0030");
literal.testingOnlySetBeginColumn(1);
literal.testingOnlySetEndColumn(6);
assertEquals("\\u0030", literal.getEscapedValue());
assertEquals("0", literal.getImage());
}
private static final String TEST1 = "public class Foo {" + PMD.EOL + " String x = \"foo\";" + PMD.EOL + "}";
private static final String TEST2 = "public class Foo {" + PMD.EOL + " int x = 42;" + PMD.EOL + "}";
private static final String TEST3 = "public class Foo {" + PMD.EOL + " int x = 42;" + PMD.EOL + "}";
private static final String TEST4 = "public class Foo {" + PMD.EOL + " long x = 42L;" + PMD.EOL + "}";
private static final String TEST5 = "public class Foo {" + PMD.EOL + " float x = 3.14159f;" + PMD.EOL + "}";
private static final String TEST6 = "public class Foo {" + PMD.EOL + " double x = 3.14159;" + PMD.EOL + "}";
private static final String TEST7 = "public class Foo {" + PMD.EOL + " char x = 'x';" + PMD.EOL + "}";
}

View File

@ -0,0 +1,221 @@
package net.sourceforge.pmd.lang.java.ast
import io.kotlintest.shouldBe
import io.kotlintest.specs.FunSpec
import net.sourceforge.pmd.lang.ast.test.shouldBe
/**
* @author Clément Fournier
* @since 7.0.0
*/
class ASTLiteralTest : FunSpec({
testGroup("String literal") {
"\"\"" should matchExpr<ASTStringLiteral> {
it::isStringLiteral shouldBe true
it::getUnescapedValue shouldBe ""
it::getImage shouldBe "\"\""
}
"\"foo\"" should matchExpr<ASTStringLiteral> {
it::getUnescapedValue shouldBe "foo"
it::getImage shouldBe "\"foo\""
}
"\"foo\\t\"" should matchExpr<ASTStringLiteral> {
it::getUnescapedValue shouldBe "foo\t"
it::getImage shouldBe "\"foo\\t\""
}
}
testGroup("String literal escapes") {
"\"abc\u1234abc\"" should matchExpr<ASTStringLiteral> {
it::getUnescapedValue shouldBe "abc\u1234abc"
it::getImage shouldBe "\"abc\u1234abc\""
}
"\"abc\\u1234abc\"" should matchExpr<ASTStringLiteral> {
it::getUnescapedValue shouldBe "abc\u1234abc"
it::getImage shouldBe "\"abc\\u1234abc\""
}
"\"abcüabc\"" should matchExpr<ASTStringLiteral> {
it::getUnescapedValue shouldBe "abcüabc"
it::getImage shouldBe "\"abcüabc\""
}
}
testGroup("Char literal") {
"'c'" should matchExpr<ASTCharLiteral> {
it::isCharLiteral shouldBe true
it::getUnescapedValue shouldBe 'c'
it::getImage shouldBe "'c'"
}
"'\t'" should matchExpr<ASTCharLiteral> {
it::getUnescapedValue shouldBe '\t'
it::getImage shouldBe "'\t'"
}
"'\\t'" should matchExpr<ASTCharLiteral> {
it::getUnescapedValue shouldBe '\t'
it::getImage shouldBe "'\\t'"
}
}
testGroup("Class literals") {
"void.class" should matchExpr<ASTClassLiteral> {
it::isClassLiteral shouldBe true
it::isVoid shouldBe true
it.typeNode.shouldBeEmpty()
}
"Integer.class" should matchExpr<ASTClassLiteral> {
it::isClassLiteral shouldBe true
it::isVoid shouldBe false
it.typeNode shouldBePresent child<ASTClassOrInterfaceType> {}
}
"int.class" should matchExpr<ASTClassLiteral> {
it::isVoid shouldBe false
it.typeNode shouldBePresent child<ASTPrimitiveType> {}
}
"int[].class" should matchExpr<ASTClassLiteral> {
it::isVoid shouldBe false
it.typeNode shouldBePresent child<ASTArrayType> {
it.elementType shouldBe child<ASTPrimitiveType> {}
it.dimensions shouldBe child {
it.size shouldBe 1
child<ASTArrayTypeDim> {}
}
}
}
}
testGroup("Boolean literals") {
"true" should matchExpr<ASTBooleanLiteral> {
it::isBooleanLiteral shouldBe true
it::isTrue shouldBe true
}
"false" should matchExpr<ASTBooleanLiteral> {
it::isBooleanLiteral shouldBe true
it::isTrue shouldBe false
}
}
testGroup("Null literal") {
"null" should matchExpr<ASTNullLiteral> {
it::isBooleanLiteral shouldBe false
it::isStringLiteral shouldBe false
it::isNullLiteral shouldBe true
}
}
testGroup("Numeric literals") {
"12" should matchExpr<ASTNumericLiteral> {
it::isStringLiteral shouldBe false
it::isCharLiteral shouldBe false
it::isNumericLiteral shouldBe true
it::isIntLiteral shouldBe true
it::getValueAsInt shouldBe 12
it::getValueAsLong shouldBe 12L
it::getValueAsFloat shouldBe 12.0f
it::getValueAsDouble shouldBe 12.0
it::getImage shouldBe "12"
}
"1___234" should matchExpr<ASTNumericLiteral> {
it::isCharLiteral shouldBe false
it::isNumericLiteral shouldBe true
it::isIntLiteral shouldBe true
it::getValueAsInt shouldBe 1234
it::getImage shouldBe "1___234"
}
"0b0000_0010" should matchExpr<ASTNumericLiteral> {
it::isCharLiteral shouldBe false
it::isNumericLiteral shouldBe true
it::isIntLiteral shouldBe true
it::getValueAsInt shouldBe 2
it::getImage shouldBe "0b0000_0010"
}
"-0X0000_000f" should matchExpr<ASTNumericLiteral> {
it::isCharLiteral shouldBe false
it::isNumericLiteral shouldBe true
it::isIntLiteral shouldBe true
it::getValueAsInt shouldBe -15
it::getImage shouldBe "-0X0000_000f"
}
"12l" should matchExpr<ASTNumericLiteral> {
it::isCharLiteral shouldBe false
it::isNumericLiteral shouldBe true
it::isIntLiteral shouldBe false
it::isLongLiteral shouldBe true
it::getValueAsInt shouldBe 12
it::getValueAsLong shouldBe 12L
it::getValueAsFloat shouldBe 12.0f
it::getValueAsDouble shouldBe 12.0
it::getImage shouldBe "12l"
}
"12L" should matchExpr<ASTNumericLiteral> {
it::isLongLiteral shouldBe true
it::getValueAsInt shouldBe 12
it::getValueAsLong shouldBe 12L
it::getImage shouldBe "12L"
}
"12d" should matchExpr<ASTNumericLiteral> {
it::isIntLiteral shouldBe false
it::isNumericLiteral shouldBe true
it::isDoubleLiteral shouldBe true
it::getValueAsInt shouldBe 12
it::getValueAsFloat shouldBe 12.0f
it::getValueAsDouble shouldBe 12.0
it::getImage shouldBe "12d"
}
"12f" should matchExpr<ASTNumericLiteral> {
it::isIntLiteral shouldBe false
it::isDoubleLiteral shouldBe false
it::isNumericLiteral shouldBe true
it::isFloatLiteral shouldBe true
it::getValueAsInt shouldBe 12
it::getValueAsFloat shouldBe 12.0f
it::getValueAsDouble shouldBe 12.0
it::getImage shouldBe "12f"
}
"-3_456.123_456" should matchExpr<ASTNumericLiteral> {
it::isIntLiteral shouldBe false
it::isDoubleLiteral shouldBe true
it::isNumericLiteral shouldBe true
it::isFloatLiteral shouldBe false
it::getValueAsInt shouldBe -3456
it::getValueAsFloat shouldBe -3456.123456f
it::getValueAsDouble shouldBe -3456.123456
it::getImage shouldBe "-3_456.123_456"
}
}
})