Exempt analysis_options.yaml from test requirement (#2168)

diff --git a/app_dart/lib/src/request_handlers/github/webhook_subscription.dart b/app_dart/lib/src/request_handlers/github/webhook_subscription.dart
index fadc313..0e59cd9 100644
--- a/app_dart/lib/src/request_handlers/github/webhook_subscription.dart
+++ b/app_dart/lib/src/request_handlers/github/webhook_subscription.dart
@@ -246,13 +246,7 @@
       final bool addedCode = linesAdded > 0 || linesDeleted != linesTotal;
 
       if (addedCode &&
-          !filename.contains('AUTHORS') &&
-          !filename.contains('pubspec.yaml') &&
-          !filename.contains('.ci.yaml') &&
-          !filename.contains('.cirrus.yml') &&
-          !filename.contains('.github') &&
-          !filename.endsWith('.md') &&
-          !filename.contains('CODEOWNERS') &&
+          !_isTestExempt(filename) &&
           !filename.startsWith('dev/bots/') &&
           !filename.endsWith('.gitignore')) {
         needsTests = !_allChangesAreCodeComments(file);
@@ -305,6 +299,20 @@
         objectiveCTestRegex.hasMatch(filename);
   }
 
+  /// Returns true if changes to [filename] are exempt from the testing
+  /// requirement, across repositories.
+  bool _isTestExempt(String filename) {
+    return filename.contains('.ci.yaml') ||
+        filename.contains('.cirrus.yml') ||
+        filename.contains('analysis_options.yaml') ||
+        filename.contains('AUTHORS') ||
+        filename.contains('CODEOWNERS') ||
+        filename.contains('pubspec.yaml') ||
+        // Exempt categories.
+        filename.contains('.github/') ||
+        filename.endsWith('.md');
+  }
+
   /// Returns the set of labels applicable to a file in the framework repo.
   static Set<String> getLabelsForFrameworkPath(String filepath) {
     final Set<String> labels = <String>{};
@@ -453,14 +461,8 @@
       final bool addedCode = linesAdded > 0 || linesDeleted != linesTotal;
 
       if (addedCode &&
-          !filename.endsWith('AUTHORS') &&
-          !filename.endsWith('CODEOWNERS') &&
-          !filename.endsWith('pubspec.yaml') &&
-          !filename.endsWith('.ci.yaml') &&
-          !filename.endsWith('.cirrus.yml') &&
+          !_isTestExempt(filename) &&
           !filename.contains('.ci/') &&
-          !filename.contains('.github/') &&
-          !filename.endsWith('.md') &&
           // Custom package-specific test runners. These do not count as tests
           // for the purposes of testing a change that otherwise needs tests,
           // but since they are the driver for tests they don't need test
diff --git a/app_dart/test/request_handlers/github/webhook_subscription_test.dart b/app_dart/test/request_handlers/github/webhook_subscription_test.dart
index 78afbf5..53e3391 100644
--- a/app_dart/test/request_handlers/github/webhook_subscription_test.dart
+++ b/app_dart/test/request_handlers/github/webhook_subscription_test.dart
@@ -1140,6 +1140,26 @@
       ));
     });
 
+    test('Framework no comment if only analysis options changed', () async {
+      const int issueNumber = 123;
+      tester.message = generateGithubWebhookMessage(action: 'opened', number: issueNumber);
+      final RepositorySlug slug = RepositorySlug('flutter', 'flutter');
+
+      when(pullRequestsService.listFiles(slug, issueNumber)).thenAnswer(
+        (_) => Stream<PullRequestFile>.fromIterable(<PullRequestFile>[
+          PullRequestFile()..filename = 'analysis_options.yaml',
+        ]),
+      );
+
+      await tester.post(webhook);
+
+      verifyNever(issuesService.createComment(
+        slug,
+        issueNumber,
+        argThat(contains(config.missingTestsPullRequestMessageValue)),
+      ));
+    });
+
     test('Framework no comment if only CODEOWNERS changed', () async {
       const int issueNumber = 123;
       tester.message = generateGithubWebhookMessage(action: 'opened', number: issueNumber);