Fix handling of literal dollar sign followed by a number in Intl messages
-------------
Created by MOE: https://github.com/google/moe
MOE_MIGRATED_REVID=121323209
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 0d6af5d..2b54da3 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -4,6 +4,8 @@
  * Add a NumberFormat.simpleCurrency constructor which will attempt to
    automatically determine the currency symbol. Very simple implementation but
    can be expanded to be per-locale.
+ * Fix a problem where, in a message, a literal dollar sign followed by a number
+   was seen as a valid identifier, resulting in invalid code being generated.
 
 ## 0.12.7+1
  * Change the signature for args and examples in Intl.plural/gender/select to
diff --git a/lib/src/intl_message.dart b/lib/src/intl_message.dart
index 7ff41f2..ebc83d5 100644
--- a/lib/src/intl_message.dart
+++ b/lib/src/intl_message.dart
@@ -159,11 +159,7 @@
   /// code.
   String toCode();
 
-  /// Escape the string for use in generated Dart code and
-  /// optionally validate that it
-  /// doesn't  doesn't contain any illegal interpolations. We only allow
-  /// simple variables ("$foo", but not "${foo}") and Intl.gender/plural
-  /// calls.
+  /// Escape the string for use in generated Dart code.
   String escapeAndValidateString(String value) {
     const Map<String, String> escapes = const {
       r"\": r"\\",
@@ -175,30 +171,13 @@
       "\t": r"\t",
       "\v": r"\v",
       "'": r"\'",
+      r"$" : r"\$"
     };
 
-    String _escape(String s) => (escapes[s] == null) ? s : escapes[s];
+    String _escape(String s) => escapes[s] ?? s;
 
     var escaped = value.splitMapJoin("", onNonMatch: _escape);
-    return disallowInvalidInterpolations(escaped);
-  }
-
-  /// Disallow ${} expressions, only allow $variable so as to avoid malicious
-  /// code. Disallow any usage of "${". If that makes a false positive
-  /// on a translation that legitimately contains "\\${" or other variations,
-  /// we'll live with that rather than risk a false negative.
-  String disallowInvalidInterpolations(String input) {
-    var validInterpolations = new RegExp(r"(\$\w+)|(\${\w+})");
-    var validMatches = validInterpolations.allMatches(input);
-    String escapeInvalidMatches(Match m) {
-      var valid = validMatches.any((x) => x.start == m.start);
-      if (valid) {
-        return m.group(0);
-      } else {
-        return "\\${m.group(0)}";
-      }
-    }
-    return input.replaceAllMapped("\$", escapeInvalidMatches);
+    return escaped;
   }
 
   /// Expand this string out into a printed form. The function [f] will be
diff --git a/test/message_extraction/make_hardcoded_translation.dart b/test/message_extraction/make_hardcoded_translation.dart
index a813c18..ff9d7d2 100644
--- a/test/message_extraction/make_hardcoded_translation.dart
+++ b/test/message_extraction/make_hardcoded_translation.dart
@@ -71,7 +71,8 @@
       "=1{{amount} dollar Canadien}"
       "other{{amount} dollars Canadiens}}}"
       "other{N'importe quoi}"
-      "}}"
+  "}}",
+  "literalDollar": "Cinq sous est US\$0.05"
 };
 
 /// A list of the German translations that we will produce.
@@ -130,7 +131,8 @@
       "=1{{amount} Kanadischer dollar}"
       "other{{amount} Kanadischen dollar}}}"
       "other{whatever}"
-      "}"
+      "}",
+  "literalDollar": "Fünf Cent US \$ 0.05"
 };
 
 /// The output directory for translated files.
diff --git a/test/message_extraction/sample_with_messages.dart b/test/message_extraction/sample_with_messages.dart
index 3d365b9..590daea 100644
--- a/test/message_extraction/sample_with_messages.dart
+++ b/test/message_extraction/sample_with_messages.dart
@@ -143,6 +143,9 @@
     meaning: 'rent as a verb',
     desc: "The action of renting, as in rent a car");
 
+literalDollar() => Intl.message("Five cents is US\$0.05",
+    name: "literalDollar", desc: "Literal dollar sign with valid number");
+
 printStuff(Intl locale) {
   // Use a name that's not a literal so this will get skipped. Then we have
   // a name that's not in the original but we include it in the French
@@ -228,6 +231,7 @@
     printOut(sameContentsDifferentName());
     printOut(rentAsVerb());
     printOut(rentToBePaid());
+    printOut(literalDollar());
   });
 }
 
diff --git a/test/message_extraction/verify_messages.dart b/test/message_extraction/verify_messages.dart
index 712dc12..f9bacc1 100644
--- a/test/message_extraction/verify_messages.dart
+++ b/test/message_extraction/verify_messages.dart
@@ -73,6 +73,7 @@
   verify('Hello World');
   verify('rent');
   verify('rent');
+  verify('Five cents is US\$0.05');
 
   var fr_lines =
       expanded.skip(1).skipWhile((line) => !line.contains('----')).toList();
@@ -137,6 +138,8 @@
   verify('Bonjour tout le monde');
   verify('louer');
   verify('loyer');
+  // Using a non-French format for the currency to test interpolation.
+  verify('Cinq sous est US\$0.05');
 
   var de_lines =
       fr_lines.skip(1).skipWhile((line) => !line.contains('----')).toList();
@@ -202,4 +205,5 @@
   verify('Hallo Welt');
   verify('mieten');
   verify('Miete');
+  verify('Fünf Cent US \$ 0.05');
 }