Roll forward of cl/148686729 with a workaround for analyzer crashing

Cloned from CL 148686729 by 'g4 patch'.
Original change by alanknight@alanknight:moreerrors:1055:citc on 2017/02/27 13:27:47.

Add a way to detect Intl.messages called before locale is initialized.

That might be valid, but if the result is cached the cached version will be untranslated.

-------------
Created by MOE: https://github.com/google/moe
MOE_MIGRATED_REVID=149009079
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 78462d1..de06b47 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -8,6 +8,9 @@
  * Remove the cacheBlocker parameter from HttpRequestDataReader
  * Optimize padding numbers when printing
  * Remove the out of date example directory
+ * Add a facility to check if messages are being called before locale
+   initialization, which can lead to errors if the results are being cached. See
+   UninitializedLocaleData.throwOnFallback.
 
 ## 0.14.0
  * MAJOR BREAKING CHANGE! Remove message extraction and code generation into a
diff --git a/lib/src/intl_helpers.dart b/lib/src/intl_helpers.dart
index 9d9cb5f..a54609a 100644
--- a/lib/src/intl_helpers.dart
+++ b/lib/src/intl_helpers.dart
@@ -19,15 +19,45 @@
 class UninitializedLocaleData<F> implements MessageLookup {
   final String message;
   final F fallbackData;
-  const UninitializedLocaleData(this.message, this.fallbackData);
+  UninitializedLocaleData(this.message, this.fallbackData);
 
   operator [](String key) =>
       (key == 'en_US') ? fallbackData : _throwException();
 
-  String lookupMessage(String message_str, String locale, String name,
-          List args, String meaning,
-          {MessageIfAbsent ifAbsent}) =>
-      message_str;
+  /// If a message is looked up before any locale initialization, record it,
+  /// and throw an exception with that information once the locale is
+  /// initialized.
+  ///
+  /// Set this during development to find issues with race conditions between
+  /// message caching and locale initialization. If the results of Intl.message
+  /// calls aren't being cached, then this won't help.
+  ///
+  /// There's nothing that actually sets this, so checking this requires
+  /// patching the code here.
+  static final bool throwOnFallback = false;
+
+  /// The messages that were called before the locale was initialized.
+  List<String> _badMessages = [];
+
+  void _reportErrors() {
+    if (throwOnFallback && _badMessages.length > 0) {
+      throw new StateError(
+          "The following messages were called before locale initialization:"
+          " $_uninitializedMessages");
+    }
+  }
+
+  String get _uninitializedMessages =>
+      (_badMessages.toSet().toList()..sort()).join("\n    ");
+
+  String lookupMessage(
+      String message_str, String locale, String name, List args, String meaning,
+      {MessageIfAbsent ifAbsent}) {
+    if (throwOnFallback) {
+      _badMessages.add(name ?? message_str);
+    }
+    return message_str;
+  }
 
   /// Given an initial locale or null, returns the locale that will be used
   /// for messages.
@@ -67,13 +97,15 @@
 /// by the implementing package so that we're not dependent on its
 /// implementation.
 MessageLookup messageLookup =
-    const UninitializedLocaleData('initializeMessages(<locale>)', null);
+    new UninitializedLocaleData('initializeMessages(<locale>)', null);
 
 /// Initialize the message lookup mechanism. This is for internal use only.
 /// User applications should import `message_lookup_by_library.dart` and call
 /// `initializeMessages`
 void initializeInternalMessageLookup(Function lookupFunction) {
   if (messageLookup is UninitializedLocaleData) {
+    // This line has to be precisely this way to work around an analyzer crash.
+    (messageLookup as UninitializedLocaleData)._reportErrors();
     messageLookup = lookupFunction();
   }
 }