Skip to content

[format_command] - fixed fallback logic within formatter command class #9410

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
43 changes: 37 additions & 6 deletions script/tool/lib/src/format_command.dart
Original file line number Diff line number Diff line change
Expand Up @@ -272,16 +272,47 @@ class FormatCommand extends PackageLoopingCommand {
}

Future<String> _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',
<String>['--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<void> _formatJava(Iterable<String> files, String formatterPath) async {
final Iterable<String> javaFiles =
_getPathsWithExtensions(files, <String>{'.java'});
Expand Down
66 changes: 66 additions & 0 deletions script/tool/test/format_command_test.dart
Original file line number Diff line number Diff line change
Expand Up @@ -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 = <String>['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>[
FakeProcessInfo(MockProcess(exitCode: 1), <String>['--version']),
];

// 4) Stub `which -a swift-format` to return nothing
processRunner.mockProcessesForExecutable['which'] = <FakeProcessInfo>[
FakeProcessInfo(MockProcess(stdout: ''), <String>['-a', 'swift-format']),
];

// 5) Stub `xcrun --find swift-format` to return a real path
processRunner.mockProcessesForExecutable['xcrun'] = <FakeProcessInfo>[
FakeProcessInfo(
MockProcess(stdout: '/usr/bin/swift-format\n'),
<String>['--find', 'swift-format'],
),
];

// 6) Stub the fallback binary so that --version, -i and lint all succeed
processRunner.mockProcessesForExecutable['/usr/bin/swift-format'] = <FakeProcessInfo>[
FakeProcessInfo(MockProcess(), <String>['--version']),
FakeProcessInfo(MockProcess(), <String>['-i']),
FakeProcessInfo(MockProcess(), <String>['lint', '--parallel', '--strict']),
];

// 7) Run the command
await runCapturingPrint(runner, <String>['format', '--swift']);

// 8) Verify we called the fallback binary (not `xcrun` in the final recording)
expect(
processRunner.recordedCalls,
orderedEquals(<ProcessCall>[
// First invocation of the fallback swift-format
const ProcessCall(
'/usr/bin/swift-format',
<String>['--version'],
null,
),
// Then formatting
ProcessCall(
'/usr/bin/swift-format',
<String>['-i', ...getPackagesDirRelativePaths(plugin, files)],
packagesDir.path,
),
// Finally linting
ProcessCall(
'/usr/bin/swift-format',
<String>['lint', '--parallel', '--strict', ...getPackagesDirRelativePaths(plugin, files)],
packagesDir.path,
),
]),
);
});
// ─── END xcrun fallback test ────────────────────────────────────────────────
});

test('skips known non-repo files', () async {
Expand Down