diff --git a/Package.swift b/Package.swift index ed219201f..9ab3fa332 100644 --- a/Package.swift +++ b/Package.swift @@ -33,6 +33,14 @@ let package = Package( name: "SwiftFormatConfiguration", targets: ["SwiftFormatConfiguration"] ), + .plugin( + name: "FormatPlugin", + targets: ["Format Source Code"] + ), + .plugin( + name: "LintPlugin", + targets: ["Lint Source Code"] + ), ], dependencies: [ // See the "Dependencies" section below. @@ -91,7 +99,32 @@ let package = Package( .product(name: "SwiftSyntax", package: "swift-syntax"), ] ), - + .plugin( + name: "Format Source Code", + capability: .command( + intent: .sourceCodeFormatting(), + permissions: [ + .writeToPackageDirectory(reason: "This command formats the Swift source files") + ] + ), + dependencies: [ + .target(name: "swift-format") + ], + path: "Plugins/FormatPlugin" + ), + .plugin( + name: "Lint Source Code", + capability: .command( + intent: .custom( + verb: "lint-source-code", + description: "Lint source code for a specified target." + ) + ), + dependencies: [ + .target(name: "swift-format") + ], + path: "Plugins/LintPlugin" + ), .executableTarget( name: "generate-pipeline", dependencies: [ diff --git a/Plugins/FormatPlugin/plugin.swift b/Plugins/FormatPlugin/plugin.swift new file mode 100644 index 000000000..a89f7d652 --- /dev/null +++ b/Plugins/FormatPlugin/plugin.swift @@ -0,0 +1,72 @@ +import PackagePlugin +import Foundation + +@main +struct FormatPlugin { + func format(tool: PluginContext.Tool, targetDirectories: [String], configurationFilePath: String?) throws { + let swiftFormatExec = URL(fileURLWithPath: tool.path.string) + + var arguments: [String] = ["format"] + + arguments.append(contentsOf: targetDirectories) + + arguments.append(contentsOf: ["--recursive", "--parallel", "--in-place"]) + + if let configurationFilePath = configurationFilePath { + arguments.append(contentsOf: ["--configuration", configurationFilePath]) + } + + let process = try Process.run(swiftFormatExec, arguments: arguments) + process.waitUntilExit() + + if process.terminationReason == .exit && process.terminationStatus == 0 { + print("Formatted the source code.") + } + else { + let problem = "\(process.terminationReason):\(process.terminationStatus)" + Diagnostics.error("swift-format invocation failed: \(problem)") + } + } +} + +extension FormatPlugin: CommandPlugin { + func performCommand( + context: PluginContext, + arguments: [String] + ) async throws { + let swiftFormatTool = try context.tool(named: "swift-format") + + var argExtractor = ArgumentExtractor(arguments) + let targetNames = argExtractor.extractOption(named: "target") + let targetsToFormat = try context.package.targets(named: targetNames) + + let configurationFilePath = argExtractor.extractOption(named: "configuration").first + + let sourceCodeTargets = targetsToFormat.compactMap{ $0 as? SourceModuleTarget } + + try format( + tool: swiftFormatTool, + targetDirectories: sourceCodeTargets.map(\.directory.string), + configurationFilePath: configurationFilePath + ) + } +} + +#if canImport(XcodeProjectPlugin) +import XcodeProjectPlugin + +extension FormatPlugin: XcodeCommandPlugin { + func performCommand(context: XcodeProjectPlugin.XcodePluginContext, arguments: [String]) throws { + let swiftFormatTool = try context.tool(named: "swift-format") + + var argExtractor = ArgumentExtractor(arguments) + let configurationFilePath = argExtractor.extractOption(named: "configuration").first + + try format( + tool: swiftFormatTool, + targetDirectories: [context.xcodeProject.directory.string], + configurationFilePath: configurationFilePath + ) + } +} +#endif diff --git a/Plugins/LintPlugin/plugin.swift b/Plugins/LintPlugin/plugin.swift new file mode 100644 index 000000000..b5563a5bb --- /dev/null +++ b/Plugins/LintPlugin/plugin.swift @@ -0,0 +1,72 @@ +import PackagePlugin +import Foundation + +@main +struct LintPlugin { + func lint(tool: PluginContext.Tool, targetDirectories: [String], configurationFilePath: String?) throws { + let swiftFormatExec = URL(fileURLWithPath: tool.path.string) + + var arguments: [String] = ["lint"] + + arguments.append(contentsOf: targetDirectories) + + arguments.append(contentsOf: ["--recursive", "--parallel", "--strict"]) + + if let configurationFilePath = configurationFilePath { + arguments.append(contentsOf: ["--configuration", configurationFilePath]) + } + + let process = try Process.run(swiftFormatExec, arguments: arguments) + process.waitUntilExit() + + if process.terminationReason == .exit && process.terminationStatus == 0 { + print("Lintted the source code.") + } + else { + let problem = "\(process.terminationReason):\(process.terminationStatus)" + Diagnostics.error("swift-format invocation failed: \(problem)") + } + } +} + +extension LintPlugin: CommandPlugin { + func performCommand( + context: PluginContext, + arguments: [String] + ) async throws { + let swiftFormatTool = try context.tool(named: "swift-format") + + // Extract the arguments that specify what targets to format. + var argExtractor = ArgumentExtractor(arguments) + let targetNames = argExtractor.extractOption(named: "target") + let targetsToFormat = try context.package.targets(named: targetNames) + + let configurationFilePath = argExtractor.extractOption(named: "configuration").first + + let sourceCodeTargets = targetsToFormat.compactMap { $0 as? SourceModuleTarget } + + try lint( + tool: swiftFormatTool, + targetDirectories: sourceCodeTargets.map(\.directory.string), + configurationFilePath: configurationFilePath + ) + } +} + +#if canImport(XcodeProjectPlugin) +import XcodeProjectPlugin + +extension LintPlugin: XcodeCommandPlugin { + func performCommand(context: XcodeProjectPlugin.XcodePluginContext, arguments: [String]) throws { + let swiftFormatTool = try context.tool(named: "swift-format") + var argExtractor = ArgumentExtractor(arguments) + let configurationFilePath = argExtractor.extractOption(named: "configuration").first + + try lint( + tool: swiftFormatTool, + targetDirectories: [context.xcodeProject.directory.string], + configurationFilePath: configurationFilePath + ) + } +} +#endif