[flutter_plugin_tools] Fix federated safety check (#4368)

The new safety check doesn't allow simple platform-interface-only
changes because it doesn't actually check that a non-interface package
is actually modified before failing it for a modified platform
interface.

This fixes that, and adds a test case covering it.
diff --git a/script/tool/lib/src/federation_safety_check_command.dart b/script/tool/lib/src/federation_safety_check_command.dart
index cb0da16..fd53d6c 100644
--- a/script/tool/lib/src/federation_safety_check_command.dart
+++ b/script/tool/lib/src/federation_safety_check_command.dart
@@ -135,6 +135,13 @@
       return PackageResult.success();
     }
 
+    final List<String> changedPackageFiles =
+        _changedDartFiles[package.directory.basename] ?? <String>[];
+    if (changedPackageFiles.isEmpty) {
+      print('No Dart changes.');
+      return PackageResult.success();
+    }
+
     // If the change would be flagged, but it appears to be a mass change
     // rather than a plugin-specific change, allow it with a warning.
     //
diff --git a/script/tool/test/federation_safety_check_command_test.dart b/script/tool/test/federation_safety_check_command_test.dart
index 4ae3ec5..e23485f 100644
--- a/script/tool/test/federation_safety_check_command_test.dart
+++ b/script/tool/test/federation_safety_check_command_test.dart
@@ -125,6 +125,47 @@
     );
   });
 
+  test('allows changes to just an interface package', () async {
+    final Directory pluginGroupDir = packagesDir.childDirectory('foo');
+    final Directory platformInterface =
+        createFakePlugin('foo_platform_interface', pluginGroupDir);
+    createFakePlugin('foo', pluginGroupDir);
+    createFakePlugin('foo_ios', pluginGroupDir);
+    createFakePlugin('foo_android', pluginGroupDir);
+
+    final String changedFileOutput = <File>[
+      platformInterface.childDirectory('lib').childFile('foo.dart'),
+      platformInterface.childFile('pubspec.yaml'),
+    ].map((File file) => file.path).join('\n');
+    processRunner.mockProcessesForExecutable['git-diff'] = <io.Process>[
+      MockProcess(stdout: changedFileOutput),
+    ];
+
+    final List<String> output =
+        await runCapturingPrint(runner, <String>['federation-safety-check']);
+
+    expect(
+      output,
+      containsAllInOrder(<Matcher>[
+        contains('Running for foo/foo...'),
+        contains('No Dart changes.'),
+        contains('Running for foo_android...'),
+        contains('No Dart changes.'),
+        contains('Running for foo_ios...'),
+        contains('No Dart changes.'),
+        contains('Running for foo_platform_interface...'),
+        contains('Ran for 3 package(s)'),
+        contains('Skipped 1 package(s)'),
+      ]),
+    );
+    expect(
+      output,
+      isNot(contains(<Matcher>[
+        contains('No published changes for foo_platform_interface'),
+      ])),
+    );
+  });
+
   test('allows changes to multiple non-interface packages', () async {
     final Directory pluginGroupDir = packagesDir.childDirectory('foo');
     final Directory appFacing = createFakePlugin('foo', pluginGroupDir);