[flutter_tools] Reduce context usage in analyze command and tests (#49589)
This commit is contained in:
parent
bafa03e5b5
commit
d2f889b4f0
@ -67,7 +67,14 @@ Future<void> main(List<String> args) async {
|
||||
final bool verboseHelp = help && verbose;
|
||||
|
||||
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(),
|
||||
AttachCommand(verboseHelp: verboseHelp),
|
||||
BuildCommand(verboseHelp: verboseHelp),
|
||||
|
@ -4,14 +4,32 @@
|
||||
|
||||
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/logger.dart';
|
||||
import '../base/terminal.dart';
|
||||
import '../globals.dart' as globals;
|
||||
import '../runner/flutter_command.dart';
|
||||
import 'analyze_continuously.dart';
|
||||
import 'analyze_once.dart';
|
||||
|
||||
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',
|
||||
negatable: false,
|
||||
help: 'Include all the examples and tests from the Flutter repository.',
|
||||
@ -59,6 +77,12 @@ class AnalyzeCommand extends FlutterCommand {
|
||||
/// The working directory for testing analysis using dartanalyzer.
|
||||
final Directory workingDirectory;
|
||||
|
||||
final FileSystem _fileSystem;
|
||||
final Logger _logger;
|
||||
final AnsiTerminal _terminal;
|
||||
final ProcessManager _processManager;
|
||||
final Platform _platform;
|
||||
|
||||
@override
|
||||
String get name => 'analyze';
|
||||
|
||||
@ -73,7 +97,7 @@ class AnalyzeCommand extends FlutterCommand {
|
||||
}
|
||||
|
||||
// Or we're not in a project directory.
|
||||
if (!globals.fs.file('pubspec.yaml').existsSync()) {
|
||||
if (!_fileSystem.file('pubspec.yaml').existsSync()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@ -87,6 +111,13 @@ class AnalyzeCommand extends FlutterCommand {
|
||||
argResults,
|
||||
runner.getRepoRoots(),
|
||||
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();
|
||||
} else {
|
||||
await AnalyzeOnce(
|
||||
@ -94,6 +125,13 @@ class AnalyzeCommand extends FlutterCommand {
|
||||
runner.getRepoRoots(),
|
||||
runner.getRepoPackages(),
|
||||
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();
|
||||
}
|
||||
return FlutterCommandResult.success();
|
||||
|
@ -5,20 +5,47 @@
|
||||
import 'dart:async';
|
||||
|
||||
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 '../base/common.dart';
|
||||
import '../base/file_system.dart';
|
||||
import '../base/logger.dart';
|
||||
import '../base/terminal.dart';
|
||||
import '../base/utils.dart';
|
||||
import '../cache.dart';
|
||||
import '../globals.dart' as globals;
|
||||
|
||||
/// Common behavior for `flutter analyze` and `flutter analyze --watch`
|
||||
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.
|
||||
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.
|
||||
Future<void> analyze();
|
||||
@ -26,7 +53,7 @@ abstract class AnalyzeBase {
|
||||
void dumpErrors(Iterable<String> errors) {
|
||||
if (argResults['write'] != null) {
|
||||
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 {
|
||||
resultsFile.lockSync();
|
||||
resultsFile.writeStringSync(errors.join('\n'));
|
||||
@ -34,7 +61,7 @@ abstract class AnalyzeBase {
|
||||
resultsFile.close();
|
||||
}
|
||||
} 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 {
|
||||
'issues': errorCount,
|
||||
'missingDartDocs': membersMissingDocumentation,
|
||||
};
|
||||
globals.fs.file(benchmarkOut).writeAsStringSync(toPrettyJson(data));
|
||||
globals.printStatus('Analysis benchmark written to $benchmarkOut ($data).');
|
||||
fileSystem.file(benchmarkOut).writeAsStringSync(toPrettyJson(data));
|
||||
logger.printStatus('Analysis benchmark written to $benchmarkOut ($data).');
|
||||
}
|
||||
|
||||
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 {
|
||||
// This is a map from dependency targets (lib directories) to a list
|
||||
// of places that ask for that target (.packages or pubspec.yaml files)
|
||||
|
@ -5,23 +5,38 @@
|
||||
import 'dart:async';
|
||||
|
||||
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/file_system.dart';
|
||||
import '../base/io.dart';
|
||||
import '../base/logger.dart';
|
||||
import '../base/terminal.dart';
|
||||
import '../base/utils.dart';
|
||||
import '../cache.dart';
|
||||
import '../dart/analysis.dart';
|
||||
import '../dart/sdk.dart' as sdk;
|
||||
import '../globals.dart' as globals;
|
||||
import 'analyze_base.dart';
|
||||
|
||||
class AnalyzeContinuously extends AnalyzeBase {
|
||||
AnalyzeContinuously(ArgResults argResults, this.repoRoots, this.repoPackages) : super(argResults);
|
||||
|
||||
final List<String> repoRoots;
|
||||
final List<Directory> repoPackages;
|
||||
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,
|
||||
);
|
||||
|
||||
String analysisTarget;
|
||||
bool firstAnalysis = true;
|
||||
@ -42,18 +57,24 @@ class AnalyzeContinuously extends AnalyzeBase {
|
||||
directories = repoRoots;
|
||||
analysisTarget = 'Flutter repository';
|
||||
|
||||
globals.printTrace('Analyzing Flutter repository:');
|
||||
logger.printTrace('Analyzing Flutter repository:');
|
||||
for (final String projectPath in repoRoots) {
|
||||
globals.printTrace(' ${globals.fs.path.relative(projectPath)}');
|
||||
logger.printTrace(' ${fileSystem.path.relative(projectPath)}');
|
||||
}
|
||||
} else {
|
||||
directories = <String>[globals.fs.currentDirectory.path];
|
||||
analysisTarget = globals.fs.currentDirectory.path;
|
||||
directories = <String>[fileSystem.currentDirectory.path];
|
||||
analysisTarget = fileSystem.currentDirectory.path;
|
||||
}
|
||||
|
||||
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.onErrors.listen(_handleAnalysisErrors);
|
||||
|
||||
@ -66,7 +87,7 @@ class AnalyzeContinuously extends AnalyzeBase {
|
||||
if (exitCode != 0) {
|
||||
throwToolExit(message, exitCode: exitCode);
|
||||
}
|
||||
globals.printStatus(message);
|
||||
logger.printStatus(message);
|
||||
|
||||
if (server.didServerErrorOccur) {
|
||||
throwToolExit('Server error(s) occurred.');
|
||||
@ -77,9 +98,9 @@ class AnalyzeContinuously extends AnalyzeBase {
|
||||
if (isAnalyzing) {
|
||||
analysisStatus?.cancel();
|
||||
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();
|
||||
analysisTimer = Stopwatch()..start();
|
||||
} else {
|
||||
@ -87,12 +108,12 @@ class AnalyzeContinuously extends AnalyzeBase {
|
||||
analysisStatus = null;
|
||||
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.
|
||||
final List<AnalysisError> errors = <AnalysisError>[];
|
||||
for (final String path in analysisErrors.keys.toList()) {
|
||||
if (globals.fs.isFileSync(path)) {
|
||||
if (fileSystem.isFileSync(path)) {
|
||||
errors.addAll(analysisErrors[path]);
|
||||
} else {
|
||||
analysisErrors.remove(path);
|
||||
@ -113,9 +134,9 @@ class AnalyzeContinuously extends AnalyzeBase {
|
||||
errors.sort();
|
||||
|
||||
for (final AnalysisError error in errors) {
|
||||
globals.printStatus(error.toString());
|
||||
logger.printStatus(error.toString());
|
||||
if (error.code != null) {
|
||||
globals.printTrace('error code: ${error.code}');
|
||||
logger.printTrace('error code: ${error.code}');
|
||||
}
|
||||
}
|
||||
|
||||
@ -148,9 +169,9 @@ class AnalyzeContinuously extends AnalyzeBase {
|
||||
final String files = '${analyzedPaths.length} ${pluralize('file', analyzedPaths.length)}';
|
||||
final String seconds = (analysisTimer.elapsedMilliseconds / 1000.0).toStringAsFixed(2);
|
||||
if (undocumentedMembers > 0) {
|
||||
globals.printStatus('$errorsMessage • $dartdocMessage • analyzed $files in $seconds seconds');
|
||||
logger.printStatus('$errorsMessage • $dartdocMessage • analyzed $files in $seconds seconds');
|
||||
} else {
|
||||
globals.printStatus('$errorsMessage • analyzed $files in $seconds seconds');
|
||||
logger.printStatus('$errorsMessage • analyzed $files in $seconds seconds');
|
||||
}
|
||||
|
||||
if (firstAnalysis && isBenchmarking) {
|
||||
|
@ -5,15 +5,18 @@
|
||||
import 'dart:async';
|
||||
|
||||
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/file_system.dart';
|
||||
import '../base/logger.dart';
|
||||
import '../base/terminal.dart';
|
||||
import '../base/utils.dart';
|
||||
import '../cache.dart';
|
||||
import '../dart/analysis.dart';
|
||||
import '../dart/sdk.dart' as sdk;
|
||||
import '../globals.dart' as globals;
|
||||
import 'analyze.dart';
|
||||
import 'analyze_base.dart';
|
||||
|
||||
@ -21,13 +24,24 @@ import 'analyze_base.dart';
|
||||
class AnalyzeOnce extends AnalyzeBase {
|
||||
AnalyzeOnce(
|
||||
ArgResults argResults,
|
||||
this.repoRoots,
|
||||
this.repoPackages, {
|
||||
List<String> repoRoots,
|
||||
List<Directory> repoPackages, {
|
||||
@required FileSystem fileSystem,
|
||||
@required Logger logger,
|
||||
@required Platform platform,
|
||||
@required ProcessManager processManager,
|
||||
@required AnsiTerminal terminal,
|
||||
this.workingDirectory,
|
||||
}) : super(argResults);
|
||||
|
||||
final List<String> repoRoots;
|
||||
final List<Directory> repoPackages;
|
||||
}) : super(
|
||||
argResults,
|
||||
repoRoots: repoRoots,
|
||||
repoPackages: repoPackages,
|
||||
fileSystem: fileSystem,
|
||||
logger: logger,
|
||||
platform: platform,
|
||||
processManager: processManager,
|
||||
terminal: terminal,
|
||||
);
|
||||
|
||||
/// The working directory for testing analysis using dartanalyzer.
|
||||
final Directory workingDirectory;
|
||||
@ -35,14 +49,14 @@ class AnalyzeOnce extends AnalyzeBase {
|
||||
@override
|
||||
Future<void> analyze() async {
|
||||
final String currentDirectory =
|
||||
(workingDirectory ?? globals.fs.currentDirectory).path;
|
||||
(workingDirectory ?? fileSystem.currentDirectory).path;
|
||||
|
||||
// find directories 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) {
|
||||
for (final String directory in directories) {
|
||||
final FileSystemEntityType type = globals.fs.typeSync(directory);
|
||||
final FileSystemEntityType type = fileSystem.typeSync(directory);
|
||||
|
||||
if (type == FileSystemEntityType.notFound) {
|
||||
throwToolExit("'$directory' does not exist");
|
||||
@ -79,6 +93,11 @@ class AnalyzeOnce extends AnalyzeBase {
|
||||
final AnalysisServer server = AnalysisServer(
|
||||
sdkPath,
|
||||
directories.toList(),
|
||||
fileSystem: fileSystem,
|
||||
platform: platform,
|
||||
logger: logger,
|
||||
processManager: processManager,
|
||||
terminal: terminal,
|
||||
);
|
||||
|
||||
StreamSubscription<bool> subscription;
|
||||
@ -108,9 +127,9 @@ class AnalyzeOnce extends AnalyzeBase {
|
||||
final Stopwatch timer = Stopwatch()..start();
|
||||
final String message = directories.length > 1
|
||||
? '${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
|
||||
? globals.logger.startProgress('Analyzing $message...', timeout: timeoutConfiguration.slowOperation)
|
||||
? logger.startProgress('Analyzing $message...', timeout: timeoutConfiguration.slowOperation)
|
||||
: null;
|
||||
|
||||
await analysisCompleter.future;
|
||||
@ -135,11 +154,11 @@ class AnalyzeOnce extends AnalyzeBase {
|
||||
|
||||
// report errors
|
||||
if (errors.isNotEmpty && (argResults['preamble'] as bool)) {
|
||||
globals.printStatus('');
|
||||
logger.printStatus('');
|
||||
}
|
||||
errors.sort();
|
||||
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);
|
||||
@ -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).
|
||||
if (errors.isNotEmpty) {
|
||||
final int errorCount = errors.length;
|
||||
globals.printStatus('');
|
||||
logger.printStatus('');
|
||||
if (undocumentedMembers > 0) {
|
||||
throwToolExit('$errorCount ${pluralize('issue', errorCount)} found. (ran in ${seconds}s; $dartdocMessage)');
|
||||
} else {
|
||||
@ -168,9 +187,9 @@ class AnalyzeOnce extends AnalyzeBase {
|
||||
|
||||
if (argResults['congratulate'] as bool) {
|
||||
if (undocumentedMembers > 0) {
|
||||
globals.printStatus('No issues found! (ran in ${seconds}s; $dartdocMessage)');
|
||||
logger.printStatus('No issues found! (ran in ${seconds}s; $dartdocMessage)');
|
||||
} else {
|
||||
globals.printStatus('No issues found! (ran in ${seconds}s)');
|
||||
logger.printStatus('No issues found! (ran in ${seconds}s)');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -5,18 +5,39 @@
|
||||
import 'dart:async';
|
||||
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/file_system.dart';
|
||||
import '../base/io.dart';
|
||||
import '../base/logger.dart';
|
||||
import '../base/terminal.dart';
|
||||
import '../base/utils.dart';
|
||||
import '../convert.dart';
|
||||
import '../globals.dart' as globals;
|
||||
|
||||
/// An interface to the Dart analysis server.
|
||||
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 List<String> directories;
|
||||
final FileSystem _fileSystem;
|
||||
final ProcessManager _processManager;
|
||||
final Logger _logger;
|
||||
final Platform _platform;
|
||||
final AnsiTerminal _terminal;
|
||||
|
||||
Process _process;
|
||||
final StreamController<bool> _analyzingController =
|
||||
@ -29,9 +50,9 @@ class AnalysisServer {
|
||||
|
||||
Future<void> start() async {
|
||||
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>[
|
||||
globals.fs.path.join(sdkPath, 'bin', 'dart'),
|
||||
_fileSystem.path.join(sdkPath, 'bin', 'dart'),
|
||||
snapshot,
|
||||
'--disable-server-feature-completion',
|
||||
'--disable-server-feature-search',
|
||||
@ -39,14 +60,14 @@ class AnalysisServer {
|
||||
sdkPath,
|
||||
];
|
||||
|
||||
globals.printTrace('dart ${command.skip(1).join(' ')}');
|
||||
_process = await globals.processManager.start(command);
|
||||
_logger.printTrace('dart ${command.skip(1).join(' ')}');
|
||||
_process = await _processManager.start(command);
|
||||
// This callback hookup can't throw.
|
||||
unawaited(_process.exitCode.whenComplete(() => _process = null));
|
||||
|
||||
final Stream<String> errorStream =
|
||||
_process.stderr.transform<String>(utf8.decoder).transform<String>(const LineSplitter());
|
||||
errorStream.listen(globals.printError);
|
||||
errorStream.listen(_logger.printError);
|
||||
|
||||
final Stream<String> inStream =
|
||||
_process.stdout.transform<String>(utf8.decoder).transform<String>(const LineSplitter());
|
||||
@ -73,11 +94,11 @@ class AnalysisServer {
|
||||
'params': params,
|
||||
});
|
||||
_process.stdin.writeln(message);
|
||||
globals.printTrace('==> $message');
|
||||
_logger.printTrace('==> $message');
|
||||
}
|
||||
|
||||
void _handleServerResponse(String line) {
|
||||
globals.printTrace('<== $line');
|
||||
_logger.printTrace('<== $line');
|
||||
|
||||
final dynamic response = json.decode(line);
|
||||
|
||||
@ -98,10 +119,10 @@ class AnalysisServer {
|
||||
} else if (response['error'] != null) {
|
||||
// Fields are 'code', 'message', and 'stackTrace'.
|
||||
final Map<String, dynamic> error = castStringKeyedMap(response['error']);
|
||||
globals.printError(
|
||||
_logger.printError(
|
||||
'Error response from the server: ${error['code']} ${error['message']}');
|
||||
if (error['stackTrace'] != null) {
|
||||
globals.printError(error['stackTrace'] as String);
|
||||
_logger.printError(error['stackTrace'] as String);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -117,9 +138,9 @@ class AnalysisServer {
|
||||
|
||||
void _handleServerError(Map<String, dynamic> error) {
|
||||
// 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) {
|
||||
globals.printError(error['stackTrace'] as String);
|
||||
_logger.printError(error['stackTrace'] as String);
|
||||
}
|
||||
_didServerErrorOccur = true;
|
||||
}
|
||||
@ -130,7 +151,13 @@ class AnalysisServer {
|
||||
final List<dynamic> errorsList = issueInfo['errors'] as List<dynamic>;
|
||||
final List<AnalysisError> errors = errorsList
|
||||
.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();
|
||||
if (!_errorsController.isClosed) {
|
||||
_errorsController.add(FileAnalysisErrors(file, errors));
|
||||
@ -152,7 +179,17 @@ enum _AnalysisSeverity {
|
||||
}
|
||||
|
||||
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>{
|
||||
'INFO': _AnalysisSeverity.info,
|
||||
@ -160,7 +197,7 @@ class AnalysisError implements Comparable<AnalysisError> {
|
||||
'ERROR': _AnalysisSeverity.error,
|
||||
};
|
||||
|
||||
static final String _separator = globals.platform.isWindows ? '-' : '•';
|
||||
String get _separator => _platform.isWindows ? '-' : '•';
|
||||
|
||||
// "severity":"INFO","type":"TODO","location":{
|
||||
// "file":"/Users/.../lib/test.dart","offset":362,"length":72,"startLine":15,"startColumn":4
|
||||
@ -171,9 +208,9 @@ class AnalysisError implements Comparable<AnalysisError> {
|
||||
String get colorSeverity {
|
||||
switch(_severityLevel) {
|
||||
case _AnalysisSeverity.error:
|
||||
return globals.terminal.color(severity, TerminalColor.red);
|
||||
return _terminal.color(severity, TerminalColor.red);
|
||||
case _AnalysisSeverity.warning:
|
||||
return globals.terminal.color(severity, TerminalColor.yellow);
|
||||
return _terminal.color(severity, TerminalColor.yellow);
|
||||
case _AnalysisSeverity.info:
|
||||
case _AnalysisSeverity.none:
|
||||
return severity;
|
||||
@ -224,7 +261,7 @@ class AnalysisError implements Comparable<AnalysisError> {
|
||||
final String padding = ' ' * math.max(0, 7 - severity.length);
|
||||
return '$padding${colorSeverity.toLowerCase()} $_separator '
|
||||
'$messageSentenceFragment $_separator '
|
||||
'${globals.fs.path.relative(file)}:$startLine:$startColumn $_separator '
|
||||
'${_fileSystem.path.relative(file)}:$startLine:$startColumn $_separator '
|
||||
'$code';
|
||||
}
|
||||
|
||||
|
@ -5,12 +5,15 @@
|
||||
import 'dart:async';
|
||||
|
||||
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/pub.dart';
|
||||
import 'package:flutter_tools/src/dart/sdk.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/context.dart';
|
||||
@ -18,10 +21,21 @@ import '../../src/context.dart';
|
||||
void main() {
|
||||
AnalysisServer server;
|
||||
Directory tempDir;
|
||||
FileSystem fileSystem;
|
||||
Platform platform;
|
||||
ProcessManager processManager;
|
||||
AnsiTerminal terminal;
|
||||
Logger logger;
|
||||
|
||||
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();
|
||||
tempDir = globals.fs.systemTempDirectory.createTempSync('flutter_analysis_test.');
|
||||
tempDir = fileSystem.systemTempDirectory.createTempSync('flutter_analysis_test.');
|
||||
});
|
||||
|
||||
tearDown(() {
|
||||
@ -29,13 +43,36 @@ void main() {
|
||||
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', () {
|
||||
testUsingContext('AnalysisServer success', () async {
|
||||
_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;
|
||||
final Future<bool> onDone = server.onAnalyzing.where((bool analyzing) => analyzing == false).first;
|
||||
@ -45,18 +82,21 @@ void main() {
|
||||
await onDone;
|
||||
|
||||
expect(errorCount, 0);
|
||||
}, overrides: <Type, Generator>{
|
||||
OperatingSystemUtils: () => globals.os,
|
||||
Pub: () => const Pub(),
|
||||
});
|
||||
});
|
||||
|
||||
testUsingContext('AnalysisServer errors', () async {
|
||||
_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;
|
||||
final Future<bool> onDone = server.onAnalyzing.where((bool analyzing) => analyzing == false).first;
|
||||
@ -68,15 +108,18 @@ void main() {
|
||||
await onDone;
|
||||
|
||||
expect(errorCount, greaterThan(0));
|
||||
}, overrides: <Type, Generator>{
|
||||
OperatingSystemUtils: () => globals.os,
|
||||
Pub: () => const Pub(),
|
||||
});
|
||||
|
||||
testUsingContext('Returns no errors when source is error-free', () async {
|
||||
const String contents = "StringBuffer bar = StringBuffer('baz');";
|
||||
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;
|
||||
final Future<bool> onDone = server.onAnalyzing.where((bool analyzing) => analyzing == false).first;
|
||||
@ -86,24 +129,5 @@ void main() {
|
||||
await server.start();
|
||||
await onDone;
|
||||
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 @@
|
||||
import 'package:file/file.dart';
|
||||
import 'package:file/memory.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 '../../src/common.dart';
|
||||
@ -13,6 +12,23 @@ import '../../src/context.dart';
|
||||
|
||||
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() {
|
||||
FileSystem fs;
|
||||
Directory tempDir;
|
||||
|
@ -8,250 +8,326 @@ import 'package:platform/platform.dart';
|
||||
|
||||
import 'package:flutter_tools/src/base/common.dart';
|
||||
import 'package:flutter_tools/src/base/file_system.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/cache.dart';
|
||||
import 'package:flutter_tools/src/commands/analyze.dart';
|
||||
import 'package:flutter_tools/src/runner/flutter_command.dart';
|
||||
import 'package:flutter_tools/src/runner/flutter_command_runner.dart';
|
||||
import 'package:flutter_tools/src/globals.dart' as globals;
|
||||
import 'package:process/process.dart';
|
||||
|
||||
import '../../src/common.dart';
|
||||
import '../../src/context.dart';
|
||||
|
||||
final Generator _kNoColorTerminalPlatform = () => FakePlatform.fromPlatform(const LocalPlatform())..stdoutSupportsAnsi = false;
|
||||
final Map<Type, Generator> noColorTerminalOverride = <Type, Generator>{
|
||||
Platform: _kNoColorTerminalPlatform,
|
||||
};
|
||||
final Platform _kNoColorTerminalPlatform = FakePlatform.fromPlatform(const LocalPlatform())..stdoutSupportsAnsi = false;
|
||||
|
||||
void main() {
|
||||
final String analyzerSeparator = globals.platform.isWindows ? '-' : '•';
|
||||
String analyzerSeparator;
|
||||
FileSystem fileSystem;
|
||||
Platform platform;
|
||||
BufferLogger logger;
|
||||
AnsiTerminal terminal;
|
||||
ProcessManager processManager;
|
||||
Directory tempDir;
|
||||
String projectPath;
|
||||
File libMain;
|
||||
|
||||
group('analyze once', () {
|
||||
setUpAll(() {
|
||||
Cache.disableLocking();
|
||||
Cache.flutterRoot = FlutterCommandRunner.defaultFlutterRoot;
|
||||
});
|
||||
|
||||
void _createDotPackages(String projectPath) {
|
||||
final StringBuffer flutterRootUri = StringBuffer('file://');
|
||||
final String canonicalizedFlutterRootPath = globals.fs.path.canonicalize(Cache.flutterRoot);
|
||||
if (globals.platform.isWindows) {
|
||||
flutterRootUri
|
||||
..write('/')
|
||||
..write(canonicalizedFlutterRootPath.replaceAll(r'\', '/'));
|
||||
} else {
|
||||
flutterRootUri.write(canonicalizedFlutterRootPath);
|
||||
Future<void> runCommand({
|
||||
FlutterCommand command,
|
||||
List<String> arguments,
|
||||
List<String> statusTextContains,
|
||||
List<String> errorTextContains,
|
||||
bool toolExit = false,
|
||||
String exitMessageContains,
|
||||
}) async {
|
||||
try {
|
||||
arguments.insert(0, '--flutter-root=${Cache.flutterRoot}');
|
||||
await createTestCommandRunner(command).run(arguments);
|
||||
expect(toolExit, isFalse, reason: 'Expected ToolExit exception');
|
||||
} on ToolExit catch (e) {
|
||||
if (!toolExit) {
|
||||
testLogger.clear();
|
||||
rethrow;
|
||||
}
|
||||
final String dotPackagesSrc = '''
|
||||
# Generated
|
||||
if (exitMessageContains != null) {
|
||||
expect(e.message, contains(exitMessageContains));
|
||||
}
|
||||
}
|
||||
assertContains(logger.statusText, statusTextContains);
|
||||
assertContains(logger.errorText, errorTextContains);
|
||||
|
||||
logger.clear();
|
||||
}
|
||||
|
||||
void _createDotPackages(String projectPath) {
|
||||
final StringBuffer flutterRootUri = StringBuffer('file://');
|
||||
final String canonicalizedFlutterRootPath = fileSystem.path.canonicalize(Cache.flutterRoot);
|
||||
if (platform.isWindows) {
|
||||
flutterRootUri
|
||||
..write('/')
|
||||
..write(canonicalizedFlutterRootPath.replaceAll('\\', '/'));
|
||||
} else {
|
||||
flutterRootUri.write(canonicalizedFlutterRootPath);
|
||||
}
|
||||
final String dotPackagesSrc = '''# Generated
|
||||
flutter:$flutterRootUri/packages/flutter/lib/
|
||||
sky_engine:$flutterRootUri/bin/cache/pkg/sky_engine/lib/
|
||||
flutter_project:lib/
|
||||
''';
|
||||
globals.fs.file(globals.fs.path.join(projectPath, '.packages'))
|
||||
..createSync(recursive: true)
|
||||
..writeAsStringSync(dotPackagesSrc);
|
||||
fileSystem.file(fileSystem.path.join(projectPath, '.packages'))
|
||||
..createSync(recursive: true)
|
||||
..writeAsStringSync(dotPackagesSrc);
|
||||
}
|
||||
|
||||
setUpAll(() {
|
||||
Cache.disableLocking();
|
||||
Cache.flutterRoot = FlutterCommandRunner.defaultFlutterRoot;
|
||||
processManager = const LocalProcessManager();
|
||||
terminal = AnsiTerminal(platform: platform, stdio: Stdio());
|
||||
fileSystem = const LocalFileSystem();
|
||||
platform = const LocalPlatform();
|
||||
logger = BufferLogger(
|
||||
outputPreferences: OutputPreferences.test(),
|
||||
terminal: terminal,
|
||||
);
|
||||
analyzerSeparator = platform.isWindows ? '-' : '•';
|
||||
tempDir = fileSystem.systemTempDirectory.createTempSync('flutter_analyze_once_test_1.').absolute;
|
||||
projectPath = fileSystem.path.join(tempDir.path, 'flutter_project');
|
||||
fileSystem.file(fileSystem.path.join(projectPath, 'pubspec.yaml'))
|
||||
..createSync(recursive: true)
|
||||
..writeAsStringSync(pubspecYamlSrc);
|
||||
_createDotPackages(projectPath);
|
||||
});
|
||||
|
||||
setUp(() {
|
||||
libMain = fileSystem.file(fileSystem.path.join(projectPath, 'lib', 'main.dart'))
|
||||
..createSync(recursive: true)
|
||||
..writeAsStringSync(mainDartSrc);
|
||||
});
|
||||
|
||||
tearDownAll(() {
|
||||
tryToDelete(tempDir);
|
||||
});
|
||||
|
||||
// Analyze in the current directory - no arguments
|
||||
testUsingContext('working directory', () async {
|
||||
await runCommand(
|
||||
command: AnalyzeCommand(
|
||||
workingDirectory: fileSystem.directory(projectPath),
|
||||
fileSystem: fileSystem,
|
||||
logger: logger,
|
||||
platform: platform,
|
||||
processManager: processManager,
|
||||
terminal: terminal,
|
||||
),
|
||||
arguments: <String>['analyze', '--no-pub'],
|
||||
statusTextContains: <String>['No issues found!'],
|
||||
);
|
||||
});
|
||||
|
||||
// Analyze a specific file outside the current directory
|
||||
testUsingContext('passing one file throws', () async {
|
||||
await runCommand(
|
||||
command: AnalyzeCommand(
|
||||
platform: platform,
|
||||
fileSystem: fileSystem,
|
||||
logger: logger,
|
||||
processManager: processManager,
|
||||
terminal: terminal,
|
||||
),
|
||||
arguments: <String>['analyze', '--no-pub', libMain.path],
|
||||
toolExit: true,
|
||||
exitMessageContains: 'is not a directory',
|
||||
);
|
||||
});
|
||||
|
||||
// Analyze in the current directory - no arguments
|
||||
testUsingContext('working directory with errors', () async {
|
||||
// Break the code to produce the "Avoid empty else" hint
|
||||
// that is upgraded to a warning in package:flutter/analysis_options_user.yaml
|
||||
// to assert that we are using the default Flutter analysis options.
|
||||
// Also insert a statement that should not trigger a lint here
|
||||
// but will trigger a lint later on when an analysis_options.yaml is added.
|
||||
String source = await libMain.readAsString();
|
||||
source = source.replaceFirst(
|
||||
'return MaterialApp(',
|
||||
'if (debugPrintRebuildDirtyWidgets) {} else ; return MaterialApp(',
|
||||
);
|
||||
source = source.replaceFirst(
|
||||
'onPressed: _incrementCounter,',
|
||||
'// onPressed: _incrementCounter,',
|
||||
);
|
||||
source = source.replaceFirst(
|
||||
'_counter++;',
|
||||
'_counter++; throw "an error message";',
|
||||
);
|
||||
libMain.writeAsStringSync(source);
|
||||
|
||||
// Analyze in the current directory - no arguments
|
||||
await runCommand(
|
||||
command: AnalyzeCommand(
|
||||
workingDirectory: fileSystem.directory(projectPath),
|
||||
platform: platform,
|
||||
fileSystem: fileSystem,
|
||||
logger: logger,
|
||||
processManager: processManager,
|
||||
terminal: terminal,
|
||||
),
|
||||
arguments: <String>['analyze', '--no-pub'],
|
||||
statusTextContains: <String>[
|
||||
'Analyzing',
|
||||
'info $analyzerSeparator Avoid empty else statements',
|
||||
'info $analyzerSeparator Avoid empty statements',
|
||||
'info $analyzerSeparator The declaration \'_incrementCounter\' isn\'t',
|
||||
],
|
||||
exitMessageContains: '3 issues found.',
|
||||
toolExit: true,
|
||||
);
|
||||
});
|
||||
|
||||
// Analyze in the current directory - no arguments
|
||||
testUsingContext('working directory with local options', () async {
|
||||
// Insert an analysis_options.yaml file in the project
|
||||
// which will trigger a lint for broken code that was inserted earlier
|
||||
final File optionsFile = fileSystem.file(fileSystem.path.join(projectPath, 'analysis_options.yaml'));
|
||||
try {
|
||||
optionsFile.writeAsStringSync('''
|
||||
include: package:flutter/analysis_options_user.yaml
|
||||
linter:
|
||||
rules:
|
||||
- only_throw_errors
|
||||
''');
|
||||
String source = libMain.readAsStringSync();
|
||||
source = source.replaceFirst(
|
||||
'onPressed: _incrementCounter,',
|
||||
'// onPressed: _incrementCounter,',
|
||||
);
|
||||
source = source.replaceFirst(
|
||||
'_counter++;',
|
||||
'_counter++; throw "an error message";',
|
||||
);
|
||||
libMain.writeAsStringSync(source);
|
||||
|
||||
// Analyze in the current directory - no arguments
|
||||
await runCommand(
|
||||
command: AnalyzeCommand(
|
||||
workingDirectory: fileSystem.directory(projectPath),
|
||||
platform: platform,
|
||||
fileSystem: fileSystem,
|
||||
logger: logger,
|
||||
processManager: processManager,
|
||||
terminal: terminal,
|
||||
),
|
||||
arguments: <String>['analyze', '--no-pub'],
|
||||
statusTextContains: <String>[
|
||||
'Analyzing',
|
||||
'info $analyzerSeparator The declaration \'_incrementCounter\' isn\'t',
|
||||
'info $analyzerSeparator Only throw instances of classes extending either Exception or Error',
|
||||
],
|
||||
exitMessageContains: '2 issues found.',
|
||||
toolExit: true,
|
||||
);
|
||||
} finally {
|
||||
if (optionsFile.existsSync()) {
|
||||
optionsFile.deleteSync();
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
group('default libMain', () {
|
||||
Directory tempDir;
|
||||
String projectPath;
|
||||
File libMain;
|
||||
testUsingContext('analyze once no duplicate issues', () async {
|
||||
final Directory tempDir = fileSystem.systemTempDirectory.createTempSync('flutter_analyze_once_test_2.').absolute;
|
||||
_createDotPackages(tempDir.path);
|
||||
|
||||
setUpAll(() async {
|
||||
tempDir = globals.fs.systemTempDirectory.createTempSync('flutter_analyze_once_test_1.').absolute;
|
||||
projectPath = globals.fs.path.join(tempDir.path, 'flutter_project');
|
||||
globals.fs.file(globals.fs.path.join(projectPath, 'pubspec.yaml'))
|
||||
..createSync(recursive: true)
|
||||
..writeAsStringSync(pubspecYamlSrc);
|
||||
_createDotPackages(projectPath);
|
||||
});
|
||||
|
||||
setUp(() {
|
||||
libMain = globals.fs.file(globals.fs.path.join(projectPath, 'lib', 'main.dart'))
|
||||
..createSync(recursive: true)
|
||||
..writeAsStringSync(mainDartSrc);
|
||||
});
|
||||
|
||||
tearDownAll(() {
|
||||
tryToDelete(tempDir);
|
||||
});
|
||||
|
||||
// Analyze in the current directory - no arguments
|
||||
testUsingContext('working directory', () async {
|
||||
await runCommand(
|
||||
command: AnalyzeCommand(workingDirectory: globals.fs.directory(projectPath)),
|
||||
arguments: <String>['analyze', '--no-pub'],
|
||||
statusTextContains: <String>['No issues found!'],
|
||||
);
|
||||
});
|
||||
|
||||
// Analyze a specific file outside the current directory
|
||||
testUsingContext('passing one file throws', () async {
|
||||
await runCommand(
|
||||
command: AnalyzeCommand(),
|
||||
arguments: <String>['analyze', '--no-pub', libMain.path],
|
||||
toolExit: true,
|
||||
exitMessageContains: 'is not a directory',
|
||||
);
|
||||
});
|
||||
|
||||
// Analyze in the current directory - no arguments
|
||||
testUsingContext('working directory with errors', () async {
|
||||
// Break the code to produce the "Avoid empty else" hint
|
||||
// that is upgraded to a warning in package:flutter/analysis_options_user.yaml
|
||||
// to assert that we are using the default Flutter analysis options.
|
||||
// Also insert a statement that should not trigger a lint here
|
||||
// but will trigger a lint later on when an analysis_options.yaml is added.
|
||||
String source = await libMain.readAsString();
|
||||
source = source.replaceFirst(
|
||||
'return MaterialApp(',
|
||||
'if (debugPrintRebuildDirtyWidgets) {} else ; return MaterialApp(',
|
||||
);
|
||||
source = source.replaceFirst(
|
||||
'onPressed: _incrementCounter,',
|
||||
'// onPressed: _incrementCounter,',
|
||||
);
|
||||
source = source.replaceFirst(
|
||||
'_counter++;',
|
||||
'_counter++; throw "an error message";',
|
||||
);
|
||||
libMain.writeAsStringSync(source);
|
||||
|
||||
// Analyze in the current directory - no arguments
|
||||
await runCommand(
|
||||
command: AnalyzeCommand(workingDirectory: globals.fs.directory(projectPath)),
|
||||
arguments: <String>['analyze', '--no-pub'],
|
||||
statusTextContains: <String>[
|
||||
'Analyzing',
|
||||
'info $analyzerSeparator Avoid empty else statements',
|
||||
'info $analyzerSeparator Avoid empty statements',
|
||||
"info $analyzerSeparator The declaration '_incrementCounter' isn't",
|
||||
],
|
||||
exitMessageContains: '3 issues found.',
|
||||
toolExit: true,
|
||||
);
|
||||
});
|
||||
|
||||
// Analyze in the current directory - no arguments
|
||||
testUsingContext('working directory with local options', () async {
|
||||
// Insert an analysis_options.yaml file in the project
|
||||
// which will trigger a lint for broken code that was inserted earlier
|
||||
final File optionsFile = globals.fs.file(globals.fs.path.join(projectPath, 'analysis_options.yaml'));
|
||||
try {
|
||||
optionsFile.writeAsStringSync('''
|
||||
include: package:flutter/analysis_options_user.yaml
|
||||
linter:
|
||||
rules:
|
||||
- only_throw_errors
|
||||
''');
|
||||
String source = libMain.readAsStringSync();
|
||||
source = source.replaceFirst(
|
||||
'onPressed: _incrementCounter,',
|
||||
'// onPressed: _incrementCounter,',
|
||||
);
|
||||
source = source.replaceFirst(
|
||||
'_counter++;',
|
||||
'_counter++; throw "an error message";',
|
||||
);
|
||||
libMain.writeAsStringSync(source);
|
||||
|
||||
// Analyze in the current directory - no arguments
|
||||
await runCommand(
|
||||
command: AnalyzeCommand(workingDirectory: globals.fs.directory(projectPath)),
|
||||
arguments: <String>['analyze', '--no-pub'],
|
||||
statusTextContains: <String>[
|
||||
'Analyzing',
|
||||
"info $analyzerSeparator The declaration '_incrementCounter' isn't",
|
||||
'info $analyzerSeparator Only throw instances of classes extending either Exception or Error',
|
||||
],
|
||||
exitMessageContains: '2 issues found.',
|
||||
toolExit: true,
|
||||
);
|
||||
} finally {
|
||||
if (optionsFile.existsSync()) {
|
||||
optionsFile.deleteSync();
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
testUsingContext('no duplicate issues', () async {
|
||||
final Directory tempDir = globals.fs.systemTempDirectory.createTempSync('flutter_analyze_once_test_2.').absolute;
|
||||
_createDotPackages(tempDir.path);
|
||||
|
||||
try {
|
||||
final File foo = globals.fs.file(globals.fs.path.join(tempDir.path, 'foo.dart'));
|
||||
foo.writeAsStringSync('''
|
||||
try {
|
||||
final File foo = fileSystem.file(fileSystem.path.join(tempDir.path, 'foo.dart'));
|
||||
foo.writeAsStringSync('''
|
||||
import 'bar.dart';
|
||||
|
||||
void foo() => bar();
|
||||
''');
|
||||
|
||||
final File bar = globals.fs.file(globals.fs.path.join(tempDir.path, 'bar.dart'));
|
||||
bar.writeAsStringSync('''
|
||||
final File bar = fileSystem.file(fileSystem.path.join(tempDir.path, 'bar.dart'));
|
||||
bar.writeAsStringSync('''
|
||||
import 'dart:async'; // unused
|
||||
|
||||
void bar() {
|
||||
}
|
||||
''');
|
||||
|
||||
// Analyze in the current directory - no arguments
|
||||
await runCommand(
|
||||
command: AnalyzeCommand(workingDirectory: tempDir),
|
||||
arguments: <String>['analyze', '--no-pub'],
|
||||
statusTextContains: <String>[
|
||||
'Analyzing',
|
||||
],
|
||||
exitMessageContains: '1 issue found.',
|
||||
toolExit: true,
|
||||
);
|
||||
} finally {
|
||||
tryToDelete(tempDir);
|
||||
}
|
||||
});
|
||||
// Analyze in the current directory - no arguments
|
||||
await runCommand(
|
||||
command: AnalyzeCommand(
|
||||
workingDirectory: tempDir,
|
||||
platform: platform,
|
||||
fileSystem: fileSystem,
|
||||
logger: logger,
|
||||
processManager: processManager,
|
||||
terminal: terminal,
|
||||
),
|
||||
arguments: <String>['analyze', '--no-pub'],
|
||||
statusTextContains: <String>[
|
||||
'Analyzing',
|
||||
],
|
||||
exitMessageContains: '1 issue found.',
|
||||
toolExit: true,
|
||||
);
|
||||
} finally {
|
||||
tryToDelete(tempDir);
|
||||
}
|
||||
});
|
||||
|
||||
testUsingContext('returns no issues when source is error-free', () async {
|
||||
const String contents = '''
|
||||
testUsingContext('analyze once returns no issues when source is error-free', () async {
|
||||
const String contents = '''
|
||||
StringBuffer bar = StringBuffer('baz');
|
||||
''';
|
||||
final Directory tempDir = globals.fs.systemTempDirectory.createTempSync('flutter_analyze_once_test_3.');
|
||||
_createDotPackages(tempDir.path);
|
||||
final Directory tempDir = fileSystem.systemTempDirectory.createTempSync('flutter_analyze_once_test_3.');
|
||||
_createDotPackages(tempDir.path);
|
||||
|
||||
tempDir.childFile('main.dart').writeAsStringSync(contents);
|
||||
try {
|
||||
await runCommand(
|
||||
command: AnalyzeCommand(workingDirectory: globals.fs.directory(tempDir)),
|
||||
arguments: <String>['analyze', '--no-pub'],
|
||||
statusTextContains: <String>['No issues found!'],
|
||||
);
|
||||
} finally {
|
||||
tryToDelete(tempDir);
|
||||
}
|
||||
}, overrides: <Type, Generator>{
|
||||
...noColorTerminalOverride
|
||||
});
|
||||
tempDir.childFile('main.dart').writeAsStringSync(contents);
|
||||
try {
|
||||
await runCommand(
|
||||
command: AnalyzeCommand(
|
||||
workingDirectory: fileSystem.directory(tempDir),
|
||||
platform: _kNoColorTerminalPlatform,
|
||||
fileSystem: fileSystem,
|
||||
logger: logger,
|
||||
processManager: processManager,
|
||||
terminal: terminal,
|
||||
),
|
||||
arguments: <String>['analyze', '--no-pub'],
|
||||
statusTextContains: <String>['No issues found!'],
|
||||
);
|
||||
} finally {
|
||||
tryToDelete(tempDir);
|
||||
}
|
||||
});
|
||||
|
||||
testUsingContext('returns no issues for todo comments', () async {
|
||||
const String contents = '''
|
||||
testUsingContext('analyze once returns no issues for todo comments', () async {
|
||||
const String contents = '''
|
||||
// TODO(foobar):
|
||||
StringBuffer bar = StringBuffer('baz');
|
||||
''';
|
||||
final Directory tempDir = globals.fs.systemTempDirectory.createTempSync('flutter_analyze_once_test_4.');
|
||||
_createDotPackages(tempDir.path);
|
||||
final Directory tempDir = fileSystem.systemTempDirectory.createTempSync('flutter_analyze_once_test_4.');
|
||||
_createDotPackages(tempDir.path);
|
||||
|
||||
tempDir.childFile('main.dart').writeAsStringSync(contents);
|
||||
try {
|
||||
await runCommand(
|
||||
command: AnalyzeCommand(workingDirectory: globals.fs.directory(tempDir)),
|
||||
arguments: <String>['analyze', '--no-pub'],
|
||||
statusTextContains: <String>['No issues found!'],
|
||||
);
|
||||
} finally {
|
||||
tryToDelete(tempDir);
|
||||
}
|
||||
}, overrides: <Type, Generator>{
|
||||
...noColorTerminalOverride
|
||||
});
|
||||
tempDir.childFile('main.dart').writeAsStringSync(contents);
|
||||
try {
|
||||
await runCommand(
|
||||
command: AnalyzeCommand(
|
||||
workingDirectory: fileSystem.directory(tempDir),
|
||||
platform: _kNoColorTerminalPlatform,
|
||||
terminal: terminal,
|
||||
processManager: processManager,
|
||||
logger: logger,
|
||||
fileSystem: fileSystem,
|
||||
),
|
||||
arguments: <String>['analyze', '--no-pub'],
|
||||
statusTextContains: <String>['No issues found!'],
|
||||
);
|
||||
} finally {
|
||||
tryToDelete(tempDir);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@ -265,33 +341,6 @@ void assertContains(String text, List<String> patterns) {
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> runCommand({
|
||||
FlutterCommand command,
|
||||
List<String> arguments,
|
||||
List<String> statusTextContains,
|
||||
List<String> errorTextContains,
|
||||
bool toolExit = false,
|
||||
String exitMessageContains,
|
||||
}) async {
|
||||
try {
|
||||
arguments.insert(0, '--flutter-root=${Cache.flutterRoot}');
|
||||
await createTestCommandRunner(command).run(arguments);
|
||||
expect(toolExit, isFalse, reason: 'Expected ToolExit exception');
|
||||
} on ToolExit catch (e) {
|
||||
if (!toolExit) {
|
||||
testLogger.clear();
|
||||
rethrow;
|
||||
}
|
||||
if (exitMessageContains != null) {
|
||||
expect(e.message, contains(exitMessageContains));
|
||||
}
|
||||
}
|
||||
assertContains(testLogger.statusText, statusTextContains);
|
||||
assertContains(testLogger.errorText, errorTextContains);
|
||||
|
||||
testLogger.clear();
|
||||
}
|
||||
|
||||
const String mainDartSrc = r'''
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
|
@ -1,77 +0,0 @@
|
||||
// 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;
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user