From b10e75dcd9537ba39b624bf1f9d725e48ad8af4d Mon Sep 17 00:00:00 2001 From: David Skrundz Date: Tue, 17 Sep 2024 16:06:48 -0600 Subject: [PATCH] Initial commit --- .gitignore | 8 +++ Package.resolved | 15 ++++ Package.swift | 40 +++++++++++ Sources/Superman/Superman.swift | 5 ++ Sources/SupermanMacros/Error.swift | 9 +++ Sources/SupermanMacros/SupermanMacro.swift | 62 +++++++++++++++++ Sources/SupermanSample/main.swift | 50 ++++++++++++++ Tests/SupermanTests/SupermanTest.swift | 79 ++++++++++++++++++++++ 8 files changed, 268 insertions(+) create mode 100644 .gitignore create mode 100644 Package.resolved create mode 100644 Package.swift create mode 100644 Sources/Superman/Superman.swift create mode 100644 Sources/SupermanMacros/Error.swift create mode 100644 Sources/SupermanMacros/SupermanMacro.swift create mode 100644 Sources/SupermanSample/main.swift create mode 100644 Tests/SupermanTests/SupermanTest.swift diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..0023a53 --- /dev/null +++ b/.gitignore @@ -0,0 +1,8 @@ +.DS_Store +/.build +/Packages +xcuserdata/ +DerivedData/ +.swiftpm/configuration/registries.json +.swiftpm/xcode/package.xcworkspace/contents.xcworkspacedata +.netrc diff --git a/Package.resolved b/Package.resolved new file mode 100644 index 0000000..6690937 --- /dev/null +++ b/Package.resolved @@ -0,0 +1,15 @@ +{ + "originHash" : "e51397b3369f3d57e433298148acc6f90a2116f05c1952cc811f11150b1ce7c1", + "pins" : [ + { + "identity" : "swift-syntax", + "kind" : "remoteSourceControl", + "location" : "https://github.com/apple/swift-syntax.git", + "state" : { + "revision" : "cb53fa1bd3219b0b23ded7dfdd3b2baff266fd25", + "version" : "600.0.0" + } + } + ], + "version" : 3 +} diff --git a/Package.swift b/Package.swift new file mode 100644 index 0000000..987ed9f --- /dev/null +++ b/Package.swift @@ -0,0 +1,40 @@ +// swift-tools-version: 6.0 + +import PackageDescription +import CompilerPluginSupport + +let package = Package( + name: "Superman", + platforms: [.macOS(.v10_15), .iOS(.v13), .tvOS(.v13), .watchOS(.v6), .macCatalyst(.v13)], + products: [ + .library( + name: "Superman", + targets: ["Superman"] + ), + .executable( + name: "SupermanSample", + targets: ["SupermanSample"] + ), + ], + dependencies: [ + .package(url: "https://github.com/apple/swift-syntax.git", from: "600.0.0"), + ], + targets: [ + .macro( + name: "SupermanMacros", + dependencies: [ + .product(name: "SwiftSyntaxMacros", package: "swift-syntax"), + .product(name: "SwiftCompilerPlugin", package: "swift-syntax") + ] + ), + .target(name: "Superman", dependencies: ["SupermanMacros"]), + .executableTarget(name: "SupermanSample", dependencies: ["Superman"]), + .testTarget( + name: "SupermanTests", + dependencies: [ + "SupermanMacros", + .product(name: "SwiftSyntaxMacrosTestSupport", package: "swift-syntax"), + ] + ), + ] +) diff --git a/Sources/Superman/Superman.swift b/Sources/Superman/Superman.swift new file mode 100644 index 0000000..73fa801 --- /dev/null +++ b/Sources/Superman/Superman.swift @@ -0,0 +1,5 @@ +@attached(body) +public macro SuperFirst() = #externalMacro(module: "SupermanMacros", type: "SuperFirstMacro") + +@attached(body) +public macro SuperLast() = #externalMacro(module: "SupermanMacros", type: "SuperLastMacro") diff --git a/Sources/SupermanMacros/Error.swift b/Sources/SupermanMacros/Error.swift new file mode 100644 index 0000000..d9788c3 --- /dev/null +++ b/Sources/SupermanMacros/Error.swift @@ -0,0 +1,9 @@ +enum Error: Swift.Error, CustomStringConvertible { + case message(String) + + var description: String { + switch self { + case .message(let text): return text + } + } +} diff --git a/Sources/SupermanMacros/SupermanMacro.swift b/Sources/SupermanMacros/SupermanMacro.swift new file mode 100644 index 0000000..98df010 --- /dev/null +++ b/Sources/SupermanMacros/SupermanMacro.swift @@ -0,0 +1,62 @@ +import SwiftCompilerPlugin +import SwiftSyntax +import SwiftSyntaxMacros + +@main +struct SupermanPlugin: CompilerPlugin { + let providingMacros: [Macro.Type] = [ + SuperFirstMacro.self, + SuperLastMacro.self, + ] +} + +public struct SuperFirstMacro: BodyMacro { + public static func expansion(of node: AttributeSyntax, + providingBodyFor declaration: some DeclSyntaxProtocol & WithOptionalCodeBlockSyntax, + in context: some MacroExpansionContext) throws -> [CodeBlockItemSyntax] { + // Only allow Functions + guard let funcDecl = declaration.as(FunctionDeclSyntax.self) else { + throw Error.message("@SuperFirst only works on functions") + } + + // Only allow Void Functions + guard funcDecl.signature.returnClause?.type.as(IdentifierTypeSyntax.self)?.name.text != "Void" else { + throw Error.message("@SuperFirst only works on Void functions") + } + + // Requires Function Body + guard let funcBody = funcDecl.body else { + throw Error.message("@SuperFirst only works on functions with bodies") + } + + let arguments = funcDecl.signature.parameterClause.parameters + let callArgs = arguments.map { argument in + "\(argument.firstName): \(argument.secondName ?? argument.firstName)" + }.joined(separator: ", ") + + return ["super.\(funcDecl.name)(\(raw: callArgs))"] + Array(funcBody.statements) + } +} + +public struct SuperLastMacro: BodyMacro { + public static func expansion(of node: AttributeSyntax, + providingBodyFor declaration: some DeclSyntaxProtocol & WithOptionalCodeBlockSyntax, + in context: some MacroExpansionContext) throws -> [CodeBlockItemSyntax] { + // Only allow Functions + guard let funcDecl = declaration.as(FunctionDeclSyntax.self) else { + throw Error.message("@SuperLast only works on functions") + } + + // Requires Function Body + guard let funcBody = funcDecl.body else { + throw Error.message("@SuperLast only works on functions with bodies") + } + + let arguments = funcDecl.signature.parameterClause.parameters + let callArgs = arguments.map { argument in + "\(argument.firstName): \(argument.secondName ?? argument.firstName)" + }.joined(separator: ", ") + + return Array(funcBody.statements) + ["return super.\(funcDecl.name)(\(raw: callArgs))"] + } +} diff --git a/Sources/SupermanSample/main.swift b/Sources/SupermanSample/main.swift new file mode 100644 index 0000000..4654455 --- /dev/null +++ b/Sources/SupermanSample/main.swift @@ -0,0 +1,50 @@ +import Superman + +class Base { + func superFirst() { + print("Base superFirst") + } + + func superFirstArgs(_ b: Bool, i: Int, double d: Double) { + print("Base superFirstArgs (\(b), \(i), \(d))") + } + + func superLast() { + print("Base superLast") + } + + func superLastArgs(_ b: Bool, i: Int, double d: Double) { + print("Base superLastArgs (\(b), \(i), \(d))") + } +} + +class Child: Base { + @SuperFirst + override func superFirst() { + print("Child superFirst") + } + + @SuperFirst + override func superFirstArgs(_ b: Bool, i: Int, double d: Double) { + print("Child superFirstArgs (\(b), \(i), \(d))") + } + + @SuperLast + override func superLast() { + print("Child superLast") + } + + @SuperLast + override func superLastArgs(_ b: Bool, i: Int, double d: Double) { + print("Child superLastArgs (\(b), \(i), \(d))") + } +} + +func main() { + let child = Child() + child.superFirst() + child.superLast() + child.superFirstArgs(true, i: 1, double: 0.1) + child.superLastArgs(false, i: 0, double: 1.0) +} +main() diff --git a/Tests/SupermanTests/SupermanTest.swift b/Tests/SupermanTests/SupermanTest.swift new file mode 100644 index 0000000..b218735 --- /dev/null +++ b/Tests/SupermanTests/SupermanTest.swift @@ -0,0 +1,79 @@ +import SwiftSyntaxMacros +import SwiftSyntaxMacrosTestSupport +import XCTest + +#if canImport(SupermanMacros) +import SupermanMacros + +let testMacros: [String: Macro.Type] = [ + "SuperFirst": SuperFirstMacro.self, + "SuperLast": SuperLastMacro.self, +] + +final class QSLibsTests: XCTestCase { + func testSuperFirst() throws { + assertMacroExpansion(""" +@SuperFirst +func function() { + print() +} +""", + expandedSource: """ +func function() { + super.function() + print() +} +""", + macros: testMacros) + } + + func testSuperLast() throws { + assertMacroExpansion(""" +@SuperLast +func function() { + print() +} +""", + expandedSource: """ +func function() { + print() + return super.function() +} +""", + macros: testMacros) + } + + func testSuperFirstArgs() throws { + assertMacroExpansion(""" +@SuperFirst +func function(_ b: Bool, i: Int, double d: Double) { + print() +} +""", + expandedSource: """ +func function(_ b: Bool, i: Int, double d: Double) { + super.function(_ : b, i: i, double : d) + print() +} +""", + macros: testMacros) + } + + func testSuperLastArgs() throws { + assertMacroExpansion(""" +@SuperLast +func function(_ b: Bool, i: Int, double d: Double) { + print() +} +""", + expandedSource: """ +func function(_ b: Bool, i: Int, double d: Double) { + print() + return super.function(_ : b, i: i, double : d) +} +""", + macros: testMacros) + } +} + +#endif