Files
sdk/pkg/kernel/lib/error_formatter.dart
T
Peter von der Ahé e14726a3f4 Use absolute URIs instead of relative paths
We change the type of FileUriNode.fileUri from String to Uri, which in principle
doesn't change the binary format. However, we did notice that LibraryParts
weren't serialized as specified in binary.md, so we also fixed that.

Since fileUris are stored as strings in a separate table, Uri.parse is only called
once per unique URI.

Fasta only uses relativizeUri when printing diagnostics, and URIs stored in
expectation files (golden files) are relativized using String.replaceAll.

Change-Id: Ib2dc1b80c03a0cdaf84e48b8b3ba73b16bdf8a40
Reviewed-on: https://dart-review.googlesource.com/25421
Commit-Queue: Peter von der Ahé <ahe@google.com>
Reviewed-by: Jens Johansen <jensj@google.com>
Reviewed-by: Sigmund Cherem <sigmund@google.com>
2017-12-04 11:34:24 +00:00

186 lines
5.3 KiB
Dart

#!/usr/bin/env dart
// Copyright (c) 2017, 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.
import 'package:kernel/kernel.dart';
import 'package:kernel/naive_type_checker.dart';
import 'package:kernel/text/ast_to_text.dart';
class ErrorFormatter implements FailureListener {
List<String> failures = <String>[];
int get numberOfFailures => failures.length;
@override
void reportNotAssignable(TreeNode where, DartType from, DartType to) {
reportFailure(
where,
'${ansiBlue}${from}${ansiReset} ${ansiYellow}is not assignable to'
'${ansiReset} ${ansiBlue}${to}${ansiReset}');
}
@override
void reportInvalidOverride(
Member ownMember, Member superMember, String message) {
reportFailure(ownMember, '''
Incompatible override of ${superMember} with ${ownMember}:
${_realign(message, ' ')}''');
}
@override
void reportFailure(TreeNode where, String message) {
final context = _findEnclosingMember(where);
String sourceLocation = '<unknown source>';
String sourceLine = null;
// Try finding original source line.
final fileOffset = _findFileOffset(where);
if (fileOffset != TreeNode.noOffset) {
final fileUri = _fileUriOf(context);
final program = context.enclosingProgram;
final source = program.uriToSource[fileUri];
final location = program.getLocation(fileUri, fileOffset);
final lineStart = source.lineStarts[location.line - 1];
final lineEnd = (location.line < source.lineStarts.length)
? source.lineStarts[location.line]
: (source.source.length - 1);
if (lineStart < source.source.length &&
lineEnd < source.source.length &&
lineStart < lineEnd) {
sourceLocation = '${fileUri}:${location.line}';
sourceLine = new String.fromCharCodes(
source.source.getRange(lineStart, lineEnd));
}
}
// Find the name of the enclosing member.
var name = "", body = context;
if (context is Procedure || context is Constructor) {
final parent = context.parent;
final parentName =
parent is Class ? parent.name : (parent as Library).name;
name = "${parentName}::${context.name.name}";
body = context;
} else {
final field = context as Field;
if (where is Field) {
name = "${field.parent}.${field.name}";
} else {
name = "field initializer for ${field.parent}.${field.name}";
}
}
String failure = '''
-----------------------------------------------------------------------
In ${name} at ${sourceLocation}:
${message.replaceAll('\n', '\n ')}
Kernel:
|
| ${_realign(HighlightingPrinter.stringifyContainingLines(body, where))}
|
''';
if (sourceLine != null) {
failure = '''$failure
Source:
|
| ${_realign(sourceLine)}
|
''';
}
failures.add(failure);
}
static Uri _fileUriOf(Member context) {
if (context is Procedure) {
return context.fileUri;
} else if (context is Field) {
return context.fileUri;
} else {
final klass = context.enclosingClass;
if (klass != null) {
return klass.fileUri;
}
return context.enclosingLibrary.fileUri;
}
}
static String _realign(String str, [String prefix = '| ']) =>
str.trimRight().replaceAll('\n', '\n${prefix}');
static int _findFileOffset(TreeNode context) {
while (context != null && context.fileOffset == TreeNode.noOffset) {
context = context.parent;
}
return context?.fileOffset ?? TreeNode.noOffset;
}
static Member _findEnclosingMember(TreeNode n) {
var context = n;
while (context is! Member) {
context = context.parent;
}
return context;
}
}
/// Extension of a [Printer] that highlights the given node using ANSI
/// escape sequences.
class HighlightingPrinter extends Printer {
final highlight;
HighlightingPrinter(this.highlight)
: super(new StringBuffer(), syntheticNames: globalDebuggingNames);
@override
bool shouldHighlight(Node node) => highlight == node;
static const kHighlightStart = ansiRed;
static const kHighlightEnd = ansiReset;
@override
void startHighlight(Node node) {
sink.write(kHighlightStart);
}
@override
void endHighlight(Node node) {
sink.write(kHighlightEnd);
}
/// Stringify the given [node] but only return lines that contain string
/// representation of the [highlight] node.
static String stringifyContainingLines(Node node, Node highlight) {
if (node == highlight) {
assert(node is Member);
final firstLine = debugNodeToString(node).split('\n').first;
return "${kHighlightStart}${firstLine}${kHighlightEnd}";
}
final HighlightingPrinter p = new HighlightingPrinter(highlight);
p.writeNode(node);
final String text = p.sink.toString();
return _onlyHighlightedLines(text).join('\n');
}
static Iterable<String> _onlyHighlightedLines(String text) sync* {
for (var line
in text.split('\n').skipWhile((l) => !l.contains(kHighlightStart))) {
yield line;
if (line.contains(kHighlightEnd)) {
break;
}
}
}
}
const ansiBlue = "\u001b[1;34m";
const ansiYellow = "\u001b[1;33m";
const ansiRed = "\u001b[1;31m";
const ansiReset = "\u001b[0;0m";