analyze_once.dart 5.61 KB
Newer Older
1 2 3 4 5 6 7 8
// Copyright 2015 The Chromium Authors. 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 'package:args/args.dart';

9
import '../base/common.dart';
10
import '../base/file_system.dart';
11
import '../base/logger.dart';
12
import '../base/utils.dart';
13 14
import '../cache.dart';
import '../dart/analysis.dart';
15
import '../dart/sdk.dart' as sdk;
16 17 18 19 20 21
import '../globals.dart';
import 'analyze.dart';
import 'analyze_base.dart';

/// An aspect of the [AnalyzeCommand] to perform once time analysis.
class AnalyzeOnce extends AnalyzeBase {
22 23 24 25
  AnalyzeOnce(
    ArgResults argResults,
    this.repoRoots,
    this.repoPackages, {
26 27
    this.workingDirectory,
  }) : super(argResults);
28

29
  final List<String> repoRoots;
30 31
  final List<Directory> repoPackages;

32
  /// The working directory for testing analysis using dartanalyzer.
33
  final Directory workingDirectory;
34 35

  @override
36
  Future<void> analyze() async {
37 38 39 40
    final String currentDirectory =
        (workingDirectory ?? fs.currentDirectory).path;

    // find directories from argResults.rest
41
    final Set<String> directories = Set<String>.from(argResults.rest
42 43 44 45 46
        .map<String>((String path) => fs.path.canonicalize(path)));
    if (directories.isNotEmpty) {
      for (String directory in directories) {
        final FileSystemEntityType type = fs.typeSync(directory);

47
        if (type == FileSystemEntityType.notFound) {
48
          throwToolExit("'$directory' does not exist");
49
        } else if (type != FileSystemEntityType.directory) {
50
          throwToolExit("'$directory' is not a directory");
51 52 53 54
        }
      }
    }

55 56
    if (argResults['flutter-repo']) {
      // check for conflicting dependencies
57
      final PackageDependencyTracker dependencies = PackageDependencyTracker();
58 59
      dependencies.checkForConflictingDependencies(repoPackages, dependencies);
      directories.addAll(repoRoots);
60
      if (argResults.wasParsed('current-package') && argResults['current-package'])
61 62
        directories.add(currentDirectory);
    } else {
63
      if (argResults['current-package'])
64
        directories.add(currentDirectory);
65 66
    }

67
    if (directories.isEmpty)
68
      throwToolExit('Nothing to analyze.', exitCode: 0);
69

70
    // analyze all
71
    final Completer<void> analysisCompleter = Completer<void>();
72
    final List<AnalysisError> errors = <AnalysisError>[];
73

74
    final String sdkPath = argResults['dart-sdk'] ?? sdk.dartSdkPath;
75

76
    final AnalysisServer server = AnalysisServer(
77 78 79
      sdkPath,
      directories.toList(),
    );
80 81 82 83 84 85 86

    StreamSubscription<bool> subscription;
    subscription = server.onAnalyzing.listen((bool isAnalyzing) {
      if (!isAnalyzing) {
        analysisCompleter.complete();
        subscription?.cancel();
        subscription = null;
87
      }
88 89
    });
    server.onErrors.listen((FileAnalysisErrors fileErrors) {
90 91
      // Record the issues found (but filter out to do comments).
      errors.addAll(fileErrors.errors.where((AnalysisError error) => error.type != 'TODO'));
92 93 94
    });

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

102
    Cache.releaseLockEarly();
103

104
    // collect results
105
    final Stopwatch timer = Stopwatch()..start();
106 107 108 109
    final String message = directories.length > 1
        ? '${directories.length} ${directories.length == 1 ? 'directory' : 'directories'}'
        : fs.path.basename(directories.first);
    final Status progress = argResults['preamble']
110
        ? logger.startProgress('Analyzing $message...', timeout: timeoutConfiguration.slowOperation)
111 112 113 114 115 116
        : null;

    await analysisCompleter.future;
    progress?.cancel();
    timer.stop();

117 118 119 120 121 122
    // count missing dartdocs
    final int undocumentedMembers = errors.where((AnalysisError error) {
      return error.code == 'public_member_api_docs';
    }).length;
    if (!argResults['dartdocs'])
      errors.removeWhere((AnalysisError error) => error.code == 'public_member_api_docs');
123

124
    // emit benchmarks
125
    if (isBenchmarking)
126
      writeBenchmark(timer, errors.length, undocumentedMembers);
127

128 129
    // --write
    dumpErrors(errors.map<String>((AnalysisError error) => error.toLegacyString()));
130

131 132
    // report errors
    if (errors.isNotEmpty && argResults['preamble'])
133 134
      printStatus('');
    errors.sort();
135
    for (AnalysisError error in errors)
136
      printStatus(error.toString(), hangingIndent: 7);
137

138 139 140 141 142 143 144 145
    final String seconds = (timer.elapsedMilliseconds / 1000.0).toStringAsFixed(1);

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

    // We consider any level of error to be an error exit (we don't report different levels).
    if (errors.isNotEmpty) {
149
      final int errorCount = errors.length;
150 151
      printStatus('');
      if (undocumentedMembers > 0) {
152
        throwToolExit('$errorCount ${pluralize('issue', errorCount)} found. (ran in ${seconds}s; $dartdocMessage)');
153
      } else {
154
        throwToolExit('$errorCount ${pluralize('issue', errorCount)} found. (ran in ${seconds}s)');
155 156
      }
    }
157

158 159 160 161
    if (server.didServerErrorOccur) {
      throwToolExit('Server error(s) occurred. (ran in ${seconds}s)');
    }

162 163
    if (argResults['congratulate']) {
      if (undocumentedMembers > 0) {
164
        printStatus('No issues found! (ran in ${seconds}s; $dartdocMessage)');
165 166 167 168
      } else {
        printStatus('No issues found! (ran in ${seconds}s)');
      }
    }
169 170
  }
}