Indent error pointer correctly in the presence of tabs.

Change-Id: I8e8aa9bcc70e9c4a0e951376f24db51aa51069b2
Reviewed-on: https://dart-review.googlesource.com/60426
Commit-Queue: Aske Simon Christensen <askesc@google.com>
Reviewed-by: Peter von der Ahé <ahe@google.com>
diff --git a/pkg/front_end/lib/src/fasta/command_line_reporting.dart b/pkg/front_end/lib/src/fasta/command_line_reporting.dart
index c7f93ee..e682431 100644
--- a/pkg/front_end/lib/src/fasta/command_line_reporting.dart
+++ b/pkg/front_end/lib/src/fasta/command_line_reporting.dart
@@ -10,6 +10,10 @@
 
 import 'dart:io' show exitCode;
 
+import 'dart:math' show min;
+
+import 'dart:typed_data' show Uint8List;
+
 import 'package:kernel/ast.dart' show Location;
 
 import 'colors.dart' show green, magenta, red;
@@ -27,6 +31,8 @@
 
 import 'severity.dart' show Severity;
 
+import 'scanner/characters.dart' show $CARET, $SPACE, $TAB;
+
 import 'util/relativize.dart' show relativizeUri;
 
 const bool hideWarnings = false;
@@ -75,8 +81,21 @@
       if (sourceLine == null) {
         sourceLine = "";
       } else if (sourceLine.isNotEmpty) {
-        String indentation = " " * (location.column - 1);
-        String pointer = indentation + ("^" * length);
+        // TODO(askesc): Much more could be done to indent properly in the
+        // presence of all sorts of unicode weirdness.
+        // This handling covers the common case of single-width characters
+        // indented with spaces and/or tabs, using no surrogates.
+        int indentLength = location.column - 1;
+        Uint8List indentation = new Uint8List(indentLength + length)
+          ..fillRange(0, indentLength, $SPACE)
+          ..fillRange(indentLength, indentLength + length, $CARET);
+        int lengthInSourceLine = min(indentation.length, sourceLine.length);
+        for (int i = 0; i < lengthInSourceLine; i++) {
+          if (sourceLine.codeUnitAt(i) == $TAB) {
+            indentation[i] = $TAB;
+          }
+        }
+        String pointer = new String.fromCharCodes(indentation);
         if (pointer.length > sourceLine.length) {
           // Truncate the carets to handle messages that span multiple lines.
           int pointerLength = sourceLine.length;
diff --git a/pkg/front_end/testcases/tabs.dart b/pkg/front_end/testcases/tabs.dart
new file mode 100644
index 0000000..28a958f
--- /dev/null
+++ b/pkg/front_end/testcases/tabs.dart
@@ -0,0 +1,16 @@
+// Copyright (c) 2018, 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.
+
+// Test that error messages are indented correctly
+// in the presence of tabs.
+
+test() {
+	print(one);
+		print(two);
+		  print(three);
+	   	print(four);
+	   	  print(five);
+}
+
+main() {}
diff --git a/pkg/front_end/testcases/tabs.dart.direct.expect b/pkg/front_end/testcases/tabs.dart.direct.expect
new file mode 100644
index 0000000..b542e17
--- /dev/null
+++ b/pkg/front_end/testcases/tabs.dart.direct.expect
@@ -0,0 +1,12 @@
+library;
+import self as self;
+import "dart:core" as core;
+
+static method test() → dynamic {
+  core::print(throw new core::NoSuchMethodError::withInvocation(null, new core::_InvocationMirror::_withType(#one, 33, const <core::Type>[], const <dynamic>[], core::Map::unmodifiable<core::Symbol, dynamic>(const <core::Symbol, dynamic>{}))));
+  core::print(throw new core::NoSuchMethodError::withInvocation(null, new core::_InvocationMirror::_withType(#two, 33, const <core::Type>[], const <dynamic>[], core::Map::unmodifiable<core::Symbol, dynamic>(const <core::Symbol, dynamic>{}))));
+  core::print(throw new core::NoSuchMethodError::withInvocation(null, new core::_InvocationMirror::_withType(#three, 33, const <core::Type>[], const <dynamic>[], core::Map::unmodifiable<core::Symbol, dynamic>(const <core::Symbol, dynamic>{}))));
+  core::print(throw new core::NoSuchMethodError::withInvocation(null, new core::_InvocationMirror::_withType(#four, 33, const <core::Type>[], const <dynamic>[], core::Map::unmodifiable<core::Symbol, dynamic>(const <core::Symbol, dynamic>{}))));
+  core::print(throw new core::NoSuchMethodError::withInvocation(null, new core::_InvocationMirror::_withType(#five, 33, const <core::Type>[], const <dynamic>[], core::Map::unmodifiable<core::Symbol, dynamic>(const <core::Symbol, dynamic>{}))));
+}
+static method main() → dynamic {}
diff --git a/pkg/front_end/testcases/tabs.dart.direct.transformed.expect b/pkg/front_end/testcases/tabs.dart.direct.transformed.expect
new file mode 100644
index 0000000..b542e17
--- /dev/null
+++ b/pkg/front_end/testcases/tabs.dart.direct.transformed.expect
@@ -0,0 +1,12 @@
+library;
+import self as self;
+import "dart:core" as core;
+
+static method test() → dynamic {
+  core::print(throw new core::NoSuchMethodError::withInvocation(null, new core::_InvocationMirror::_withType(#one, 33, const <core::Type>[], const <dynamic>[], core::Map::unmodifiable<core::Symbol, dynamic>(const <core::Symbol, dynamic>{}))));
+  core::print(throw new core::NoSuchMethodError::withInvocation(null, new core::_InvocationMirror::_withType(#two, 33, const <core::Type>[], const <dynamic>[], core::Map::unmodifiable<core::Symbol, dynamic>(const <core::Symbol, dynamic>{}))));
+  core::print(throw new core::NoSuchMethodError::withInvocation(null, new core::_InvocationMirror::_withType(#three, 33, const <core::Type>[], const <dynamic>[], core::Map::unmodifiable<core::Symbol, dynamic>(const <core::Symbol, dynamic>{}))));
+  core::print(throw new core::NoSuchMethodError::withInvocation(null, new core::_InvocationMirror::_withType(#four, 33, const <core::Type>[], const <dynamic>[], core::Map::unmodifiable<core::Symbol, dynamic>(const <core::Symbol, dynamic>{}))));
+  core::print(throw new core::NoSuchMethodError::withInvocation(null, new core::_InvocationMirror::_withType(#five, 33, const <core::Type>[], const <dynamic>[], core::Map::unmodifiable<core::Symbol, dynamic>(const <core::Symbol, dynamic>{}))));
+}
+static method main() → dynamic {}
diff --git a/pkg/front_end/testcases/tabs.dart.outline.expect b/pkg/front_end/testcases/tabs.dart.outline.expect
new file mode 100644
index 0000000..a29647d
--- /dev/null
+++ b/pkg/front_end/testcases/tabs.dart.outline.expect
@@ -0,0 +1,7 @@
+library;
+import self as self;
+
+static method test() → dynamic
+  ;
+static method main() → dynamic
+  ;
diff --git a/pkg/front_end/testcases/tabs.dart.strong.expect b/pkg/front_end/testcases/tabs.dart.strong.expect
new file mode 100644
index 0000000..83b1b86
--- /dev/null
+++ b/pkg/front_end/testcases/tabs.dart.strong.expect
@@ -0,0 +1,23 @@
+library;
+import self as self;
+import "dart:core" as core;
+
+static const field dynamic #errors = const <dynamic>["pkg/front_end/testcases/tabs.dart:9:8: Error: Getter not found: 'one'.
+\tprint(one);
+\t      ^^^", "pkg/front_end/testcases/tabs.dart:10:9: Error: Getter not found: 'two'.
+\t\tprint(two);
+\t\t      ^^^", "pkg/front_end/testcases/tabs.dart:11:11: Error: Getter not found: 'three'.
+\t\t  print(three);
+\t\t        ^^^^^", "pkg/front_end/testcases/tabs.dart:12:12: Error: Getter not found: 'four'.
+\t   \tprint(four);
+\t   \t      ^^^^", "pkg/front_end/testcases/tabs.dart:13:14: Error: Getter not found: 'five'.
+\t   \t  print(five);
+\t   \t        ^^^^"]/* from null */;
+static method test() → dynamic {
+  core::print(throw new core::NoSuchMethodError::withInvocation(null, new core::_InvocationMirror::_withType(#one, 33, const <core::Type>[], const <dynamic>[], core::Map::unmodifiable<core::Symbol, dynamic>(const <core::Symbol, dynamic>{}))));
+  core::print(throw new core::NoSuchMethodError::withInvocation(null, new core::_InvocationMirror::_withType(#two, 33, const <core::Type>[], const <dynamic>[], core::Map::unmodifiable<core::Symbol, dynamic>(const <core::Symbol, dynamic>{}))));
+  core::print(throw new core::NoSuchMethodError::withInvocation(null, new core::_InvocationMirror::_withType(#three, 33, const <core::Type>[], const <dynamic>[], core::Map::unmodifiable<core::Symbol, dynamic>(const <core::Symbol, dynamic>{}))));
+  core::print(throw new core::NoSuchMethodError::withInvocation(null, new core::_InvocationMirror::_withType(#four, 33, const <core::Type>[], const <dynamic>[], core::Map::unmodifiable<core::Symbol, dynamic>(const <core::Symbol, dynamic>{}))));
+  core::print(throw new core::NoSuchMethodError::withInvocation(null, new core::_InvocationMirror::_withType(#five, 33, const <core::Type>[], const <dynamic>[], core::Map::unmodifiable<core::Symbol, dynamic>(const <core::Symbol, dynamic>{}))));
+}
+static method main() → dynamic {}
diff --git a/pkg/front_end/testcases/tabs.dart.strong.transformed.expect b/pkg/front_end/testcases/tabs.dart.strong.transformed.expect
new file mode 100644
index 0000000..83b1b86
--- /dev/null
+++ b/pkg/front_end/testcases/tabs.dart.strong.transformed.expect
@@ -0,0 +1,23 @@
+library;
+import self as self;
+import "dart:core" as core;
+
+static const field dynamic #errors = const <dynamic>["pkg/front_end/testcases/tabs.dart:9:8: Error: Getter not found: 'one'.
+\tprint(one);
+\t      ^^^", "pkg/front_end/testcases/tabs.dart:10:9: Error: Getter not found: 'two'.
+\t\tprint(two);
+\t\t      ^^^", "pkg/front_end/testcases/tabs.dart:11:11: Error: Getter not found: 'three'.
+\t\t  print(three);
+\t\t        ^^^^^", "pkg/front_end/testcases/tabs.dart:12:12: Error: Getter not found: 'four'.
+\t   \tprint(four);
+\t   \t      ^^^^", "pkg/front_end/testcases/tabs.dart:13:14: Error: Getter not found: 'five'.
+\t   \t  print(five);
+\t   \t        ^^^^"]/* from null */;
+static method test() → dynamic {
+  core::print(throw new core::NoSuchMethodError::withInvocation(null, new core::_InvocationMirror::_withType(#one, 33, const <core::Type>[], const <dynamic>[], core::Map::unmodifiable<core::Symbol, dynamic>(const <core::Symbol, dynamic>{}))));
+  core::print(throw new core::NoSuchMethodError::withInvocation(null, new core::_InvocationMirror::_withType(#two, 33, const <core::Type>[], const <dynamic>[], core::Map::unmodifiable<core::Symbol, dynamic>(const <core::Symbol, dynamic>{}))));
+  core::print(throw new core::NoSuchMethodError::withInvocation(null, new core::_InvocationMirror::_withType(#three, 33, const <core::Type>[], const <dynamic>[], core::Map::unmodifiable<core::Symbol, dynamic>(const <core::Symbol, dynamic>{}))));
+  core::print(throw new core::NoSuchMethodError::withInvocation(null, new core::_InvocationMirror::_withType(#four, 33, const <core::Type>[], const <dynamic>[], core::Map::unmodifiable<core::Symbol, dynamic>(const <core::Symbol, dynamic>{}))));
+  core::print(throw new core::NoSuchMethodError::withInvocation(null, new core::_InvocationMirror::_withType(#five, 33, const <core::Type>[], const <dynamic>[], core::Map::unmodifiable<core::Symbol, dynamic>(const <core::Symbol, dynamic>{}))));
+}
+static method main() → dynamic {}