Skip to content

Commit

Permalink
Refactor ErrorProneCompiler
Browse files Browse the repository at this point in the history
The existing implementation depends on all of the built-in checks
through BuiltInScannerSuppliers. Refactor out a base implementation that
doesn't include a default scanner supplier for use in tests.

MOE_MIGRATED_REVID=124969040
  • Loading branch information
cushon committed Jun 16, 2016
1 parent cb39c83 commit 38a8a42
Show file tree
Hide file tree
Showing 7 changed files with 320 additions and 226 deletions.
236 changes: 236 additions & 0 deletions core/src/main/java/com/google/errorprone/BaseErrorProneCompiler.java
@@ -0,0 +1,236 @@
/*
* Copyright 2016 Google Inc. 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.errorprone;

import static com.google.common.base.Preconditions.checkNotNull;
import static com.google.common.base.StandardSystemProperty.JAVA_SPECIFICATION_VERSION;

import com.google.common.collect.Iterables;
import com.google.errorprone.scanner.ErrorProneScannerTransformer;
import com.google.errorprone.scanner.Scanner;
import com.google.errorprone.scanner.ScannerSupplier;

import com.sun.tools.javac.api.JavacTaskImpl;
import com.sun.tools.javac.api.JavacTool;
import com.sun.tools.javac.api.MultiTaskListener;
import com.sun.tools.javac.file.JavacFileManager;
import com.sun.tools.javac.main.Main;
import com.sun.tools.javac.main.Main.Result;
import com.sun.tools.javac.util.Context;
import com.sun.tools.javac.util.JavacMessages;

import java.io.PrintWriter;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;

import javax.annotation.processing.Processor;
import javax.tools.DiagnosticListener;
import javax.tools.JavaFileManager;
import javax.tools.JavaFileObject;

/** An Error Prone compiler that matches the interface of {@link com.sun.tools.javac.Main}. */
public class BaseErrorProneCompiler {

private final DiagnosticListener<? super JavaFileObject> diagnosticListener;
private final PrintWriter errOutput;
private final String compilerName;
private final ScannerSupplier scannerSupplier;

private BaseErrorProneCompiler(
String compilerName,
PrintWriter errOutput,
DiagnosticListener<? super JavaFileObject> diagnosticListener,
ScannerSupplier scannerSupplier) {
this.errOutput = errOutput;
this.compilerName = compilerName;
this.diagnosticListener = diagnosticListener;
this.scannerSupplier = checkNotNull(scannerSupplier, "scannerSupplier must not be null");
}

/** Returns a {@link BaseErrorProneCompiler} builder. */
public static Builder builder() {
return new Builder();
}

/** A {@link BaseErrorProneCompiler} builder. */
public static class Builder {
private DiagnosticListener<? super JavaFileObject> diagnosticListener = null;
private PrintWriter errOutput = new PrintWriter(System.err, true);
private String compilerName = "javac (with error-prone)";
private ScannerSupplier scannerSupplier;

public BaseErrorProneCompiler build() {
return new BaseErrorProneCompiler(
compilerName, errOutput, diagnosticListener, scannerSupplier);
}

public Builder named(String compilerName) {
this.compilerName = compilerName;
return this;
}

public Builder redirectOutputTo(PrintWriter errOutput) {
this.errOutput = errOutput;
return this;
}

public Builder listenToDiagnostics(DiagnosticListener<? super JavaFileObject> listener) {
this.diagnosticListener = listener;
return this;
}

public Builder report(ScannerSupplier scannerSupplier) {
this.scannerSupplier = scannerSupplier;
return this;
}
}

public Result run(String[] args) {
Context context = new Context();
JavacFileManager.preRegister(context);
return run(args, context);
}

/**
* Default to compiling with the same -source and -target as the host's javac.
*
* <p>This prevents, e.g., targeting Java 8 by default when using error-prone on JDK7.
*/
private Iterable<String> defaultToLatestSupportedLanguageLevel(Iterable<String> args) {

String overrideLanguageLevel;
switch (JAVA_SPECIFICATION_VERSION.value()) {
case "1.7":
overrideLanguageLevel = "7";
break;
case "1.8":
overrideLanguageLevel = "8";
break;
default:
return args;
}

return Iterables.concat(
Arrays.asList(
// suppress xlint 'options' warnings to avoid diagnostics like:
// 'bootstrap class path not set in conjunction with -source 1.7'
"-Xlint:-options", "-source", overrideLanguageLevel, "-target", overrideLanguageLevel),
args);
}

/**
* Sets javac's {@code -XDcompilePolicy} flag to ensure that all classes in a
* file are attributed before any of them are lowered. Error Prone depends on
* this behavior when analyzing files that contain multiple top-level classes.
*
* @throws InvalidCommandLineOptionException if the {@code -XDcompilePolicy}
* flag is passed in the existing arguments with an unsupported value
*/
private Iterable<String> setCompilePolicyToByFile(Iterable<String> args)
throws InvalidCommandLineOptionException {
for (String arg : args) {
if (arg.startsWith("-XDcompilePolicy")) {
String value = arg.substring(arg.indexOf('=') + 1);
switch (value) {
case "byfile":
case "simple":
break;
default:
throw new InvalidCommandLineOptionException(
String.format("-XDcompilePolicy=%s is not supported by Error Prone", value));
}
// don't do anything if a valid policy is already set
return args;
}
}
return Iterables.concat(args, Arrays.asList("-XDcompilePolicy=byfile"));
}

private String[] prepareCompilation(String[] argv, Context context)
throws InvalidCommandLineOptionException {

Iterable<String> newArgs = defaultToLatestSupportedLanguageLevel(Arrays.asList(argv));
newArgs = setCompilePolicyToByFile(newArgs);
ErrorProneOptions epOptions = ErrorProneOptions.processArgs(newArgs);

argv = epOptions.getRemainingArgs();

if (diagnosticListener != null) {
context.put(DiagnosticListener.class, diagnosticListener);
}

Scanner scanner = scannerSupplier.applyOverrides(epOptions).get();
CodeTransformer transformer = ErrorProneScannerTransformer.create(scanner);

setupMessageBundle(context);
MultiTaskListener.instance(context)
.add(new ErrorProneAnalyzer(transformer, epOptions, context));

return argv;
}

private Result run(String[] argv, Context context) {
try {
argv = prepareCompilation(argv, context);
} catch (InvalidCommandLineOptionException e) {
errOutput.println(e.getMessage());
errOutput.flush();
return Result.CMDERR;
}

return new Main(compilerName, errOutput).compile(argv, context);
}

public Result run(String[] argv, List<JavaFileObject> javaFileObjects) {
Context context = new Context();
return run(argv, context, null, javaFileObjects, Collections.<Processor>emptyList());
}

public Result run(
String[] argv,
Context context,
JavaFileManager fileManager,
List<JavaFileObject> javaFileObjects,
Iterable<? extends Processor> processors) {

try {
argv = prepareCompilation(argv, context);
} catch (InvalidCommandLineOptionException e) {
errOutput.println(e.getMessage());
errOutput.flush();
return Result.CMDERR;
}

JavacTool tool = JavacTool.create();
JavacTaskImpl task =
(JavacTaskImpl)
tool.getTask(
errOutput, fileManager, null, Arrays.asList(argv), null, javaFileObjects, context);
if (processors != null) {
task.setProcessors(processors);
}
return task.doCall();
}

/**
* Registers our message bundle.
*/
public static void setupMessageBundle(Context context) {
JavacMessages.instance(context).add("com.google.errorprone.errors");
}
}

0 comments on commit 38a8a42

Please sign in to comment.