Unverified Commit d2f889b4 authored by Jonah Williams's avatar Jonah Williams Committed by GitHub

[flutter_tools] Reduce context usage in analyze command and tests (#49589)

parent bafa03e5
...@@ -67,7 +67,14 @@ Future<void> main(List<String> args) async { ...@@ -67,7 +67,14 @@ Future<void> main(List<String> args) async {
final bool verboseHelp = help && verbose; final bool verboseHelp = help && verbose;
await runner.run(args, <FlutterCommand>[ await runner.run(args, <FlutterCommand>[
AnalyzeCommand(verboseHelp: verboseHelp), AnalyzeCommand(
verboseHelp: verboseHelp,
fileSystem: globals.fs,
platform: globals.platform,
processManager: globals.processManager,
logger: globals.logger,
terminal: globals.terminal,
),
AssembleCommand(), AssembleCommand(),
AttachCommand(verboseHelp: verboseHelp), AttachCommand(verboseHelp: verboseHelp),
BuildCommand(verboseHelp: verboseHelp), BuildCommand(verboseHelp: verboseHelp),
......
...@@ -4,14 +4,32 @@ ...@@ -4,14 +4,32 @@
import 'dart:async'; import 'dart:async';
import 'package:meta/meta.dart';
import 'package:platform/platform.dart';
import 'package:process/process.dart';
import '../base/file_system.dart'; import '../base/file_system.dart';
import '../base/logger.dart';
import '../base/terminal.dart';
import '../globals.dart' as globals; import '../globals.dart' as globals;
import '../runner/flutter_command.dart'; import '../runner/flutter_command.dart';
import 'analyze_continuously.dart'; import 'analyze_continuously.dart';
import 'analyze_once.dart'; import 'analyze_once.dart';
class AnalyzeCommand extends FlutterCommand { class AnalyzeCommand extends FlutterCommand {
AnalyzeCommand({bool verboseHelp = false, this.workingDirectory}) { AnalyzeCommand({
bool verboseHelp = false,
this.workingDirectory,
@required FileSystem fileSystem,
@required Platform platform,
@required AnsiTerminal terminal,
@required Logger logger,
@required ProcessManager processManager,
}) : _fileSystem = fileSystem,
_processManager = processManager,
_logger = logger,
_terminal = terminal,
_platform = platform {
argParser.addFlag('flutter-repo', argParser.addFlag('flutter-repo',
negatable: false, negatable: false,
help: 'Include all the examples and tests from the Flutter repository.', help: 'Include all the examples and tests from the Flutter repository.',
...@@ -59,6 +77,12 @@ class AnalyzeCommand extends FlutterCommand { ...@@ -59,6 +77,12 @@ class AnalyzeCommand extends FlutterCommand {
/// The working directory for testing analysis using dartanalyzer. /// The working directory for testing analysis using dartanalyzer.
final Directory workingDirectory; final Directory workingDirectory;
final FileSystem _fileSystem;
final Logger _logger;
final AnsiTerminal _terminal;
final ProcessManager _processManager;
final Platform _platform;
@override @override
String get name => 'analyze'; String get name => 'analyze';
...@@ -73,7 +97,7 @@ class AnalyzeCommand extends FlutterCommand { ...@@ -73,7 +97,7 @@ class AnalyzeCommand extends FlutterCommand {
} }
// Or we're not in a project directory. // Or we're not in a project directory.
if (!globals.fs.file('pubspec.yaml').existsSync()) { if (!_fileSystem.file('pubspec.yaml').existsSync()) {
return false; return false;
} }
...@@ -87,6 +111,13 @@ class AnalyzeCommand extends FlutterCommand { ...@@ -87,6 +111,13 @@ class AnalyzeCommand extends FlutterCommand {
argResults, argResults,
runner.getRepoRoots(), runner.getRepoRoots(),
runner.getRepoPackages(), runner.getRepoPackages(),
fileSystem: _fileSystem,
// TODO(jonahwilliams): determine a better way to inject the logger,
// since it is constructed on-demand.
logger: _logger ?? globals.logger,
platform: _platform,
processManager: _processManager,
terminal: _terminal,
).analyze(); ).analyze();
} else { } else {
await AnalyzeOnce( await AnalyzeOnce(
...@@ -94,6 +125,13 @@ class AnalyzeCommand extends FlutterCommand { ...@@ -94,6 +125,13 @@ class AnalyzeCommand extends FlutterCommand {
runner.getRepoRoots(), runner.getRepoRoots(),
runner.getRepoPackages(), runner.getRepoPackages(),
workingDirectory: workingDirectory, workingDirectory: workingDirectory,
fileSystem: _fileSystem,
// TODO(jonahwilliams): determine a better way to inject the logger,
// since it is constructed on-demand.
logger: _logger ?? globals.logger,
platform: _platform,
processManager: _processManager,
terminal: _terminal,
).analyze(); ).analyze();
} }
return FlutterCommandResult.success(); return FlutterCommandResult.success();
......
...@@ -5,20 +5,47 @@ ...@@ -5,20 +5,47 @@
import 'dart:async'; import 'dart:async';
import 'package:args/args.dart'; import 'package:args/args.dart';
import 'package:meta/meta.dart';
import 'package:platform/platform.dart';
import 'package:process/process.dart';
import 'package:yaml/yaml.dart' as yaml; import 'package:yaml/yaml.dart' as yaml;
import '../base/common.dart'; import '../base/common.dart';
import '../base/file_system.dart'; import '../base/file_system.dart';
import '../base/logger.dart';
import '../base/terminal.dart';
import '../base/utils.dart'; import '../base/utils.dart';
import '../cache.dart'; import '../cache.dart';
import '../globals.dart' as globals; import '../globals.dart' as globals;
/// Common behavior for `flutter analyze` and `flutter analyze --watch` /// Common behavior for `flutter analyze` and `flutter analyze --watch`
abstract class AnalyzeBase { abstract class AnalyzeBase {
AnalyzeBase(this.argResults); AnalyzeBase(this.argResults, {
@required this.repoRoots,
@required this.repoPackages,
@required this.fileSystem,
@required this.logger,
@required this.platform,
@required this.processManager,
@required this.terminal,
});
/// The parsed argument results for execution. /// The parsed argument results for execution.
final ArgResults argResults; final ArgResults argResults;
@protected
final List<String> repoRoots;
@protected
final List<Directory> repoPackages;
@protected
final FileSystem fileSystem;
@protected
final Logger logger;
@protected
final ProcessManager processManager;
@protected
final Platform platform;
@protected
final AnsiTerminal terminal;
/// Called by [AnalyzeCommand] to start the analysis process. /// Called by [AnalyzeCommand] to start the analysis process.
Future<void> analyze(); Future<void> analyze();
...@@ -26,7 +53,7 @@ abstract class AnalyzeBase { ...@@ -26,7 +53,7 @@ abstract class AnalyzeBase {
void dumpErrors(Iterable<String> errors) { void dumpErrors(Iterable<String> errors) {
if (argResults['write'] != null) { if (argResults['write'] != null) {
try { try {
final RandomAccessFile resultsFile = globals.fs.file(argResults['write']).openSync(mode: FileMode.write); final RandomAccessFile resultsFile = fileSystem.file(argResults['write']).openSync(mode: FileMode.write);
try { try {
resultsFile.lockSync(); resultsFile.lockSync();
resultsFile.writeStringSync(errors.join('\n')); resultsFile.writeStringSync(errors.join('\n'));
...@@ -34,7 +61,7 @@ abstract class AnalyzeBase { ...@@ -34,7 +61,7 @@ abstract class AnalyzeBase {
resultsFile.close(); resultsFile.close();
} }
} catch (e) { } catch (e) {
globals.printError('Failed to save output to "${argResults['write']}": $e'); logger.printError('Failed to save output to "${argResults['write']}": $e');
} }
} }
} }
...@@ -46,30 +73,13 @@ abstract class AnalyzeBase { ...@@ -46,30 +73,13 @@ abstract class AnalyzeBase {
'issues': errorCount, 'issues': errorCount,
'missingDartDocs': membersMissingDocumentation, 'missingDartDocs': membersMissingDocumentation,
}; };
globals.fs.file(benchmarkOut).writeAsStringSync(toPrettyJson(data)); fileSystem.file(benchmarkOut).writeAsStringSync(toPrettyJson(data));
globals.printStatus('Analysis benchmark written to $benchmarkOut ($data).'); logger.printStatus('Analysis benchmark written to $benchmarkOut ($data).');
} }
bool get isBenchmarking => argResults['benchmark'] as bool; bool get isBenchmarking => argResults['benchmark'] as bool;
} }
/// Return true if [fileList] contains a path that resides inside the Flutter repository.
/// If [fileList] is empty, then return true if the current directory resides inside the Flutter repository.
bool inRepo(List<String> fileList) {
if (fileList == null || fileList.isEmpty) {
fileList = <String>[globals.fs.path.current];
}
final String root = globals.fs.path.normalize(globals.fs.path.absolute(Cache.flutterRoot));
final String prefix = root + globals.fs.path.separator;
for (String file in fileList) {
file = globals.fs.path.normalize(globals.fs.path.absolute(file));
if (file == root || file.startsWith(prefix)) {
return true;
}
}
return false;
}
class PackageDependency { class PackageDependency {
// This is a map from dependency targets (lib directories) to a list // This is a map from dependency targets (lib directories) to a list
// of places that ask for that target (.packages or pubspec.yaml files) // of places that ask for that target (.packages or pubspec.yaml files)
......
...@@ -5,23 +5,38 @@ ...@@ -5,23 +5,38 @@
import 'dart:async'; import 'dart:async';
import 'package:args/args.dart'; import 'package:args/args.dart';
import 'package:meta/meta.dart';
import 'package:platform/platform.dart';
import 'package:process/process.dart';
import '../base/common.dart'; import '../base/common.dart';
import '../base/file_system.dart'; import '../base/file_system.dart';
import '../base/io.dart'; import '../base/io.dart';
import '../base/logger.dart'; import '../base/logger.dart';
import '../base/terminal.dart';
import '../base/utils.dart'; import '../base/utils.dart';
import '../cache.dart'; import '../cache.dart';
import '../dart/analysis.dart'; import '../dart/analysis.dart';
import '../dart/sdk.dart' as sdk; import '../dart/sdk.dart' as sdk;
import '../globals.dart' as globals;
import 'analyze_base.dart'; import 'analyze_base.dart';
class AnalyzeContinuously extends AnalyzeBase { class AnalyzeContinuously extends AnalyzeBase {
AnalyzeContinuously(ArgResults argResults, this.repoRoots, this.repoPackages) : super(argResults); AnalyzeContinuously(ArgResults argResults, List<String> repoRoots, List<Directory> repoPackages, {
@required FileSystem fileSystem,
final List<String> repoRoots; @required Logger logger,
final List<Directory> repoPackages; @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,
);
String analysisTarget; String analysisTarget;
bool firstAnalysis = true; bool firstAnalysis = true;
...@@ -42,18 +57,24 @@ class AnalyzeContinuously extends AnalyzeBase { ...@@ -42,18 +57,24 @@ class AnalyzeContinuously extends AnalyzeBase {
directories = repoRoots; directories = repoRoots;
analysisTarget = 'Flutter repository'; analysisTarget = 'Flutter repository';
globals.printTrace('Analyzing Flutter repository:'); logger.printTrace('Analyzing Flutter repository:');
for (final String projectPath in repoRoots) { for (final String projectPath in repoRoots) {
globals.printTrace(' ${globals.fs.path.relative(projectPath)}'); logger.printTrace(' ${fileSystem.path.relative(projectPath)}');
} }
} else { } else {
directories = <String>[globals.fs.currentDirectory.path]; directories = <String>[fileSystem.currentDirectory.path];
analysisTarget = globals.fs.currentDirectory.path; analysisTarget = fileSystem.currentDirectory.path;
} }
final String sdkPath = argResults['dart-sdk'] as String ?? sdk.dartSdkPath; final String sdkPath = argResults['dart-sdk'] as String ?? sdk.dartSdkPath;
final AnalysisServer server = AnalysisServer(sdkPath, directories); final AnalysisServer server = AnalysisServer(sdkPath, directories,
fileSystem: fileSystem,
logger: logger,
platform: platform,
processManager: processManager,
terminal: terminal,
);
server.onAnalyzing.listen((bool isAnalyzing) => _handleAnalysisStatus(server, isAnalyzing)); server.onAnalyzing.listen((bool isAnalyzing) => _handleAnalysisStatus(server, isAnalyzing));
server.onErrors.listen(_handleAnalysisErrors); server.onErrors.listen(_handleAnalysisErrors);
...@@ -66,7 +87,7 @@ class AnalyzeContinuously extends AnalyzeBase { ...@@ -66,7 +87,7 @@ class AnalyzeContinuously extends AnalyzeBase {
if (exitCode != 0) { if (exitCode != 0) {
throwToolExit(message, exitCode: exitCode); throwToolExit(message, exitCode: exitCode);
} }
globals.printStatus(message); logger.printStatus(message);
if (server.didServerErrorOccur) { if (server.didServerErrorOccur) {
throwToolExit('Server error(s) occurred.'); throwToolExit('Server error(s) occurred.');
...@@ -77,9 +98,9 @@ class AnalyzeContinuously extends AnalyzeBase { ...@@ -77,9 +98,9 @@ class AnalyzeContinuously extends AnalyzeBase {
if (isAnalyzing) { if (isAnalyzing) {
analysisStatus?.cancel(); analysisStatus?.cancel();
if (!firstAnalysis) { if (!firstAnalysis) {
globals.printStatus('\n'); logger.printStatus('\n');
} }
analysisStatus = globals.logger.startProgress('Analyzing $analysisTarget...', timeout: timeoutConfiguration.slowOperation); analysisStatus = logger.startProgress('Analyzing $analysisTarget...', timeout: timeoutConfiguration.slowOperation);
analyzedPaths.clear(); analyzedPaths.clear();
analysisTimer = Stopwatch()..start(); analysisTimer = Stopwatch()..start();
} else { } else {
...@@ -87,12 +108,12 @@ class AnalyzeContinuously extends AnalyzeBase { ...@@ -87,12 +108,12 @@ class AnalyzeContinuously extends AnalyzeBase {
analysisStatus = null; analysisStatus = null;
analysisTimer.stop(); analysisTimer.stop();
globals.logger.printStatus(globals.terminal.clearScreen(), newline: false); logger.printStatus(terminal.clearScreen(), newline: false);
// Remove errors for deleted files, sort, and print errors. // Remove errors for deleted files, sort, and print errors.
final List<AnalysisError> errors = <AnalysisError>[]; final List<AnalysisError> errors = <AnalysisError>[];
for (final String path in analysisErrors.keys.toList()) { for (final String path in analysisErrors.keys.toList()) {
if (globals.fs.isFileSync(path)) { if (fileSystem.isFileSync(path)) {
errors.addAll(analysisErrors[path]); errors.addAll(analysisErrors[path]);
} else { } else {
analysisErrors.remove(path); analysisErrors.remove(path);
...@@ -113,9 +134,9 @@ class AnalyzeContinuously extends AnalyzeBase { ...@@ -113,9 +134,9 @@ class AnalyzeContinuously extends AnalyzeBase {
errors.sort(); errors.sort();
for (final AnalysisError error in errors) { for (final AnalysisError error in errors) {
globals.printStatus(error.toString()); logger.printStatus(error.toString());
if (error.code != null) { if (error.code != null) {
globals.printTrace('error code: ${error.code}'); logger.printTrace('error code: ${error.code}');
} }
} }
...@@ -148,9 +169,9 @@ class AnalyzeContinuously extends AnalyzeBase { ...@@ -148,9 +169,9 @@ class AnalyzeContinuously extends AnalyzeBase {
final String files = '${analyzedPaths.length} ${pluralize('file', analyzedPaths.length)}'; final String files = '${analyzedPaths.length} ${pluralize('file', analyzedPaths.length)}';
final String seconds = (analysisTimer.elapsedMilliseconds / 1000.0).toStringAsFixed(2); final String seconds = (analysisTimer.elapsedMilliseconds / 1000.0).toStringAsFixed(2);
if (undocumentedMembers > 0) { if (undocumentedMembers > 0) {
globals.printStatus('$errorsMessage$dartdocMessage • analyzed $files in $seconds seconds'); logger.printStatus('$errorsMessage$dartdocMessage • analyzed $files in $seconds seconds');
} else { } else {
globals.printStatus('$errorsMessage • analyzed $files in $seconds seconds'); logger.printStatus('$errorsMessage • analyzed $files in $seconds seconds');
} }
if (firstAnalysis && isBenchmarking) { if (firstAnalysis && isBenchmarking) {
......
...@@ -5,15 +5,18 @@ ...@@ -5,15 +5,18 @@
import 'dart:async'; import 'dart:async';
import 'package:args/args.dart'; import 'package:args/args.dart';
import 'package:meta/meta.dart';
import 'package:platform/platform.dart';
import 'package:process/process.dart';
import '../base/common.dart'; import '../base/common.dart';
import '../base/file_system.dart'; import '../base/file_system.dart';
import '../base/logger.dart'; import '../base/logger.dart';
import '../base/terminal.dart';
import '../base/utils.dart'; import '../base/utils.dart';
import '../cache.dart'; import '../cache.dart';
import '../dart/analysis.dart'; import '../dart/analysis.dart';
import '../dart/sdk.dart' as sdk; import '../dart/sdk.dart' as sdk;
import '../globals.dart' as globals;
import 'analyze.dart'; import 'analyze.dart';
import 'analyze_base.dart'; import 'analyze_base.dart';
...@@ -21,13 +24,24 @@ import 'analyze_base.dart'; ...@@ -21,13 +24,24 @@ import 'analyze_base.dart';
class AnalyzeOnce extends AnalyzeBase { class AnalyzeOnce extends AnalyzeBase {
AnalyzeOnce( AnalyzeOnce(
ArgResults argResults, ArgResults argResults,
this.repoRoots, List<String> repoRoots,
this.repoPackages, { List<Directory> repoPackages, {
@required FileSystem fileSystem,
@required Logger logger,
@required Platform platform,
@required ProcessManager processManager,
@required AnsiTerminal terminal,
this.workingDirectory, this.workingDirectory,
}) : super(argResults); }) : super(
argResults,
final List<String> repoRoots; repoRoots: repoRoots,
final List<Directory> repoPackages; repoPackages: repoPackages,
fileSystem: fileSystem,
logger: logger,
platform: platform,
processManager: processManager,
terminal: terminal,
);
/// The working directory for testing analysis using dartanalyzer. /// The working directory for testing analysis using dartanalyzer.
final Directory workingDirectory; final Directory workingDirectory;
...@@ -35,14 +49,14 @@ class AnalyzeOnce extends AnalyzeBase { ...@@ -35,14 +49,14 @@ class AnalyzeOnce extends AnalyzeBase {
@override @override
Future<void> analyze() async { Future<void> analyze() async {
final String currentDirectory = final String currentDirectory =
(workingDirectory ?? globals.fs.currentDirectory).path; (workingDirectory ?? fileSystem.currentDirectory).path;
// find directories from argResults.rest // find directories from argResults.rest
final Set<String> directories = Set<String>.from(argResults.rest final Set<String> directories = Set<String>.from(argResults.rest
.map<String>((String path) => globals.fs.path.canonicalize(path))); .map<String>((String path) => fileSystem.path.canonicalize(path)));
if (directories.isNotEmpty) { if (directories.isNotEmpty) {
for (final String directory in directories) { for (final String directory in directories) {
final FileSystemEntityType type = globals.fs.typeSync(directory); final FileSystemEntityType type = fileSystem.typeSync(directory);
if (type == FileSystemEntityType.notFound) { if (type == FileSystemEntityType.notFound) {
throwToolExit("'$directory' does not exist"); throwToolExit("'$directory' does not exist");
...@@ -79,6 +93,11 @@ class AnalyzeOnce extends AnalyzeBase { ...@@ -79,6 +93,11 @@ class AnalyzeOnce extends AnalyzeBase {
final AnalysisServer server = AnalysisServer( final AnalysisServer server = AnalysisServer(
sdkPath, sdkPath,
directories.toList(), directories.toList(),
fileSystem: fileSystem,
platform: platform,
logger: logger,
processManager: processManager,
terminal: terminal,
); );
StreamSubscription<bool> subscription; StreamSubscription<bool> subscription;
...@@ -108,9 +127,9 @@ class AnalyzeOnce extends AnalyzeBase { ...@@ -108,9 +127,9 @@ class AnalyzeOnce extends AnalyzeBase {
final Stopwatch timer = Stopwatch()..start(); final Stopwatch timer = Stopwatch()..start();
final String message = directories.length > 1 final String message = directories.length > 1
? '${directories.length} ${directories.length == 1 ? 'directory' : 'directories'}' ? '${directories.length} ${directories.length == 1 ? 'directory' : 'directories'}'
: globals.fs.path.basename(directories.first); : fileSystem.path.basename(directories.first);
final Status progress = argResults['preamble'] as bool final Status progress = argResults['preamble'] as bool
? globals.logger.startProgress('Analyzing $message...', timeout: timeoutConfiguration.slowOperation) ? logger.startProgress('Analyzing $message...', timeout: timeoutConfiguration.slowOperation)
: null; : null;
await analysisCompleter.future; await analysisCompleter.future;
...@@ -135,11 +154,11 @@ class AnalyzeOnce extends AnalyzeBase { ...@@ -135,11 +154,11 @@ class AnalyzeOnce extends AnalyzeBase {
// report errors // report errors
if (errors.isNotEmpty && (argResults['preamble'] as bool)) { if (errors.isNotEmpty && (argResults['preamble'] as bool)) {
globals.printStatus(''); logger.printStatus('');
} }
errors.sort(); errors.sort();
for (final AnalysisError error in errors) { for (final AnalysisError error in errors) {
globals.printStatus(error.toString(), hangingIndent: 7); logger.printStatus(error.toString(), hangingIndent: 7);
} }
final String seconds = (timer.elapsedMilliseconds / 1000.0).toStringAsFixed(1); final String seconds = (timer.elapsedMilliseconds / 1000.0).toStringAsFixed(1);
...@@ -154,7 +173,7 @@ class AnalyzeOnce extends AnalyzeBase { ...@@ -154,7 +173,7 @@ class AnalyzeOnce extends AnalyzeBase {
// We consider any level of error to be an error exit (we don't report different levels). // We consider any level of error to be an error exit (we don't report different levels).
if (errors.isNotEmpty) { if (errors.isNotEmpty) {
final int errorCount = errors.length; final int errorCount = errors.length;
globals.printStatus(''); logger.printStatus('');
if (undocumentedMembers > 0) { if (undocumentedMembers > 0) {
throwToolExit('$errorCount ${pluralize('issue', errorCount)} found. (ran in ${seconds}s; $dartdocMessage)'); throwToolExit('$errorCount ${pluralize('issue', errorCount)} found. (ran in ${seconds}s; $dartdocMessage)');
} else { } else {
...@@ -168,9 +187,9 @@ class AnalyzeOnce extends AnalyzeBase { ...@@ -168,9 +187,9 @@ class AnalyzeOnce extends AnalyzeBase {
if (argResults['congratulate'] as bool) { if (argResults['congratulate'] as bool) {
if (undocumentedMembers > 0) { if (undocumentedMembers > 0) {
globals.printStatus('No issues found! (ran in ${seconds}s; $dartdocMessage)'); logger.printStatus('No issues found! (ran in ${seconds}s; $dartdocMessage)');
} else { } else {
globals.printStatus('No issues found! (ran in ${seconds}s)'); logger.printStatus('No issues found! (ran in ${seconds}s)');
} }
} }
} }
......
...@@ -5,18 +5,39 @@ ...@@ -5,18 +5,39 @@
import 'dart:async'; import 'dart:async';
import 'dart:math' as math; import 'dart:math' as math;
import 'package:meta/meta.dart';
import 'package:platform/platform.dart';
import 'package:process/process.dart';
import '../base/common.dart'; import '../base/common.dart';
import '../base/file_system.dart';
import '../base/io.dart'; import '../base/io.dart';
import '../base/logger.dart';
import '../base/terminal.dart'; import '../base/terminal.dart';
import '../base/utils.dart'; import '../base/utils.dart';
import '../convert.dart'; import '../convert.dart';
import '../globals.dart' as globals;
/// An interface to the Dart analysis server.
class AnalysisServer { class AnalysisServer {
AnalysisServer(this.sdkPath, this.directories); AnalysisServer(this.sdkPath, this.directories, {
@required FileSystem fileSystem,
@required ProcessManager processManager,
@required Logger logger,
@required Platform platform,
@required AnsiTerminal terminal,
}) : _fileSystem = fileSystem,
_processManager = processManager,
_logger = logger,
_platform = platform,
_terminal = terminal;
final String sdkPath; final String sdkPath;
final List<String> directories; final List<String> directories;
final FileSystem _fileSystem;
final ProcessManager _processManager;
final Logger _logger;
final Platform _platform;
final AnsiTerminal _terminal;
Process _process; Process _process;
final StreamController<bool> _analyzingController = final StreamController<bool> _analyzingController =
...@@ -29,9 +50,9 @@ class AnalysisServer { ...@@ -29,9 +50,9 @@ class AnalysisServer {
Future<void> start() async { Future<void> start() async {
final String snapshot = final String snapshot =
globals.fs.path.join(sdkPath, 'bin/snapshots/analysis_server.dart.snapshot'); _fileSystem.path.join(sdkPath, 'bin/snapshots/analysis_server.dart.snapshot');
final List<String> command = <String>[ final List<String> command = <String>[
globals.fs.path.join(sdkPath, 'bin', 'dart'), _fileSystem.path.join(sdkPath, 'bin', 'dart'),
snapshot, snapshot,
'--disable-server-feature-completion', '--disable-server-feature-completion',
'--disable-server-feature-search', '--disable-server-feature-search',
...@@ -39,14 +60,14 @@ class AnalysisServer { ...@@ -39,14 +60,14 @@ class AnalysisServer {
sdkPath, sdkPath,
]; ];
globals.printTrace('dart ${command.skip(1).join(' ')}'); _logger.printTrace('dart ${command.skip(1).join(' ')}');
_process = await globals.processManager.start(command); _process = await _processManager.start(command);
// This callback hookup can't throw. // This callback hookup can't throw.
unawaited(_process.exitCode.whenComplete(() => _process = null)); unawaited(_process.exitCode.whenComplete(() => _process = null));
final Stream<String> errorStream = final Stream<String> errorStream =
_process.stderr.transform<String>(utf8.decoder).transform<String>(const LineSplitter()); _process.stderr.transform<String>(utf8.decoder).transform<String>(const LineSplitter());
errorStream.listen(globals.printError); errorStream.listen(_logger.printError);
final Stream<String> inStream = final Stream<String> inStream =
_process.stdout.transform<String>(utf8.decoder).transform<String>(const LineSplitter()); _process.stdout.transform<String>(utf8.decoder).transform<String>(const LineSplitter());
...@@ -73,11 +94,11 @@ class AnalysisServer { ...@@ -73,11 +94,11 @@ class AnalysisServer {
'params': params, 'params': params,
}); });
_process.stdin.writeln(message); _process.stdin.writeln(message);
globals.printTrace('==> $message'); _logger.printTrace('==> $message');
} }
void _handleServerResponse(String line) { void _handleServerResponse(String line) {
globals.printTrace('<== $line'); _logger.printTrace('<== $line');
final dynamic response = json.decode(line); final dynamic response = json.decode(line);
...@@ -98,10 +119,10 @@ class AnalysisServer { ...@@ -98,10 +119,10 @@ class AnalysisServer {
} else if (response['error'] != null) { } else if (response['error'] != null) {
// Fields are 'code', 'message', and 'stackTrace'. // Fields are 'code', 'message', and 'stackTrace'.
final Map<String, dynamic> error = castStringKeyedMap(response['error']); final Map<String, dynamic> error = castStringKeyedMap(response['error']);
globals.printError( _logger.printError(
'Error response from the server: ${error['code']} ${error['message']}'); 'Error response from the server: ${error['code']} ${error['message']}');
if (error['stackTrace'] != null) { if (error['stackTrace'] != null) {
globals.printError(error['stackTrace'] as String); _logger.printError(error['stackTrace'] as String);
} }
} }
} }
...@@ -117,9 +138,9 @@ class AnalysisServer { ...@@ -117,9 +138,9 @@ class AnalysisServer {
void _handleServerError(Map<String, dynamic> error) { void _handleServerError(Map<String, dynamic> error) {
// Fields are 'isFatal', 'message', and 'stackTrace'. // Fields are 'isFatal', 'message', and 'stackTrace'.
globals.printError('Error from the analysis server: ${error['message']}'); _logger.printError('Error from the analysis server: ${error['message']}');
if (error['stackTrace'] != null) { if (error['stackTrace'] != null) {
globals.printError(error['stackTrace'] as String); _logger.printError(error['stackTrace'] as String);
} }
_didServerErrorOccur = true; _didServerErrorOccur = true;
} }
...@@ -130,7 +151,13 @@ class AnalysisServer { ...@@ -130,7 +151,13 @@ class AnalysisServer {
final List<dynamic> errorsList = issueInfo['errors'] as List<dynamic>; final List<dynamic> errorsList = issueInfo['errors'] as List<dynamic>;
final List<AnalysisError> errors = errorsList final List<AnalysisError> errors = errorsList
.map<Map<String, dynamic>>(castStringKeyedMap) .map<Map<String, dynamic>>(castStringKeyedMap)
.map<AnalysisError>((Map<String, dynamic> json) => AnalysisError(json)) .map<AnalysisError>((Map<String, dynamic> json) {
return AnalysisError(json,
fileSystem: _fileSystem,
platform: _platform,
terminal: _terminal,
);
})
.toList(); .toList();
if (!_errorsController.isClosed) { if (!_errorsController.isClosed) {
_errorsController.add(FileAnalysisErrors(file, errors)); _errorsController.add(FileAnalysisErrors(file, errors));
...@@ -152,7 +179,17 @@ enum _AnalysisSeverity { ...@@ -152,7 +179,17 @@ enum _AnalysisSeverity {
} }
class AnalysisError implements Comparable<AnalysisError> { class AnalysisError implements Comparable<AnalysisError> {
AnalysisError(this.json); AnalysisError(this.json, {
@required Platform platform,
@required AnsiTerminal terminal,
@required FileSystem fileSystem,
}) : _platform = platform,
_terminal = terminal,
_fileSystem = fileSystem;
final Platform _platform;
final AnsiTerminal _terminal;
final FileSystem _fileSystem;
static final Map<String, _AnalysisSeverity> _severityMap = <String, _AnalysisSeverity>{ static final Map<String, _AnalysisSeverity> _severityMap = <String, _AnalysisSeverity>{
'INFO': _AnalysisSeverity.info, 'INFO': _AnalysisSeverity.info,
...@@ -160,7 +197,7 @@ class AnalysisError implements Comparable<AnalysisError> { ...@@ -160,7 +197,7 @@ class AnalysisError implements Comparable<AnalysisError> {
'ERROR': _AnalysisSeverity.error, 'ERROR': _AnalysisSeverity.error,
}; };
static final String _separator = globals.platform.isWindows ? '-' : '•'; String get _separator => _platform.isWindows ? '-' : '•';
// "severity":"INFO","type":"TODO","location":{ // "severity":"INFO","type":"TODO","location":{
// "file":"/Users/.../lib/test.dart","offset":362,"length":72,"startLine":15,"startColumn":4 // "file":"/Users/.../lib/test.dart","offset":362,"length":72,"startLine":15,"startColumn":4
...@@ -171,9 +208,9 @@ class AnalysisError implements Comparable<AnalysisError> { ...@@ -171,9 +208,9 @@ class AnalysisError implements Comparable<AnalysisError> {
String get colorSeverity { String get colorSeverity {
switch(_severityLevel) { switch(_severityLevel) {
case _AnalysisSeverity.error: case _AnalysisSeverity.error:
return globals.terminal.color(severity, TerminalColor.red); return _terminal.color(severity, TerminalColor.red);
case _AnalysisSeverity.warning: case _AnalysisSeverity.warning:
return globals.terminal.color(severity, TerminalColor.yellow); return _terminal.color(severity, TerminalColor.yellow);
case _AnalysisSeverity.info: case _AnalysisSeverity.info:
case _AnalysisSeverity.none: case _AnalysisSeverity.none:
return severity; return severity;
...@@ -224,7 +261,7 @@ class AnalysisError implements Comparable<AnalysisError> { ...@@ -224,7 +261,7 @@ class AnalysisError implements Comparable<AnalysisError> {
final String padding = ' ' * math.max(0, 7 - severity.length); final String padding = ' ' * math.max(0, 7 - severity.length);
return '$padding${colorSeverity.toLowerCase()} $_separator ' return '$padding${colorSeverity.toLowerCase()} $_separator '
'$messageSentenceFragment $_separator ' '$messageSentenceFragment $_separator '
'${globals.fs.path.relative(file)}:$startLine:$startColumn $_separator ' '${_fileSystem.path.relative(file)}:$startLine:$startColumn $_separator '
'$code'; '$code';
} }
......
...@@ -5,12 +5,15 @@ ...@@ -5,12 +5,15 @@
import 'dart:async'; import 'dart:async';
import 'package:flutter_tools/src/base/file_system.dart'; import 'package:flutter_tools/src/base/file_system.dart';
import 'package:flutter_tools/src/base/os.dart'; import 'package:flutter_tools/src/base/io.dart';
import 'package:flutter_tools/src/base/logger.dart';
import 'package:flutter_tools/src/base/terminal.dart';
import 'package:flutter_tools/src/dart/analysis.dart'; import 'package:flutter_tools/src/dart/analysis.dart';
import 'package:flutter_tools/src/dart/pub.dart'; import 'package:flutter_tools/src/dart/pub.dart';
import 'package:flutter_tools/src/dart/sdk.dart'; import 'package:flutter_tools/src/dart/sdk.dart';
import 'package:flutter_tools/src/runner/flutter_command_runner.dart'; import 'package:flutter_tools/src/runner/flutter_command_runner.dart';
import 'package:flutter_tools/src/globals.dart' as globals; import 'package:platform/platform.dart';
import 'package:process/process.dart';
import '../../src/common.dart'; import '../../src/common.dart';
import '../../src/context.dart'; import '../../src/context.dart';
...@@ -18,10 +21,21 @@ import '../../src/context.dart'; ...@@ -18,10 +21,21 @@ import '../../src/context.dart';
void main() { void main() {
AnalysisServer server; AnalysisServer server;
Directory tempDir; Directory tempDir;
FileSystem fileSystem;
Platform platform;
ProcessManager processManager;
AnsiTerminal terminal;
Logger logger;
setUp(() { setUp(() {
platform = const LocalPlatform();
fileSystem = const LocalFileSystem();
platform = const LocalPlatform();
processManager = const LocalProcessManager();
terminal = AnsiTerminal(platform: platform, stdio: Stdio());
logger = BufferLogger(outputPreferences: OutputPreferences.test(), terminal: terminal);
FlutterCommandRunner.initFlutterRoot(); FlutterCommandRunner.initFlutterRoot();
tempDir = globals.fs.systemTempDirectory.createTempSync('flutter_analysis_test.'); tempDir = fileSystem.systemTempDirectory.createTempSync('flutter_analysis_test.');
}); });
tearDown(() { tearDown(() {
...@@ -29,13 +43,36 @@ void main() { ...@@ -29,13 +43,36 @@ void main() {
return server?.dispose(); return server?.dispose();
}); });
void _createSampleProject(Directory directory, { bool brokenCode = false }) {
final File pubspecFile = fileSystem.file(fileSystem.path.join(directory.path, 'pubspec.yaml'));
pubspecFile.writeAsStringSync('''
name: foo_project
''');
final File dartFile = fileSystem.file(fileSystem.path.join(directory.path, 'lib', 'main.dart'));
dartFile.parent.createSync();
dartFile.writeAsStringSync('''
void main() {
print('hello world');
${brokenCode ? 'prints("hello world");' : ''}
}
''');
}
group('analyze --watch', () { group('analyze --watch', () {
testUsingContext('AnalysisServer success', () async { testUsingContext('AnalysisServer success', () async {
_createSampleProject(tempDir); _createSampleProject(tempDir);
await pub.get(context: PubContext.flutterTests, directory: tempDir.path); await const Pub().get(context: PubContext.flutterTests, directory: tempDir.path);
server = AnalysisServer(dartSdkPath, <String>[tempDir.path]); server = AnalysisServer(dartSdkPath, <String>[tempDir.path],
fileSystem: fileSystem,
platform: platform,
processManager: processManager,
logger: logger,
terminal: terminal,
);
int errorCount = 0; int errorCount = 0;
final Future<bool> onDone = server.onAnalyzing.where((bool analyzing) => analyzing == false).first; final Future<bool> onDone = server.onAnalyzing.where((bool analyzing) => analyzing == false).first;
...@@ -45,18 +82,21 @@ void main() { ...@@ -45,18 +82,21 @@ void main() {
await onDone; await onDone;
expect(errorCount, 0); expect(errorCount, 0);
}, overrides: <Type, Generator>{
OperatingSystemUtils: () => globals.os,
Pub: () => const Pub(),
}); });
}); });
testUsingContext('AnalysisServer errors', () async { testUsingContext('AnalysisServer errors', () async {
_createSampleProject(tempDir, brokenCode: true); _createSampleProject(tempDir, brokenCode: true);
await pub.get(context: PubContext.flutterTests, directory: tempDir.path); await const Pub().get(context: PubContext.flutterTests, directory: tempDir.path);
server = AnalysisServer(dartSdkPath, <String>[tempDir.path]); server = AnalysisServer(dartSdkPath, <String>[tempDir.path],
fileSystem: fileSystem,
platform: platform,
processManager: processManager,
logger: logger,
terminal: terminal,
);
int errorCount = 0; int errorCount = 0;
final Future<bool> onDone = server.onAnalyzing.where((bool analyzing) => analyzing == false).first; final Future<bool> onDone = server.onAnalyzing.where((bool analyzing) => analyzing == false).first;
...@@ -68,15 +108,18 @@ void main() { ...@@ -68,15 +108,18 @@ void main() {
await onDone; await onDone;
expect(errorCount, greaterThan(0)); expect(errorCount, greaterThan(0));
}, overrides: <Type, Generator>{
OperatingSystemUtils: () => globals.os,
Pub: () => const Pub(),
}); });
testUsingContext('Returns no errors when source is error-free', () async { testUsingContext('Returns no errors when source is error-free', () async {
const String contents = "StringBuffer bar = StringBuffer('baz');"; const String contents = "StringBuffer bar = StringBuffer('baz');";
tempDir.childFile('main.dart').writeAsStringSync(contents); tempDir.childFile('main.dart').writeAsStringSync(contents);
server = AnalysisServer(dartSdkPath, <String>[tempDir.path]); server = AnalysisServer(dartSdkPath, <String>[tempDir.path],
fileSystem: fileSystem,
platform: platform,
processManager: processManager,
logger: logger,
terminal: terminal,
);
int errorCount = 0; int errorCount = 0;
final Future<bool> onDone = server.onAnalyzing.where((bool analyzing) => analyzing == false).first; final Future<bool> onDone = server.onAnalyzing.where((bool analyzing) => analyzing == false).first;
...@@ -86,24 +129,5 @@ void main() { ...@@ -86,24 +129,5 @@ void main() {
await server.start(); await server.start();
await onDone; await onDone;
expect(errorCount, 0); expect(errorCount, 0);
}, overrides: <Type, Generator>{
OperatingSystemUtils: () => globals.os,
Pub: () => const Pub(),
}); });
} }
void _createSampleProject(Directory directory, { bool brokenCode = false }) {
final File pubspecFile = globals.fs.file(globals.fs.path.join(directory.path, 'pubspec.yaml'));
pubspecFile.writeAsStringSync('''
name: foo_project
''');
final File dartFile = globals.fs.file(globals.fs.path.join(directory.path, 'lib', 'main.dart'));
dartFile.parent.createSync();
dartFile.writeAsStringSync('''
void main() {
print('hello world');
${brokenCode ? 'prints("hello world");' : ''}
}
''');
}
...@@ -5,7 +5,6 @@ ...@@ -5,7 +5,6 @@
import 'package:file/file.dart'; import 'package:file/file.dart';
import 'package:file/memory.dart'; import 'package:file/memory.dart';
import 'package:flutter_tools/src/cache.dart'; import 'package:flutter_tools/src/cache.dart';
import 'package:flutter_tools/src/commands/analyze_base.dart';
import 'package:flutter_tools/src/globals.dart' as globals; import 'package:flutter_tools/src/globals.dart' as globals;
import '../../src/common.dart'; import '../../src/common.dart';
...@@ -13,6 +12,23 @@ import '../../src/context.dart'; ...@@ -13,6 +12,23 @@ import '../../src/context.dart';
const String _kFlutterRoot = '/data/flutter'; const String _kFlutterRoot = '/data/flutter';
/// Return true if [fileList] contains a path that resides inside the Flutter repository.
/// If [fileList] is empty, then return true if the current directory resides inside the Flutter repository.
bool inRepo(List<String> fileList) {
if (fileList == null || fileList.isEmpty) {
fileList = <String>[globals.fs.path.current];
}
final String root = globals.fs.path.normalize(globals.fs.path.absolute(Cache.flutterRoot));
final String prefix = root + globals.fs.path.separator;
for (String file in fileList) {
file = globals.fs.path.normalize(globals.fs.path.absolute(file));
if (file == root || file.startsWith(prefix)) {
return true;
}
}
return false;
}
void main() { void main() {
FileSystem fs; FileSystem fs;
Directory tempDir; Directory tempDir;
......
// Copyright 2014 The Flutter 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:flutter_tools/src/base/file_system.dart';
import 'package:flutter_tools/src/cache.dart';
import 'package:flutter_tools/src/dart/analysis.dart';
import 'package:flutter_tools/src/dart/pub.dart';
import 'package:flutter_tools/src/dart/sdk.dart';
import 'package:flutter_tools/src/globals.dart' as globals;
import '../../src/common.dart';
import '../../src/context.dart';
void main() {
testSampleProject('ui', 'Window');
testSampleProject('html', 'HttpStatus');
testSampleProject('js', 'allowInterop');
testSampleProject('js_util', 'jsify');
}
void testSampleProject(String lib, String member) {
testUsingContext('contains dart:$lib', () async {
Cache.disableLocking();
final Directory projectDirectory = globals.fs.systemTempDirectory.createTempSync('flutter_sdk_validation_${lib}_test.').absolute;
try {
final File pubspecFile = globals.fs.file(globals.fs.path.join(projectDirectory.path, 'pubspec.yaml'));
pubspecFile.writeAsStringSync('''
name: ${lib}_project
dependencies:
flutter:
sdk: flutter
''');
final File dartFile = globals.fs.file(globals.fs.path.join(projectDirectory.path, 'lib', 'main.dart'));
dartFile.parent.createSync();
dartFile.writeAsStringSync('''
import 'dart:$lib' as $lib;
void main() {
// ignore: unnecessary_statements
$lib.$member;
}
''');
await pub.get(context: PubContext.flutterTests, directory: projectDirectory.path);
final AnalysisServer server = AnalysisServer(dartSdkPath, <String>[projectDirectory.path]);
try {
final int errorCount = await analyze(server);
expect(errorCount, 0);
} finally {
await server.dispose();
}
} finally {
tryToDelete(projectDirectory);
Cache.enableLocking();
}
}, skip: true);
}
Future<int> analyze(AnalysisServer server) async {
int errorCount = 0;
final Future<bool> onDone = server.onAnalyzing.where((bool analyzing) => analyzing == false).first;
server.onErrors.listen((FileAnalysisErrors result) {
for (final AnalysisError error in result.errors) {
print(error.toString().trim());
}
errorCount += result.errors.length;
});
await server.start();
await onDone;
return errorCount;
}
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment