// Copyright 2017 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' as io; import 'package:file/file.dart'; import 'package:http/http.dart' as http; import 'package:path/path.dart' as p; import 'package:quiver/iterables.dart'; import 'common.dart'; const String _googleFormatterUrl = 'https://github.com/google/google-java-format/releases/download/google-java-format-1.3/google-java-format-1.3-all-deps.jar'; class FormatCommand extends PluginCommand { FormatCommand( Directory packagesDir, FileSystem fileSystem, { ProcessRunner processRunner = const ProcessRunner(), }) : super(packagesDir, fileSystem, processRunner: processRunner) { argParser.addFlag('travis', hide: true); argParser.addOption('clang-format', defaultsTo: 'clang-format', help: 'Path to executable of clang-format v5.'); } @override final String name = 'format'; @override final String description = 'Formats the code of all packages (Java, Objective-C, C++, and Dart).\n\n' 'This command requires "git", "flutter" and "clang-format" v5 to be in ' 'your path.'; @override Future run() async { checkSharding(); final String googleFormatterPath = await _getGoogleFormatterPath(); await _formatDart(); await _formatJava(googleFormatterPath); await _formatCppAndObjectiveC(); if (argResults['travis']) { final bool modified = await _didModifyAnything(); if (modified) { throw ToolExit(1); } } } Future _didModifyAnything() async { final io.ProcessResult modifiedFiles = await processRunner .runAndExitOnError('git', ['ls-files', '--modified'], workingDir: packagesDir); print('\n\n'); if (modifiedFiles.stdout.isEmpty) { print('All files formatted correctly.'); return false; } print('These files are not formatted correctly (see diff below):'); LineSplitter.split(modifiedFiles.stdout) .map((String line) => ' $line') .forEach(print); print('\nTo fix run "pub global activate flutter_plugin_tools && ' 'pub global run flutter_plugin_tools format" or copy-paste ' 'this command into your terminal:'); print('patch -p1 <['diff'], workingDir: packagesDir); print(diff.stdout); print('DONE'); return true; } Future _formatCppAndObjectiveC() async { print('Formatting all .cc, .cpp, .mm, .m, and .h files...'); final Iterable allFiles = [] ..addAll(await _getFilesWithExtension('.h')) ..addAll(await _getFilesWithExtension('.m')) ..addAll(await _getFilesWithExtension('.mm')) ..addAll(await _getFilesWithExtension('.cc')) ..addAll(await _getFilesWithExtension('.cpp')); // Split this into multiple invocations to avoid a // 'ProcessException: Argument list too long'. final Iterable> batches = partition(allFiles, 100); for (List batch in batches) { await processRunner.runAndStream(argResults['clang-format'], ['-i', '--style=Google']..addAll(batch), workingDir: packagesDir, exitOnError: true); } } Future _formatJava(String googleFormatterPath) async { print('Formatting all .java files...'); final Iterable javaFiles = await _getFilesWithExtension('.java'); await processRunner.runAndStream('java', ['-jar', googleFormatterPath, '--replace']..addAll(javaFiles), workingDir: packagesDir, exitOnError: true); } Future _formatDart() async { // This actually should be fine for non-Flutter Dart projects, no need to // specifically shell out to dartfmt -w in that case. print('Formatting all .dart files...'); final Iterable dartFiles = await _getFilesWithExtension('.dart'); if (dartFiles.isEmpty) { print( 'No .dart files to format. If you set the `--exclude` flag, most likey they were skipped'); } else { await processRunner.runAndStream( 'flutter', ['format']..addAll(dartFiles), workingDir: packagesDir, exitOnError: true); } } Future> _getFilesWithExtension(String extension) async => getFiles() .where((File file) => p.extension(file.path) == extension) .map((File file) => file.path) .toList(); Future _getGoogleFormatterPath() async { final String javaFormatterPath = p.join( p.dirname(p.fromUri(io.Platform.script)), 'google-java-format-1.3-all-deps.jar'); final File javaFormatterFile = fileSystem.file(javaFormatterPath); if (!javaFormatterFile.existsSync()) { print('Downloading Google Java Format...'); final http.Response response = await http.get(_googleFormatterUrl); javaFormatterFile.writeAsBytesSync(response.bodyBytes); } return javaFormatterPath; } }