analyze_continuously.dart 5.7 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
import '../base/common.dart';
10
import '../base/file_system.dart';
11
import '../base/io.dart';
12
import '../base/logger.dart';
13
import '../base/terminal.dart';
14 15
import '../base/utils.dart';
import '../cache.dart';
16
import '../dart/analysis.dart';
17
import '../dart/sdk.dart' as sdk;
18
import '../globals.dart';
19
import 'analyze_base.dart';
20

21
class AnalyzeContinuously extends AnalyzeBase {
22
  AnalyzeContinuously(ArgResults argResults, this.repoRoots, this.repoPackages) : super(argResults);
23

24
  final List<String> repoRoots;
25
  final List<Directory> repoPackages;
26

27 28
  String analysisTarget;
  bool firstAnalysis = true;
29
  Set<String> analyzedPaths = <String>{};
30 31 32 33 34 35
  Map<String, List<AnalysisError>> analysisErrors = <String, List<AnalysisError>>{};
  Stopwatch analysisTimer;
  int lastErrorCount = 0;
  Status analysisStatus;

  @override
36
  Future<void> analyze() async {
37 38
    List<String> directories;

39
    if (argResults['flutter-repo'] as bool) {
40
      final PackageDependencyTracker dependencies = PackageDependencyTracker();
41
      dependencies.checkForConflictingDependencies(repoPackages, dependencies);
42 43

      directories = repoRoots;
44
      analysisTarget = 'Flutter repository';
45

46
      printTrace('Analyzing Flutter repository:');
47
      for (String projectPath in repoRoots) {
48
        printTrace('  ${fs.path.relative(projectPath)}');
49
      }
50
    } else {
51 52
      directories = <String>[fs.currentDirectory.path];
      analysisTarget = fs.currentDirectory.path;
53 54
    }

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

57
    final AnalysisServer server = AnalysisServer(sdkPath, directories);
58 59 60 61 62 63 64 65
    server.onAnalyzing.listen((bool isAnalyzing) => _handleAnalysisStatus(server, isAnalyzing));
    server.onErrors.listen(_handleAnalysisErrors);

    Cache.releaseLockEarly();

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

66
    final String message = 'Analysis server exited with code $exitCode.';
67
    if (exitCode != 0) {
68
      throwToolExit(message, exitCode: exitCode);
69
    }
70
    printStatus(message);
71

72
    if (server.didServerErrorOccur) {
73
      throwToolExit('Server error(s) occurred.');
74
    }
75 76 77 78 79
  }

  void _handleAnalysisStatus(AnalysisServer server, bool isAnalyzing) {
    if (isAnalyzing) {
      analysisStatus?.cancel();
80
      if (!firstAnalysis) {
81
        printStatus('\n');
82
      }
83
      analysisStatus = logger.startProgress('Analyzing $analysisTarget...', timeout: timeoutConfiguration.slowOperation);
84
      analyzedPaths.clear();
85
      analysisTimer = Stopwatch()..start();
86
    } else {
Devon Carew's avatar
Devon Carew committed
87
      analysisStatus?.stop();
88
      analysisStatus = null;
89 90 91 92 93
      analysisTimer.stop();

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

      // Remove errors for deleted files, sort, and print errors.
94
      final List<AnalysisError> errors = <AnalysisError>[];
95
      for (String path in analysisErrors.keys.toList()) {
96
        if (fs.isFileSync(path)) {
97
          errors.addAll(analysisErrors[path]);
98 99 100 101 102
        } else {
          analysisErrors.remove(path);
        }
      }

103 104 105 106 107 108
      int issueCount = errors.length;

      // count missing dartdocs
      final int undocumentedMembers = errors.where((AnalysisError error) {
        return error.code == 'public_member_api_docs';
      }).length;
109
      if (!(argResults['dartdocs'] as bool)) {
110 111 112 113
        errors.removeWhere((AnalysisError error) => error.code == 'public_member_api_docs');
        issueCount -= undocumentedMembers;
      }

114
      errors.sort();
115

116
      for (AnalysisError error in errors) {
117
        printStatus(error.toString());
118
        if (error.code != null) {
119
          printTrace('error code: ${error.code}');
120
        }
121 122
      }

123
      dumpErrors(errors.map<String>((AnalysisError error) => error.toLegacyString()));
124 125 126

      // Print an analysis summary.
      String errorsMessage;
127
      final int issueDiff = issueCount - lastErrorCount;
128 129
      lastErrorCount = issueCount;

130
      if (firstAnalysis) {
131
        errorsMessage = '$issueCount ${pluralize('issue', issueCount)} found';
132
      } else if (issueDiff > 0) {
133
        errorsMessage = '$issueCount ${pluralize('issue', issueCount)} found ($issueDiff new)';
134
      } else if (issueDiff < 0) {
135
        errorsMessage = '$issueCount ${pluralize('issue', issueCount)} found (${-issueDiff} fixed)';
136
      } else if (issueCount != 0) {
137
        errorsMessage = '$issueCount ${pluralize('issue', issueCount)} found';
138
      } else {
139
        errorsMessage = 'no issues found';
140
      }
141

142 143 144 145 146 147 148
      String dartdocMessage;
      if (undocumentedMembers == 1) {
        dartdocMessage = 'one public member lacks documentation';
      } else {
        dartdocMessage = '$undocumentedMembers public members lack documentation';
      }

149 150
      final String files = '${analyzedPaths.length} ${pluralize('file', analyzedPaths.length)}';
      final String seconds = (analysisTimer.elapsedMilliseconds / 1000.0).toStringAsFixed(2);
151 152 153 154 155
      if (undocumentedMembers > 0) {
        printStatus('$errorsMessage$dartdocMessage • analyzed $files in $seconds seconds');
      } else {
        printStatus('$errorsMessage • analyzed $files in $seconds seconds');
      }
156 157

      if (firstAnalysis && isBenchmarking) {
158
        writeBenchmark(analysisTimer, issueCount, undocumentedMembers);
159
        server.dispose().whenComplete(() { exit(issueCount > 0 ? 1 : 0); });
160 161 162 163 164 165 166
      }

      firstAnalysis = false;
    }
  }

  void _handleAnalysisErrors(FileAnalysisErrors fileErrors) {
167
    fileErrors.errors.removeWhere((AnalysisError error) => error.type == 'TODO');
168 169 170 171 172

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