mirror of
https://github.com/dart-lang/sdk
synced 2024-09-16 02:07:06 +00:00
efe0ddccab
Change-Id: I5eacb92ea6ce00d83b3440a473cca8dad0892a87 Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/197165 Reviewed-by: Dmitry Stefantsov <dmitryas@google.com> Commit-Queue: Johnni Winther <johnniwinther@google.com>
180 lines
5.3 KiB
Dart
180 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 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";
|