blob: 0baa65174e68e71d25a723d2fbdde7380f532f0e [file] [log] [blame]
// Copyright (c) 2012, the Dart project authors. Please see the AUTHORS file
// for details. All rights reserved. Use of this source code is governed by a
// BSD-style license that can be found in the LICENSE file.
package com.google.dart.compiler;
import com.google.common.io.Closeables;
import com.google.dart.compiler.CompilerConfiguration.ErrorFormat;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.PrintStream;
import java.io.Reader;
/**
* An error formatter that scans the source file and prints the error line and
* some context around it. This formatter has two modes: with or without color.
* When using colors, it prints the error message in red, and it highlights the
* portion of the line containing the error in red. Without colors, it prints an
* extra line underlying the portion of the line containing the error.
*/
public class PrettyErrorFormatter extends DefaultErrorFormatter {
public static final String ERROR_BOLD_COLOR = "\033[31;1m";
public static final String ERROR_COLOR = "\033[31m";
// Mix ANSI with xterm colors, giving ANSI priority. The terminal should ignore xterm codes
// if it doesn't support them.
public static final String WARNING_BOLD_COLOR = "\033[33;1m\033[38;5;202m";
public static final String WARNING_COLOR = "\033[33m\033[38;5;208m";
public static final String NO_COLOR = "\033[0m";
private final boolean useColor;
public PrettyErrorFormatter(PrintStream outputStream,
boolean useColor,
ErrorFormat errorFormat) {
super(outputStream, errorFormat);
this.useColor = useColor;
}
@Override
public void format(DartCompilationError event) {
Source sourceFile = event.getSource();
// if this is an unknown source type, default to the basic error formatter
if (!(sourceFile instanceof DartSource) && !(sourceFile instanceof LibrarySource)) {
super.format(event);
return;
}
BufferedReader reader = null;
try {
Reader sourceReader = sourceFile.getSourceReader();
if (sourceReader != null) {
reader = new BufferedReader(sourceReader);
}
// get the error line and the line above it (note: line starts at 1)
int line = event.getLineNumber();
String lineBefore = null;
String lineText = null;
if (reader != null) {
lineBefore = getLineAt(reader, line - 1);
lineText = getLineAt(reader, 1);
}
// if there is no line to highlight, default to the basic error formatter
if (lineText == null) {
super.format(event);
return;
}
// get column/length and ensure they are within the line limits.
int col = event.getColumnNumber() - 1;
int length = event.getLength();
col = between(col, 0, lineText.length());
length = between(length, 0, lineText.length() - col);
length = length == 0 ? lineText.length() - col : length;
// print the error message
StringBuilder buf = new StringBuilder();
if (useColor) {
buf.append(event.getErrorCode().getErrorSeverity() == ErrorSeverity.WARNING
? WARNING_BOLD_COLOR : ERROR_BOLD_COLOR);
}
appendError(buf, event);
if (useColor) {
buf.append(NO_COLOR);
}
buf.append("\n");
// show the previous line for context
if (lineBefore != null) {
buf.append(String.format("%6d: %s\n", line - 1, lineBefore));
}
if (useColor) {
// highlight error in red
buf.append(String.format("%6d: %s%s%s%s%s\n",
line,
lineText.substring(0, col),
event.getErrorCode().getErrorSeverity() == ErrorSeverity.WARNING
? WARNING_COLOR : ERROR_COLOR,
lineText.substring(col, col + length),
NO_COLOR,
lineText.substring(col + length)));
} else {
// print the error line without formatting
buf.append(String.format("%6d: %s\n", line, lineText));
// underline error portion
buf.append(" ");
for (int i = 0; i < col; ++i) {
buf.append(' ');
}
buf.append('~');
if (length > 1) {
for (int i = 0; i < length - 2; ++i) {
buf.append('~');
}
buf.append('~');
}
buf.append('\n');
}
outputStream.print(buf.toString());
} catch (IOException ex) {
super.format(event);
} finally {
if (reader != null) {
Closeables.closeQuietly(reader);
}
}
}
private String getLineAt(BufferedReader reader, int line) throws IOException {
if (line <= 0) {
return null;
}
String currentLine = null;
// TODO(sigmund): do something more efficient - we currently do a linear
// scan of the file every time an error is reported. This will not scale
// when many errors are reported on the same file.
while ((currentLine = reader.readLine()) != null && line-- > 1){}
return currentLine;
}
/**
* Returns the closest value in {@code [start,end]} to the given value. If
* the given range is entirely empty, then {@code start} is returned.
*/
private static int between(int val, int start, int end) {
return Math.max(start, Math.min(val, end));
}
}