blob: 1068c0dfc746cd63efaad8faaed11365fe8cba57 [file] [log] [blame]
/*
* Copyright (C) 2011 The Android Open Source Project
*
* 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.android.tools.lint;
import static com.android.tools.lint.detector.api.TextFormat.RAW;
import static com.android.tools.lint.detector.api.TextFormat.TEXT;
import com.android.annotations.NonNull;
import com.android.annotations.Nullable;
import com.android.tools.lint.client.api.IssueRegistry;
import com.android.tools.lint.detector.api.Issue;
import com.android.tools.lint.detector.api.Location;
import com.android.tools.lint.detector.api.Position;
import com.android.tools.lint.detector.api.Severity;
import com.android.tools.lint.detector.api.TextFormat;
import com.android.utils.SdkUtils;
import com.google.common.annotations.Beta;
import com.google.common.base.Joiner;
import com.google.common.base.Splitter;
import java.io.File;
import java.io.IOException;
import java.io.Writer;
import java.util.List;
/**
* A reporter which emits lint warnings as plain text strings
* <p>
* <b>NOTE: This is not a public or final API; if you rely on this be prepared
* to adjust your code for the next tools release.</b>
*/
@Beta
public class TextReporter extends Reporter {
private final Writer mWriter;
private final boolean mClose;
private final LintCliFlags mFlags;
/**
* Constructs a new {@link TextReporter}
*
* @param client the client
* @param flags the flags
* @param writer the writer to write into
* @param close whether the writer should be closed when done
*/
public TextReporter(@NonNull LintCliClient client, @NonNull LintCliFlags flags,
@NonNull Writer writer, boolean close) {
this(client, flags, null, writer, close);
}
/**
* Constructs a new {@link TextReporter}
*
* @param client the client
* @param flags the flags
* @param file the file corresponding to the writer, if any
* @param writer the writer to write into
* @param close whether the writer should be closed when done
*/
public TextReporter(@NonNull LintCliClient client, @NonNull LintCliFlags flags,
@Nullable File file, @NonNull Writer writer, boolean close) {
super(client, file);
mFlags = flags;
mWriter = writer;
mClose = close;
}
@Override
public void write(int errorCount, int warningCount, List<Warning> issues) throws IOException {
boolean abbreviate = !mFlags.isShowEverything();
StringBuilder output = new StringBuilder(issues.size() * 200);
if (issues.isEmpty()) {
if (mDisplayEmpty) {
mWriter.write("No issues found.");
mWriter.write('\n');
mWriter.flush();
}
} else {
Issue lastIssue = null;
for (Warning warning : issues) {
if (warning.issue != lastIssue) {
explainIssue(output, lastIssue);
lastIssue = warning.issue;
}
int startLength = output.length();
if (warning.path != null) {
output.append(warning.path);
output.append(':');
if (warning.line >= 0) {
output.append(Integer.toString(warning.line + 1));
output.append(':');
}
if (startLength < output.length()) {
output.append(' ');
}
}
Severity severity = warning.severity;
if (severity == Severity.FATAL) {
// Treat the fatal error as an error such that we don't display
// both "Fatal:" and "Error:" etc in the error output.
severity = Severity.ERROR;
}
output.append(severity.getDescription());
output.append(':');
output.append(' ');
output.append(RAW.convertTo(warning.message, TEXT));
if (warning.issue != null) {
output.append(' ').append('[');
output.append(warning.issue.getId());
output.append(']');
}
output.append('\n');
if (warning.errorLine != null && !warning.errorLine.isEmpty()) {
output.append(warning.errorLine);
}
if (warning.location != null && warning.location.getSecondary() != null) {
Location location = warning.location.getSecondary();
boolean omitted = false;
while (location != null) {
if (location.getMessage() != null
&& !location.getMessage().isEmpty()) {
output.append(" "); //$NON-NLS-1$
String path = mClient.getDisplayPath(warning.project,
location.getFile());
output.append(path);
Position start = location.getStart();
if (start != null) {
int line = start.getLine();
if (line >= 0) {
output.append(':');
output.append(Integer.toString(line + 1));
}
}
if (location.getMessage() != null
&& !location.getMessage().isEmpty()) {
output.append(':');
output.append(' ');
output.append(RAW.convertTo(location.getMessage(), TEXT));
}
output.append('\n');
} else {
omitted = true;
}
location = location.getSecondary();
}
if (!abbreviate && omitted) {
location = warning.location.getSecondary();
StringBuilder sb = new StringBuilder(100);
sb.append("Also affects: ");
int begin = sb.length();
while (location != null) {
if (location.getMessage() == null
|| location.getMessage().isEmpty()) {
if (sb.length() > begin) {
sb.append(", ");
}
String path = mClient.getDisplayPath(warning.project,
location.getFile());
sb.append(path);
Position start = location.getStart();
if (start != null) {
int line = start.getLine();
if (line >= 0) {
sb.append(':');
sb.append(Integer.toString(line + 1));
}
}
}
location = location.getSecondary();
}
String wrapped = Main.wrap(sb.toString(), Main.MAX_LINE_WIDTH, " "); //$NON-NLS-1$
output.append(wrapped);
}
}
if (warning.isVariantSpecific()) {
List<String> names;
if (warning.includesMoreThanExcludes()) {
output.append("Applies to variants: ");
names = warning.getIncludedVariantNames();
} else {
output.append("Does not apply to variants: ");
names = warning.getExcludedVariantNames();
}
output.append(Joiner.on(", ").join(names));
output.append('\n');
}
}
explainIssue(output, lastIssue);
mWriter.write(output.toString());
mWriter.write(String.format("%1$d errors, %2$d warnings",
errorCount, warningCount));
mWriter.write('\n');
mWriter.flush();
if (mClose) {
mWriter.close();
if (!mClient.getFlags().isQuiet() && mOutput != null) {
String path = mOutput.getAbsolutePath();
System.out.println(String.format("Wrote text report to %1$s", path));
}
}
}
}
private void explainIssue(@NonNull StringBuilder output, @Nullable Issue issue)
throws IOException {
if (issue == null || !mFlags.isExplainIssues() || issue == IssueRegistry.LINT_ERROR) {
return;
}
String explanation = issue.getExplanation(TextFormat.TEXT);
if (explanation.trim().isEmpty()) {
return;
}
String indent = " ";
String formatted = SdkUtils.wrap(explanation, Main.MAX_LINE_WIDTH - indent.length(), null);
output.append('\n');
output.append(indent);
output.append("Explanation for issues of type \"").append(issue.getId()).append("\":\n");
for (String line : Splitter.on('\n').split(formatted)) {
if (!line.isEmpty()) {
output.append(indent);
output.append(line);
}
output.append('\n');
}
List<String> moreInfo = issue.getMoreInfo();
if (!moreInfo.isEmpty()) {
for (String url : moreInfo) {
if (formatted.contains(url)) {
continue;
}
output.append(indent);
output.append(url);
output.append('\n');
}
output.append('\n');
}
}
boolean isWriteToConsole() {
return mOutput == null;
}
}