Skip to content

Commit d34da87

Browse files
feat(v1.0.4): Add error handling, timeout, and diagnostics improvements
- Wrap process execution in error handling to prevent crashes - Add 5-minute timeout to prevent indefinite hangs - Standardize logging with Diagnostics API instead of print() - Add file count feedback to LintPlugin - Add troubleshooting documentation to README
1 parent 921292b commit d34da87

3 files changed

Lines changed: 89 additions & 27 deletions

File tree

Plugins/FormatPlugin/FormatPlugin.swift

Lines changed: 36 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -71,7 +71,7 @@ struct FormatPlugin: CommandPlugin {
7171
formatArguments += allSourceFiles.map { $0.path(percentEncoded: false) }
7272

7373
// Print what we're about to do
74-
print("Formatting \(allSourceFiles.count) Swift files in \(targetsToFormat.count) target(s)...")
74+
Diagnostics.remark("Formatting \(allSourceFiles.count) Swift files in \(targetsToFormat.count) target(s)...")
7575

7676
// Execute swift-format
7777
let process = Process()
@@ -83,50 +83,61 @@ struct FormatPlugin: CommandPlugin {
8383
process.standardOutput = outputPipe
8484
process.standardError = errorPipe
8585

86-
try process.run()
87-
process.waitUntilExit()
86+
do {
87+
try process.run()
8888

89-
// Handle output
90-
let outputData = outputPipe.fileHandleForReading.readDataToEndOfFile()
91-
let errorData = errorPipe.fileHandleForReading.readDataToEndOfFile()
89+
// Wait for process with 5-minute timeout
90+
let timeout: TimeInterval = 300 // 5 minutes
91+
let deadline = Date().addingTimeInterval(timeout)
9292

93-
if let output = String(data: outputData, encoding: .utf8), !output.isEmpty {
94-
print(output)
95-
}
93+
while process.isRunning && Date() < deadline {
94+
try await Task.sleep(nanoseconds: 100_000_000) // 0.1 seconds
95+
}
9696

97-
if let errorOutput = String(data: errorData, encoding: .utf8), !errorOutput.isEmpty {
98-
printError(errorOutput)
99-
}
97+
if process.isRunning {
98+
process.terminate()
99+
Diagnostics.error("swift-format timed out after \(Int(timeout))s and was terminated")
100+
return
101+
}
100102

101-
if process.terminationStatus == 0 {
102-
print("✅ Formatting completed successfully!")
103-
} else {
104-
Diagnostics.error("swift-format failed with exit code \(process.terminationStatus)")
103+
// Handle output
104+
let outputData = outputPipe.fileHandleForReading.readDataToEndOfFile()
105+
let errorData = errorPipe.fileHandleForReading.readDataToEndOfFile()
106+
107+
if let output = String(data: outputData, encoding: .utf8), !output.isEmpty {
108+
Diagnostics.remark(output)
109+
}
110+
111+
if let errorOutput = String(data: errorData, encoding: .utf8), !errorOutput.isEmpty {
112+
Diagnostics.warning(errorOutput)
113+
}
114+
115+
if process.terminationStatus == 0 {
116+
Diagnostics.remark("Formatting completed successfully!")
117+
} else {
118+
Diagnostics.error("swift-format failed with exit code \(process.terminationStatus)")
119+
}
120+
} catch {
121+
Diagnostics.error("Failed to execute swift-format: \(error.localizedDescription)")
105122
}
106123
}
107124

108125
/// Finds configuration file in package root.
109126
/// Supports both .swiftformat and .swift-format naming conventions.
110127
private func findConfigurationFile(in packageDirectory: URL) -> URL? {
128+
let fileManager = FileManager.default
111129
let swiftformatConfig = packageDirectory.appending(path: ".swiftformat")
112130
let swiftFormatConfig = packageDirectory.appending(path: ".swift-format")
113131

114132
// Prefer .swiftformat first
115-
if FileManager.default.fileExists(atPath: swiftformatConfig.path) {
133+
if fileManager.fileExists(atPath: swiftformatConfig.path) {
116134
return swiftformatConfig
117135
}
118136
// Fall back to .swift-format
119-
if FileManager.default.fileExists(atPath: swiftFormatConfig.path) {
137+
if fileManager.fileExists(atPath: swiftFormatConfig.path) {
120138
return swiftFormatConfig
121139
}
122140

123141
return nil
124142
}
125143
}
126-
127-
// Helper for printing to stderr
128-
fileprivate func printError(_ message: String) {
129-
if let data = message.data(using: .utf8) {
130-
FileHandle.standardError.write(data)
131-
}
132-
}

Plugins/LintPlugin/LintPlugin.swift

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,8 @@ struct LintPlugin: BuildToolPlugin {
2525
.filter { $0.type == .source && $0.url.pathExtension == "swift" }
2626
.map { $0.url }
2727

28+
Diagnostics.remark("Linting \(swiftSourceFiles.count) Swift file(s)...")
29+
2830
guard !swiftSourceFiles.isEmpty else {
2931
return []
3032
}
@@ -67,15 +69,16 @@ struct LintPlugin: BuildToolPlugin {
6769
/// Finds configuration file in package root.
6870
/// Supports both .swiftformat and .swift-format naming conventions.
6971
private func findConfigurationFile(in packageDirectory: URL) -> URL? {
72+
let fileManager = FileManager.default
7073
let swiftformatConfig = packageDirectory.appending(path: ".swiftformat")
7174
let swiftFormatConfig = packageDirectory.appending(path: ".swift-format")
7275

7376
// Prefer .swiftformat first
74-
if FileManager.default.fileExists(atPath: swiftformatConfig.path) {
77+
if fileManager.fileExists(atPath: swiftformatConfig.path) {
7578
return swiftformatConfig
7679
}
7780
// Fall back to .swift-format
78-
if FileManager.default.fileExists(atPath: swiftFormatConfig.path) {
81+
if fileManager.fileExists(atPath: swiftFormatConfig.path) {
7982
return swiftFormatConfig
8083
}
8184

@@ -95,6 +98,8 @@ extension LintPlugin: XcodeBuildToolPlugin {
9598
.filter { $0.type == .source && $0.url.pathExtension == "swift" }
9699
.map { $0.url }
97100

101+
Diagnostics.remark("Linting \(swiftSourceFiles.count) Swift file(s) in Xcode project...")
102+
98103
guard !swiftSourceFiles.isEmpty else {
99104
return []
100105
}

README.md

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -85,6 +85,52 @@ Example:
8585
- swift-format tool (included in Xcode)
8686
- Xcode 14+ (for Xcode project support)
8787

88+
## Troubleshooting
89+
90+
### "swift-format not found" error
91+
92+
The plugin requires swift-format to be installed in your Swift toolchain.
93+
94+
**Solution:**
95+
- Ensure you have a recent Swift version installed (6.0+)
96+
- swift-format is included with Xcode
97+
- Run `swift-format --version` to verify it's available
98+
99+
### Build hangs with Lint plugin
100+
101+
The Lint plugin has a 5-minute timeout to prevent indefinite hangs, but extremely slow disk I/O could cause delays.
102+
103+
**Solution:**
104+
- Check if your disk is full: `df -h`
105+
- Try building a smaller target first
106+
- Disable other background processes consuming I/O
107+
108+
### "Configuration file not found" warning
109+
110+
This is normal if you don't have a `.swiftformat` configuration file. The plugin will use swift-format's default rules.
111+
112+
**Solution (optional):**
113+
- Create `.swiftformat` in your package root to customize rules
114+
- See [swift-format documentation](https://github.com/apple/swift-format) for configuration options
115+
116+
### FormatPlugin fails with "Permission denied"
117+
118+
The Format plugin needs write permission to your source files.
119+
120+
**Solution:**
121+
- Ensure your project files are writable: `ls -l Sources/`
122+
- Approve the permission prompt when running the plugin
123+
- Check disk permissions: `chmod u+w Sources/**/*.swift`
124+
125+
### No output from Lint plugin during build
126+
127+
This is normal behavior. The Lint plugin runs silently unless there are formatting issues.
128+
129+
**To verify it's running:**
130+
- Build with verbose output: `swift build -v`
131+
- Look for "Running SwiftFormat Lint" in the output
132+
- The plugin will show warnings if formatting issues are found
133+
88134
## License
89135

90136
MIT License - see [LICENSE](LICENSE) file.

0 commit comments

Comments
 (0)