From eb2079629aacfb14db5aef3c051c059f017321d3 Mon Sep 17 00:00:00 2001 From: TheFa Date: Mon, 9 Jun 2025 23:07:04 -0400 Subject: [PATCH] fixed fallback logic within formatter command class --- script/tool/lib/src/format_command.dart | 43 ++++++++++++--- script/tool/test/format_command_test.dart | 66 +++++++++++++++++++++++ 2 files changed, 103 insertions(+), 6 deletions(-) diff --git a/script/tool/lib/src/format_command.dart b/script/tool/lib/src/format_command.dart index 7d13919d96e..24f1804347a 100644 --- a/script/tool/lib/src/format_command.dart +++ b/script/tool/lib/src/format_command.dart @@ -272,16 +272,47 @@ class FormatCommand extends PackageLoopingCommand { } Future _findValidSwiftFormat() async { - final String swiftFormat = getStringArg(_swiftFormatPathArg); - if (await _hasDependency(swiftFormat)) { - return swiftFormat; + final String swiftFormatArg = getStringArg(_swiftFormatPathArg); + + // 1) Explicit override. + if (await _hasDependency(swiftFormatArg)) { + return swiftFormatArg; + } + + // 2) Scan for any `swift-format` on PATH. + for (final String candidate in await _whichAll('swift-format')) { + if (await _hasDependency(candidate)) { + return candidate; } + } - printError('Unable to run "swift-format". Make sure that it is in your ' - 'path, or provide a full path with --$_swiftFormatPathArg.'); - throw ToolExit(_exitDependencyMissing); + // 3) On macOS, fall back to Xcode’s bundled tool via xcrun. + if (platform.isMacOS) { + try { + final io.ProcessResult result = await processRunner.run( + 'xcrun', + ['--find', 'swift-format'], + ); + if (result.exitCode == 0) { + final String found = (result.stdout as String).trim(); + if (found.isNotEmpty && await _hasDependency(found)) { + return found; + } + } + } on io.ProcessException { + // Ignore and continue to error. + } } + printError( + 'Unable to run "swift-format". Make sure it is in your ' + 'path, provide a full path with --$_swiftFormatPathArg, ' + 'or install Xcode 16+.', + ); + throw ToolExit(_exitDependencyMissing); +} + + Future _formatJava(Iterable files, String formatterPath) async { final Iterable javaFiles = _getPathsWithExtensions(files, {'.java'}); diff --git a/script/tool/test/format_command_test.dart b/script/tool/test/format_command_test.dart index 56911300912..11f2b83937e 100644 --- a/script/tool/test/format_command_test.dart +++ b/script/tool/test/format_command_test.dart @@ -745,6 +745,72 @@ void main() { contains('Failed to format Swift files: exit code 1.'), ])); }); + + // ─── BEGIN xcrun fallback test ───────────────────────────────────────────── + test('uses xcrun fallback when swift-format not on PATH or --swift-format-path', + () async { + // 1) Force macOS behavior + mockPlatform.isMacOS = true; + + // 2) Make a fake plugin with one Swift file + const files = ['macos/foo.swift']; + final plugin = createFakePlugin('a_plugin', packagesDir, extraFiles: files); + fakePubGet(plugin); + + // 3) Stub out the default `swift-format --version` to fail + processRunner.mockProcessesForExecutable['swift-format'] = [ + FakeProcessInfo(MockProcess(exitCode: 1), ['--version']), + ]; + + // 4) Stub `which -a swift-format` to return nothing + processRunner.mockProcessesForExecutable['which'] = [ + FakeProcessInfo(MockProcess(stdout: ''), ['-a', 'swift-format']), + ]; + + // 5) Stub `xcrun --find swift-format` to return a real path + processRunner.mockProcessesForExecutable['xcrun'] = [ + FakeProcessInfo( + MockProcess(stdout: '/usr/bin/swift-format\n'), + ['--find', 'swift-format'], + ), + ]; + + // 6) Stub the fallback binary so that --version, -i and lint all succeed + processRunner.mockProcessesForExecutable['/usr/bin/swift-format'] = [ + FakeProcessInfo(MockProcess(), ['--version']), + FakeProcessInfo(MockProcess(), ['-i']), + FakeProcessInfo(MockProcess(), ['lint', '--parallel', '--strict']), + ]; + + // 7) Run the command + await runCapturingPrint(runner, ['format', '--swift']); + + // 8) Verify we called the fallback binary (not `xcrun` in the final recording) + expect( + processRunner.recordedCalls, + orderedEquals([ + // First invocation of the fallback swift-format + const ProcessCall( + '/usr/bin/swift-format', + ['--version'], + null, + ), + // Then formatting + ProcessCall( + '/usr/bin/swift-format', + ['-i', ...getPackagesDirRelativePaths(plugin, files)], + packagesDir.path, + ), + // Finally linting + ProcessCall( + '/usr/bin/swift-format', + ['lint', '--parallel', '--strict', ...getPackagesDirRelativePaths(plugin, files)], + packagesDir.path, + ), + ]), + ); + }); + // ─── END xcrun fallback test ──────────────────────────────────────────────── }); test('skips known non-repo files', () async {