Skip to content

Commit

Permalink
Implement SwiftLintPlugin as a standalone cli tool (#3)
Browse files Browse the repository at this point in the history
  • Loading branch information
mallexxx authored Apr 18, 2024
1 parent d51beaf commit 5cd7f20
Show file tree
Hide file tree
Showing 6 changed files with 602 additions and 47 deletions.
18 changes: 18 additions & 0 deletions Package.resolved
Original file line number Diff line number Diff line change
@@ -1,5 +1,14 @@
{
"pins" : [
{
"identity" : "swift-argument-parser",
"kind" : "remoteSourceControl",
"location" : "https://github.com/apple/swift-argument-parser.git",
"state" : {
"revision" : "46989693916f56d1186bd59ac15124caef896560",
"version" : "1.3.1"
}
},
{
"identity" : "swift-macro-testing",
"kind" : "remoteSourceControl",
Expand All @@ -26,6 +35,15 @@
"revision" : "64889f0c732f210a935a0ad7cda38f77f876262d",
"version" : "509.1.1"
}
},
{
"identity" : "xcodeeditor",
"kind" : "remoteSourceControl",
"location" : "https://github.com/appsquickly/XcodeEditor.git",
"state" : {
"branch" : "master",
"revision" : "f3234db7fc40d8e917e169bc937ed03ca76ba885"
}
}
],
"version" : 2
Expand Down
29 changes: 15 additions & 14 deletions Package.swift
Original file line number Diff line number Diff line change
Expand Up @@ -11,50 +11,51 @@ let package = Package(
.macOS("11.4")
],
products: [
.plugin(name: "SwiftLintPlugin", targets: ["SwiftLintPlugin"]),
.library(name: "Macros", targets: ["Macros"]),
.executable(name: "SwiftLintTool", targets: ["SwiftLintTool"]),
],
dependencies: [
// Depend on the Swift 5.9 release of SwiftSyntax
.package(url: "https://github.com/apple/swift-syntax.git", from: "509.0.0"),
.package(url: "https://github.com/pointfreeco/swift-macro-testing.git", from: "0.2.2"),
.package(url: "https://github.com/appsquickly/XcodeEditor.git", branch: "master"),
.package(url: "https://github.com/apple/swift-argument-parser.git", from: "1.3.0"),
],
targets: [
.plugin(
name: "SwiftLintPlugin",
capability: .buildTool(),
dependencies: [
.target(name: "SwiftLintBinary", condition: .when(platforms: [.macOS]))
]
),
.binaryTarget(
name: "SwiftLintBinary",
url: "https://github.com/realm/SwiftLint/releases/download/0.54.0/SwiftLintBinary-macos.artifactbundle.zip",
checksum: "963121d6babf2bf5fd66a21ac9297e86d855cbc9d28322790646b88dceca00f1"
),
.executableTarget(
name: "SwiftLintTool",
dependencies: [
"SwiftLintBinary",
.product(name: "XcodeEditor", package: "XcodeEditor"),
.product(name: "ArgumentParser", package: "swift-argument-parser"),
],
path: "Plugins/SwiftLintPlugin"
),
// Macro implementation that performs the source transformation of a macro.
.macro(
name: "MacrosImplementation",
dependencies: [
.product(name: "SwiftSyntaxMacros", package: "swift-syntax"),
.product(name: "SwiftCompilerPlugin", package: "swift-syntax")
],
plugins: [.plugin(name: "SwiftLintPlugin")]
]
),
// Library that exposes a macro as part of its API, which is used in client programs.
.target(
name: "Macros",
dependencies: ["MacrosImplementation"],
plugins: [.plugin(name: "SwiftLintPlugin")]
dependencies: ["MacrosImplementation"]
),
.testTarget(
name: "MacrosTests",
dependencies: [
"MacrosImplementation",
.product(name: "SwiftSyntaxMacrosTestSupport", package: "swift-syntax"),
.product(name: "MacroTesting", package: "swift-macro-testing"),
],
plugins: [.plugin(name: "SwiftLintPlugin")]
]
),
]
)
114 changes: 114 additions & 0 deletions Plugins/SwiftLintPlugin/PathExtension.swift
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,94 @@
//

import Foundation

#if canImport(PackagePlugin)
import PackagePlugin
#else
public struct Path: Hashable {

let string: String

var stringValue: String { string }

init(_ string: String) {
self.string = string
}

/// The last path component (without any extension).
public var stem: String {
let filename = self.lastComponent
if let ext = self.extension {
return String(filename.dropLast(ext.count + 1))
} else {
return filename
}
}

var lastComponent: String {
(string as NSString).lastPathComponent
}

var `extension`: String? {
let ext = (string as NSString).pathExtension
if ext.isEmpty { return nil }
return ext
}

func removingLastComponent() -> Path {
Path((string as NSString).deletingLastPathComponent)
}

func appending(subpath: String) -> Path {
return Path(string + (string.hasSuffix("/") ? "" : "/") + subpath)
}

func appending(_ components: [String]) -> Path {
return self.appending(subpath: components.joined(separator: "/"))
}

func appending(_ components: String...) -> Path {
return self.appending(components)
}

func appending(_ path: Path) -> Path {
return appending(subpath: path.string)
}

}

extension Path: CustomStringConvertible {

@available(_PackageDescription, deprecated: 6.0)
public var description: String {
return self.string
}
}

extension Path: Codable {

@available(_PackageDescription, deprecated: 6.0)
public func encode(to encoder: Encoder) throws {
var container = encoder.singleValueContainer()
try container.encode(self.string)
}

@available(_PackageDescription, deprecated: 6.0)
public init(from decoder: Decoder) throws {
let container = try decoder.singleValueContainer()
let string = try container.decode(String.self)
self.init(string)
}
}

public extension String.StringInterpolation {

@available(_PackageDescription, deprecated: 6.0)
mutating func appendInterpolation(_ path: Path) {
self.appendInterpolation(path.string)
}
}
#endif

extension Path {

Expand Down Expand Up @@ -70,4 +157,31 @@ extension Path {
URL(fileURLWithPath: self.string)
}

var isAbsolute: Bool {
string.hasPrefix("/")
}

var exists: Bool {
return FileManager.default.fileExists(atPath: string)
}

var isDirectory: Bool {
var isDirectory: ObjCBool = false
guard FileManager.default.fileExists(atPath: string, isDirectory: &isDirectory) else { return false }
return isDirectory.boolValue
}

func getDirectoryContents(filter: (Path) throws -> Bool = { _ in true }) rethrows -> [Path] {
var files: [Path] = []
let fileManager = FileManager.default

guard let enumerator = fileManager.enumerator(atPath: self.string) else { return files }

for case let file as String in enumerator where try filter(Path(file)) {
files.append(Path(file))
}

return files
}

}
54 changes: 54 additions & 0 deletions Plugins/SwiftLintPlugin/ProcessExtension.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
//
// ProcessExtension.swift
//
// Copyright © 2023 DuckDuckGo. All rights reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//

import Foundation

// Contains extensions for standalone swiftlint tool to run build system plugin code
#if !canImport(PackagePlugin)

extension Process {

convenience init(_ command: String, _ args: [String], workDirectory: Path? = nil) {
self.init()
self.executableURL = URL(fileURLWithPath: command)
self.arguments = args
if let workDirectory = workDirectory {
self.currentDirectoryURL = workDirectory.url
}
}

func executeCommand() throws -> String {
let pipe = Pipe()
self.standardOutput = pipe
try self.run()

let data = try pipe.fileHandleForReading.readToEnd() ?? Data()
guard let output = String(data: data, encoding: .utf8) else {
throw CocoaError(.fileReadUnknownStringEncoding, userInfo: [NSLocalizedDescriptionKey: "could not decode data \(data.base64EncodedString())"])
}

return output.trimmingCharacters(in: .whitespacesAndNewlines)
}

static func which(_ commandName: String) -> Process {
Process("/usr/bin/which", [commandName])
}

}

#endif
Loading

0 comments on commit 5cd7f20

Please sign in to comment.