Allow interpolations on adjacent strings (#2721)

diff --git a/CHANGELOG.md b/CHANGELOG.md
index 53e32fa..4ee59cc 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -4,6 +4,8 @@
   not the entire body and doc comment.
 - update `unnecessary_getters_setters` to allow otherwise "unnecessary" getters
   and setters with annotations.
+- update `missing_whitespace_between_adjacent_strings` to allow String
+  interpolations at the beginning and end of String literals.
 
 # 1.6.1
 
diff --git a/lib/src/rules/missing_whitespace_between_adjacent_strings.dart b/lib/src/rules/missing_whitespace_between_adjacent_strings.dart
index bce3915..6567826 100644
--- a/lib/src/rules/missing_whitespace_between_adjacent_strings.dart
+++ b/lib/src/rules/missing_whitespace_between_adjacent_strings.dart
@@ -57,7 +57,7 @@
 
   @override
   void visitAdjacentStrings(AdjacentStrings node) {
-    // skip regexp
+    // Skip strings passed to `RegExp()` or any method named `matches`.
     var parent = node.parent;
     if (parent is ArgumentList) {
       var parentParent = parent.parent;
@@ -73,11 +73,11 @@
     for (var i = 0; i < node.strings.length - 1; i++) {
       var current = node.strings[i];
       var next = node.strings[i + 1];
-      if (_visit(current, (l) => _endsWithWhitespace(l.last)) ||
-          _visit(next, (l) => _startsWithWhitespace(l.first))) {
+      if (_visit(current, (l) => l.last.endsWithWhitespace) ||
+          _visit(next, (l) => l.first.startsWithWhitespace)) {
         continue;
       }
-      if (!_visit(current, (l) => l.any(_hasWhitespace))) {
+      if (!_visit(current, (l) => l.any((e) => e.hasWhitespace))) {
         continue;
       }
       rule.reportLint(current);
@@ -90,27 +90,39 @@
     if (string is SimpleStringLiteral) {
       return test([string.value]);
     } else if (string is StringInterpolation) {
-      return test(string.elements.map((e) {
-        if (e is InterpolationString) return e.value;
-        return '';
-      }));
+      var interpolationSubstitutions = <String>[];
+      for (var e in string.elements) {
+        // Given a [StringInterpolation] like '$text', the elements include
+        // empty [InterpolationString]s on either side of the
+        // [InterpolationExpression]. Don't include them in the evaluation.
+        if (e is InterpolationString && e.value.isNotEmpty) {
+          interpolationSubstitutions.add(e.value);
+        }
+        if (e is InterpolationExpression) {
+          // Treat an interpolation expression as a string with whitespace. This
+          // prevents over-reporting on adjascent Strings which start or end
+          // with interpolations.
+          interpolationSubstitutions.add(' ');
+        }
+      }
+      return test(interpolationSubstitutions);
     }
     throw ArgumentError('${string.runtimeType}: $string');
   }
 
-  bool _hasWhitespace(String value) => _whitespaces.any(value.contains);
-  bool _endsWithWhitespace(String value) => _whitespaces.any(value.endsWith);
-  bool _startsWithWhitespace(String value) =>
-      _whitespaces.any(value.startsWith);
-
   static bool _isRegExpInstanceCreation(AstNode? node) {
     if (node is InstanceCreationExpression) {
       var constructorElement = node.constructorName.staticElement;
-      return constructorElement != null &&
-          constructorElement.enclosingElement.name == 'RegExp';
+      return constructorElement?.enclosingElement.name == 'RegExp';
     }
     return false;
   }
 }
 
-const _whitespaces = [' ', '\n', '\r', '\t'];
+extension on String {
+  bool get hasWhitespace => whitespaces.any(contains);
+  bool get endsWithWhitespace => whitespaces.any(endsWith);
+  bool get startsWithWhitespace => whitespaces.any(startsWith);
+
+  static const whitespaces = [' ', '\n', '\r', '\t'];
+}
diff --git a/test_data/rules/missing_whitespace_between_adjacent_strings.dart b/test_data/rules/missing_whitespace_between_adjacent_strings.dart
index bd3ac35..061bde4 100644
--- a/test_data/rules/missing_whitespace_between_adjacent_strings.dart
+++ b/test_data/rules/missing_whitespace_between_adjacent_strings.dart
@@ -20,6 +20,11 @@
   f(RegExp('(\n)+' '(\n)+' '(\n)+')); // OK
   new Unresolved('aaa' 'bbb'); // OK
   matches('(\n)+' '(\n)+' '(\n)+'); // OK
+
+  f('Hello' // OK
+    '${1 == 2 ? ', world' : ''}');
+  f('${1 == 2 ? 'Hello ' : ''}' // OK
+    'world');
 }
 
-void matches(String value){}
+void matches(String value) {}