Rewrite the analyze-sample-code script to also analyze snippets (#23893)

This rewrites the sample code analysis script to be a little less of a hack (but still not pretty), and to handle snippets as well.

It also changes the semantics of how sample code is handled: the namespace for the sample code is now limited to the file that it appears in, so some additional "Examples can assume:" blocks were added. The upside of this is that there will be far fewer name collisions.

I fixed the output too: no longer will you get 4000 lines of numbered output with the error at the top and have to grep for the actual problem. It gives the filename and line number of the original location of the code (in the comment in the tree), and prints out the source code on the line that caused the problem along with the error.

For snippets, it prints out the location of the start of the snippet and the source code line that causes the problem. It can't print out the original line, because snippets get formatted when they are written, so the line might not be in the same place.
diff --git a/dev/bots/test/analyze-sample-code_test.dart b/dev/bots/test/analyze-sample-code_test.dart
index 41ac7c4..8d3d883 100644
--- a/dev/bots/test/analyze-sample-code_test.dart
+++ b/dev/bots/test/analyze-sample-code_test.dart
@@ -2,68 +2,29 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-import 'dart:convert';
 import 'dart:io';
 
 import 'common.dart';
 
 void main() {
-  test('analyze-sample-code', () async {
-    final Process process = await Process.start(
+  test('analyze-sample-code', () {
+    final ProcessResult process = Process.runSync(
       '../../bin/cache/dart-sdk/bin/dart',
       <String>['analyze-sample-code.dart', 'test/analyze-sample-code-test-input'],
     );
-    final List<String> stdout = await process.stdout.transform<String>(utf8.decoder).transform<String>(const LineSplitter()).toList();
-    final List<String> stderr = await process.stderr.transform<String>(utf8.decoder).transform<String>(const LineSplitter()).toList();
-    final Match line = RegExp(r'^(.+)/main\.dart:[0-9]+:[0-9]+: .+$').matchAsPrefix(stdout[1]);
-    expect(line, isNot(isNull));
-    final String directory = line.group(1);
-    Directory(directory).deleteSync(recursive: true);
-    expect(await process.exitCode, 1);
-    expect(stderr, isEmpty);
-    expect(stdout, <String>[
-      'Found 2 sample code sections.',
-      "$directory/main.dart:1:8: Unused import: 'dart:async'",
-      "$directory/main.dart:2:8: Unused import: 'dart:convert'",
-      "$directory/main.dart:3:8: Unused import: 'dart:math'",
-      "$directory/main.dart:4:8: Unused import: 'dart:typed_data'",
-      "$directory/main.dart:5:8: Unused import: 'dart:ui'",
-      "$directory/main.dart:6:8: Unused import: 'package:flutter_test/flutter_test.dart'",
-      "$directory/main.dart:9:8: Target of URI doesn't exist: 'package:flutter/known_broken_documentation.dart'",
-      'test/analyze-sample-code-test-input/known_broken_documentation.dart:27:5: Unnecessary new keyword (unnecessary_new)',
-      "test/analyze-sample-code-test-input/known_broken_documentation.dart:27:9: Undefined class 'Opacity' (undefined_class)",
-      "test/analyze-sample-code-test-input/known_broken_documentation.dart:29:20: Undefined class 'Text' (undefined_class)",
-      'test/analyze-sample-code-test-input/known_broken_documentation.dart:39:5: Unnecessary new keyword (unnecessary_new)',
-      "test/analyze-sample-code-test-input/known_broken_documentation.dart:39:9: Undefined class 'Opacity' (undefined_class)",
-      "test/analyze-sample-code-test-input/known_broken_documentation.dart:41:20: Undefined class 'Text' (undefined_class)",
-      'test/analyze-sample-code-test-input/known_broken_documentation.dart:42:5: unexpected comma at end of sample code',
-      'Kept $directory because it had errors (see above).',
-      '-------8<-------',
-      '     1: // generated code',
-      "     2: import 'dart:async';",
-      "     3: import 'dart:convert';",
-      "     4: import 'dart:math' as math;",
-      "     5: import 'dart:typed_data';",
-      "     6: import 'dart:ui' as ui;",
-      "     7: import 'package:flutter_test/flutter_test.dart';",
-      '     8: ',
-      '     9: // test/analyze-sample-code-test-input/known_broken_documentation.dart',
-      "    10: import 'package:flutter/known_broken_documentation.dart';",
-      '    11: ',
-      '    12: bool _visible = true;',
-      '    13: dynamic expression1 = ',
-      '    14: new Opacity(',
-      '    15:   opacity: _visible ? 1.0 : 0.0,',
-      "    16:   child: const Text('Poor wandering ones!'),",
-      '    17: )',
-      '    18: ;',
-      '    19: dynamic expression2 = ',
-      '    20: new Opacity(',
-      '    21:   opacity: _visible ? 1.0 : 0.0,',
-      "    22:   child: const Text('Poor wandering ones!'),",
-      '    23: ),',
-      '    24: ;',
-      '-------8<-------',
+    final List<String> stdoutLines = process.stdout.toString().split('\n');
+    final List<String> stderrLines = process.stderr.toString().split('\n')
+      ..removeWhere((String line) => line.startsWith('Analyzer output:'));
+    expect(process.exitCode, isNot(equals(0)));
+    expect(stderrLines, <String>[
+      'known_broken_documentation.dart:27:9: new Opacity(',
+      '>>> Unnecessary new keyword (unnecessary_new)',
+      'known_broken_documentation.dart:39:9: new Opacity(',
+      '>>> Unnecessary new keyword (unnecessary_new)',
+      '',
+      'Found 1 sample code errors.',
+      '',
     ]);
-  }, skip: !Platform.isLinux);
+    expect(stdoutLines, <String>['Found 2 sample code sections.', 'Starting analysis of samples.', '']);
+  }, skip: Platform.isWindows);
 }