Fix header for multispan with null secondary urls (#72)

If a multiple span highlight was created where all the secondary spans
had `null` URLs they were filtered out from the comparison checking
whether multiple files are involved.

This bug was caused during the null safety migration. A similar bug
previously existed if the primary span had a null URL and is also fixed.

- Take a nullable list in `isAllTheSame`. Avoid using `null` as a
  sentinel for no item seen, use `.first` and `.skip(1)` to compare all
  elements.
- Add tests for the case of primary and secondary spans with a null URL.
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 6f78c4b..6fda0a0 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,5 +1,8 @@
 # 1.8.1
 
+* Fix a bug where the URL header for the highlights with multiple files would
+  get omitted only one span has a non-null URI.
+
 # 1.8.0
 
 * Stable release for null safety.
diff --git a/lib/src/highlighter.dart b/lib/src/highlighter.dart
index 5d28fc3..19d8965 100644
--- a/lib/src/highlighter.dart
+++ b/lib/src/highlighter.dart
@@ -107,8 +107,7 @@
                 .where((highlight) => isMultiline(highlight.span))
                 .length)
             .reduce(math.max),
-        _multipleFiles =
-            !isAllTheSame(_lines.map((line) => line.url).whereType<Uri>());
+        _multipleFiles = !isAllTheSame(_lines.map((line) => line.url));
 
   /// Returns whether [lines] contains any adjacent lines from the same source
   /// file that aren't adjacent in the original file.
diff --git a/lib/src/utils.dart b/lib/src/utils.dart
index 72c6173..ccc88bd 100644
--- a/lib/src/utils.dart
+++ b/lib/src/utils.dart
@@ -16,12 +16,11 @@
 
 /// Returns whether all elements of [iter] are the same value, according to
 /// `==`.
-bool isAllTheSame(Iterable<Object> iter) {
-  Object? lastValue;
-  for (var value in iter) {
-    if (lastValue == null) {
-      lastValue = value;
-    } else if (value != lastValue) {
+bool isAllTheSame(Iterable<Object?> iter) {
+  if (iter.isEmpty) return true;
+  final firstValue = iter.first;
+  for (var value in iter.skip(1)) {
+    if (value != firstValue) {
       return false;
     }
   }
diff --git a/pubspec.yaml b/pubspec.yaml
index 12382a5..89599df 100644
--- a/pubspec.yaml
+++ b/pubspec.yaml
@@ -1,5 +1,5 @@
 name: source_span
-version: 1.8.1-dev
+version: 1.8.1
 
 description: A library for identifying source spans and locations.
 homepage: https://github.com/dart-lang/source_span
diff --git a/test/multiple_highlight_test.dart b/test/multiple_highlight_test.dart
index 6d5ec9d..b0c28a5 100644
--- a/test/multiple_highlight_test.dart
+++ b/test/multiple_highlight_test.dart
@@ -261,14 +261,14 @@
     });
   });
 
-  test('highlights multiple files with their URLs', () {
-    final file2 = SourceFile.fromString('''
+  group('writes headers when highlighting multiple files', () {
+    test('writes all file URLs', () {
+      final span2 = SourceFile.fromString('''
 quibble bibble boop
-''', url: 'file2.txt');
+''', url: 'file2.txt').span(8, 14);
 
-    expect(
-        file.span(31, 34).highlightMultiple('one', {file2.span(8, 14): 'two'}),
-        equals("""
+      expect(
+          file.span(31, 34).highlightMultiple('one', {span2: 'two'}), equals("""
   ,--> file1.txt
 3 | zip zap zop
   |     ^^^ one
@@ -277,5 +277,38 @@
 1 | quibble bibble boop
   |         ====== two
   '"""));
+    });
+
+    test('allows secondary spans to have null URL', () {
+      final span2 = SourceSpan(SourceLocation(1, sourceUrl: null),
+          SourceLocation(4, sourceUrl: null), 'foo');
+
+      expect(
+          file.span(31, 34).highlightMultiple('one', {span2: 'two'}), equals("""
+  ,--> file1.txt
+3 | zip zap zop
+  |     ^^^ one
+  '
+  ,
+1 | foo
+  | === two
+  '"""));
+    });
+
+    test('allows primary span to have null URL', () {
+      final span1 = SourceSpan(SourceLocation(1, sourceUrl: null),
+          SourceLocation(4, sourceUrl: null), 'foo');
+
+      expect(
+          span1.highlightMultiple('one', {file.span(31, 34): 'two'}), equals("""
+  ,
+1 | foo
+  | ^^^ one
+  '
+  ,--> file1.txt
+3 | zip zap zop
+  |     === two
+  '"""));
+    });
   });
 }