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

import 'dart:async';

7
import 'package:args/args.dart';
8 9 10
import 'package:meta/meta.dart';
import 'package:platform/platform.dart';
import 'package:process/process.dart';
11

12
import '../base/common.dart';
13
import '../base/file_system.dart';
14
import '../base/io.dart';
15
import '../base/logger.dart';
16
import '../base/terminal.dart';
17 18
import '../base/utils.dart';
import '../cache.dart';
19
import '../dart/analysis.dart';
20
import '../dart/sdk.dart' as sdk;
21
import 'analyze_base.dart';
22

23
class AnalyzeContinuously extends AnalyzeBase {
24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39
  AnalyzeContinuously(ArgResults argResults, List<String> repoRoots, List<Directory> repoPackages, {
    @required FileSystem fileSystem,
    @required Logger logger,
    @required AnsiTerminal terminal,
    @required Platform platform,
    @required ProcessManager processManager,
  }) : super(
        argResults,
        repoPackages: repoPackages,
        repoRoots: repoRoots,
        fileSystem: fileSystem,
        logger: logger,
        platform: platform,
        terminal: terminal,
        processManager: processManager,
      );
40

41 42
  String analysisTarget;
  bool firstAnalysis = true;
43
  Set<String> analyzedPaths = <String>{};
44 45 46 47 48 49
  Map<String, List<AnalysisError>> analysisErrors = <String, List<AnalysisError>>{};
  Stopwatch analysisTimer;
  int lastErrorCount = 0;
  Status analysisStatus;

  @override
50
  Future<void> analyze() async {
51 52
    List<String> directories;

53
    if (argResults['flutter-repo'] as bool) {
54
      final PackageDependencyTracker dependencies = PackageDependencyTracker();
55
      dependencies.checkForConflictingDependencies(repoPackages, dependencies);
56 57

      directories = repoRoots;
58
      analysisTarget = 'Flutter repository';
59

60
      logger.printTrace('Analyzing Flutter repository:');
61
      for (final String projectPath in repoRoots) {
62
        logger.printTrace('  ${fileSystem.path.relative(projectPath)}');
63
      }
64
    } else {
65 66
      directories = <String>[fileSystem.currentDirectory.path];
      analysisTarget = fileSystem.currentDirectory.path;
67 68
    }

69
    final String sdkPath = argResults['dart-sdk'] as String ?? sdk.dartSdkPath;
70

71 72 73 74 75 76 77
    final AnalysisServer server = AnalysisServer(sdkPath, directories,
      fileSystem: fileSystem,
      logger: logger,
      platform: platform,
      processManager: processManager,
      terminal: terminal,
    );
78 79 80 81 82 83 84 85
    server.onAnalyzing.listen((bool isAnalyzing) => _handleAnalysisStatus(server, isAnalyzing));
    server.onErrors.listen(_handleAnalysisErrors);

    Cache.releaseLockEarly();

    await server.start();
    final int exitCode = await server.onExit;

86
    final String message = 'Analysis server exited with code $exitCode.';
87
    if (exitCode != 0) {
88
      throwToolExit(message, exitCode: exitCode);
89
    }
90
    logger.printStatus(message);
91

92
    if (server.didServerErrorOccur) {
93
      throwToolExit('Server error(s) occurred.');
94
    }
95 96 97 98 99
  }

  void _handleAnalysisStatus(AnalysisServer server, bool isAnalyzing) {
    if (isAnalyzing) {
      analysisStatus?.cancel();
100
      if (!firstAnalysis) {
101
        logger.printStatus('\n');
102
      }
103
      analysisStatus = logger.startProgress('Analyzing $analysisTarget...', timeout: timeoutConfiguration.slowOperation);
104
      analyzedPaths.clear();
105
      analysisTimer = Stopwatch()..start();
106
    } else {
Devon Carew's avatar
Devon Carew committed
107
      analysisStatus?.stop();
108
      analysisStatus = null;
109 110
      analysisTimer.stop();

111
      logger.printStatus(terminal.clearScreen(), newline: false);
112 113

      // Remove errors for deleted files, sort, and print errors.
114
      final List<AnalysisError> errors = <AnalysisError>[];
115
      for (final String path in analysisErrors.keys.toList()) {
116
        if (fileSystem.isFileSync(path)) {
117
          errors.addAll(analysisErrors[path]);
118 119 120 121 122
        } else {
          analysisErrors.remove(path);
        }
      }

123 124 125 126 127 128
      int issueCount = errors.length;

      // count missing dartdocs
      final int undocumentedMembers = errors.where((AnalysisError error) {
        return error.code == 'public_member_api_docs';
      }).length;
129
      if (!(argResults['dartdocs'] as bool)) {
130 131 132 133
        errors.removeWhere((AnalysisError error) => error.code == 'public_member_api_docs');
        issueCount -= undocumentedMembers;
      }

134
      errors.sort();
135

136
      for (final AnalysisError error in errors) {
137
        logger.printStatus(error.toString());
138
        if (error.code != null) {
139
          logger.printTrace('error code: ${error.code}');
140
        }
141 142
      }

143
      dumpErrors(errors.map<String>((AnalysisError error) => error.toLegacyString()));
144 145 146

      // Print an analysis summary.
      String errorsMessage;
147
      final int issueDiff = issueCount - lastErrorCount;
148 149
      lastErrorCount = issueCount;

150
      if (firstAnalysis) {
151
        errorsMessage = '$issueCount ${pluralize('issue', issueCount)} found';
152
      } else if (issueDiff > 0) {
153
        errorsMessage = '$issueCount ${pluralize('issue', issueCount)} found ($issueDiff new)';
154
      } else if (issueDiff < 0) {
155
        errorsMessage = '$issueCount ${pluralize('issue', issueCount)} found (${-issueDiff} fixed)';
156
      } else if (issueCount != 0) {
157
        errorsMessage = '$issueCount ${pluralize('issue', issueCount)} found';
158
      } else {
159
        errorsMessage = 'no issues found';
160
      }
161

162 163 164 165 166 167 168
      String dartdocMessage;
      if (undocumentedMembers == 1) {
        dartdocMessage = 'one public member lacks documentation';
      } else {
        dartdocMessage = '$undocumentedMembers public members lack documentation';
      }

169 170
      final String files = '${analyzedPaths.length} ${pluralize('file', analyzedPaths.length)}';
      final String seconds = (analysisTimer.elapsedMilliseconds / 1000.0).toStringAsFixed(2);
171
      if (undocumentedMembers > 0) {
172
        logger.printStatus('$errorsMessage$dartdocMessage • analyzed $files in $seconds seconds');
173
      } else {
174
        logger.printStatus('$errorsMessage • analyzed $files in $seconds seconds');
175
      }
176 177

      if (firstAnalysis && isBenchmarking) {
178
        writeBenchmark(analysisTimer, issueCount, undocumentedMembers);
179
        server.dispose().whenComplete(() { exit(issueCount > 0 ? 1 : 0); });
180 181 182 183 184 185 186
      }

      firstAnalysis = false;
    }
  }

  void _handleAnalysisErrors(FileAnalysisErrors fileErrors) {
187
    fileErrors.errors.removeWhere((AnalysisError error) => error.type == 'TODO');
188 189 190 191 192

    analyzedPaths.add(fileErrors.file);
    analysisErrors[fileErrors.file] = fileErrors.errors;
  }
}