mirror of
https://github.com/flutter/packages.git
synced 2025-06-29 22:33:11 +08:00
[tool] Add Kotlin autoformatting (#5374)
Updates the tooling to fetch and use `ktfmt` for Kotlin code, the same way it currently does for the Google Java formatter, and applies that formatting (in the default mode; see discussion in the linked issue) to the repository. In the future we could revisit the formatter or mode, but since this currently seems to be the most consistent with our other languages and to google3 formatting this is likely the option we'll want to stick with. Fixes https://github.com/flutter/flutter/issues/118756
This commit is contained in:
@ -32,9 +32,12 @@ const int _exitJavaFormatFailed = 5;
|
||||
const int _exitGitFailed = 6;
|
||||
const int _exitDependencyMissing = 7;
|
||||
const int _exitSwiftFormatFailed = 8;
|
||||
const int _exitKotlinFormatFailed = 9;
|
||||
|
||||
final Uri _googleFormatterUrl = Uri.https('github.com',
|
||||
final Uri _javaFormatterUrl = Uri.https('github.com',
|
||||
'/google/google-java-format/releases/download/google-java-format-1.3/google-java-format-1.3-all-deps.jar');
|
||||
final Uri _kotlinFormatterUrl = Uri.https('maven.org',
|
||||
'/maven2/com/facebook/ktfmt/0.46/ktfmt-0.46-jar-with-dependencies.jar');
|
||||
|
||||
/// A command to format all package code.
|
||||
class FormatCommand extends PackageCommand {
|
||||
@ -62,14 +65,15 @@ class FormatCommand extends PackageCommand {
|
||||
|
||||
@override
|
||||
final String description =
|
||||
'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.';
|
||||
'Formats the code of all packages (C++, Dart, Java, Kotlin, Objective-C, '
|
||||
'and optionally Swift).\n\n'
|
||||
'This command requires "git", "flutter", "java", and "clang-format" v5 '
|
||||
'to be in your path.';
|
||||
|
||||
@override
|
||||
Future<void> run() async {
|
||||
final String googleFormatterPath = await _getGoogleFormatterPath();
|
||||
final String javaFormatterPath = await _getJavaFormatterPath();
|
||||
final String kotlinFormatterPath = await _getKotlinFormatterPath();
|
||||
|
||||
// This class is not based on PackageLoopingCommand because running the
|
||||
// formatters separately for each package is an order of magnitude slower,
|
||||
@ -77,7 +81,8 @@ class FormatCommand extends PackageCommand {
|
||||
final Iterable<String> files =
|
||||
await _getFilteredFilePaths(getFiles(), relativeTo: packagesDir);
|
||||
await _formatDart(files);
|
||||
await _formatJava(files, googleFormatterPath);
|
||||
await _formatJava(files, javaFormatterPath);
|
||||
await _formatKotlin(files, kotlinFormatterPath);
|
||||
await _formatCppAndObjectiveC(files);
|
||||
final String? swiftFormat = getNullableStringArg(_swiftFormatArg);
|
||||
if (swiftFormat != null) {
|
||||
@ -187,8 +192,7 @@ class FormatCommand extends PackageCommand {
|
||||
throw ToolExit(_exitDependencyMissing);
|
||||
}
|
||||
|
||||
Future<void> _formatJava(
|
||||
Iterable<String> files, String googleFormatterPath) async {
|
||||
Future<void> _formatJava(Iterable<String> files, String formatterPath) async {
|
||||
final Iterable<String> javaFiles =
|
||||
_getPathsWithExtensions(files, <String>{'.java'});
|
||||
if (javaFiles.isNotEmpty) {
|
||||
@ -202,7 +206,7 @@ class FormatCommand extends PackageCommand {
|
||||
|
||||
print('Formatting .java files...');
|
||||
final int exitCode = await _runBatched(
|
||||
java, <String>['-jar', googleFormatterPath, '--replace'],
|
||||
java, <String>['-jar', formatterPath, '--replace'],
|
||||
files: javaFiles);
|
||||
if (exitCode != 0) {
|
||||
printError('Failed to format Java files: exit code $exitCode.');
|
||||
@ -211,6 +215,30 @@ class FormatCommand extends PackageCommand {
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> _formatKotlin(
|
||||
Iterable<String> files, String formatterPath) async {
|
||||
final Iterable<String> kotlinFiles =
|
||||
_getPathsWithExtensions(files, <String>{'.kt'});
|
||||
if (kotlinFiles.isNotEmpty) {
|
||||
final String java = getStringArg('java');
|
||||
if (!await _hasDependency(java)) {
|
||||
printError(
|
||||
'Unable to run "java". Make sure that it is in your path, or '
|
||||
'provide a full path with --java.');
|
||||
throw ToolExit(_exitDependencyMissing);
|
||||
}
|
||||
|
||||
print('Formatting .kt files...');
|
||||
final int exitCode = await _runBatched(
|
||||
java, <String>['-jar', formatterPath],
|
||||
files: kotlinFiles);
|
||||
if (exitCode != 0) {
|
||||
printError('Failed to format Kotlin files: exit code $exitCode.');
|
||||
throw ToolExit(_exitKotlinFormatFailed);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> _formatDart(Iterable<String> files) async {
|
||||
final Iterable<String> dartFiles =
|
||||
_getPathsWithExtensions(files, <String>{'.dart'});
|
||||
@ -283,7 +311,7 @@ class FormatCommand extends PackageCommand {
|
||||
(String filePath) => extensions.contains(path.extension(filePath)));
|
||||
}
|
||||
|
||||
Future<String> _getGoogleFormatterPath() async {
|
||||
Future<String> _getJavaFormatterPath() async {
|
||||
final String javaFormatterPath = path.join(
|
||||
path.dirname(path.fromUri(platform.script)),
|
||||
'google-java-format-1.3-all-deps.jar');
|
||||
@ -292,13 +320,29 @@ class FormatCommand extends PackageCommand {
|
||||
|
||||
if (!javaFormatterFile.existsSync()) {
|
||||
print('Downloading Google Java Format...');
|
||||
final http.Response response = await http.get(_googleFormatterUrl);
|
||||
final http.Response response = await http.get(_javaFormatterUrl);
|
||||
javaFormatterFile.writeAsBytesSync(response.bodyBytes);
|
||||
}
|
||||
|
||||
return javaFormatterPath;
|
||||
}
|
||||
|
||||
Future<String> _getKotlinFormatterPath() async {
|
||||
final String kotlinFormatterPath = path.join(
|
||||
path.dirname(path.fromUri(platform.script)),
|
||||
'ktfmt-0.46-jar-with-dependencies.jar');
|
||||
final File kotlinFormatterFile =
|
||||
packagesDir.fileSystem.file(kotlinFormatterPath);
|
||||
|
||||
if (!kotlinFormatterFile.existsSync()) {
|
||||
print('Downloading ktfmt...');
|
||||
final http.Response response = await http.get(_kotlinFormatterUrl);
|
||||
kotlinFormatterFile.writeAsBytesSync(response.bodyBytes);
|
||||
}
|
||||
|
||||
return kotlinFormatterPath;
|
||||
}
|
||||
|
||||
/// Returns true if [command] can be run successfully.
|
||||
Future<bool> _hasDependency(String command) async {
|
||||
// Some versions of Java accept both -version and --version, but some only
|
||||
|
@ -22,6 +22,7 @@ void main() {
|
||||
late FormatCommand analyzeCommand;
|
||||
late CommandRunner<void> runner;
|
||||
late String javaFormatPath;
|
||||
late String kotlinFormatPath;
|
||||
|
||||
setUp(() {
|
||||
fileSystem = MemoryFileSystem();
|
||||
@ -34,12 +35,16 @@ void main() {
|
||||
platform: mockPlatform,
|
||||
);
|
||||
|
||||
// Create the java formatter file that the command checks for, to avoid
|
||||
// a download.
|
||||
// Create the Java and Kotlin formatter files that the command checks for,
|
||||
// to avoid a download.
|
||||
final p.Context path = analyzeCommand.path;
|
||||
javaFormatPath = path.join(path.dirname(path.fromUri(mockPlatform.script)),
|
||||
'google-java-format-1.3-all-deps.jar');
|
||||
fileSystem.file(javaFormatPath).createSync(recursive: true);
|
||||
kotlinFormatPath = path.join(
|
||||
path.dirname(path.fromUri(mockPlatform.script)),
|
||||
'ktfmt-0.46-jar-with-dependencies.jar');
|
||||
fileSystem.file(kotlinFormatPath).createSync(recursive: true);
|
||||
|
||||
runner = CommandRunner<void>('format_command', 'Test for format_command');
|
||||
runner.addCommand(analyzeCommand);
|
||||
@ -428,6 +433,62 @@ void main() {
|
||||
]));
|
||||
});
|
||||
|
||||
group('kotlin-format', () {
|
||||
test('formats .kt files', () async {
|
||||
const List<String> files = <String>[
|
||||
'android/src/main/kotlin/io/flutter/plugins/a_plugin/a.kt',
|
||||
'android/src/main/kotlin/io/flutter/plugins/a_plugin/b.kt',
|
||||
];
|
||||
final RepositoryPackage plugin = createFakePlugin(
|
||||
'a_plugin',
|
||||
packagesDir,
|
||||
extraFiles: files,
|
||||
);
|
||||
|
||||
await runCapturingPrint(runner, <String>['format']);
|
||||
|
||||
expect(
|
||||
processRunner.recordedCalls,
|
||||
orderedEquals(<ProcessCall>[
|
||||
const ProcessCall('java', <String>['-version'], null),
|
||||
ProcessCall(
|
||||
'java',
|
||||
<String>[
|
||||
'-jar',
|
||||
kotlinFormatPath,
|
||||
...getPackagesDirRelativePaths(plugin, files)
|
||||
],
|
||||
packagesDir.path),
|
||||
]));
|
||||
});
|
||||
|
||||
test('fails if Kotlin formatter fails', () async {
|
||||
const List<String> files = <String>[
|
||||
'android/src/main/kotlin/io/flutter/plugins/a_plugin/a.kt',
|
||||
'android/src/main/kotlin/io/flutter/plugins/a_plugin/b.kt',
|
||||
];
|
||||
createFakePlugin('a_plugin', packagesDir, extraFiles: files);
|
||||
|
||||
processRunner.mockProcessesForExecutable['java'] = <FakeProcessInfo>[
|
||||
FakeProcessInfo(
|
||||
MockProcess(), <String>['-version']), // check for working java
|
||||
FakeProcessInfo(MockProcess(exitCode: 1), <String>['-jar']), // format
|
||||
];
|
||||
Error? commandError;
|
||||
final List<String> output = await runCapturingPrint(
|
||||
runner, <String>['format'], errorHandler: (Error e) {
|
||||
commandError = e;
|
||||
});
|
||||
|
||||
expect(commandError, isA<ToolExit>());
|
||||
expect(
|
||||
output,
|
||||
containsAllInOrder(<Matcher>[
|
||||
contains('Failed to format Kotlin files: exit code 1.'),
|
||||
]));
|
||||
});
|
||||
});
|
||||
|
||||
group('swift-format', () {
|
||||
test('formats Swift if --swift-format flag is provided', () async {
|
||||
const List<String> files = <String>[
|
||||
|
Reference in New Issue
Block a user