diff --git a/script/tool/lib/src/format_command.dart b/script/tool/lib/src/format_command.dart index 27309d2d1759..0bd237186bc9 100644 --- a/script/tool/lib/src/format_command.dart +++ b/script/tool/lib/src/format_command.dart @@ -31,6 +31,7 @@ const int _exitFlutterFormatFailed = 4; const int _exitJavaFormatFailed = 5; const int _exitGitFailed = 6; const int _exitDependencyMissing = 7; +const int _exitSwiftFormatFailed = 8; final Uri _googleFormatterUrl = Uri.https('github.com', '/google/google-java-format/releases/download/google-java-format-1.3/google-java-format-1.3-all-deps.jar'); @@ -44,18 +45,25 @@ class FormatCommand extends PackageCommand { super.platform, }) { argParser.addFlag('fail-on-change', hide: true); - argParser.addOption('clang-format', + argParser.addOption(_clangFormatArg, defaultsTo: 'clang-format', help: 'Path to "clang-format" executable.'); - argParser.addOption('java', + argParser.addOption(_javaArg, defaultsTo: 'java', help: 'Path to "java" executable.'); + argParser.addOption(_swiftFormatArg, + help: 'Path to "swift-format" executable.'); } + static const String _clangFormatArg = 'clang-format'; + static const String _javaArg = 'java'; + static const String _swiftFormatArg = 'swift-format'; + @override final String name = 'format'; @override final String description = - 'Formats the code of all packages (Java, Objective-C, C++, and Dart).\n\n' + 'Formats the code of all packages (Java, Objective-C, C++, Dart, and ' + 'optionally Swift).\n\n' 'This command requires "git", "flutter" and "clang-format" v5 to be in ' 'your path.'; @@ -71,6 +79,10 @@ class FormatCommand extends PackageCommand { await _formatDart(files); await _formatJava(files, googleFormatterPath); await _formatCppAndObjectiveC(files); + final String? swiftFormat = getNullableStringArg(_swiftFormatArg); + if (swiftFormat != null) { + await _formatSwift(swiftFormat, files); + } if (getBoolArg('fail-on-change')) { final bool modified = await _didModifyAnything(); @@ -141,10 +153,24 @@ class FormatCommand extends PackageCommand { } } + Future _formatSwift(String swiftFormat, Iterable files) async { + final Iterable swiftFiles = + _getPathsWithExtensions(files, {'.swift'}); + if (swiftFiles.isNotEmpty) { + print('Formatting .swift files...'); + final int exitCode = + await _runBatched(swiftFormat, ['-i'], files: swiftFiles); + if (exitCode != 0) { + printError('Failed to format Swift files: exit code $exitCode.'); + throw ToolExit(_exitSwiftFormatFailed); + } + } + } + Future _findValidClangFormat() async { - final String clangFormatArg = getStringArg('clang-format'); - if (await _hasDependency(clangFormatArg)) { - return clangFormatArg; + final String clangFormat = getStringArg(_clangFormatArg); + if (await _hasDependency(clangFormat)) { + return clangFormat; } // There is a known issue where "chromium/depot_tools/clang-format" diff --git a/script/tool/test/format_command_test.dart b/script/tool/test/format_command_test.dart index 86c3ffb0cca8..9ea1f6299853 100644 --- a/script/tool/test/format_command_test.dart +++ b/script/tool/test/format_command_test.dart @@ -428,6 +428,71 @@ void main() { ])); }); + group('swift-format', () { + test('formats Swift if --swift-format flag is provided', () async { + const List files = [ + 'macos/foo.swift', + ]; + final RepositoryPackage plugin = createFakePlugin( + 'a_plugin', + packagesDir, + extraFiles: files, + ); + + await runCapturingPrint( + runner, ['format', '--swift-format=/path/to/swift-format']); + + expect( + processRunner.recordedCalls, + orderedEquals([ + ProcessCall( + '/path/to/swift-format', + ['-i', ...getPackagesDirRelativePaths(plugin, files)], + packagesDir.path), + ])); + }); + + test('skips Swift if --swift-format flag is not provided', () async { + const List files = [ + 'macos/foo.swift', + ]; + createFakePlugin( + 'a_plugin', + packagesDir, + extraFiles: files, + ); + + await runCapturingPrint(runner, ['format']); + + expect(processRunner.recordedCalls, orderedEquals([])); + }); + + test('fails if swift-format fails', () async { + const List files = [ + 'macos/foo.swift', + ]; + createFakePlugin('a_plugin', packagesDir, extraFiles: files); + + processRunner.mockProcessesForExecutable['swift-format'] = + [ + FakeProcessInfo(MockProcess(exitCode: 1), ['-i']), + ]; + Error? commandError; + final List output = await runCapturingPrint( + runner, ['format', '--swift-format=swift-format'], + errorHandler: (Error e) { + commandError = e; + }); + + expect(commandError, isA()); + expect( + output, + containsAllInOrder([ + contains('Failed to format Swift files: exit code 1.'), + ])); + }); + }); + test('skips known non-repo files', () async { const List skipFiles = [ '/example/build/SomeFramework.framework/Headers/SomeFramework.h',