com.google.testing.compile.Parser Maven / Gradle / Ivy
Show all versions of compile-testing Show documentation
/*
* Copyright (C) 2016 Google, Inc.
*
* 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.testing.compile;
import static com.google.common.base.MoreObjects.firstNonNull;
import static java.lang.Boolean.TRUE;
import static java.nio.charset.StandardCharsets.UTF_8;
import static java.util.function.Predicate.isEqual;
import static javax.tools.Diagnostic.Kind.ERROR;
import com.google.common.base.Joiner;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableListMultimap;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.Iterables;
import com.google.common.collect.Multimaps;
import com.sun.source.tree.CompilationUnitTree;
import com.sun.source.tree.ErroneousTree;
import com.sun.source.tree.Tree;
import com.sun.source.util.JavacTask;
import com.sun.source.util.TreeScanner;
import com.sun.source.util.Trees;
import com.sun.tools.javac.api.JavacTool;
import java.io.IOException;
import java.util.List;
import java.util.Locale;
import javax.tools.Diagnostic;
import javax.tools.DiagnosticCollector;
import javax.tools.JavaCompiler;
import javax.tools.JavaFileObject;
import javax.tools.ToolProvider;
/** Methods to parse Java source files. */
public final class Parser {
/**
* Parses {@code sources} into {@linkplain CompilationUnitTree compilation units}. This method
* does not compile the sources.
*/
static ParseResult parse(Iterable sources) {
JavaCompiler compiler = ToolProvider.getSystemJavaCompiler();
DiagnosticCollector diagnosticCollector = new DiagnosticCollector<>();
InMemoryJavaFileManager fileManager =
new InMemoryJavaFileManager(
compiler.getStandardFileManager(diagnosticCollector, Locale.getDefault(), UTF_8));
JavacTask task =
((JavacTool) compiler)
.getTask(
null, // explicitly use the default because old javac logs some output on stderr
fileManager,
diagnosticCollector,
ImmutableSet.of(),
ImmutableSet.of(),
sources);
try {
Iterable parsedCompilationUnits = task.parse();
List> diagnostics = diagnosticCollector.getDiagnostics();
if (foundParseErrors(parsedCompilationUnits, diagnostics)) {
throw new IllegalStateException(
"error while parsing:\n" + Joiner.on('\n').join(diagnostics));
}
return new ParseResult(
sortDiagnosticsByKind(diagnostics), parsedCompilationUnits, Trees.instance(task));
} catch (IOException e) {
throw new RuntimeException(e);
}
}
/**
* Returns {@code true} if errors were found while parsing source files.
*
* Normally, the parser reports error diagnostics, but in some cases there are no diagnostics;
* instead the parse tree contains {@linkplain ErroneousTree "erroneous"} nodes.
*/
private static boolean foundParseErrors(
Iterable parsedCompilationUnits,
List> diagnostics) {
return diagnostics.stream().map(Diagnostic::getKind).anyMatch(isEqual(ERROR))
|| Iterables.any(parsedCompilationUnits, Parser::hasErrorNode);
}
/**
* Returns {@code true} if the tree contains at least one {@linkplain ErroneousTree "erroneous"}
* node.
*/
private static boolean hasErrorNode(Tree tree) {
return isTrue(HAS_ERRONEOUS_NODE.scan(tree, false));
}
private static final TreeScanner HAS_ERRONEOUS_NODE =
new TreeScanner() {
@Override
public Boolean visitErroneous(ErroneousTree node, Boolean p) {
return true;
}
@Override
public Boolean scan(Iterable nodes, Boolean p) {
for (Tree node : firstNonNull(nodes, ImmutableList.of())) {
if (isTrue(scan(node, p))) {
return true;
}
}
return p;
}
@Override
public Boolean scan(Tree tree, Boolean p) {
return isTrue(p) ? p : super.scan(tree, p);
}
@Override
public Boolean reduce(Boolean r1, Boolean r2) {
return isTrue(r1) || isTrue(r2);
}
};
private static boolean isTrue(Boolean p) {
return TRUE.equals(p);
}
private static ImmutableListMultimap>
sortDiagnosticsByKind(Iterable> diagnostics) {
return Multimaps.index(diagnostics, input -> input.getKind());
}
/**
* The diagnostic, parse trees, and {@link Trees} instance for a parse task.
*
* Note: It is possible for the {@link Trees} instance contained within a {@code ParseResult}
* to be invalidated by a call to {@link com.sun.tools.javac.api.JavacTaskImpl#cleanup()}. Though
* we do not currently expose the {@link JavacTask} used to create a {@code ParseResult} to {@code
* cleanup()} calls on its underlying implementation, this should be acknowledged as an
* implementation detail that could cause unexpected behavior when making calls to methods in
* {@link Trees}.
*/
static final class ParseResult {
private final ImmutableListMultimap>
diagnostics;
private final ImmutableList compilationUnits;
private final Trees trees;
ParseResult(
ImmutableListMultimap> diagnostics,
Iterable compilationUnits,
Trees trees) {
this.trees = trees;
this.compilationUnits = ImmutableList.copyOf(compilationUnits);
this.diagnostics = diagnostics;
}
ImmutableListMultimap>
diagnosticsByKind() {
return diagnostics;
}
Iterable compilationUnits() {
return compilationUnits;
}
Trees trees() {
return trees;
}
}
}