-
Notifications
You must be signed in to change notification settings - Fork 65
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
1 parent
35d9558
commit b2090fa
Showing
4 changed files
with
544 additions
and
7 deletions.
There are no files selected for viewing
125 changes: 118 additions & 7 deletions
125
mug-errorprone/src/main/java/com/google/mu/errorprone/AbstractBugChecker.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,14 +1,125 @@ | ||
package com.google.mu.errorprone; | ||
|
||
import com.google.common.collect.ImmutableMap; | ||
|
||
import static com.google.common.base.Preconditions.checkNotNull; | ||
|
||
import com.google.common.base.Strings; | ||
import com.google.errorprone.VisitorState; | ||
import com.google.errorprone.annotations.CanIgnoreReturnValue; | ||
import com.google.errorprone.bugpatterns.BugChecker; | ||
import com.google.mu.util.Substring; | ||
import com.google.mu.util.stream.BiStream; | ||
import static com.google.mu.util.stream.GuavaCollectors.toImmutableMap; | ||
import com.google.errorprone.matchers.Description; | ||
import com.sun.source.tree.MemberReferenceTree; | ||
import com.sun.source.tree.MethodInvocationTree; | ||
import com.sun.source.tree.NewClassTree; | ||
import com.sun.source.tree.Tree; | ||
|
||
/** Abstract class providing convenience to BugChecker implementations. */ | ||
/** | ||
* A convenience base class allowing subclasses to use precondition-style checking such as {@code | ||
* checkingOn(tree).require(formatString != null, "message...")}. | ||
* | ||
* <p>The subclass should implement one or multiple of the "mixin" interfaces ({@link | ||
* ConstructorCallCheck}, {@link MethodInvocationCheck}, {@link MemberReferenceCheck}). They adapt | ||
* from the {@link ErrorReport} exception thrown by the `check` abstract methods to the {@link | ||
* com.google.errorprone.matchers.Description} return value expected by Error-Prone. | ||
*/ | ||
abstract class AbstractBugChecker extends BugChecker { | ||
private static final Substring.Pattern PLACEHOLDER = Substring.between('{', '}'); | ||
private static final ImmutableMap<?, ?> MAP = BiStream.empty().collect(toImmutableMap()); | ||
/** | ||
* Mixin interface for checkers that check constructor calls. Subclasses can implement the {@link | ||
* #checkConstructorCall} and throw {@code ErrorReport} to indicate failures. | ||
*/ | ||
interface ConstructorCallCheck extends NewClassTreeMatcher { | ||
/** DO NOT override this method. Implement {@link #checkConstructorCall} instead. */ | ||
@Override | ||
public default Description matchNewClass(NewClassTree tree, VisitorState state) { | ||
return ErrorReport.checkAndReportError(tree, state, this::checkConstructorCall); | ||
} | ||
|
||
void checkConstructorCall(NewClassTree tree, VisitorState state) throws ErrorReport; | ||
} | ||
|
||
/** | ||
* Mixin interface for checkers that check member references. Subclasses can implement the {@link | ||
* #checkMemberReference} and throw {@code ErrorReport} to indicate failures. | ||
*/ | ||
interface MemberReferenceCheck extends MemberReferenceTreeMatcher { | ||
/** DO NOT override this method. Implement {@link #checkMemberReference} instead. */ | ||
@Override | ||
public default Description matchMemberReference(MemberReferenceTree tree, VisitorState state) { | ||
return ErrorReport.checkAndReportError(tree, state, this::checkMemberReference); | ||
} | ||
|
||
void checkMemberReference(MemberReferenceTree tree, VisitorState state) throws ErrorReport; | ||
} | ||
|
||
/** | ||
* Mixin interface for checkers that check method invocations. Subclasses can implement the {@link | ||
* #checkMethodInvocation} and throw {@code ErrorReport} to indicate failures. | ||
*/ | ||
interface MethodInvocationCheck extends MethodInvocationTreeMatcher { | ||
/** DO NOT override this method. Implement {@link #checkMethodInvocation} instead. */ | ||
@Override | ||
public default Description matchMethodInvocation( | ||
MethodInvocationTree tree, VisitorState state) { | ||
return ErrorReport.checkAndReportError(tree, state, this::checkMethodInvocation); | ||
} | ||
|
||
void checkMethodInvocation(MethodInvocationTree tree, VisitorState state) throws ErrorReport; | ||
} | ||
|
||
/** Starts checking on {@code node}. Errors will be reported as pertaining to this node. */ | ||
final NodeCheck checkingOn(Tree node) { | ||
checkNotNull(node); | ||
return (condition, message, args) -> { | ||
if (condition) { | ||
return checkingOn(node); | ||
} | ||
throw new ErrorReport(buildDescription(node), message, args); | ||
}; | ||
} | ||
|
||
/** Fluently checking on a tree node. */ | ||
interface NodeCheck { | ||
/** | ||
* Checks that {code condition} holds or else reports error using {@code message} with {@code | ||
* args}. If an arg is an instance of {@link Tree}, the source code of that tree node will be | ||
* reported in the error message. | ||
*/ | ||
@CanIgnoreReturnValue | ||
NodeCheck require(boolean condition, String message, Object... args) throws ErrorReport; | ||
} | ||
|
||
/** An error report of a violation. */ | ||
static final class ErrorReport extends Exception { | ||
private final Description.Builder description; | ||
private final Object[] args; | ||
|
||
private ErrorReport(Description.Builder description, String message, Object... args) { | ||
super(message, null, false, false); | ||
this.description = description; | ||
this.args = args.clone(); | ||
} | ||
|
||
private static <T extends Tree> Description checkAndReportError( | ||
T tree, VisitorState state, Checker<? super T> impl) { | ||
try { | ||
impl.check(tree, state); | ||
} catch (ErrorReport report) { | ||
return report.buildDescription(state); | ||
} | ||
return Description.NO_MATCH; | ||
} | ||
|
||
private interface Checker<T extends Tree> { | ||
void check(T tree, VisitorState state) throws ErrorReport; | ||
} | ||
|
||
private Description buildDescription(VisitorState state) { | ||
for (int i = 0; i < args.length; i++) { | ||
if (args[i] instanceof Tree) { | ||
args[i] = state.getSourceForNode((Tree) args[i]); | ||
} | ||
} | ||
return description.setMessage(Strings.lenientFormat(getMessage(), args)).build(); | ||
} | ||
} | ||
} |
79 changes: 79 additions & 0 deletions
79
mug-errorprone/src/main/java/com/google/mu/errorprone/FormatStringUtils.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,79 @@ | ||
package com.google.mu.errorprone; | ||
|
||
|
||
import static com.google.common.collect.ImmutableList.toImmutableList; | ||
import static com.google.mu.util.Optionals.optionally; | ||
import static com.google.mu.util.Substring.consecutive; | ||
import static com.google.mu.util.Substring.first; | ||
import static com.google.mu.util.Substring.BoundStyle.INCLUSIVE; | ||
|
||
import java.util.List; | ||
import java.util.Optional; | ||
|
||
import com.google.common.base.CharMatcher; | ||
import com.google.common.collect.ImmutableList; | ||
import com.google.errorprone.VisitorState; | ||
import com.google.errorprone.util.ASTHelpers; | ||
import com.google.mu.util.Substring; | ||
import com.sun.source.tree.ExpressionTree; | ||
import com.sun.source.tree.IdentifierTree; | ||
import com.sun.source.tree.MethodInvocationTree; | ||
import com.sun.source.tree.NewClassTree; | ||
import com.sun.source.tree.Tree; | ||
import com.sun.source.tree.VariableTree; | ||
import com.sun.tools.javac.api.JavacTrees; | ||
import com.sun.tools.javac.code.Symbol; | ||
import com.sun.tools.javac.code.Symbol.VarSymbol; | ||
|
||
/** Some common utils for format and unformat checks. */ | ||
final class FormatStringUtils { | ||
static final Substring.Pattern PLACEHOLDER_PATTERN = | ||
consecutive(CharMatcher.noneOf("{}")::matches).immediatelyBetween("{", INCLUSIVE, "}", INCLUSIVE); | ||
static final Substring.RepeatingPattern PLACEHOLDER_NAMES_PATTERN = | ||
consecutive(CharMatcher.noneOf("{}")::matches).immediatelyBetween("{", "}").repeatedly(); | ||
|
||
static ImmutableList<String> placeholderVariableNames(String formatString) { | ||
return PLACEHOLDER_NAMES_PATTERN | ||
.from(formatString) | ||
.map(first('=').toEnd()::removeFrom) // for Cloud resource name syntax | ||
.collect(toImmutableList()); | ||
} | ||
|
||
static Optional<ExpressionTree> getInlineStringArg(Tree expression, VisitorState state) { | ||
ImmutableList<? extends ExpressionTree> args = | ||
invocationArgs(expression).stream() | ||
.map(ASTHelpers::stripParentheses) | ||
.filter(arg -> isStringType(arg, state)) | ||
.collect(toImmutableList()); | ||
return optionally(args.size() == 1, () -> args.get(0)); | ||
} | ||
|
||
static Optional<String> findFormatString(Tree unformatter, VisitorState state) { | ||
if (unformatter instanceof IdentifierTree) { | ||
Symbol symbol = ASTHelpers.getSymbol(unformatter); | ||
if (symbol instanceof VarSymbol) { | ||
Tree def = JavacTrees.instance(state.context).getTree(symbol); | ||
if (def instanceof VariableTree) { | ||
return findFormatString(((VariableTree) def).getInitializer(), state); | ||
} | ||
} | ||
return Optional.empty(); | ||
} | ||
return getInlineStringArg(unformatter, state) | ||
.map(tree -> ASTHelpers.constValue(tree, String.class)); | ||
} | ||
|
||
private static List<? extends ExpressionTree> invocationArgs(Tree tree) { | ||
if (tree instanceof NewClassTree) { | ||
return ((NewClassTree) tree).getArguments(); | ||
} | ||
if (tree instanceof MethodInvocationTree) { | ||
return ((MethodInvocationTree) tree).getArguments(); | ||
} | ||
return ImmutableList.of(); | ||
} | ||
|
||
private static boolean isStringType(ExpressionTree arg, VisitorState state) { | ||
return ASTHelpers.isSameType(ASTHelpers.getType(arg), state.getSymtab().stringType, state); | ||
} | ||
} |
Oops, something went wrong.