// Copyright 2013 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 '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'; /// A command to format all package code. class FormatCommand extends PluginCommand { /// Creates an instance of the format command. FormatCommand( Directory packagesDir, FileSystem fileSystem, { ProcessRunner processRunner = const ProcessRunner(), }) : super(packagesDir, fileSystem, processRunner: processRunner) { argParser.addFlag('fail-on-change', hide: true); argParser.addOption('clang-format', defaultsTo: 'clang-format', help: 'Path to executable of clang-format.'); } @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 { final String googleFormatterPath = await _getGoogleFormatterPath(); await _formatDart(); await _formatJava(googleFormatterPath); await _formatCppAndObjectiveC(); if (argResults['fail-on-change'] == true) { final bool modified = await _didModifyAnything(); if (modified) { throw ToolExit(1); } } } Future _didModifyAnything() async { final io.ProcessResult modifiedFiles = await processRunner.run( 'git', ['ls-files', '--modified'], workingDir: packagesDir, exitOnError: true, logOnError: true, ); print('\n\n'); final String stdout = modifiedFiles.stdout as String; if (stdout.isEmpty) { print('All files formatted correctly.'); return false; } print('These files are not formatted correctly (see diff below):'); LineSplitter.split(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, exitOnError: true, logOnError: true, ); print(diff.stdout); print('DONE'); return true; } Future _formatCppAndObjectiveC() async { print('Formatting all .cc, .cpp, .mm, .m, and .h files...'); final Iterable allFiles = [ ...await _getFilesWithExtension('.h'), ...await _getFilesWithExtension('.m'), ...await _getFilesWithExtension('.mm'), ...await _getFilesWithExtension('.cc'), ...await _getFilesWithExtension('.cpp'), ]; // Split this into multiple invocations to avoid a // 'ProcessException: Argument list too long'. final Iterable> batches = partition(allFiles, 100); for (final List batch in batches) { await processRunner.runAndStream(argResults['clang-format'] as String, ['-i', '--style=Google', ...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', ...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', ...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; } }