├── .github └── workflows │ ├── Package.swift │ ├── release.yml │ └── test.yml ├── .gitignore ├── .spi.yml ├── LICENSE ├── Package.swift ├── Plugins ├── SwiftLintBuildToolPlugin │ ├── Path+Helpers.swift │ ├── SwiftLintBuildToolPlugin.swift │ └── SwiftLintBuildToolPluginError.swift └── SwiftLintCommandPlugin │ ├── CommandContext.swift │ └── SwiftLintCommandPlugin.swift └── README.md /.github/workflows/Package.swift: -------------------------------------------------------------------------------- 1 | // swift-tools-version:5.9 2 | 3 | import PackageDescription 4 | 5 | let package = Package( 6 | name: "SwiftLintPluginsTest", 7 | platforms: [ 8 | .macOS(.v12), 9 | ], 10 | dependencies: [ 11 | .package(path: "../SwiftLintPlugins"), 12 | ], 13 | targets: [ 14 | .executableTarget( 15 | name: "Main", 16 | plugins: [ 17 | .plugin(name: "SwiftLintBuildToolPlugin", package: "SwiftLintPlugins"), 18 | ] 19 | ), 20 | ] 21 | ) 22 | -------------------------------------------------------------------------------- /.github/workflows/release.yml: -------------------------------------------------------------------------------- 1 | name: Release 2 | 3 | on: 4 | repository_dispatch: 5 | types: [swiftlint-release] 6 | 7 | jobs: 8 | release: 9 | runs-on: ubuntu-latest 10 | permissions: 11 | contents: write 12 | steps: 13 | - name: Checkout repository 14 | uses: actions/checkout@v4 15 | - name: Update binary package 16 | run: > 17 | sed -i 18 | -e "s#/[0-9]\{1,\}\.[0-9]\{1,\}\.[0-9]\{1,\}/#/${{ github.event.client_payload.tag }}/#" 19 | -e "s/[a-fA-F0-9]\{64\}/${{ github.event.client_payload.checksum }}/" 20 | Package.swift 21 | - name: Configure Git author 22 | id: configure_git_author 23 | uses: Homebrew/actions/git-user-config@master 24 | with: 25 | username: ${{ github.repository_owner }} 26 | - name: Configure author 27 | run: | 28 | git config --local user.name "${{ steps.configure_git_author.outputs.name }}" 29 | git config --local user.email "${{ steps.configure_git_author.outputs.email }}" 30 | - name: Commit 31 | run: | 32 | git add Package.swift 33 | git commit -m "Release ${{ github.event.client_payload.tag }}" 34 | - name: Push changes 35 | run: git push 36 | env: 37 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 38 | - uses: ncipollo/release-action@v1 39 | with: 40 | name: ${{ github.event.client_payload.title }} 41 | tag: ${{ github.event.client_payload.tag }} 42 | body: > 43 | Check out the changes made in the 44 | [SwiftLint release](https://github.com/realm/SwiftLint/releases/tag/${{ github.event.client_payload.tag }}) 45 | associated with this version. 46 | draft: false 47 | -------------------------------------------------------------------------------- /.github/workflows/test.yml: -------------------------------------------------------------------------------- 1 | name: Tests 2 | 3 | on: 4 | push: 5 | branches: 6 | - main 7 | pull_request: 8 | 9 | env: 10 | XCODE_VERSION: '15.0' 11 | 12 | defaults: 13 | run: 14 | working-directory: ./SwiftLintPluginsTest 15 | 16 | jobs: 17 | plugin: 18 | name: Test Plugins 19 | runs-on: macos-13 20 | steps: 21 | - name: Select Xcode 22 | uses: maxim-lobanov/setup-xcode@v1 23 | with: 24 | xcode-version: ${{ env.XCODE_VERSION }} 25 | - name: Checkout SwiftLintPlugins 26 | uses: actions/checkout@v4 27 | with: 28 | path: SwiftLintPlugins 29 | - name: Create project directory 30 | run: mkdir SwiftLintPluginsTest 31 | working-directory: . 32 | - name: Configure project 33 | run: | 34 | swift package init --type executable 35 | cp ../SwiftLintPlugins/.github/workflows/Package.swift . 36 | echo "let myVar = 0 as! Int" > Sources/main.swift 37 | echo "included: [Sources]" > .swiftlint.yml 38 | - name: Let build fail 39 | id: failed_build 40 | run: swift build 41 | continue-on-error: true 42 | - name: Verify that build failed 43 | if: ${{ steps.failed_build.outcome != 'failure' }} 44 | run: exit 1 45 | - name: Let command fail 46 | id: failed_command 47 | run: swift package plugin --allow-writing-to-package-directory swiftlint 48 | continue-on-error: true 49 | - name: Verify that command failed 50 | if: ${{ steps.failed_command.outcome != 'failure' }} 51 | run: exit 1 52 | - name: Let Xcode build fail 53 | id: failed_xcode_build 54 | run: xcodebuild -scheme SwiftLintPluginsTest -destination 'platform=macOS' -skipPackagePluginValidation build 55 | continue-on-error: true 56 | - name: Verify that Xcode build failed 57 | if: ${{ steps.failed_xcode_build.outcome != 'failure' }} 58 | run: exit 1 59 | - name: Change to warning 60 | run: | 61 | echo "force_cast: warning" > .swiftlint.yml 62 | - name: Check that all commands pass 63 | run: | 64 | swift package plugin --allow-writing-to-package-directory swiftlint | grep -q "Force Cast Violation" 65 | xcodebuild -scheme SwiftLintPluginsTest -destination 'platform=macOS' -skipPackagePluginValidation build | grep -q "Force Cast Violation" 66 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | /.build 3 | -------------------------------------------------------------------------------- /.spi.yml: -------------------------------------------------------------------------------- 1 | version: 1 2 | metadata: 3 | authors: Danny Mösch 4 | external_links: 5 | documentation: "https://realm.github.io/SwiftLint" 6 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2020 Realm Inc. 4 | Copyright (c) 2024 Danny Mösch 5 | 6 | Permission is hereby granted, free of charge, to any person obtaining a copy 7 | of this software and associated documentation files (the "Software"), to deal 8 | in the Software without restriction, including without limitation the rights 9 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | copies of the Software, and to permit persons to whom the Software is 11 | furnished to do so, subject to the following conditions: 12 | 13 | The above copyright notice and this permission notice shall be included in all 14 | copies or substantial portions of the Software. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 22 | SOFTWARE. 23 | -------------------------------------------------------------------------------- /Package.swift: -------------------------------------------------------------------------------- 1 | // swift-tools-version: 5.9 2 | 3 | import PackageDescription 4 | 5 | let package = Package( 6 | name: "SwiftLintPlugins", 7 | platforms: [ 8 | .macOS(.v12) 9 | ], 10 | products: [ 11 | .plugin(name: "SwiftLintBuildToolPlugin", targets: ["SwiftLintBuildToolPlugin"]), 12 | .plugin(name: "SwiftLintCommandPlugin", targets: ["SwiftLintCommandPlugin"]) 13 | ], 14 | targets: [ 15 | .plugin( 16 | name: "SwiftLintBuildToolPlugin", 17 | capability: .buildTool(), 18 | dependencies: [.target(name: "SwiftLintBinary")], 19 | packageAccess: false 20 | ), 21 | .plugin( 22 | name: "SwiftLintCommandPlugin", 23 | capability: .command( 24 | intent: .custom(verb: "swiftlint", description: "SwiftLint Command Plugin"), 25 | permissions: [ 26 | .writeToPackageDirectory( 27 | reason: "When this command is run with the `--fix` option it may modify source files." 28 | ), 29 | ] 30 | ), 31 | dependencies: [.target(name: "SwiftLintBinary")], 32 | packageAccess: false 33 | ), 34 | .binaryTarget( 35 | name: "SwiftLintBinary", 36 | url: "https://github.com/realm/SwiftLint/releases/download/0.59.1/SwiftLintBinary.artifactbundle.zip", 37 | checksum: "b9f915a58a818afcc66846740d272d5e73f37baf874e7809ff6f246ea98ad8a2" 38 | ) 39 | ] 40 | ) 41 | -------------------------------------------------------------------------------- /Plugins/SwiftLintBuildToolPlugin/Path+Helpers.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | import PackagePlugin 3 | 4 | extension Path { 5 | var directoryContainsConfigFile: Bool { 6 | FileManager.default.fileExists(atPath: "\(self)/.swiftlint.yml") 7 | } 8 | 9 | var depth: Int { 10 | URL(fileURLWithPath: "\(self)").pathComponents.count 11 | } 12 | 13 | func isDescendant(of path: Path) -> Bool { 14 | "\(self)".hasPrefix("\(path)") 15 | } 16 | 17 | func resolveWorkingDirectory(in directory: Path) throws -> Path { 18 | guard "\(self)".hasPrefix("\(directory)") else { 19 | throw SwiftLintBuildToolPluginError.pathNotInDirectory(path: self, directory: directory) 20 | } 21 | 22 | let path: Path? = sequence(first: self) { path in 23 | let path: Path = path.removingLastComponent() 24 | guard "\(path)".hasPrefix("\(directory)") else { 25 | return nil 26 | } 27 | return path 28 | } 29 | .reversed() 30 | .first(where: \.directoryContainsConfigFile) 31 | 32 | return path ?? directory 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /Plugins/SwiftLintBuildToolPlugin/SwiftLintBuildToolPlugin.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | import PackagePlugin 3 | 4 | @main 5 | struct SwiftLintBuildToolPlugin: BuildToolPlugin { 6 | func createBuildCommands( 7 | context: PluginContext, 8 | target: Target 9 | ) async throws -> [Command] { 10 | try makeCommand(executable: context.tool(named: "swiftlint"), 11 | swiftFiles: (target as? SourceModuleTarget).flatMap(swiftFiles) ?? [], 12 | environment: environment(context: context, target: target), 13 | pluginWorkDirectory: context.pluginWorkDirectory) 14 | } 15 | 16 | /// Collects the paths of the Swift files to be linted. 17 | private func swiftFiles(target: SourceModuleTarget) -> [Path] { 18 | target 19 | .sourceFiles(withSuffix: "swift") 20 | .map(\.path) 21 | } 22 | 23 | /// Creates an environment dictionary containing a value for the `BUILD_WORKSPACE_DIRECTORY` key. 24 | /// 25 | /// This method locates the topmost `.swiftlint.yml` config file within the package directory for this target 26 | /// and sets the config file's containing directory as the `BUILD_WORKSPACE_DIRECTORY` value. The package 27 | /// directory is used if a config file is not found. 28 | /// 29 | /// The `BUILD_WORKSPACE_DIRECTORY` environment variable controls SwiftLint's working directory. 30 | /// 31 | /// Reference: [https://github.com/realm/SwiftLint/blob/0.50.3/Source/swiftlint/Commands/SwiftLint.swift#L7]( 32 | /// https://github.com/realm/SwiftLint/blob/0.50.3/Source/swiftlint/Commands/SwiftLint.swift#L7 33 | /// ) 34 | private func environment( 35 | context: PluginContext, 36 | target: Target 37 | ) throws -> [String: String] { 38 | let workingDirectory: Path = try target.directory.resolveWorkingDirectory(in: context.package.directory) 39 | return ["BUILD_WORKSPACE_DIRECTORY": "\(workingDirectory)"] 40 | } 41 | 42 | private func makeCommand( 43 | executable: PluginContext.Tool, 44 | swiftFiles: [Path], 45 | environment: [String: String], 46 | pluginWorkDirectory path: Path 47 | ) throws -> [Command] { 48 | // Don't lint anything if there are no Swift source files in this target 49 | guard !swiftFiles.isEmpty else { 50 | return [] 51 | } 52 | // Outputs the environment to the build log for reference. 53 | print("Environment:", environment) 54 | let arguments: [String] = [ 55 | "lint", 56 | "--quiet", 57 | // We always pass all of the Swift source files in the target to the tool, 58 | // so we need to ensure that any exclusion rules in the configuration are 59 | // respected. 60 | "--force-exclude", 61 | ] 62 | // Determine whether we need to enable cache or not (for Xcode Cloud we don't) 63 | let cacheArguments: [String] 64 | if ProcessInfo.processInfo.environment["CI_XCODE_CLOUD"] == "TRUE" { 65 | cacheArguments = ["--no-cache"] 66 | } else { 67 | let cachePath: Path = path.appending("Cache") 68 | try FileManager.default.createDirectory(atPath: cachePath.string, withIntermediateDirectories: true) 69 | cacheArguments = ["--cache-path", "\(cachePath)"] 70 | } 71 | let outputPath: Path = path.appending("Output") 72 | try FileManager.default.createDirectory(atPath: outputPath.string, withIntermediateDirectories: true) 73 | return [ 74 | .prebuildCommand( 75 | displayName: "SwiftLint", 76 | executable: executable.path, 77 | arguments: arguments + cacheArguments + swiftFiles.map(\.string), 78 | environment: environment, 79 | outputFilesDirectory: outputPath), 80 | ] 81 | } 82 | } 83 | 84 | #if canImport(XcodeProjectPlugin) 85 | 86 | import XcodeProjectPlugin 87 | 88 | // swiftlint:disable:next no_grouping_extension 89 | extension SwiftLintBuildToolPlugin: XcodeBuildToolPlugin { 90 | func createBuildCommands( 91 | context: XcodePluginContext, 92 | target: XcodeTarget 93 | ) throws -> [Command] { 94 | try makeCommand(executable: context.tool(named: "swiftlint"), 95 | swiftFiles: swiftFiles(target: target), 96 | environment: environment(context: context, target: target), 97 | pluginWorkDirectory: context.pluginWorkDirectory) 98 | } 99 | 100 | /// Collects the paths of the Swift files to be linted. 101 | private func swiftFiles(target: XcodeTarget) -> [Path] { 102 | target 103 | .inputFiles 104 | .filter { $0.type == .source && $0.path.extension == "swift" } 105 | .map(\.path) 106 | } 107 | 108 | /// Creates an environment dictionary containing a value for the `BUILD_WORKSPACE_DIRECTORY` key. 109 | /// 110 | /// This method locates the topmost `.swiftlint.yml` config file within the project directory for this target's 111 | /// Swift source files and sets the config file's containing directory as the `BUILD_WORKSPACE_DIRECTORY` value. 112 | /// The project directory is used if a config file is not found. 113 | /// 114 | /// The `BUILD_WORKSPACE_DIRECTORY` environment variable controls SwiftLint's working directory. 115 | /// 116 | /// Reference: [https://github.com/realm/SwiftLint/blob/0.50.3/Source/swiftlint/Commands/SwiftLint.swift#L7]( 117 | /// https://github.com/realm/SwiftLint/blob/0.50.3/Source/swiftlint/Commands/SwiftLint.swift#L7 118 | /// ) 119 | private func environment( 120 | context: XcodePluginContext, 121 | target: XcodeTarget 122 | ) throws -> [String: String] { 123 | let projectDirectory: Path = context.xcodeProject.directory 124 | let swiftFiles: [Path] = swiftFiles(target: target) 125 | let swiftFilesNotInProjectDirectory: [Path] = swiftFiles.filter { !$0.isDescendant(of: projectDirectory) } 126 | 127 | guard swiftFilesNotInProjectDirectory.isEmpty else { 128 | throw SwiftLintBuildToolPluginError.swiftFilesNotInProjectDirectory(projectDirectory) 129 | } 130 | 131 | let directories: [Path] = try swiftFiles.map { try $0.resolveWorkingDirectory(in: projectDirectory) } 132 | let workingDirectory: Path = directories.min { $0.depth < $1.depth } ?? projectDirectory 133 | let swiftFilesNotInWorkingDirectory: [Path] = swiftFiles.filter { !$0.isDescendant(of: workingDirectory) } 134 | 135 | guard swiftFilesNotInWorkingDirectory.isEmpty else { 136 | throw SwiftLintBuildToolPluginError.swiftFilesNotInWorkingDirectory(workingDirectory) 137 | } 138 | 139 | return ["BUILD_WORKSPACE_DIRECTORY": "\(workingDirectory)"] 140 | } 141 | } 142 | 143 | #endif 144 | -------------------------------------------------------------------------------- /Plugins/SwiftLintBuildToolPlugin/SwiftLintBuildToolPluginError.swift: -------------------------------------------------------------------------------- 1 | import PackagePlugin 2 | 3 | enum SwiftLintBuildToolPluginError: Error, CustomStringConvertible { 4 | case pathNotInDirectory(path: Path, directory: Path) 5 | case swiftFilesNotInProjectDirectory(Path) 6 | case swiftFilesNotInWorkingDirectory(Path) 7 | 8 | var description: String { 9 | switch self { 10 | case let .pathNotInDirectory(path, directory): 11 | "Path '\(path)' is not in directory '\(directory)'." 12 | case let .swiftFilesNotInProjectDirectory(directory): 13 | "Swift files are not in project directory '\(directory)'." 14 | case let .swiftFilesNotInWorkingDirectory(directory): 15 | "Swift files are not in working directory '\(directory)'." 16 | } 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /Plugins/SwiftLintCommandPlugin/CommandContext.swift: -------------------------------------------------------------------------------- 1 | import PackagePlugin 2 | 3 | protocol CommandContext { 4 | var tool: String { get throws } 5 | 6 | var cacheDirectory: String { get } 7 | 8 | var workingDirectory: String { get } 9 | 10 | var unitName: String { get } 11 | 12 | var subUnitName: String { get } 13 | 14 | func targets(named names: [String]) throws -> [(paths: [String], name: String)] 15 | } 16 | 17 | extension PluginContext: CommandContext { 18 | var tool: String { 19 | get throws { 20 | try tool(named: "swiftlint").path.string 21 | } 22 | } 23 | 24 | var cacheDirectory: String { 25 | pluginWorkDirectory.string 26 | } 27 | 28 | var workingDirectory: String { 29 | package.directory.string 30 | } 31 | 32 | var unitName: String { 33 | "package" 34 | } 35 | 36 | var subUnitName: String { 37 | "module" 38 | } 39 | 40 | func targets(named names: [String]) throws -> [(paths: [String], name: String)] { 41 | let targets = names.isEmpty 42 | ? package.targets 43 | : try package.targets(named: names) 44 | return targets.compactMap { target in 45 | guard let target = target.sourceModule else { 46 | Diagnostics.warning("Target '\(target.name)' is not a source module; skipping it") 47 | return nil 48 | } 49 | // Packages have a 1-to-1 mapping between targets and directories. 50 | return (paths: [target.directory.string], name: target.name) 51 | } 52 | } 53 | } 54 | 55 | #if canImport(XcodeProjectPlugin) 56 | 57 | import XcodeProjectPlugin 58 | 59 | extension XcodePluginContext: CommandContext { 60 | var tool: String { 61 | get throws { 62 | try tool(named: "swiftlint").path.string 63 | } 64 | } 65 | 66 | var cacheDirectory: String { 67 | pluginWorkDirectory.string 68 | } 69 | 70 | var workingDirectory: String { 71 | xcodeProject.directory.string 72 | } 73 | 74 | var unitName: String { 75 | "project" 76 | } 77 | 78 | var subUnitName: String { 79 | "target" 80 | } 81 | 82 | func targets(named names: [String]) throws -> [(paths: [String], name: String)] { 83 | if names.isEmpty { 84 | return [(paths: [xcodeProject.directory.string], name: xcodeProject.displayName)] 85 | } 86 | return xcodeProject.targets 87 | .filter { names.contains($0.displayName) } 88 | .map { (paths: $0.inputFiles.map(\.path.string).filter { $0.hasSuffix(".swift") }, name: $0.displayName) } 89 | } 90 | } 91 | 92 | #endif 93 | -------------------------------------------------------------------------------- /Plugins/SwiftLintCommandPlugin/SwiftLintCommandPlugin.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | import PackagePlugin 3 | 4 | private let commandsNotExpectingPaths: Set = [ 5 | "docs", 6 | "generate-docs", 7 | "baseline", 8 | "reporters", 9 | "rules", 10 | "version", 11 | ] 12 | 13 | private let commandsWithoutCachPathOption: Set = commandsNotExpectingPaths.union([ 14 | "analyze", 15 | ]) 16 | 17 | @main 18 | struct SwiftLintCommandPlugin: CommandPlugin { 19 | func performCommand(context: PluginContext, arguments: [String]) throws { 20 | try lintFiles(context: context, arguments: arguments) 21 | } 22 | } 23 | 24 | #if canImport(XcodeProjectPlugin) 25 | 26 | import XcodeProjectPlugin 27 | 28 | extension SwiftLintCommandPlugin: XcodeCommandPlugin { 29 | func performCommand(context: XcodePluginContext, arguments: [String]) throws { 30 | try lintFiles(context: context, arguments: arguments) 31 | } 32 | } 33 | 34 | #endif 35 | 36 | extension SwiftLintCommandPlugin { 37 | private func lintFiles(context: some CommandContext, arguments: [String]) throws { 38 | guard !arguments.contains("--cache-path") else { 39 | Diagnostics.error("Caching is managed by the plugin and so setting `--cache-path` is not allowed") 40 | return 41 | } 42 | var argExtractor = ArgumentExtractor(arguments) 43 | let targetNames = argExtractor.extractOption(named: "target") 44 | let remainingArguments = argExtractor.remainingArguments 45 | 46 | if !commandsNotExpectingPaths.isDisjoint(with: remainingArguments) { 47 | try lintFiles(with: context, arguments: remainingArguments) 48 | return 49 | } 50 | guard !targetNames.isEmpty else { 51 | if let pathArgument = remainingArguments.last, FileManager.default.fileExists(atPath: pathArgument) { 52 | Diagnostics.remark("No targets provided. Files provided in path arguments will be linted.") 53 | try lintFiles(in: [], with: context, arguments: remainingArguments) 54 | } else { 55 | try lintFiles(with: context, arguments: remainingArguments) 56 | } 57 | return 58 | } 59 | for target in try context.targets(named: targetNames) { 60 | try lintFiles(in: target.paths, for: target.name, with: context, arguments: remainingArguments) 61 | } 62 | } 63 | 64 | private func lintFiles(in paths: [String] = ["."], 65 | for targetName: String? = nil, 66 | with context: some CommandContext, 67 | arguments: [String]) throws { 68 | let process = Process() 69 | process.currentDirectoryURL = URL(fileURLWithPath: context.workingDirectory) 70 | process.executableURL = URL(fileURLWithPath: try context.tool) 71 | process.arguments = arguments 72 | if commandsWithoutCachPathOption.isDisjoint(with: arguments) { 73 | process.arguments! += ["--cache-path", context.cacheDirectory] 74 | } 75 | if commandsNotExpectingPaths.isDisjoint(with: arguments) { 76 | process.arguments! += paths 77 | } 78 | 79 | try process.run() 80 | process.waitUntilExit() 81 | 82 | let module = targetName.map { "\(context.subUnitName) '\($0)'" } ?? context.unitName 83 | switch process.terminationReason { 84 | case .exit: 85 | Diagnostics.remark("Finished running in \(module)") 86 | case .uncaughtSignal: 87 | Diagnostics.error("Got uncaught signal while running in \(module)") 88 | @unknown default: 89 | Diagnostics.error("Stopped running in \(module) due to unexpected termination reason") 90 | } 91 | 92 | if process.terminationStatus != EXIT_SUCCESS { 93 | Diagnostics.error(""" 94 | Command found error violations or unsuccessfully stopped running with \ 95 | exit code \(process.terminationStatus) in \(module) 96 | """ 97 | ) 98 | } 99 | } 100 | } 101 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # SwiftLint Plugins 2 | 3 | [![Supported Swift versions](https://img.shields.io/endpoint?url=https%3A%2F%2Fswiftpackageindex.com%2Fapi%2Fpackages%2FSimplyDanny%2FSwiftLintPlugins%2Fbadge%3Ftype%3Dswift-versions)](https://swiftpackageindex.com/SimplyDanny/SwiftLintPlugins) 4 | [![Supported platforms](https://img.shields.io/endpoint?url=https%3A%2F%2Fswiftpackageindex.com%2Fapi%2Fpackages%2FSimplyDanny%2FSwiftLintPlugins%2Fbadge%3Ftype%3Dplatforms)](https://swiftpackageindex.com/SimplyDanny/SwiftLintPlugins) 5 | 6 | This repository provides the Swift Package Manager plugins (and only the plugins) that are developed as a part of 7 | [SwiftLint](https://github.com/realm/swiftlint). For comprehensive advice on how to integrate them into your projects 8 | refer to the main repository, especially [Installation](https://github.com/realm/SwiftLint#installation) and 9 | [Setup](https://github.com/realm/SwiftLint#setup). 10 | 11 | > [!NOTE] 12 | > This is only an excerpt of the official [SwiftLint](https://github.com/realm/swiftlint) project. The plugins will be 13 | > developed there next to SwiftLint as a command-line tool itself. The plugin source code is kept in sync with SwiftLint 14 | > and so are the releases. Please report issues and propose changes to the plugins in the main source repository. 15 | 16 | Offering the plugins in a separate package has multiple advantages you should be aware of: 17 | 18 | * No need to clone the whole SwiftLint repository. 19 | * SwiftLint itself is included as a binary dependency, thus the consumer doesn't need to build it first. 20 | * There are no other dependencies that need to be downloaded, resolved and compiled. 21 | * There is especially no induced dependency on [SwiftSyntax](https://github.com/apple/swift-syntax) which would require 22 | a lot of build time alone. 23 | * For projects having adopted Swift macros or depend on SwiftSyntax for other reasons, there is no version conflict 24 | caused by the fact that SwiftLint has to rely on a fixed and pretty current version. 25 | * As this Swift package doesn't provide any build products, there is no way to add them as dependencies to iOS, 26 | watchOS, ... targets. They would fail to build if you were to do that. 27 | 28 | That said, you are perfectly free to consume the plugins directly from the 29 | [SwiftLint](https://github.com/realm/swiftlint) repository instead if you like to. Both ways are functionally 30 | equivalent, however one comes with the aforementioned conveniences. 31 | --------------------------------------------------------------------------------