From a0227cab15e230a564fb6316347b8e0ce3d21f6c Mon Sep 17 00:00:00 2001 From: Hixie Date: Wed, 11 Nov 2015 10:29:05 -0800 Subject: [PATCH] flutter analyze command Other changes in this patch: - Make the 'flutter' tool say "Updating flutter tool..." when it calls pub get, to avoid confusion about what the pub get output is about. - Make the bash flutter tool call pub get when the revision has changed. (This was already happening on Windows.) - Fix a raft of bugs found by the analyzer. - Fix some style nits in various bits of code that happened to be near things the analyzer noticed. - Remove the logic in "flutter test" that would run "pub get", since upon further reflexion it was determined it didn't work anyway. We'll probably have to add better diagnostics here and say to run the updater script. - Remove the native velocity tracker script, since it was testing code that has since been removed. Notes on ignored warnings: - We ignore warnings in any packages that are not in the Flutter repo or in the author's current directory. - We ignore various irrelevant Strong Mode warnings. We still enable strong mode because even though it's not really relevant to our needs, it does (more or less accidentally) catch a few things that are helpful to us. - We allow CONSTANTS_LIKE_THIS, since we get some of those from other platforms that we are copying for sanity and consistency. - We allow one-member abstract classes since we have a number of them where it's perfectly reasonable. - We unfortunately still ignore warnings in mojom.dart autogenerated files. We should really fix those but that's a separate patch. - We verify the actual source file when we see the 'Name non-constant identifiers using lowerCamelCase.' lint, to allow one-letter variables that use capital letters (e.g. for physics expressions) and to allow multiple-underscore variable names. - We ignore all errors on lines that contain the following magic incantation and a "#" character: // analyzer doesn't like constructor tear-offs - For all remaining errors, if the line contains a comment of the form // analyzer says "..." ...then we ignore any errors that have that "..." string in them. --- bin/flutter | 12 +- bin/flutter.bat | 3 +- examples/fitness/lib/main.dart | 2 +- examples/game/example_effect_line.dart | 2 +- examples/widgets/tabs.dart | 6 +- packages/cassowary/lib/constraint.dart | 10 +- packages/cassowary/lib/expression.dart | 10 +- packages/cassowary/lib/solver.dart | 6 +- packages/cassowary/lib/symbol.dart | 14 +- packages/cassowary/lib/variable.dart | 2 +- packages/flutter/lib/src/http/http.dart | 2 +- .../flutter/lib/src/rendering/binding.dart | 4 + packages/flutter_sprites/lib/src/sound.dart | 6 +- packages/flutter_tools/lib/executable.dart | 2 + packages/flutter_tools/lib/src/artifacts.dart | 4 +- .../lib/src/commands/analyze.dart | 302 ++++++++++++++++++ .../flutter_tools/lib/src/commands/build.dart | 2 +- .../lib/src/commands/daemon.dart | 14 +- .../lib/src/commands/flutter_command.dart | 2 +- .../flutter_tools/lib/src/commands/init.dart | 48 ++- .../lib/src/commands/run_mojo.dart | 8 +- .../flutter_tools/lib/src/commands/start.dart | 2 +- .../flutter_tools/lib/src/commands/test.dart | 20 -- packages/flutter_tools/lib/src/device.dart | 73 ++--- packages/flutter_tools/lib/src/process.dart | 18 +- .../newton/lib/src/friction_simulation.dart | 5 +- .../newton/lib/src/spring_simulation.dart | 3 +- .../gestures/velocity_tracker_bench.dart | 18 -- packages/unit/test/rendering/image_test.dart | 3 + travis/test.sh | 18 +- 30 files changed, 451 insertions(+), 170 deletions(-) create mode 100644 packages/flutter_tools/lib/src/commands/analyze.dart diff --git a/bin/flutter b/bin/flutter index 9d96ad347d..f879c22fa4 100755 --- a/bin/flutter +++ b/bin/flutter @@ -12,16 +12,10 @@ SCRIPT_PATH="$FLUTTER_TOOLS_DIR/bin/flutter_tools.dart" # TODO(abarth): We shouldn't require dart to be on the user's path. DART=dart -if [ "$FLUTTER_TOOLS_DIR/pubspec.yaml" -nt "$FLUTTER_TOOLS_DIR/pubspec.lock" ]; then - (cd "$FLUTTER_TOOLS_DIR"; pub get) - if [ -f "$SNAPSHOT_PATH" ]; then - rm "$SNAPSHOT_PATH" - fi -fi - REVISION=`(cd "$FLUTTER_ROOT"; git rev-parse HEAD)` - -if [ ! -f "$SNAPSHOT_PATH" ] || [ ! -f "$STAMP_PATH" ] || [ `cat "$STAMP_PATH"` != $REVISION ]; then +if [ ! -f "$SNAPSHOT_PATH" ] || [ ! -f "$STAMP_PATH" ] || [ `cat "$STAMP_PATH"` != $REVISION ] || [ "$FLUTTER_TOOLS_DIR/pubspec.yaml" -nt "$FLUTTER_TOOLS_DIR/pubspec.lock" ]; then + echo Updating flutter tool... + (cd "$FLUTTER_TOOLS_DIR"; pub get) $DART --snapshot="$SNAPSHOT_PATH" --package-root="$FLUTTER_TOOLS_DIR/packages" "$SCRIPT_PATH" echo -n $REVISION > "$STAMP_PATH" fi diff --git a/bin/flutter.bat b/bin/flutter.bat index 2cb79e220f..4b0ebbb5d7 100644 --- a/bin/flutter.bat +++ b/bin/flutter.bat @@ -29,6 +29,7 @@ GOTO :after_snapshot :do_snapshot CD "%flutter_tools_dir%" +ECHO Updating flutter tool... CALL pub.bat get CD "%flutter_root%" CALL %dart% --snapshot="%snapshot_path%" --package-root="%flutter_tools_dir%\packages" "%script_path%" @@ -42,4 +43,4 @@ CALL %dart% "%snapshot_path%" %* IF /I "%ERRORLEVEL%" EQU "253" ( CALL %dart% --snapshot="%snapshot_path%" --package-root="%flutter_tools_dir%\packages" "%script_path%" CALL %dart% "%snapshot_path%" %* -) \ No newline at end of file +) diff --git a/examples/fitness/lib/main.dart b/examples/fitness/lib/main.dart index a8feff0c02..029b877acd 100644 --- a/examples/fitness/lib/main.dart +++ b/examples/fitness/lib/main.dart @@ -124,7 +124,7 @@ class FitnessAppState extends State { }); } - Widget build(BuildContext) { + Widget build(BuildContext context) { return new MaterialApp( theme: new ThemeData( brightness: ThemeBrightness.light, diff --git a/examples/game/example_effect_line.dart b/examples/game/example_effect_line.dart index c34ebebf75..090f50831a 100644 --- a/examples/game/example_effect_line.dart +++ b/examples/game/example_effect_line.dart @@ -38,7 +38,7 @@ final ThemeData _theme = new ThemeData( ); class TestAppState extends State { - TestApp() { + TestAppState() { _testBed = new TestBed(_labelTexts[_selectedLine]); } diff --git a/examples/widgets/tabs.dart b/examples/widgets/tabs.dart index c34dab9ea2..cd9c0679e0 100644 --- a/examples/widgets/tabs.dart +++ b/examples/widgets/tabs.dart @@ -44,10 +44,10 @@ class TabbedNavigatorAppState extends State { TabNavigator _buildIconLabelsTabNavigator(int n) { Iterable views = ["event", "home", "android", "alarm", "face", "language"] - .map((icon_name) { + .map((String iconName) { return new TabNavigatorView( - label: new TabLabel(icon: "action/$icon_name"), - builder: (BuildContext context) => _buildContent(icon_name) + label: new TabLabel(icon: "action/$iconName"), + builder: (BuildContext context) => _buildContent(iconName) ); }); return _buildTabNavigator(n, views.toList(), const ValueKey('iconLabelsTabNavigator')); diff --git a/packages/cassowary/lib/constraint.dart b/packages/cassowary/lib/constraint.dart index 83a5946cc2..97820d484a 100644 --- a/packages/cassowary/lib/constraint.dart +++ b/packages/cassowary/lib/constraint.dart @@ -21,20 +21,20 @@ class Constraint { switch (relation) { case Relation.equalTo: - buffer.write(" == 0 "); + buffer.write(' == 0 '); break; case Relation.greaterThanOrEqualTo: - buffer.write(" >= 0 "); + buffer.write(' >= 0 '); break; case Relation.lessThanOrEqualTo: - buffer.write(" <= 0 "); + buffer.write(' <= 0 '); break; } - buffer.write(" | priority = ${priority}"); + buffer.write(' | priority = $priority'); if (priority == Priority.required) { - buffer.write(" (required)"); + buffer.write(' (required)'); } return buffer.toString(); diff --git a/packages/cassowary/lib/expression.dart b/packages/cassowary/lib/expression.dart index 0c7f1e9ce2..44fee17512 100644 --- a/packages/cassowary/lib/expression.dart +++ b/packages/cassowary/lib/expression.dart @@ -58,7 +58,7 @@ class Expression extends _EquationMember { _createConstraint(value, Relation.lessThanOrEqualTo); operator ==(_EquationMember value) => - _createConstraint(value, Relation.equalTo); + _createConstraint(value, Relation.equalTo); // analyzer says "Type check failed" // analyzer says "The return type 'Constraint' is not a 'bool', as defined by the method '=='" Expression operator +(_EquationMember m) { if (m is ConstantMember) { @@ -140,7 +140,7 @@ class Expression extends _EquationMember { if (args == null) { throw new ParserException( - "Could not find constant multiplicand or multiplier", [this, m]); + 'Could not find constant multiplicand or multiplier', [this, m]); return null; } @@ -150,7 +150,7 @@ class Expression extends _EquationMember { _EquationMember operator /(_EquationMember m) { if (!m.isConstant) { throw new ParserException( - "The divisor was not a constant expression", [this, m]); + 'The divisor was not a constant expression', [this, m]); return null; } @@ -160,10 +160,10 @@ class Expression extends _EquationMember { String toString() { StringBuffer buffer = new StringBuffer(); - terms.forEach((t) => buffer.write("${t}")); + terms.forEach((t) => buffer.write('$t')); if (constant != 0.0) { - buffer.write(constant.sign > 0.0 ? "+" : "-"); + buffer.write(constant.sign > 0.0 ? '+' : '-'); buffer.write(constant.abs()); } diff --git a/packages/cassowary/lib/solver.dart b/packages/cassowary/lib/solver.dart index 249560def1..3569c7b277 100644 --- a/packages/cassowary/lib/solver.dart +++ b/packages/cassowary/lib/solver.dart @@ -410,10 +410,10 @@ class Solver { double temp = row.coefficientForSymbol(entering); if (temp < 0.0) { - double temp_ratio = -row.constant / temp; + double tempRatio = -row.constant / temp; - if (temp_ratio < ratio) { - ratio = temp_ratio; + if (tempRatio < ratio) { + ratio = tempRatio; result.first = symbol; result.second = row; } diff --git a/packages/cassowary/lib/symbol.dart b/packages/cassowary/lib/symbol.dart index cd7bcf491c..ab0531adb7 100644 --- a/packages/cassowary/lib/symbol.dart +++ b/packages/cassowary/lib/symbol.dart @@ -13,24 +13,24 @@ class _Symbol { _Symbol(this.type, this.tick); String toString() { - String typeString = "unknown"; + String typeString = 'unknown'; switch (type) { case _SymbolType.invalid: - typeString = "i"; + typeString = 'i'; break; case _SymbolType.external: - typeString = "v"; + typeString = 'v'; break; case _SymbolType.slack: - typeString = "s"; + typeString = 's'; break; case _SymbolType.error: - typeString = "e"; + typeString = 'e'; break; case _SymbolType.dummy: - typeString = "d"; + typeString = 'd'; break; } - return "${typeString}${tick}"; + return '$typeString$tick'; } } diff --git a/packages/cassowary/lib/variable.dart b/packages/cassowary/lib/variable.dart index 93850e716e..1b278fc45b 100644 --- a/packages/cassowary/lib/variable.dart +++ b/packages/cassowary/lib/variable.dart @@ -21,7 +21,7 @@ class Variable { return res; } - String get debugName => _elvis(name, "variable${_tick}"); + String get debugName => _elvis(name, 'variable$_tick'); String toString() => debugName; } diff --git a/packages/flutter/lib/src/http/http.dart b/packages/flutter/lib/src/http/http.dart index d6b492f085..e70a70451c 100644 --- a/packages/flutter/lib/src/http/http.dart +++ b/packages/flutter/lib/src/http/http.dart @@ -114,7 +114,7 @@ Future read(url) => Future readBytes(url) => _withClient((client) => client.readBytes(url)); -Future _withClient(Future fn(MojoClient)) { +Future _withClient(Future fn(MojoClient client)) { var client = new MojoClient(); var future = fn(client); return future.whenComplete(client.close); diff --git a/packages/flutter/lib/src/rendering/binding.dart b/packages/flutter/lib/src/rendering/binding.dart index c69da99db1..bd5fbd43f9 100644 --- a/packages/flutter/lib/src/rendering/binding.dart +++ b/packages/flutter/lib/src/rendering/binding.dart @@ -132,7 +132,11 @@ class _PointerEventConverter { return 'mouse'; case PointerKind.STYLUS: return 'stylus'; + case PointerKind.INVERTED_STYLUS: + return 'invertedStylus'; } + assert(false); + return ''; } } diff --git a/packages/flutter_sprites/lib/src/sound.dart b/packages/flutter_sprites/lib/src/sound.dart index aadf317143..1ebd43d40a 100644 --- a/packages/flutter_sprites/lib/src/sound.dart +++ b/packages/flutter_sprites/lib/src/sound.dart @@ -3,7 +3,7 @@ part of flutter_sprites; // TODO: The sound effects should probably use Android's SoundPool instead of // MediaPlayer as it is more efficient and flexible for playing back sound effects -typedef void SoundEffectStreamCallback(SoundEffectStream); +typedef void SoundEffectStreamCallback(SoundEffectStream stream); class SoundEffect { SoundEffect(this._pipeFuture); @@ -137,8 +137,8 @@ class SoundEffectPlayer { } } -typedef void SoundTrackCallback(SoundTrack); -typedef void SoundTrackBufferingCallback(SoundTrack, int); +typedef void SoundTrackCallback(SoundTrack soundTrack); +typedef void SoundTrackBufferingCallback(SoundTrack soundTrack, int index); class SoundTrack { MediaPlayerProxy _player; diff --git a/packages/flutter_tools/lib/executable.dart b/packages/flutter_tools/lib/executable.dart index 39b4e26910..44cabd491b 100644 --- a/packages/flutter_tools/lib/executable.dart +++ b/packages/flutter_tools/lib/executable.dart @@ -9,6 +9,7 @@ import 'package:args/command_runner.dart'; import 'package:logging/logging.dart'; import 'package:stack_trace/stack_trace.dart'; +import 'src/commands/analyze.dart'; import 'src/commands/build.dart'; import 'src/commands/cache.dart'; import 'src/commands/daemon.dart'; @@ -46,6 +47,7 @@ Future main(List args) async { }); FlutterCommandRunner runner = new FlutterCommandRunner() + ..addCommand(new AnalyzeCommand()) ..addCommand(new BuildCommand()) ..addCommand(new CacheCommand()) ..addCommand(new DaemonCommand()) diff --git a/packages/flutter_tools/lib/src/artifacts.dart b/packages/flutter_tools/lib/src/artifacts.dart index 6310851bbf..c0e49388f6 100644 --- a/packages/flutter_tools/lib/src/artifacts.dart +++ b/packages/flutter_tools/lib/src/artifacts.dart @@ -48,9 +48,9 @@ String _getCloudStorageBaseUrl({String category, String platform, String revisio // In the fullness of time, we'll have a consistent URL pattern for all of // our artifacts, but, for the time being, Mac OS X artifacts are stored in a // different cloud storage bucket. - return 'https://storage.googleapis.com/mojo_infra/flutter/${platform}/${revision}/'; + return 'https://storage.googleapis.com/mojo_infra/flutter/$platform/$revision/'; } - return 'https://storage.googleapis.com/mojo/sky/${category}/${platform}/${revision}/'; + return 'https://storage.googleapis.com/mojo/sky/$category/$platform/$revision/'; } enum ArtifactType { diff --git a/packages/flutter_tools/lib/src/commands/analyze.dart b/packages/flutter_tools/lib/src/commands/analyze.dart new file mode 100644 index 0000000000..f4fa293240 --- /dev/null +++ b/packages/flutter_tools/lib/src/commands/analyze.dart @@ -0,0 +1,302 @@ +// Copyright 2015 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +import 'dart:async'; +import 'dart:convert'; +import 'dart:io'; + +import 'package:logging/logging.dart'; +import 'package:path/path.dart' as path; + +import '../artifacts.dart'; +import '../build_configuration.dart'; +import '../process.dart'; +import 'flutter_command.dart'; + +final Logger _logging = new Logger('sky_tools.analyze'); + +class AnalyzeCommand extends FlutterCommand { + String get name => 'analyze'; + String get description => 'Runs a carefully configured dartanalyzer over the current project\'s dart code.'; + + AnalyzeCommand() { + argParser.addFlag('flutter-repo', help: 'Include all the examples and tests from the Flutter repository.', defaultsTo: false); + argParser.addFlag('current-directory', help: 'Include all the Dart files in the current directory, if any.', defaultsTo: true); + argParser.addFlag('current-package', help: 'Include the lib/main.dart file from the current directory, if any.', defaultsTo: true); + argParser.addFlag('congratulate', help: 'Show output even when there are no errors, warnings, hints, or lints.', defaultsTo: true); + } + + bool get requiresProjectRoot => false; + + @override + Future runInProject() async { + Set pubSpecDirectories = new Set(); + List dartFiles = argResults.rest.toList(); + + for (String file in dartFiles) { + // TODO(ianh): figure out how dartanalyzer decides which .packages file to use when given a random file + pubSpecDirectories.add(path.dirname(file)); + } + + if (argResults['flutter-repo']) { + // .../examples/*/*.dart + // .../examples/*/lib/main.dart + Directory examples = new Directory(path.join(ArtifactStore.flutterRoot, 'examples')); + for (FileSystemEntity entry in examples.listSync()) { + if (entry is Directory) { + bool foundOne = false; + for (FileSystemEntity subentry in entry.listSync()) { + if (subentry is File && subentry.path.endsWith('.dart')) { + dartFiles.add(subentry.path); + foundOne = true; + } else if (subentry is Directory && path.basename(subentry.path) == 'lib') { + String mainPath = path.join(subentry.path, 'main.dart'); + if (FileSystemEntity.isFileSync(mainPath)) { + dartFiles.add(mainPath); + foundOne = true; + } + } + } + if (foundOne) + pubSpecDirectories.add(entry.path); + } + } + + bool foundTest = false; + Directory flutterDir = new Directory(path.join(ArtifactStore.flutterRoot, 'packages/unit')); // See https://github.com/flutter/flutter/issues/50 + + // .../packages/unit/test/*/*_test.dart + Directory tests = new Directory(path.join(flutterDir.path, 'test')); + for (FileSystemEntity entry in tests.listSync()) { + if (entry is Directory) { + for (FileSystemEntity subentry in entry.listSync()) { + if (subentry is File && subentry.path.endsWith('_test.dart')) { + dartFiles.add(subentry.path); + foundTest = true; + } + } + } + } + + // .../packages/unit/benchmark/*/*_bench.dart + Directory benchmarks = new Directory(path.join(flutterDir.path, 'benchmark')); + for (FileSystemEntity entry in benchmarks.listSync()) { + if (entry is Directory) { + for (FileSystemEntity subentry in entry.listSync()) { + if (subentry is File && subentry.path.endsWith('_bench.dart')) { + dartFiles.add(subentry.path); + foundTest = true; + } + } + } + } + + if (foundTest) + pubSpecDirectories.add(flutterDir.path); + + // .../packages/*/bin/*.dart + Directory packages = new Directory(path.join(ArtifactStore.flutterRoot, 'packages')); + for (FileSystemEntity entry in packages.listSync()) { + if (entry is Directory) { + bool foundOne = false; + Directory binDirectory = new Directory(path.join(entry.path, 'bin')); + if (binDirectory.existsSync()) { + for (FileSystemEntity subentry in binDirectory.listSync()) { + if (subentry is File && subentry.path.endsWith('.dart')) { + dartFiles.add(subentry.path); + foundOne = true; + } + } + } + if (foundOne) + pubSpecDirectories.add(entry.path); + } + } + } + + if (argResults['current-directory']) { + // ./*.dart + Directory currentDirectory = new Directory('.'); + bool foundOne = false; + for (FileSystemEntity entry in currentDirectory.listSync()) { + if (entry is File && entry.path.endsWith('.dart')) { + dartFiles.add(entry.path); + foundOne = true; + } + } + if (foundOne) + pubSpecDirectories.add('.'); + } + + if (argResults['current-package']) { + // ./lib/main.dart + String mainPath = 'lib/main.dart'; + if (FileSystemEntity.isFileSync(mainPath)) { + dartFiles.add(mainPath); + pubSpecDirectories.add('.'); + } + } + + // prepare a Dart file that references all the above Dart files + StringBuffer mainBody = new StringBuffer(); + for (int index = 0; index < dartFiles.length; index += 1) { + mainBody.writeln('import \'${path.normalize(path.absolute(dartFiles[index]))}\' as file$index;'); + } + mainBody.writeln('void main() { }'); + + // prepare a union of all the .packages files + Map packages = {}; + bool hadInconsistentRequirements = false; + for (Directory directory in pubSpecDirectories.map((path) => new Directory(path))) { + File dotPackages = new File(path.join(directory.path, '.packages')); + if (dotPackages.existsSync()) { + Map dependencies = {}; + dotPackages + .readAsStringSync() + .split('\n') + .where((line) => !line.startsWith(new RegExp(r'^ *#'))) + .forEach((line) { + int colon = line.indexOf(':'); + if (colon > 0) + dependencies[line.substring(0, colon)] = path.normalize(path.absolute(directory.path, path.fromUri(line.substring(colon+1)))); + }); + for (String package in dependencies.keys) { + if (packages.containsKey(package)) { + if (packages[package] != dependencies[package]) { + _logging.warning('Inconsistent requirements for $package; using ${packages[package]} (and not ${dependencies[package]}).'); + hadInconsistentRequirements = true; + } + } else { + packages[package] = dependencies[package]; + } + } + } + } + if (hadInconsistentRequirements) { + if (argResults['flutter-repo']) + _logging.warning('You may need to run "dart ${path.normalize(path.relative(path.join(ArtifactStore.flutterRoot, 'dev/update_packages.dart')))}".'); + if (argResults['current-directory'] || argResults['current-package']) + _logging.warning('You may need to run "pub get".'); + } + + String buildDir = buildConfigurations.firstWhere((BuildConfiguration config) => config.testable, orElse: () => null)?.buildDir; + if (buildDir != null) { + packages['sky_engine'] = path.join(buildDir, 'gen/dart-pkg/sky_engine/lib'); + packages['sky_services'] = path.join(buildDir, 'gen/dart-pkg/sky_services/lib'); + } + + StringBuffer packagesBody = new StringBuffer(); + for (String package in packages.keys) + packagesBody.writeln('$package:${path.toUri(packages[package])}'); + + // save the Dart file and the .packages file to disk + Directory host = Directory.systemTemp.createTempSync('flutter-analyze-'); + File mainFile = new File(path.join(host.path, 'main.dart'))..writeAsStringSync(mainBody.toString()); + File packagesFile = new File(path.join(host.path, '.packages'))..writeAsStringSync(packagesBody.toString()); + + List cmd = [ + sdkBinaryName('dartanalyzer'), + // do not set '--warnings', since that will include the entire Dart SDK + '--ignore-unrecognized-flags', + '--supermixin', + '--enable-strict-call-checks', + '--enable_type_checks', + '--strong', + '--package-warnings', + '--fatal-warnings', + '--strong-hints', + '--fatal-hints', + '--lints', + '--packages', packagesFile.path, + mainFile.path + ]; + + _logging.info(cmd.join(' ')); + Process process = await Process.start( + cmd[0], + cmd.sublist(1), + workingDirectory: host.path + ); + int errorCount = 0; + StringBuffer output = new StringBuffer(); + process.stdout.transform(UTF8.decoder).listen((String data) { + output.write(data); + }); + process.stderr.transform(UTF8.decoder).listen((String data) { + // dartanalyzer doesn't seem to ever output anything on stderr + errorCount += 1; + print(data); + }); + + // host.deleteSync(); + + int exitCode = await process.exitCode; + + List patternsToSkip = [ + 'Analyzing [${mainFile.path}]...', + new RegExp('^\\[hint\\] Unused import \\(${mainFile.path},'), + new RegExp(r'^\[.+\] .+ \(.+/\.pub-cache/.+'), + new RegExp(r'^\[error\] Invalid override\. The type of [^ ]+ \(.+\) is not a subtype of [^ ]+ \(.+\)\.'), // we allow type narrowing + new RegExp(r'^\[warning\] .+ will need runtime check to cast to type .+'), // https://github.com/dart-lang/sdk/issues/24542 + new RegExp(r'^\[error\] Type check failed: .*\(dynamic\) is not of type'), // allow unchecked casts from dynamic + new RegExp('^\\[error\\] Target of URI does not exist: \'dart:ui_internals\''), // https://github.com/flutter/flutter/issues/83 + new RegExp(r'\[lint\] Prefer using lowerCamelCase for constant names.'), // sometimes we have no choice (e.g. when matching other platforms) + new RegExp(r'\[lint\] Avoid defining a one-member abstract class when a simple function will do.'), // too many false-positives; code review should catch real instances + new RegExp(r'\[0-9]+ (error|warning|hint|lint).+found\.'), + '', + ]; + + RegExp generalPattern = new RegExp(r'^\[(error|warning|hint|lint)\] (.+) \(([^(),]+), line ([0-9]+), col ([0-9]+)\)$'); + RegExp ignorePattern = new RegExp(r'// analyzer says "([^"]+)"'); + RegExp constructorTearOffsPattern = new RegExp('.+#.+// analyzer doesn\'t like constructor tear-offs'); + RegExp allowedIdentifiers = new RegExp(r'_?([A-Z]|_+)\b'); + + List errorLines = output.toString().split('\n'); + for (String errorLine in errorLines) { + if (patternsToSkip.every((Pattern pattern) => pattern.allMatches(errorLine).isEmpty)) { + Match groups = generalPattern.firstMatch(errorLine); + if (groups != null) { + String level = groups[1]; + String filename = groups[3]; + String errorMessage = groups[2]; + int lineNumber = int.parse(groups[4]); + int colNumber = int.parse(groups[5]); + File source = new File(filename); + List sourceLines = source.readAsLinesSync(); + String sourceLine = sourceLines[lineNumber-1]; + bool shouldIgnore = false; + if (filename.endsWith('.mojom.dart')) { + shouldIgnore = true; + } else if (level == 'lint' && errorMessage == 'Name non-constant identifiers using lowerCamelCase.') { + if (allowedIdentifiers.matchAsPrefix(sourceLine, colNumber-1) != null) + shouldIgnore = true; + } else if (constructorTearOffsPattern.allMatches(sourceLine).isNotEmpty) { + shouldIgnore = true; + } else { + Iterable ignoreGroups = ignorePattern.allMatches(sourceLine); + for (Match ignoreGroup in ignoreGroups) { + if (errorMessage.contains(ignoreGroup[1])) { + shouldIgnore = true; + break; + } + } + } + if (shouldIgnore) + continue; + } + print(errorLine); + errorCount += 1; + } + } + + if (exitCode < 0 || exitCode > 3) // 1 = hints, 2 = warnings, 3 = errors + return exitCode; + + if (errorCount > 1) + return 1; + if (argResults['congratulate']) + print('No analyzer warnings!'); + return 0; + } +} \ No newline at end of file diff --git a/packages/flutter_tools/lib/src/commands/build.dart b/packages/flutter_tools/lib/src/commands/build.dart index caee7e6939..b892c10d59 100644 --- a/packages/flutter_tools/lib/src/commands/build.dart +++ b/packages/flutter_tools/lib/src/commands/build.dart @@ -93,7 +93,7 @@ dynamic _loadManifest(String manifestPath) { } ArchiveFile _createFile(String key, String assetBase) { - File file = new File('${assetBase}/${key}'); + File file = new File('$assetBase/$key'); if (!file.existsSync()) return null; List content = file.readAsBytesSync(); diff --git a/packages/flutter_tools/lib/src/commands/daemon.dart b/packages/flutter_tools/lib/src/commands/daemon.dart index cd82c5fab0..e39129699d 100644 --- a/packages/flutter_tools/lib/src/commands/daemon.dart +++ b/packages/flutter_tools/lib/src/commands/daemon.dart @@ -60,7 +60,7 @@ class DaemonCommand extends FlutterCommand { stdout.writeln('[${JSON.encode(command)}]'); }, daemonCommand: this); - return daemon.onExit; + return await daemon.onExit; } } @@ -98,19 +98,19 @@ class Daemon { var id = command['id']; if (id == null) { - _logging.severe('no id for command: ${command}'); + _logging.severe('no id for command: $command'); return; } try { String event = command['event']; if (event.indexOf('.') == -1) - throw 'command not understood: ${event}'; + throw 'command not understood: $event'; String prefix = event.substring(0, event.indexOf('.')); String name = event.substring(event.indexOf('.') + 1); if (_domains[prefix] == null) - throw 'no domain for command: ${command}'; + throw 'no domain for command: $command'; _domains[prefix].handleEvent(name, id, command['params']); } catch (error, trace) { @@ -144,7 +144,7 @@ abstract class Domain { new Future.sync(() { if (_handlers.containsKey(name)) return _handlers[name](args); - throw 'command not understood: ${name}'; + throw 'command not understood: $name'; }).then((result) { if (result == null) { _send({'id': id}); @@ -153,7 +153,7 @@ abstract class Domain { } }).catchError((error, trace) { _send({'id': id, 'error': _toJsonable(error)}); - _logging.warning('error handling ${name}', error, trace); + _logging.warning('error handling $name', error, trace); }); } @@ -210,5 +210,5 @@ class AppDomain extends Domain { dynamic _toJsonable(dynamic obj) { if (obj is String || obj is int || obj is bool || obj is Map || obj is List || obj == null) return obj; - return '${obj}'; + return '$obj'; } diff --git a/packages/flutter_tools/lib/src/commands/flutter_command.dart b/packages/flutter_tools/lib/src/commands/flutter_command.dart index f31ad565c9..4ff335a0d7 100644 --- a/packages/flutter_tools/lib/src/commands/flutter_command.dart +++ b/packages/flutter_tools/lib/src/commands/flutter_command.dart @@ -56,7 +56,7 @@ abstract class FlutterCommand extends Command { } } - return runInProject(); + return await runInProject(); } Future runInProject(); diff --git a/packages/flutter_tools/lib/src/commands/init.dart b/packages/flutter_tools/lib/src/commands/init.dart index 85ef4415cd..3c705315e4 100644 --- a/packages/flutter_tools/lib/src/commands/init.dart +++ b/packages/flutter_tools/lib/src/commands/init.dart @@ -6,12 +6,15 @@ import 'dart:async'; import 'dart:io'; import 'package:args/command_runner.dart'; +import 'package:logging/logging.dart'; import 'package:mustache4dart/mustache4dart.dart' as mustache; import 'package:path/path.dart' as p; import '../artifacts.dart'; import '../process.dart'; +final Logger _logging = new Logger('sky_tools.init'); + class InitCommand extends Command { final String name = 'init'; final String description = 'Create a new Flutter project.'; @@ -20,7 +23,7 @@ class InitCommand extends Command { argParser.addOption('out', abbr: 'o', help: 'The output directory.'); argParser.addFlag('pub', defaultsTo: true, - help: 'Whether to run pub after the project has been created.'); + help: 'Whether to run "pub get" after the project has been created.'); } @override @@ -40,7 +43,7 @@ class InitCommand extends Command { String flutterPackagePath = p.join(flutterRoot, 'packages', 'flutter'); if (!FileSystemEntity.isFileSync(p.join(flutterPackagePath, 'pubspec.yaml'))) { - print('Unable to find package:flutter in ${flutterPackagePath}'); + print('Unable to find package:flutter in $flutterPackagePath'); return 2; } @@ -58,11 +61,7 @@ class InitCommand extends Command { '''; if (argResults['pub']) { - print("Running pub get..."); - int code = await runCommandAndStreamOutput( - [sdkBinaryName('pub'), 'get'], - workingDirectory: out.path - ); + int code = await pubGet(directory: out.path); if (code != 0) return code; } @@ -70,6 +69,41 @@ class InitCommand extends Command { print(message); return 0; } + + Future pubGet({ + String directory: '', + bool skipIfAbsent: false, + bool verbose: true + }) async { + File pubSpecYaml = new File(p.join(directory, 'pubspec.yaml')); + File pubSpecLock = new File(p.join(directory, 'pubspec.lock')); + File dotPackages = new File(p.join(directory, '.packages')); + + if (!pubSpecYaml.existsSync()) { + if (skipIfAbsent) + return 0; + _logging.severe('$directory: no pubspec.yaml found'); + return 1; + } + + if (!pubSpecLock.existsSync() || pubSpecYaml.lastModifiedSync().isAfter(pubSpecLock.lastModifiedSync())) { + if (verbose) + print("Running pub get in $directory..."); + int code = await runCommandAndStreamOutput( + [sdkBinaryName('pub'), 'get'], + workingDirectory: directory + ); + if (code != 0) + return code; + } + + if ((pubSpecLock.existsSync() && pubSpecLock.lastModifiedSync().isAfter(pubSpecYaml.lastModifiedSync())) && + (dotPackages.existsSync() && dotPackages.lastModifiedSync().isAfter(pubSpecYaml.lastModifiedSync()))) + return 0; + + _logging.severe('$directory: pubspec.yaml, pubspec.lock, and .packages are in an inconsistent state'); + return 1; + } } abstract class Template { diff --git a/packages/flutter_tools/lib/src/commands/run_mojo.dart b/packages/flutter_tools/lib/src/commands/run_mojo.dart index ce0a187547..516673ef6e 100644 --- a/packages/flutter_tools/lib/src/commands/run_mojo.dart +++ b/packages/flutter_tools/lib/src/commands/run_mojo.dart @@ -34,7 +34,7 @@ class RunMojoCommand extends Command { String _makePathAbsolute(String relativePath) { File file = new File(relativePath); if (!file.existsSync()) { - throw new Exception("Path \"${relativePath}\" does not exist"); + throw new Exception('Path "$relativePath" does not exist'); } return file.absolute.path; } @@ -79,8 +79,8 @@ class RunMojoCommand extends Command { final appPath = _makePathAbsolute(argResults['app']); Artifact artifact = ArtifactStore.getArtifact(type: ArtifactType.viewer, targetPlatform: TargetPlatform.linux); final viewerPath = _makePathAbsolute(await ArtifactStore.getPath(artifact)); - args.add('file://${appPath}'); - args.add('--url-mappings=mojo:sky_viewer=file://${viewerPath}'); + args.add('file://$appPath'); + args.add('--url-mappings=mojo:sky_viewer=file://$viewerPath'); } if (useDevtools) { @@ -115,6 +115,6 @@ class RunMojoCommand extends Command { return 1; } - return runCommandAndStreamOutput(await _getShellConfig()); + return await runCommandAndStreamOutput(await _getShellConfig()); } } diff --git a/packages/flutter_tools/lib/src/commands/start.dart b/packages/flutter_tools/lib/src/commands/start.dart index ce60e438c5..842d909b39 100644 --- a/packages/flutter_tools/lib/src/commands/start.dart +++ b/packages/flutter_tools/lib/src/commands/start.dart @@ -68,7 +68,7 @@ class StartCommand extends FlutterCommand { if (FileSystemEntity.isDirectorySync(target)) mainPath = path.join(target, 'lib', 'main.dart'); if (!FileSystemEntity.isFileSync(mainPath)) { - String message = 'Tried to run ${mainPath}, but that file does not exist.'; + String message = 'Tried to run $mainPath, but that file does not exist.'; if (!argResults.wasParsed('target')) message += '\nConsider using the -t option to specify that Dart file to start.'; stderr.writeln(message); diff --git a/packages/flutter_tools/lib/src/commands/test.dart b/packages/flutter_tools/lib/src/commands/test.dart index 8c3202c60d..5d268345c9 100644 --- a/packages/flutter_tools/lib/src/commands/test.dart +++ b/packages/flutter_tools/lib/src/commands/test.dart @@ -11,7 +11,6 @@ import 'package:test/src/executable.dart' as executable; import '../artifacts.dart'; import '../build_configuration.dart'; -import '../process.dart'; import '../test/loader.dart' as loader; import 'flutter_command.dart'; @@ -49,25 +48,6 @@ class TestCommand extends FlutterCommand { testArgs.insert(0, '--no-color'); List configs = buildConfigurations; bool foundOne = false; - - File pubSpecYaml = new File(path.join(flutterDir.path, 'pubspec.yaml')); - File pubSpecLock = new File(path.join(flutterDir.path, 'pubspec.lock')); - - if (!pubSpecYaml.existsSync()) { - print('${flutterDir.path} has no pubspec.yaml'); - return 1; - } - - if (!pubSpecLock.existsSync() || pubSpecYaml.lastModifiedSync().isAfter(pubSpecLock.lastModifiedSync())) { - print("Running pub get..."); - int code = await runCommandAndStreamOutput( - [sdkBinaryName('pub'), 'get'], - workingDirectory: flutterDir.path - ); - if (code != 0) - return code; - } - String currentDirectory = Directory.current.path; Directory.current = flutterDir.path; loader.installHook(); diff --git a/packages/flutter_tools/lib/src/device.dart b/packages/flutter_tools/lib/src/device.dart index d0ef982a83..47831e83f1 100644 --- a/packages/flutter_tools/lib/src/device.dart +++ b/packages/flutter_tools/lib/src/device.dart @@ -19,36 +19,8 @@ abstract class Device { final String id; static Map _deviceCache = {}; - factory Device._unique(String className, [String id = null]) { - if (id == null) { - if (className == AndroidDevice.className) { - id = AndroidDevice.defaultDeviceID; - } else if (className == IOSDevice.className) { - id = IOSDevice.defaultDeviceID; - } else if (className == IOSSimulator.className) { - id = IOSSimulator.defaultDeviceID; - } else { - throw 'Attempted to create a Device of unknown type $className'; - } - } - - return _deviceCache.putIfAbsent(id, () { - if (className == AndroidDevice.className) { - final device = new AndroidDevice._(id); - _deviceCache[id] = device; - return device; - } else if (className == IOSDevice.className) { - final device = new IOSDevice._(id); - _deviceCache[id] = device; - return device; - } else if (className == IOSSimulator.className) { - final device = new IOSSimulator._(id); - _deviceCache[id] = device; - return device; - } else { - throw 'Attempted to create a Device of unknown type $className'; - } - }); + static Device _unique(String id, Device constructor(String id)) { + return _deviceCache.putIfAbsent(id, () => constructor(id)); } Device._(this.id); @@ -74,7 +46,6 @@ abstract class Device { } class IOSDevice extends Device { - static const String className = 'IOSDevice'; static final String defaultDeviceID = 'default_ios_id'; static const String _macInstructions = @@ -108,7 +79,7 @@ class IOSDevice extends Device { String get name => _name; factory IOSDevice({String id, String name}) { - IOSDevice device = new Device._unique(className, id); + IOSDevice device = Device._unique(id ?? defaultDeviceID, new IOSDevice#_); // analyzer doesn't like constructor tear-offs device._name = name; return device; } @@ -224,14 +195,17 @@ class IOSDevice extends Device { } // idevicedebug hangs forever after launching the app, so kill it after // giving it plenty of time to send the launch command. - return runAndKill( - [debuggerPath, 'run', app.id], new Duration(seconds: 3)).then( - (_) { - return true; - }, onError: (e) { - _logging.info('Failure running $debuggerPath: ', e); - return false; - }); + return await runAndKill( + [debuggerPath, 'run', app.id], + new Duration(seconds: 3) + ).then( + (_) { + return true; + }, onError: (e) { + _logging.info('Failure running $debuggerPath: ', e); + return false; + } + ); } @override @@ -274,13 +248,12 @@ class IOSDevice extends Device { if (!isConnected()) { return 2; } - return runCommandAndStreamOutput([loggerPath], + return await runCommandAndStreamOutput([loggerPath], prefix: 'iOS dev: ', filter: new RegExp(r'.*SkyShell.*')); } } class IOSSimulator extends Device { - static const String className = 'IOSSimulator'; static final String defaultDeviceID = 'default_ios_sim_id'; static const String _macInstructions = @@ -299,7 +272,7 @@ class IOSSimulator extends Device { String get name => _name; factory IOSSimulator({String id, String name, String iOSSimulatorPath}) { - IOSSimulator device = new Device._unique(className, id); + IOSSimulator device = Device._unique(id ?? defaultDeviceID, new IOSSimulator#_); // analyzer doesn't like constructor tear-offs device._name = name; if (iOSSimulatorPath == null) { iOSSimulatorPath = path.join('/Applications', 'iOS Simulator.app', @@ -309,7 +282,7 @@ class IOSSimulator extends Device { return device; } - IOSSimulator._(String id) : super._(id) {} + IOSSimulator._(String id) : super._(id); static String _getRunningSimulatorID([IOSSimulator mockIOS]) { String xcrunPath = mockIOS != null ? mockIOS.xcrunPath : _xcrunPath; @@ -380,7 +353,6 @@ class IOSSimulator extends Device { } if (id == defaultDeviceID) { runDetached([iOSSimPath]); - Future checkConnection([int attempts = 20]) async { if (attempts == 0) { _logging.info('Timed out waiting for iOS Simulator $id to boot.'); @@ -388,12 +360,12 @@ class IOSSimulator extends Device { } if (!isConnected()) { _logging.info('Waiting for iOS Simulator $id to boot...'); - return new Future.delayed(new Duration(milliseconds: 500), + return await new Future.delayed(new Duration(milliseconds: 500), () => checkConnection(attempts - 1)); } return true; } - return checkConnection(); + return await checkConnection(); } else { try { runCheckedSync([xcrunPath, 'simctl', 'boot', id]); @@ -496,7 +468,7 @@ class IOSSimulator extends Device { if (clear) { runSync(['rm', logFilePath]); } - return runCommandAndStreamOutput(['tail', '-f', logFilePath], + return await runCommandAndStreamOutput(['tail', '-f', logFilePath], prefix: 'iOS sim: ', filter: new RegExp(r'.*SkyShell.*')); } } @@ -505,7 +477,6 @@ class AndroidDevice extends Device { static const String _ADB_PATH = 'adb'; static const int _observatoryPort = 8181; - static const String className = 'AndroidDevice'; static final String defaultDeviceID = 'default_android_device'; String productID; @@ -522,7 +493,7 @@ class AndroidDevice extends Device { String productID: null, String modelID: null, String deviceCodeName: null}) { - AndroidDevice device = new Device._unique(className, id); + AndroidDevice device = Device._unique(id ?? defaultDeviceID, new AndroidDevice#_); // analyzer doesn't like constructor tear-offs device.productID = productID; device.modelID = modelID; device.deviceCodeName = deviceCodeName; @@ -813,7 +784,7 @@ class AndroidDevice extends Device { clearLogs(); } - return runCommandAndStreamOutput(adbCommandForDevice([ + return await runCommandAndStreamOutput(adbCommandForDevice([ 'logcat', '-v', 'tag', // Only log the tag and the message diff --git a/packages/flutter_tools/lib/src/process.dart b/packages/flutter_tools/lib/src/process.dart index 3259b69e00..c0a547ff80 100644 --- a/packages/flutter_tools/lib/src/process.dart +++ b/packages/flutter_tools/lib/src/process.dart @@ -18,33 +18,35 @@ Future runCommandAndStreamOutput(List cmd, { String workingDirectory }) async { _logging.info(cmd.join(' ')); - Process proc = await Process.start( + Process process = await Process.start( cmd[0], - cmd.getRange(1, cmd.length).toList(), + cmd.sublist(1), workingDirectory: workingDirectory ); - proc.stdout.transform(UTF8.decoder).listen((String data) { + process.stdout.transform(UTF8.decoder).listen((String data) { List dataLines = data.trimRight().split('\n'); if (filter != null) { + // TODO(ianh): This doesn't handle IO buffering (where the data might be split half-way through a line) dataLines = dataLines.where((String s) => filter.hasMatch(s)).toList(); } if (dataLines.length > 0) { stdout.write('$prefix${dataLines.join('\n$prefix')}\n'); } }); - proc.stderr.transform(UTF8.decoder).listen((String data) { + process.stderr.transform(UTF8.decoder).listen((String data) { List dataLines = data.trimRight().split('\n'); if (filter != null) { + // TODO(ianh): This doesn't handle IO buffering (where the data might be split half-way through a line) dataLines = dataLines.where((String s) => filter.hasMatch(s)); } if (dataLines.length > 0) { stderr.write('$prefix${dataLines.join('\n$prefix')}\n'); } }); - return proc.exitCode; + return await process.exitCode; } -Future runAndKill(List cmd, Duration timeout) async { +Future runAndKill(List cmd, Duration timeout) { Future proc = runDetached(cmd); return new Future.delayed(timeout, () async { _logging.info('Intentionally killing ${cmd[0]}'); @@ -52,7 +54,7 @@ Future runAndKill(List cmd, Duration timeout) async { }); } -Future runDetached(List cmd) async { +Future runDetached(List cmd) { _logging.info(cmd.join(' ')); Future proc = Process.start( cmd[0], cmd.getRange(1, cmd.length).toList(), @@ -97,6 +99,6 @@ String _runWithLoggingSync(List cmd, {bool checked: false}) { class ProcessExit implements Exception { final int exitCode; ProcessExit(this.exitCode); - String get message => 'ProcessExit: ${exitCode}'; + String get message => 'ProcessExit: $exitCode'; String toString() => message; } diff --git a/packages/newton/lib/src/friction_simulation.dart b/packages/newton/lib/src/friction_simulation.dart index 38671b99d3..17d1ccc014 100644 --- a/packages/newton/lib/src/friction_simulation.dart +++ b/packages/newton/lib/src/friction_simulation.dart @@ -50,8 +50,9 @@ class BoundedFrictionSimulation extends FrictionSimulation { double drag, double position, double velocity, - double this._minX, - double this._maxX) : super(drag, position, velocity); + this._minX, + this._maxX + ) : super(drag, position, velocity); final double _minX; final double _maxX; diff --git a/packages/newton/lib/src/spring_simulation.dart b/packages/newton/lib/src/spring_simulation.dart index 143f96c77f..f0f216a6c5 100644 --- a/packages/newton/lib/src/spring_simulation.dart +++ b/packages/newton/lib/src/spring_simulation.dart @@ -17,7 +17,8 @@ class SpringDescription { final double damping; SpringDescription( - {double this.mass, double this.springConstant, double this.damping}) { + { this.mass, this.springConstant, this.damping } + ) { assert(mass != null); assert(springConstant != null); assert(damping != null); diff --git a/packages/unit/benchmark/gestures/velocity_tracker_bench.dart b/packages/unit/benchmark/gestures/velocity_tracker_bench.dart index cf89561a0c..94c6a15ca3 100644 --- a/packages/unit/benchmark/gestures/velocity_tracker_bench.dart +++ b/packages/unit/benchmark/gestures/velocity_tracker_bench.dart @@ -1,5 +1,3 @@ -import 'dart:ui' as ui; - import 'package:flutter/gestures.dart'; import 'package:test/test.dart'; import 'velocity_tracker_data.dart'; @@ -45,20 +43,4 @@ void main() { watch.stop(); print("Dart tracker: " + watch.elapsed.toString()); }); - - test('Native velocity tracker performance', () { - ui.VelocityTracker tracker = new ui.VelocityTracker(); - Stopwatch watch = new Stopwatch(); - watch.start(); - for (int i = 0; i < kNumIters; i++) { - for (PointerInputEvent event in events) { - if (event.type == 'pointerdown' || event.type == 'pointermove') - tracker.addPosition((event.timeStamp*1000.0).toInt(), event.x, event.y); - if (event.type == 'pointerup') - tracker.getVelocity(); - } - } - watch.stop(); - print("Native tracker: " + watch.elapsed.toString()); - }); } diff --git a/packages/unit/test/rendering/image_test.dart b/packages/unit/test/rendering/image_test.dart index b794f84a18..48996734d0 100644 --- a/packages/unit/test/rendering/image_test.dart +++ b/packages/unit/test/rendering/image_test.dart @@ -8,16 +8,19 @@ import 'rendering_tester.dart'; class SquareImage implements ui.Image { int get width => 10; int get height => 10; + void dispose() { } } class WideImage implements ui.Image { int get width => 20; int get height => 10; + void dispose() { } } class TallImage implements ui.Image { int get width => 10; int get height => 20; + void dispose() { } } void main() { diff --git a/travis/test.sh b/travis/test.sh index 2e1597bea8..fa5e48b779 100755 --- a/travis/test.sh +++ b/travis/test.sh @@ -1,12 +1,16 @@ #!/bin/bash set -ex -(cd packages/cassowary; pub global run tuneup check; pub run test -j1) -(cd packages/flutter_sprites; pub global run tuneup check) # No tests to run. -(cd packages/flutter_tools; pub global run tuneup check; pub run test -j1) -(cd packages/flx; pub global run tuneup check; pub run test -j1) -(cd packages/newton; pub global run tuneup check; pub run test -j1) -(cd packages/playfair; pub global run tuneup check) # No tests to run. -(cd packages/updater; pub global run tuneup check) # No tests to run. +# analyze all the Dart code in the repo +./bin/flutter analyze --flutter-repo --no-current-directory --no-current-package --congratulate +# flutter package tests ./bin/flutter test --engine-src-path bin/cache/travis + +(cd packages/cassowary; pub run test -j1) +# (cd packages/flutter_sprites; ) # No tests to run. +(cd packages/flutter_tools; pub run test -j1) +(cd packages/flx; pub run test -j1) +(cd packages/newton; pub run test -j1) +# (cd packages/playfair; ) # No tests to run. +# (cd packages/updater; ) # No tests to run.