blob: 992ce70d7ae91e9bdf21673cda6b70357c0afd66 [file] [log] [blame]
// Copyright 2016 The Bazel Authors. All rights reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package com.google.devtools.build.java.turbine.javac;
import static com.google.common.collect.Iterables.getOnlyElement;
import static com.google.common.truth.Truth.assertThat;
import static java.nio.charset.StandardCharsets.UTF_8;
import com.google.common.base.Joiner;
import com.sun.tools.javac.file.JavacFileManager;
import com.sun.tools.javac.parser.JavacParser;
import com.sun.tools.javac.parser.ParserFactory;
import com.sun.tools.javac.tree.JCTree.JCClassDecl;
import com.sun.tools.javac.tree.JCTree.JCCompilationUnit;
import com.sun.tools.javac.tree.JCTree.JCVariableDecl;
import com.sun.tools.javac.util.Context;
import java.io.IOError;
import java.io.IOException;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.JUnit4;
/** Unit tests for {@link TreePruner}. */
@RunWith(JUnit4.class)
public class TreePrunerTest {
Context context;
@Before
public void setUp() {
this.context = new Context();
// registers itself in the context as a side effect
new JavacFileManager(context, true, UTF_8);
}
JCCompilationUnit parseLines(String... lines) {
Context context = new Context();
try (JavacFileManager fm = new JavacFileManager(context, true, UTF_8)) {
ParserFactory parserFactory = ParserFactory.instance(context);
String input = Joiner.on('\n').join(lines);
JavacParser parser =
parserFactory.newParser(
input, /*keepDocComments=*/ false, /*keepEndPos=*/ false, /*keepLineMap=*/ false);
return parser.parseCompilationUnit();
} catch (IOException e) {
throw new IOError(e);
}
}
JCVariableDecl parseField(String line) {
JCCompilationUnit unit = parseLines("class T {", line, "}");
JCClassDecl classDecl = (JCClassDecl) getOnlyElement(unit.defs);
return (JCVariableDecl) getOnlyElement(classDecl.defs);
}
String printPruned(String line) {
JCVariableDecl tree = parseField(line);
TreePruner.prune(context, tree);
return tree.toString();
}
@Test
public void hello() {
String[] lines = {
"class Test {", //
" void f() {",
" System.err.println(\"hello\");",
" }",
"}",
};
JCCompilationUnit tree = parseLines(lines);
TreePruner.prune(context, tree);
String[] expected = {
"class Test {", //
" ",
" void f() {",
" }",
"}",
};
assertThat(prettyPrint(tree)).isEqualTo(Joiner.on('\n').join(expected));
}
@Test
public void initalizerBlocks() {
String[] lines = {
"class Test {",
" {",
" System.err.println(\"hello\");",
" }",
" static {",
" System.err.println(\"hello\");",
" }",
"}",
};
JCCompilationUnit tree = parseLines(lines);
TreePruner.prune(context, tree);
String[] expected = {
"class Test {", //
" {",
" }",
" static {",
" }",
"}",
};
assertThat(prettyPrint(tree)).isEqualTo(Joiner.on('\n').join(expected));
}
@Test
public void nonConstantFieldInitializers() {
String[] lines = {
"class Test {", //
" Object a = null;",
" int[] b = {1};",
" int c = g();",
" String d = \"\" + h();",
"}",
};
JCCompilationUnit tree = parseLines(lines);
TreePruner.prune(context, tree);
String[] expected = {
"class Test {", //
" Object a;",
" int[] b;",
" int c;",
" String d;",
"}"
};
assertThat(prettyPrint(tree)).isEqualTo(Joiner.on('\n').join(expected));
}
// JLS §4.12.4
@Test
public void constantVariable() {
// A constant variable is a final variable
assertThat(printPruned("final int x = 42;")).isEqualTo("final int x = 42");
assertThat(printPruned("int x = 42;")).isEqualTo("int x");
// ... of primitive type or type String
assertThat(printPruned("final byte x = 0;")).isEqualTo("final byte x = 0");
assertThat(printPruned("final char x = 0;")).isEqualTo("final char x = 0");
assertThat(printPruned("final short x = 0;")).isEqualTo("final short x = 0");
assertThat(printPruned("final int x = 0;")).isEqualTo("final int x = 0");
assertThat(printPruned("final long x = 0;")).isEqualTo("final long x = 0");
assertThat(printPruned("final float x = 0;")).isEqualTo("final float x = 0");
assertThat(printPruned("final double x = 0;")).isEqualTo("final double x = 0");
assertThat(printPruned("final boolean x = false;")).isEqualTo("final boolean x = false");
assertThat(printPruned("final String x = \"\";")).isEqualTo("final String x = \"\"");
assertThat(printPruned("final Object x = 42;")).isEqualTo("final Object x");
// ... that is initialized with a constant expression (§15.28)
}
// JLS §15.28
@Test
public void constantExpression() {
// A constant expression is an expression ... composed using only the following:
// Literals of primitive type and literals of type String
// Casts to primitive types and casts to type String
assertThat(printPruned("final int x = (int)42;")).isEqualTo("final int x = (int)42");
assertThat(printPruned("final int x = (Integer)42;")).isEqualTo("final int x");
// The unary operators +, -, ~, and ! (but not ++ or --)
assertThat(printPruned("final int x = +42;")).isEqualTo("final int x = +42");
assertThat(printPruned("final int x = -42;")).isEqualTo("final int x = -42");
assertThat(printPruned("final int x = ~42;")).isEqualTo("final int x = ~42");
assertThat(printPruned("final boolean x = !false;")).isEqualTo("final boolean x = !false");
assertThat(printPruned("final int x = ++y;")).isEqualTo("final int x");
assertThat(printPruned("final int x = --y;")).isEqualTo("final int x");
assertThat(printPruned("final int x = y++;")).isEqualTo("final int x");
assertThat(printPruned("final int x = y--;")).isEqualTo("final int x");
// The multiplicative operators *, /, and %
assertThat(printPruned("final int x = 1 * 0;")).isEqualTo("final int x = 1 * 0");
assertThat(printPruned("final int x = 1 / 0;")).isEqualTo("final int x = 1 / 0");
assertThat(printPruned("final int x = 1 % 0;")).isEqualTo("final int x = 1 % 0");
// The additive operators + and -
assertThat(printPruned("final int x = 1 + 0;")).isEqualTo("final int x = 1 + 0");
assertThat(printPruned("final int x = 1 - 0;")).isEqualTo("final int x = 1 - 0");
// The shift operators <<, >>, and >>>
assertThat(printPruned("final int x = 1 << 1;")).isEqualTo("final int x = 1 << 1");
assertThat(printPruned("final int x = 1 >> 1;")).isEqualTo("final int x = 1 >> 1");
assertThat(printPruned("final int x = 1 >>> 1;")).isEqualTo("final int x = 1 >>> 1");
// The relational operators <, <=, >, and >= (but not instanceof)
assertThat(printPruned("final boolean x = 1 < 1;")).isEqualTo("final boolean x = 1 < 1");
assertThat(printPruned("final boolean x = 1 <= 1;")).isEqualTo("final boolean x = 1 <= 1");
assertThat(printPruned("final boolean x = 1 > 1;")).isEqualTo("final boolean x = 1 > 1");
assertThat(printPruned("final boolean x = 1 >= 1;")).isEqualTo("final boolean x = 1 >= 1");
assertThat(printPruned("final boolean x = 1 instanceof Integer;")).isEqualTo("final boolean x");
// The equality operators == and !=
assertThat(printPruned("final boolean x = 1 == 1;")).isEqualTo("final boolean x = 1 == 1");
assertThat(printPruned("final boolean x = 1 != 1;")).isEqualTo("final boolean x = 1 != 1");
// The bitwise and logical operators &, ^, and |
assertThat(printPruned("final boolean x = 1 & 1;")).isEqualTo("final boolean x = 1 & 1");
assertThat(printPruned("final boolean x = 1 ^ 1;")).isEqualTo("final boolean x = 1 ^ 1");
assertThat(printPruned("final boolean x = 1 | 1;")).isEqualTo("final boolean x = 1 | 1");
// The conditional-and operator && and the conditional-or operator ||
assertThat(printPruned("final boolean x = true && false;"))
.isEqualTo("final boolean x = true && false");
assertThat(printPruned("final boolean x = true || false;"))
.isEqualTo("final boolean x = true || false");
// The ternary conditional operator ? :
assertThat(printPruned("final int x = true ? 1 : 0;")).isEqualTo("final int x = true ? 1 : 0");
// Parenthesized expressions whose contained expression is a constant expression
assertThat(printPruned("final int x = (1);")).isEqualTo("final int x = (1)");
// Simple names that refer to constant variables
assertThat(printPruned("final int x = CONST;")).isEqualTo("final int x = CONST");
// Qualified names of the form TypeName . Identifier that refer to constant variables
assertThat(printPruned("final int x = TypeName.Identifier;"))
.isEqualTo("final int x = TypeName.Identifier");
}
@Test
public void nonConstantExpression() {
// definitely not a constant
assertThat(printPruned("final int x = g();")).isEqualTo("final int x");
// assume all qualified identifiers refer to constant variables, but not that all select
// expressions are qualified identifiers
assertThat(printPruned("final int x = new X().y;")).isEqualTo("final int x");
}
@Test
public void constructorChaining() {
String[] lines = {
"class Test {",
" Test() {",
" this(42);",
" process();",
" }",
" Test() {",
" this(42);",
" }",
" Test() {",
" super(42);",
" }",
" Test() {}",
"}",
};
JCCompilationUnit tree = parseLines(lines);
TreePruner.prune(context, tree);
String[] expected = {
"class Test {",
" ",
" Test() {",
" this(42);",
" }",
" ",
" Test() {",
" this(42);",
" }",
" ",
" Test() {",
" super(42);",
" }",
" ",
" Test() {",
" }",
"}",
};
assertThat(prettyPrint(tree)).isEqualTo(Joiner.on('\n').join(expected));
}
@Test
public void annotationDeclaration() {
String[] lines = {
"@interface Anno {",
" int f() default CONST;",
" int CONST = 42;",
" int NONCONST = new Integer(42);",
"}",
};
JCCompilationUnit tree = parseLines(lines);
TreePruner.prune(context, tree);
String[] expected = {
"@interface Anno {",
" ",
" int f() default CONST;",
" int CONST = 42;",
" int NONCONST;",
"}",
};
assertThat(prettyPrint(tree)).isEqualTo(Joiner.on('\n').join(expected));
}
@Test
public void interfaceDeclaration() {
String[] lines = {
"interface Intf {", " int CONST = 42;", " int NONCONST = new Integer(42);", "}",
};
JCCompilationUnit tree = parseLines(lines);
TreePruner.prune(context, tree);
String[] expected = {
"interface Intf {", " int CONST = 42;", " int NONCONST;", "}",
};
assertThat(prettyPrint(tree)).isEqualTo(Joiner.on('\n').join(expected));
}
private String prettyPrint(JCCompilationUnit tree) {
return tree.toString().trim();
}
@Test
public void qualifiedSuperConstructorChaining() {
String[] lines = {
"class Test {",
" class Inner {",
" Inner(OuterInstance outer) {",
" outer.super();",
" }",
" }",
"}",
};
JCCompilationUnit tree = parseLines(lines);
TreePruner.prune(context, tree);
String[] expected = {
"class Test {",
" ",
" class Inner {",
" ",
" Inner(OuterInstance outer) {",
" outer.super();",
" }",
" }",
"}",
};
assertThat(prettyPrint(tree)).isEqualTo(Joiner.on('\n').join(expected));
}
}