blob: 5640a9ee5a232c2a50335e62c5183a3bdbbbed82 [file] [log] [blame]
#!/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 dynamic context = where is Class || where is Library
? where
: _findEnclosingMember(where);
String sourceLocation = '<unknown source>';
String? sourceLine = null;
// Try finding original source line.
final int fileOffset = _findFileOffset(where);
if (fileOffset != TreeNode.noOffset) {
final Uri fileUri = _fileUriOf(context);
final Component component = context.enclosingComponent;
final Source source = component.uriToSource[fileUri]!;
final Location location = component.getLocation(fileUri, fileOffset)!;
final List<int> lineStarts = source.lineStarts!;
final int lineStart = lineStarts[location.line - 1];
final int lineEnd = (location.line < lineStarts.length)
? 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.
String name = "";
dynamic body = context;
if (context is Class || context is Library) {
name = context.name;
} else if (context is Procedure || context is Constructor) {
final dynamic parent = context.parent;
final String? parentName =
parent is Class ? parent.name : (parent as Library).name;
name = "${parentName}::${context.name.text}";
} else {
final Field 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(FileUriNode node) {
return node.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) {
TreeNode? 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 Node highlight;
HighlightingPrinter(this.highlight)
: super(new StringBuffer(), syntheticNames: globalDebuggingNames);
@override
bool shouldHighlight(Node node) => highlight == node;
static const String kHighlightStart = ansiRed;
static const String 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) {
final String 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 (String line
in text.split('\n').skipWhile((l) => !l.contains(kHighlightStart))) {
yield line;
if (line.contains(kHighlightEnd)) {
break;
}
}
}
}
const String ansiBlue = "\u001b[1;34m";
const String ansiYellow = "\u001b[1;33m";
const String ansiRed = "\u001b[1;31m";
const String ansiReset = "\u001b[0;0m";