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

import 'dart:async';

import 'package:args/args.dart';
8
import 'package:process/process.dart';
9

10
import '../artifacts.dart';
11
import '../base/common.dart';
12
import '../base/file_system.dart';
13
import '../base/logger.dart';
14
import '../base/platform.dart';
15
import '../base/terminal.dart';
16 17 18 19
import '../dart/analysis.dart';
import 'analyze_base.dart';

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

43
  /// The working directory for testing analysis using dartanalyzer.
44
  final Directory? workingDirectory;
45 46

  @override
47
  Future<void> analyze() async {
48
    final String currentDirectory =
49
        (workingDirectory ?? fileSystem.currentDirectory).path;
50

51 52
    // find directories or files from argResults.rest
    final Set<String> items = Set<String>.of(argResults.rest
53
        .map<String>((String path) => fileSystem.path.canonicalize(path)));
54 55 56
    if (items.isNotEmpty) {
      for (final String item in items) {
        final FileSystemEntityType type = fileSystem.typeSync(item);
57

58
        if (type == FileSystemEntityType.notFound) {
59
          throwToolExit("'$item' does not exist");
60 61 62 63
        }
      }
    }

64
    if (isFlutterRepo) {
65
      // check for conflicting dependencies
66
      final PackageDependencyTracker dependencies = PackageDependencyTracker();
67
      dependencies.checkForConflictingDependencies(repoPackages, dependencies);
68
      items.addAll(repoRoots);
69
      if (argResults.wasParsed('current-package') && (argResults['current-package'] as bool)) {
70
        items.add(currentDirectory);
71
      }
72
    } else {
73 74
      if ((argResults['current-package'] as bool) && items.isEmpty) {
        items.add(currentDirectory);
75
      }
76 77
    }

78
    if (items.isEmpty) {
79
      throwToolExit('Nothing to analyze.', exitCode: 0);
80
    }
81

82
    final Completer<void> analysisCompleter = Completer<void>();
83
    final List<AnalysisError> errors = <AnalysisError>[];
84

85
    final AnalysisServer server = AnalysisServer(
86
      sdkPath,
87
      items.toList(),
88 89 90 91 92
      fileSystem: fileSystem,
      platform: platform,
      logger: logger,
      processManager: processManager,
      terminal: terminal,
93
      protocolTrafficLog: protocolTrafficLog,
94
    );
95

96 97
    Stopwatch? timer;
    Status? progress;
98
    try {
99
      StreamSubscription<bool>? subscription;
100 101

      void handleAnalysisStatus(bool isAnalyzing) {
102 103 104 105 106
        if (!isAnalyzing) {
          analysisCompleter.complete();
          subscription?.cancel();
          subscription = null;
        }
107 108 109 110 111 112 113 114 115 116 117
      }

      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);
118 119 120

      await server.start();
      // Completing the future in the callback can't fail.
121
      unawaited(server.onExit.then<void>((int? exitCode) {
122
        if (!analysisCompleter.isCompleted) {
123 124 125 126 127 128
          analysisCompleter.completeError(
            // Include the last 20 lines of server output in exception message
            Exception(
              'analysis server exited with code $exitCode and output:\n${server.getLogs(20)}',
            ),
          );
129 130 131 132 133
        }
      }));

      // collect results
      timer = Stopwatch()..start();
134 135 136
      final String message = items.length > 1
          ? '${items.length} ${items.length == 1 ? 'item' : 'items'}'
          : fileSystem.path.basename(items.first);
137
      progress = argResults['preamble'] == true
138 139 140 141 142 143 144 145 146 147 148
          ? logger.startProgress(
            'Analyzing $message...',
          )
          : null;

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

    // emit benchmarks
151
    if (isBenchmarking) {
152
      writeBenchmark(timer, errors.length);
153
    }
154

155 156
    // --write
    dumpErrors(errors.map<String>((AnalysisError error) => error.toLegacyString()));
157

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

167
    final int errorCount = errors.length;
168
    final String seconds = (timer.elapsedMilliseconds / 1000.0).toStringAsFixed(1);
169 170 171 172
    final String errorsMessage = AnalyzeBase.generateErrorsMessage(
      issueCount: errorCount,
      seconds: seconds,
    );
173

174
    if (errorCount > 0) {
175
      logger.printStatus('');
176
      throwToolExit(errorsMessage, exitCode: _isFatal(errors) ? 1 : 0);
177
    }
178

179 180
    if (argResults['congratulate'] as bool) {
      logger.printStatus(errorsMessage);
181 182
    }

183 184
    if (server.didServerErrorOccur) {
      throwToolExit('Server error(s) occurred. (ran in ${seconds}s)');
185
    }
186
  }
187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203

  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;
  }
204
}