Optimize _looksLikeClassName() in AOT builds. (#835)

* Optimize _looksLikeClassName() in AOT builds.

The AOT compiler does not currently compile regexes to machine code,
which makes them significantly slower. The regex in this function is
perf critical and makes the dart_style benchmark 65% slower on AOT than
on the JIT.

This fixes some of that regression.

Before:

// JIT
Best   : 4.93ms ========================
Best   : 4.83ms ========================
Best   : 4.80ms ========================

// AOT
Best   : 8.33ms =========================================
Best   : 8.43ms ==========================================
Best   : 8.37ms =========================================

After:

// JIT
Best   : 4.93ms ========================
Best   : 4.83ms ========================
Best   : 4.80ms ========================

// AOT
Best   : 6.93ms ==================================
Best   : 6.87ms ==================================
Best   : 6.77ms =================================

* Don't crash if identifier is "_".
diff --git a/lib/src/source_visitor.dart b/lib/src/source_visitor.dart
index ffef68d..fb2f231 100644
--- a/lib/src/source_visitor.dart
+++ b/lib/src/source_visitor.dart
@@ -23,8 +23,6 @@
 import 'style_fix.dart';
 import 'whitespace.dart';
 
-final _capitalPattern = RegExp(r"^_?[A-Z].*[a-z]");
-
 /// Visits every token of the AST and passes all of the relevant bits to a
 /// [ChunkBuilder].
 class SourceVisitor extends ThrowingAstVisitor {
@@ -76,6 +74,10 @@
     return target is SimpleIdentifier && _looksLikeClassName(target.name);
   }
 
+  /// Whether [name] appears to be a type name.
+  ///
+  /// Type names begin with a capital letter and contain at least one lowercase
+  /// letter (so that we can distinguish them from SCREAMING_CAPS constants).
   static bool _looksLikeClassName(String name) {
     // Handle the weird lowercase corelib names.
     if (name == "bool") return true;
@@ -83,7 +85,35 @@
     if (name == "int") return true;
     if (name == "num") return true;
 
-    return _capitalPattern.hasMatch(name);
+    // TODO(rnystrom): A simpler implementation is to test against the regex
+    // "_?[A-Z].*?[a-z]". However, that currently has much worse performance on
+    // AOT: https://github.com/dart-lang/sdk/issues/37785.
+    const underscore = 95;
+    const capitalA = 65;
+    const capitalZ = 90;
+    const lowerA = 97;
+    const lowerZ = 122;
+
+    var start = 0;
+    var firstChar = name.codeUnitAt(start++);
+
+    // It can be private.
+    if (firstChar == underscore) {
+      if (name.length == 1) return false;
+      firstChar = name.codeUnitAt(start++);
+    }
+
+    // It must start with a capital letter.
+    if (firstChar < capitalA || firstChar > capitalZ) return false;
+
+    // And have at least one lowercase letter in it. Otherwise it could be a
+    // SCREAMING_CAPS constant.
+    for (var i = start; i < name.length; i++) {
+      var char = name.codeUnitAt(i);
+      if (char >= lowerA && char <= lowerZ) return true;
+    }
+
+    return false;
   }
 
   /// The builder for the block that is currently being visited.