/*
* Copyright 2012 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.matchers;
import static com.google.common.base.Preconditions.checkArgument;
import static com.google.common.base.Strings.isNullOrEmpty;
import static com.google.errorprone.BugPattern.SeverityLevel.SUGGESTION;
import com.google.common.base.Preconditions;
import com.google.common.base.Predicate;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.Iterables;
import com.google.errorprone.BugPattern;
import com.google.errorprone.BugPattern.SeverityLevel;
import com.google.errorprone.fixes.Fix;
import com.sun.source.tree.Tree;
import java.util.List;
import javax.annotation.CheckReturnValue;
import javax.annotation.Nullable;
/**
* Simple data object containing the information captured about an AST match.
* Can be printed in a UI, or output in structured format for use by tools.
* @author alexeagle@google.com (Alex Eagle)
*/
public class Description {
/** Describes the sentinel value of the case where the match failed. */
public static final Description NO_MATCH =
new Description(
null, "<no match>", "<no match>", "<no match>", ImmutableList.<Fix>of(), SUGGESTION);
private static final String UNDEFINED_CHECK_NAME = "Undefined";
/**
* The AST node that matched.
*/
public final Tree node;
/**
* The name of the check that produced the match.
*/
public final String checkName;
/**
* The raw message, not including the check name or the link.
*/
private final String rawMessage;
/** The raw link URL for the check. May be null if there is no link. */
@Nullable private final String linkUrl;
/**
* A list of fixes to suggest in an error message or use in automated refactoring. Fixes are
* in order of decreasing preference, from most preferred to least preferred.
*/
public final ImmutableList<Fix> fixes;
/**
* Is this a warning, error, etc.?
*/
public final BugPattern.SeverityLevel severity;
/**
* Returns the message to be printed by the compiler when a match is found in interactive use.
* Includes the name of the check and a link for more information.
*/
public String getMessage() {
return String.format("[%s] %s", checkName, getMessageWithoutCheckName());
}
/** Returns a link associated with this finding or null if there is no link. */
@Nullable
public String getLink() {
return linkUrl;
}
/** Returns the raw message, not including a link or check name. */
public String getRawMessage() {
return rawMessage;
}
/**
* Returns the message, not including the check name but including the link.
*/
public String getMessageWithoutCheckName() {
return linkUrl != null
? String.format("%s\n%s", rawMessage, linkTextForDiagnostic(linkUrl))
: String.format("%s", rawMessage);
}
/** TODO(cushon): Remove this constructor and ensure that there's always a check name. */
@Deprecated
public Description(
Tree node, String message, Fix suggestedFix, BugPattern.SeverityLevel severity) {
this(node, UNDEFINED_CHECK_NAME, message, message, ImmutableList.of(suggestedFix), severity);
if (suggestedFix == null) {
throw new IllegalArgumentException("suggestedFix must not be null.");
}
}
private Description(
Tree node,
String checkName,
String rawMessage,
String linkUrl,
ImmutableList<Fix> fixes,
SeverityLevel severity) {
this.node = node;
this.checkName = checkName;
this.rawMessage = rawMessage;
this.linkUrl = linkUrl;
this.fixes = fixes;
this.severity = severity;
}
/** Internal-only. Has no effect if applied to a Description within a BugChecker. */
@CheckReturnValue
public Description applySeverityOverride(SeverityLevel severity) {
return new Description(node, checkName, rawMessage, linkUrl, fixes, severity);
}
@CheckReturnValue
public Description filterFixes(Predicate<? super Fix> predicate) {
return new Description(
node,
checkName,
rawMessage,
linkUrl,
ImmutableList.copyOf(Iterables.filter(fixes, predicate)),
severity);
}
/**
* Construct the link text to include in the compiler error message. Returns null if there is
* no link.
*/
private static String linkTextForDiagnostic(String linkUrl) {
return isNullOrEmpty(linkUrl) ? null : " (see " + linkUrl + ")";
}
/**
* Returns a new builder for {@link Description}s.
*/
public static Builder builder(
Tree node, String name, @Nullable String link, SeverityLevel severity, String message) {
return new Builder(node, name, link, severity, message);
}
/**
* Builder for {@code Description}s.
*/
public static class Builder {
private final Tree node;
private final String name;
private final String linkUrl;
private final SeverityLevel severity;
private final ImmutableList.Builder<Fix> fixListBuilder = ImmutableList.builder();
private String rawMessage;
private Builder(
Tree node,
String name,
@Nullable String linkUrl,
SeverityLevel severity,
String rawMessage) {
this.node = Preconditions.checkNotNull(node);
this.name = Preconditions.checkNotNull(name);
this.linkUrl = linkUrl;
this.severity = Preconditions.checkNotNull(severity);
this.rawMessage = Preconditions.checkNotNull(rawMessage);
}
/**
* Adds a suggested fix for this {@code Description}. Fixes should be added in order of
* decreasing preference. Adding an empty fix is a no-op.
*
* @param fix a suggested fix for this problem
* @throws IllegalArgumentException if {@code fix} is {@code null}
*/
public Builder addFix(Fix fix) {
checkArgument(fix != null, "fix must not be null");
if (!fix.isEmpty()) {
fixListBuilder.add(fix);
}
return this;
}
/**
* Add each fix in order.
*
* @param fixes a list of suggested fixes for this problem
* @throws IllegalArgumentException if {@code fixes} or any of its elements are {@code null}
*/
public Builder addAllFixes(List<? extends Fix> fixes) {
checkArgument(fixes != null, "fixes must not be null");
for (Fix fix : fixes) {
addFix(fix);
}
return this;
}
/**
* Set a custom error message for this {@code Description}. The custom message will be used
* instead of the summary field as the text for the diagnostic message.
*
* @param message A custom error message without the check name ("[checkname]") or link
*/
public Builder setMessage(String message) {
if (message == null) {
throw new IllegalArgumentException("message must not be null");
}
this.rawMessage = message;
return this;
}
public Description build() {
return new Description(node, name, rawMessage, linkUrl, fixListBuilder.build(), severity);
}
}
}