Warn on `pub publish` if `dart analyze` has messages (#3568)

diff --git a/lib/src/log.dart b/lib/src/log.dart
index f845aa6..61c6768 100644
--- a/lib/src/log.dart
+++ b/lib/src/log.dart
@@ -8,7 +8,6 @@
 import 'dart:io';
 
 import 'package:args/command_runner.dart';
-import 'package:meta/meta.dart';
 import 'package:path/path.dart' as p;
 import 'package:source_span/source_span.dart';
 import 'package:stack_trace/stack_trace.dart';
@@ -341,7 +340,6 @@
 /// replacing it with '[...]' if it is too long.
 ///
 /// [limit] must be more than 5.
-@visibleForTesting
 String limitLength(String input, int limit) {
   const snip = '[...]';
   assert(limit > snip.length);
diff --git a/lib/src/validator.dart b/lib/src/validator.dart
index ef04bc4..82de012 100644
--- a/lib/src/validator.dart
+++ b/lib/src/validator.dart
@@ -11,6 +11,7 @@
 import 'entrypoint.dart';
 import 'log.dart' as log;
 import 'sdk.dart';
+import 'validator/analyze.dart';
 import 'validator/changelog.dart';
 import 'validator/compiled_dartdoc.dart';
 import 'validator/dependency.dart';
@@ -130,6 +131,7 @@
       required List<String> warnings,
       required List<String> errors}) async {
     var validators = [
+      AnalyzeValidator(),
       GitignoreValidator(),
       PubspecValidator(),
       LicenseValidator(),
diff --git a/lib/src/validator/analyze.dart b/lib/src/validator/analyze.dart
new file mode 100644
index 0000000..b9af306
--- /dev/null
+++ b/lib/src/validator/analyze.dart
@@ -0,0 +1,30 @@
+// Copyright (c) 2022, the Dart project authors.  Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+import 'dart:async';
+import 'dart:io';
+
+import 'package:path/path.dart' as p;
+
+import '../io.dart';
+
+import '../log.dart';
+import '../validator.dart';
+
+/// Runs `dart analyze` and gives a warning if it returns non-zero.
+class AnalyzeValidator extends Validator {
+  @override
+  Future<void> validate() async {
+    final result = await runProcess(Platform.resolvedExecutable, [
+      'analyze',
+      '--fatal-infos',
+      if (!p.equals(entrypoint.root.dir, p.current)) entrypoint.root.dir,
+    ]);
+    if (result.exitCode != 0) {
+      final limitedOutput = limitLength(result.stdout.join('\n'), 1000);
+      warnings
+          .add('`dart analyze` found the following issue(s):\n$limitedOutput');
+    }
+  }
+}
diff --git a/test/validator/analyze_test.dart b/test/validator/analyze_test.dart
new file mode 100644
index 0000000..0717b3c
--- /dev/null
+++ b/test/validator/analyze_test.dart
@@ -0,0 +1,102 @@
+// Copyright (c) 2022, the Dart project authors.  Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+import 'package:pub/src/exit_codes.dart';
+import 'package:test/test.dart';
+
+import '../descriptor.dart' as d;
+import '../test_pub.dart';
+
+Future<void> expectValidation(
+  error,
+  int exitCode, {
+  List<String> extraArgs = const [],
+  Map<String, String> environment = const {},
+  String? workingDirectory,
+}) async {
+  await runPub(
+    error: error,
+    args: ['publish', '--dry-run', ...extraArgs],
+    environment: {'_PUB_TEST_SDK_VERSION': '1.12.0', ...environment},
+    workingDirectory: workingDirectory ?? d.path(appPath),
+    exitCode: exitCode,
+  );
+}
+
+void main() {
+  test('should consider a package valid if it contains no warnings or errors',
+      () async {
+    await d.dir(appPath, [
+      d.libPubspec('test_pkg', '1.0.0', sdk: '>=1.8.0 <=2.0.0'),
+      d.file('LICENSE', 'Eh, do what you want.'),
+      d.file('README.md', "This package isn't real."),
+      d.file('CHANGELOG.md', '# 1.0.0\nFirst version\n'),
+      d.dir('lib', [d.file('test_pkg.dart', 'int i = 1;')])
+    ]).create();
+
+    await pubGet(environment: {'_PUB_TEST_SDK_VERSION': '1.12.0'});
+
+    await expectValidation(contains('Package has 0 warnings.'), 0);
+  });
+
+  test('should warn if package contains errors, and works with --directory',
+      () async {
+    await d.dir(appPath, [
+      d.libPubspec('test_pkg', '1.0.0', sdk: '>=1.8.0 <=2.0.0'),
+      d.file('LICENSE', 'Eh, do what you want.'),
+      d.file('README.md', "This package isn't real."),
+      d.file('CHANGELOG.md', '# 1.0.0\nFirst version\n'),
+      d.dir('lib', [
+        d.file('test_pkg.dart', '''
+void main() {
+// Missing }
+''')
+      ])
+    ]).create();
+
+    await pubGet(environment: {'_PUB_TEST_SDK_VERSION': '1.12.0'});
+
+    await expectValidation(
+      allOf([
+        contains('`dart analyze` found the following issue(s):'),
+        contains('Analyzing myapp...'),
+        contains('error -'),
+        contains("Expected to find '}'."),
+        contains('Package has 1 warning.')
+      ]),
+      DATA,
+      extraArgs: ['--directory', appPath],
+      workingDirectory: d.sandbox,
+    );
+  });
+
+  test('should warn if package contains infos', () async {
+    await d.dir(appPath, [
+      d.libPubspec('test_pkg', '1.0.0', sdk: '>=1.8.0 <=2.0.0'),
+      d.file('LICENSE', 'Eh, do what you want.'),
+      d.file('README.md', "This package isn't real."),
+      d.file('CHANGELOG.md', '# 1.0.0\nFirst version\n'),
+      d.dir('lib', [
+        d.file('test_pkg.dart', '''
+void main() {
+  final a = 10; // Unused.
+}
+''')
+      ]),
+    ]).create();
+
+    await pubGet(environment: {'_PUB_TEST_SDK_VERSION': '1.12.0'});
+
+    await expectValidation(
+      allOf([
+        contains('`dart analyze` found the following issue(s):'),
+        contains('Analyzing myapp...'),
+        contains('info -'),
+        contains("The value of the local variable 'a' isn't used"),
+        contains('Package has 1 warning.')
+      ]),
+      DATA,
+    );
+  });
+}
diff --git a/test/validator/dependency_test.dart b/test/validator/dependency_test.dart
index 69fc4a8..b3adc34 100644
--- a/test/validator/dependency_test.dart
+++ b/test/validator/dependency_test.dart
@@ -39,10 +39,10 @@
 }
 
 Future<void> expectValidationWarning(error,
-    {Map<String, String> environment = const {}}) async {
+    {int count = 1, Map<String, String> environment = const {}}) async {
   if (error is String) error = contains(error);
   await expectValidation(
-    error: allOf([error, contains('Package has 1 warning.')]),
+    error: allOf([error, contains('Package has $count warning')]),
     exitCode: DATA,
     environment: environment,
   );
@@ -106,7 +106,9 @@
       await expectValidationWarning(
           allOf([
             contains('  foo: any'),
+            contains("Publishable packages can't have 'git' dependencies"),
           ]),
+          count: 2,
           environment: {'_PUB_TEST_SDK_VERSION': '2.0.0'});
     });