analyze_once.dart 6.47 KB
Newer Older
Ian Hickson's avatar
Ian Hickson committed
1
// Copyright 2014 The Flutter Authors. All rights reserved.
2 3 4
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

5 6
// @dart = 2.8

7 8 9
import 'dart:async';

import 'package:args/args.dart';
10
import 'package:meta/meta.dart';
11
import 'package:process/process.dart';
12

13
import '../artifacts.dart';
14
import '../base/common.dart';
15
import '../base/file_system.dart';
16
import '../base/logger.dart';
17
import '../base/platform.dart';
18
import '../base/terminal.dart';
19 20 21 22
import '../dart/analysis.dart';
import 'analyze_base.dart';

class AnalyzeOnce extends AnalyzeBase {
23 24
  AnalyzeOnce(
    ArgResults argResults,
25 26 27 28 29 30
    List<String> repoRoots,
    List<Directory> repoPackages, {
    @required FileSystem fileSystem,
    @required Logger logger,
    @required Platform platform,
    @required ProcessManager processManager,
31 32
    @required Terminal terminal,
    @required Artifacts artifacts,
33
    this.workingDirectory,
34 35 36 37 38 39 40 41 42
  }) : super(
        argResults,
        repoRoots: repoRoots,
        repoPackages: repoPackages,
        fileSystem: fileSystem,
        logger: logger,
        platform: platform,
        processManager: processManager,
        terminal: terminal,
43
        artifacts: artifacts,
44
      );
45

46
  /// The working directory for testing analysis using dartanalyzer.
47
  final Directory workingDirectory;
48 49

  @override
50
  Future<void> analyze() async {
51
    final String currentDirectory =
52
        (workingDirectory ?? fileSystem.currentDirectory).path;
53 54

    // find directories from argResults.rest
55
    final Set<String> directories = Set<String>.of(argResults.rest
56
        .map<String>((String path) => fileSystem.path.canonicalize(path)));
57
    if (directories.isNotEmpty) {
58
      for (final String directory in directories) {
59
        final FileSystemEntityType type = fileSystem.typeSync(directory);
60

61
        if (type == FileSystemEntityType.notFound) {
62
          throwToolExit("'$directory' does not exist");
63
        } else if (type != FileSystemEntityType.directory) {
64
          throwToolExit("'$directory' is not a directory");
65 66 67 68
        }
      }
    }

69
    if (isFlutterRepo) {
70
      // check for conflicting dependencies
71
      final PackageDependencyTracker dependencies = PackageDependencyTracker();
72 73
      dependencies.checkForConflictingDependencies(repoPackages, dependencies);
      directories.addAll(repoRoots);
74
      if (argResults.wasParsed('current-package') && (argResults['current-package'] as bool)) {
75
        directories.add(currentDirectory);
76
      }
77
    } else {
78
      if (argResults['current-package'] as bool) {
79
        directories.add(currentDirectory);
80
      }
81 82
    }

83
    if (directories.isEmpty) {
84
      throwToolExit('Nothing to analyze.', exitCode: 0);
85
    }
86

87
    final Completer<void> analysisCompleter = Completer<void>();
88
    final List<AnalysisError> errors = <AnalysisError>[];
89

90
    final AnalysisServer server = AnalysisServer(
91 92
      sdkPath,
      directories.toList(),
93 94 95 96 97
      fileSystem: fileSystem,
      platform: platform,
      logger: logger,
      processManager: processManager,
      terminal: terminal,
98
    );
99

100 101 102 103
    Stopwatch timer;
    Status progress;
    try {
      StreamSubscription<bool> subscription;
104 105

      void handleAnalysisStatus(bool isAnalyzing) {
106 107 108 109 110
        if (!isAnalyzing) {
          analysisCompleter.complete();
          subscription?.cancel();
          subscription = null;
        }
111 112 113 114 115 116 117 118 119 120 121
      }

      subscription = server.onAnalyzing.listen((bool isAnalyzing) => handleAnalysisStatus(isAnalyzing));

      void handleAnalysisErrors(FileAnalysisErrors fileErrors) {
        fileErrors.errors.removeWhere((AnalysisError error) => error.type == 'TODO');

        errors.addAll(fileErrors.errors);
      }

      server.onErrors.listen(handleAnalysisErrors);
122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147

      await server.start();
      // Completing the future in the callback can't fail.
      unawaited(server.onExit.then<void>((int exitCode) {
        if (!analysisCompleter.isCompleted) {
          analysisCompleter.completeError('analysis server exited: $exitCode');
        }
      }));

      // collect results
      timer = Stopwatch()..start();
      final String message = directories.length > 1
          ? '${directories.length} ${directories.length == 1 ? 'directory' : 'directories'}'
          : fileSystem.path.basename(directories.first);
      progress = argResults['preamble'] as bool
          ? logger.startProgress(
            'Analyzing $message...',
          )
          : null;

      await analysisCompleter.future;
    } finally {
      await server.dispose();
      progress?.cancel();
      timer?.stop();
    }
148

149 150
    final int undocumentedMembers = AnalyzeBase.countMissingDartDocs(errors);
    if (!isDartDocs) {
151
      errors.removeWhere((AnalysisError error) => error.code == 'public_member_api_docs');
152
    }
153

154
    // emit benchmarks
155
    if (isBenchmarking) {
156
      writeBenchmark(timer, errors.length, undocumentedMembers);
157
    }
158

159 160
    // --write
    dumpErrors(errors.map<String>((AnalysisError error) => error.toLegacyString()));
161

162
    // report errors
163
    if (errors.isNotEmpty && (argResults['preamble'] as bool)) {
164
      logger.printStatus('');
165
    }
166
    errors.sort();
167
    for (final AnalysisError error in errors) {
168
      logger.printStatus(error.toString(), hangingIndent: 7);
169
    }
170

171
    final int errorCount = errors.length;
172
    final String seconds = (timer.elapsedMilliseconds / 1000.0).toStringAsFixed(1);
173
    final String dartDocMessage = AnalyzeBase.generateDartDocMessage(undocumentedMembers);
174 175 176 177 178 179
    final String errorsMessage = AnalyzeBase.generateErrorsMessage(
      issueCount: errorCount,
      seconds: seconds,
      undocumentedMembers: undocumentedMembers,
      dartDocMessage: dartDocMessage,
    );
180

181
    if (errorCount > 0) {
182
      logger.printStatus('');
183
      throwToolExit(errorsMessage, exitCode: _isFatal(errors) ? 1 : 0);
184
    }
185

186 187
    if (argResults['congratulate'] as bool) {
      logger.printStatus(errorsMessage);
188 189
    }

190 191
    if (server.didServerErrorOccur) {
      throwToolExit('Server error(s) occurred. (ran in ${seconds}s)');
192
    }
193
  }
194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210

  bool _isFatal(List<AnalysisError> errors) {
    for (final AnalysisError error in errors) {
      final AnalysisSeverity severityLevel = error.writtenError.severityLevel;
      if (severityLevel == AnalysisSeverity.error) {
        return true;
      }
      if (severityLevel == AnalysisSeverity.warning &&
        (argResults['fatal-warnings'] as bool || argResults['fatal-infos'] as bool)) {
        return true;
      }
      if (severityLevel == AnalysisSeverity.info && argResults['fatal-infos'] as bool) {
        return true;
      }
    }
    return false;
  }
211
}