From 78d29e29688bba9fb4139afc678a86e512702716 Mon Sep 17 00:00:00 2001 From: Andy Kolean Date: Wed, 22 May 2024 23:50:01 -0400 Subject: [PATCH 1/8] add missing wrappers --- Sources/MacroToolkit/DeclGroup.swift | 33 ---- Sources/MacroToolkit/DeclGroup/Actor.swift | 19 +++ Sources/MacroToolkit/DeclGroup/Class.swift | 19 +++ .../MacroToolkit/DeclGroup/DeclGroup.swift | 63 +++++++ .../DeclGroup/DeclGroupProtocol.swift | 79 +++++++++ .../MacroToolkit/{ => DeclGroup}/Enum.swift | 15 +- .../MacroToolkit/DeclGroup/Extension.swift | 19 +++ Sources/MacroToolkit/DeclGroup/Struct.swift | 19 +++ Sources/MacroToolkit/DeclGroupProtocol.swift | 71 -------- .../MacroToolkit/Modifiers/AccessLevel.swift | 69 ++++++++ .../DeclarationContextModifier.swift | 31 ++++ Sources/MacroToolkit/Modifiers/Modifier.swift | 23 +++ Sources/MacroToolkit/Struct.swift | 14 -- .../MetaEnumMacro.swift | 4 +- .../OptionSetMacro.swift | 3 +- Tests/MacroToolkitTests/DeclGroupTests.swift | 161 ++++++++++++++++++ Tests/MacroToolkitTests/ModifierTests.swift | 54 ++++++ 17 files changed, 569 insertions(+), 127 deletions(-) delete mode 100644 Sources/MacroToolkit/DeclGroup.swift create mode 100644 Sources/MacroToolkit/DeclGroup/Actor.swift create mode 100644 Sources/MacroToolkit/DeclGroup/Class.swift create mode 100644 Sources/MacroToolkit/DeclGroup/DeclGroup.swift create mode 100644 Sources/MacroToolkit/DeclGroup/DeclGroupProtocol.swift rename Sources/MacroToolkit/{ => DeclGroup}/Enum.swift (60%) create mode 100644 Sources/MacroToolkit/DeclGroup/Extension.swift create mode 100644 Sources/MacroToolkit/DeclGroup/Struct.swift delete mode 100644 Sources/MacroToolkit/DeclGroupProtocol.swift create mode 100644 Sources/MacroToolkit/Modifiers/AccessLevel.swift create mode 100644 Sources/MacroToolkit/Modifiers/DeclarationContextModifier.swift create mode 100644 Sources/MacroToolkit/Modifiers/Modifier.swift delete mode 100644 Sources/MacroToolkit/Struct.swift create mode 100644 Tests/MacroToolkitTests/DeclGroupTests.swift create mode 100644 Tests/MacroToolkitTests/ModifierTests.swift diff --git a/Sources/MacroToolkit/DeclGroup.swift b/Sources/MacroToolkit/DeclGroup.swift deleted file mode 100644 index 4130e09..0000000 --- a/Sources/MacroToolkit/DeclGroup.swift +++ /dev/null @@ -1,33 +0,0 @@ -import SwiftSyntax - -// TODO: Enable initializing from an `any DeclGroupSyntax`. -/// Wraps a declaration group (a declaration with a scoped block of members). -/// For example an `enum` or a `struct` etc. -public struct DeclGroup: DeclGroupProtocol { - public var _syntax: WrappedSyntax - - public init(_ syntax: WrappedSyntax) { - _syntax = syntax - } - - public var identifier: String { - if let `struct` = asStruct { - `struct`.identifier - } else if let `enum` = asEnum { - `enum`.identifier - } else { - // TODO: Implement wrappers for all other decl group types. - fatalError("Unhandled decl group type '\(type(of: _syntax))'") - } - } - - /// Gets the decl group as a struct if it's a struct. - public var asStruct: Struct? { - Struct(_syntax) - } - - /// Gets the decl group as an enum if it's an enum. - public var asEnum: Enum? { - Enum(_syntax) - } -} diff --git a/Sources/MacroToolkit/DeclGroup/Actor.swift b/Sources/MacroToolkit/DeclGroup/Actor.swift new file mode 100644 index 0000000..101c6c6 --- /dev/null +++ b/Sources/MacroToolkit/DeclGroup/Actor.swift @@ -0,0 +1,19 @@ +import SwiftSyntax + +/// Wraps an `actor` declaration. +public struct Actor: DeclGroupProtocol { + /// The underlying syntax node for the `actor` declaration. + public var rawValue: ActorDeclSyntax + + /// The identifier (name) of the `actor`. + public var identifier: String { + _syntax.name.withoutTrivia().text + } + + /// Initializes an `Actor` instance with the given syntax node. + /// + /// - Parameter syntax: The syntax node representing the `actor` declaration. + public init(_ syntax: ActorDeclSyntax) { + rawValue = syntax + } +} diff --git a/Sources/MacroToolkit/DeclGroup/Class.swift b/Sources/MacroToolkit/DeclGroup/Class.swift new file mode 100644 index 0000000..b0feac7 --- /dev/null +++ b/Sources/MacroToolkit/DeclGroup/Class.swift @@ -0,0 +1,19 @@ +import SwiftSyntax + +/// Wraps a `class` declaration. +public struct Class: DeclGroupProtocol { + /// The underlying syntax node for the `class` declaration. + public var rawValue: ClassDeclSyntax + + /// The identifier (name) of the `class`. + public var identifier: String { + _syntax.name.withoutTrivia().text + } + + /// Initializes a `Class` instance with the given syntax node. + /// + /// - Parameter syntax: The syntax node representing the `class` declaration. + public init(_ syntax: ClassDeclSyntax) { + rawValue = syntax + } +} diff --git a/Sources/MacroToolkit/DeclGroup/DeclGroup.swift b/Sources/MacroToolkit/DeclGroup/DeclGroup.swift new file mode 100644 index 0000000..b7bc1a2 --- /dev/null +++ b/Sources/MacroToolkit/DeclGroup/DeclGroup.swift @@ -0,0 +1,63 @@ +import SwiftSyntax + +/// An enumeration representing different types of declaration groups (e.g., `struct`, `class`, `enum`, `actor`, `extension`). +/// This enum conforms to `DeclGroupProtocol` and wraps specific declaration group types. +public enum DeclGroup: DeclGroupProtocol { + case `struct`(Struct) + case `enum`(Enum) + case `class`(Class) + case actor(Actor) + case `extension`(Extension) + + /// A private computed property that returns the wrapped `DeclGroupProtocol` instance. + private var wrapped: any DeclGroupProtocol { + switch self { + case .struct(let wrapped): return wrapped + case .enum(let wrapped): return wrapped + case .class(let wrapped): return wrapped + case .actor(let wrapped): return wrapped + case .extension(let wrapped): return wrapped + } + } + + /// Initializes a `DeclGroup` instance from a `DeclGroupSyntax`. + /// + /// - Parameter rawValue: The syntax node representing the declaration group. + /// - Note: This initializer will fatalError if the syntax node does not match any known declaration group type. + public init(_ rawValue: any DeclGroupSyntax) { + switch rawValue { + case let syntax as ActorDeclSyntax: + self = .actor(Actor(syntax)) + case let syntax as ClassDeclSyntax: + self = .class(Class(syntax)) + case let syntax as EnumDeclSyntax: + self = .enum(Enum(syntax)) + case let syntax as ExtensionDeclSyntax: + self = .extension(Extension(syntax)) + case let syntax as StructDeclSyntax: + self = .struct(Struct(syntax)) + default: + fatalError("Unhandled decl group type '\(type(of: rawValue))'") + } + } + + /// The underlying syntax node for the declaration group. + public var rawValue: any DeclGroupSyntax { + switch self { + case .struct(let wrapped): return wrapped.rawValue + case .enum(let wrapped): return wrapped.rawValue + case .class(let wrapped): return wrapped.rawValue + case .actor(let wrapped): return wrapped.rawValue + case .extension(let wrapped): return wrapped.rawValue + } + } + + /// The identifier of the declaration group. + public var identifier: String { wrapped.identifier } + + /// The members of the declaration group. + public var members: [Decl] { wrapped.members } + + /// The properties declared in the declaration group. + public var properties: [Property] { wrapped.properties } +} diff --git a/Sources/MacroToolkit/DeclGroup/DeclGroupProtocol.swift b/Sources/MacroToolkit/DeclGroup/DeclGroupProtocol.swift new file mode 100644 index 0000000..4913ca6 --- /dev/null +++ b/Sources/MacroToolkit/DeclGroup/DeclGroupProtocol.swift @@ -0,0 +1,79 @@ +import SwiftSyntax + +/// A protocol for declaration groups (e.g., `struct`, `class`, `enum`) that can contain members and properties. +/// Declaration groups are higher-level constructs compared to regular declarations such as `var`. +public protocol DeclGroupProtocol: RawRepresentable { + /// The underlying syntax node for the declaration group. + var rawValue: RawValue { get } + + /// The declaration's identifier. + /// + /// Note: SwiftSyntax's `DeclGroupSyntax` protocol does not include the declaration's identifier. + /// This must be implemented manually for each declaration wrapper. This omission might be due to + /// the fact that extensions technically do not have a name, even though they are always attached to a specific identifier. + var identifier: String { get } + + /// The members of the declaration group. + var members: [Decl] { get } + + /// The properties declared in the declaration group. + var properties: [Property] { get } + + /// Initializes the declaration group with the given syntax node. + /// + /// - Parameter syntax: The underlying syntax node representing the declaration group. + init(_ syntax: RawValue) +} + +extension DeclGroupProtocol { + public var _syntax: RawValue { + get { rawValue } + } + + public init?(rawValue: RawValue) { + self.init(rawValue) + } +} + +extension DeclGroupProtocol where RawValue: DeclGroupSyntax { + public init?(_ syntax: any DeclGroupSyntax) { + guard let rawValue = syntax as? RawValue else { return nil } + self.init(rawValue: rawValue) + } + + public var members: [Decl] { + _syntax.memberBlock.members.map(\.decl).map(Decl.init) + } + + public var properties: [Property] { + members.compactMap(\.asVariable).flatMap { variable in + var bindings = variable._syntax.bindings.flatMap { binding in + Property.properties(from: binding, in: variable) + } + // For the declaration `var a, b: Int` where `a` doesn't have an annotation, + // `a` gets given the type of `b` (`Int`). To implement this, we 'drag' the + // type annotations backwards over the non-annotated bindings. + var lastSeenType: Type? + for (i, binding) in bindings.enumerated().reversed() { + if let type = binding.type { + lastSeenType = type + } else { + bindings[i].type = lastSeenType + } + } + return bindings + } + } + + public var inheritedTypes: [Type] { + _syntax.inheritanceClause?.inheritedTypes.map(\.type).map(Type.init) ?? [] + } + + public var accessLevel: AccessModifier? { + AccessModifier(modifiers: _syntax.modifiers) + } + + public var declarationContext: DeclarationContextModifier? { + DeclarationContextModifier(modifiers: _syntax.modifiers) + } +} diff --git a/Sources/MacroToolkit/Enum.swift b/Sources/MacroToolkit/DeclGroup/Enum.swift similarity index 60% rename from Sources/MacroToolkit/Enum.swift rename to Sources/MacroToolkit/DeclGroup/Enum.swift index c866994..9c05678 100644 --- a/Sources/MacroToolkit/Enum.swift +++ b/Sources/MacroToolkit/DeclGroup/Enum.swift @@ -2,16 +2,21 @@ import SwiftSyntax /// Wraps an `enum` declaration. public struct Enum: DeclGroupProtocol { - public var _syntax: EnumDeclSyntax - + /// The underlying syntax node for the `enum` declaration. + public var rawValue: EnumDeclSyntax + + /// The identifier (name) of the `enum`. public var identifier: String { _syntax.name.withoutTrivia().text } - + + /// Initializes an `Enum` instance with the given syntax node. + /// + /// - Parameter syntax: The syntax node representing the `enum` declaration. public init(_ syntax: EnumDeclSyntax) { - _syntax = syntax + rawValue = syntax } - + /// The `enum`'s cases. public var cases: [EnumCase] { _syntax.memberBlock.members diff --git a/Sources/MacroToolkit/DeclGroup/Extension.swift b/Sources/MacroToolkit/DeclGroup/Extension.swift new file mode 100644 index 0000000..573b3ee --- /dev/null +++ b/Sources/MacroToolkit/DeclGroup/Extension.swift @@ -0,0 +1,19 @@ +import SwiftSyntax + +/// Wraps an `extension` declaration. +public struct Extension: DeclGroupProtocol { + /// The underlying syntax node for the `extension` declaration. + public var rawValue: ExtensionDeclSyntax + + /// The identifier (extended type) of the `extension`. + public var identifier: String { + _syntax.extendedType.withoutTrivia().description + } + + /// Initializes an `Extension` instance with the given syntax node. + /// + /// - Parameter syntax: The syntax node representing the `extension` declaration. + public init(_ syntax: ExtensionDeclSyntax) { + rawValue = syntax + } +} diff --git a/Sources/MacroToolkit/DeclGroup/Struct.swift b/Sources/MacroToolkit/DeclGroup/Struct.swift new file mode 100644 index 0000000..4e98d49 --- /dev/null +++ b/Sources/MacroToolkit/DeclGroup/Struct.swift @@ -0,0 +1,19 @@ +import SwiftSyntax + +/// Wraps a `struct` declaration. +public struct Struct: DeclGroupProtocol { + /// The underlying syntax node for the `struct` declaration. + public var rawValue: StructDeclSyntax + + /// The identifier (name) of the `struct`. + public var identifier: String { + _syntax.name.withoutTrivia().text + } + + /// Initializes a `Struct` instance with the given syntax node. + /// + /// - Parameter syntax: The syntax node representing the `struct` declaration. + public init(_ syntax: StructDeclSyntax) { + rawValue = syntax + } +} diff --git a/Sources/MacroToolkit/DeclGroupProtocol.swift b/Sources/MacroToolkit/DeclGroupProtocol.swift deleted file mode 100644 index a4cc36e..0000000 --- a/Sources/MacroToolkit/DeclGroupProtocol.swift +++ /dev/null @@ -1,71 +0,0 @@ -import SwiftSyntax - -/// A declaration group (e.g. a `struct` or `class` rather than a regular -/// declaration such as `var`). -public protocol DeclGroupProtocol { - /// The type of the underlying syntax node being wrapped. - associatedtype WrappedSyntax: DeclGroupSyntax - /// The underlying syntax node. - var _syntax: WrappedSyntax { get } - /// The declaration's identifier. - /// - /// For some reason SwiftSyntax's `DeclGroupSyntax` protocol doesn't have the - /// declaration's identifier, so this needs to be implemented manually - /// for every declaration wrapper. Maybe due to extensions technically not - /// having a name? (although they're always attached to a specific identifier). - var identifier: String { get } - /// Wraps a syntax node. - init(_ syntax: WrappedSyntax) - -} - -extension DeclGroupProtocol { - /// Attempts to initialize the wrapper from an arbitrary decl group (succeeds - /// if the decl group is the right type of syntax). - public init?(_ syntax: any DeclGroupSyntax) { - guard let syntax = syntax.as(WrappedSyntax.self) else { - return nil - } - self.init(syntax) - } - - /// The declaration group's members. - public var members: [Decl] { - _syntax.memberBlock.members.map(\.decl).map(Decl.init) - } - - /// The declaration group's declared properties. - public var properties: [Property] { - members.compactMap(\.asVariable).flatMap { variable in - var bindings = variable._syntax.bindings.flatMap { binding in - Property.properties(from: binding, in: variable) - } - // For the declaration `var a, b: Int` where `a` doesn't have an annotation, - // `a` gets given the type of `b` (`Int`). To implement this, we 'drag' the - // type annotations backwards over the non-annotated bindings. - var lastSeenType: Type? - for (i, binding) in bindings.enumerated().reversed() { - if let type = binding.type { - lastSeenType = type - } else { - bindings[i].type = lastSeenType - } - } - return bindings - } - } - - /// The types inherited from or conformed to by the decl group. Doesn't - /// include conformances added by other declaration groups such as an - /// `extension` of the current declaration. - public var inheritedTypes: [Type] { - _syntax.inheritanceClause?.inheritedTypes.map(\.type).map(Type.init) ?? [] - } - - // TODO: Replace this with an accessLevel property - /// Whether the declaration was declared with the `public` access level - /// modifier. - public var isPublic: Bool { - _syntax.isPublic - } -} diff --git a/Sources/MacroToolkit/Modifiers/AccessLevel.swift b/Sources/MacroToolkit/Modifiers/AccessLevel.swift new file mode 100644 index 0000000..eebed4a --- /dev/null +++ b/Sources/MacroToolkit/Modifiers/AccessLevel.swift @@ -0,0 +1,69 @@ +import SwiftSyntax + +/// Represents access control levels in Swift (e.g., private, public). +public enum AccessModifier: RawRepresentable, Modifier, Comparable { + case `private` + case `fileprivate` + case `internal` + case `package` + case `public` + case `open` + + /// Initializes an `AccessModifier` from a `TokenKind`. + /// + /// - Parameter rawValue: The `TokenKind` representing an access control keyword. + public init?(rawValue: TokenKind) { + switch rawValue { + case .keyword(.private): + self = .private + case .keyword(.fileprivate): + self = .fileprivate + case .keyword(.internal): + self = .internal + case .keyword(.package): + self = .package + case .keyword(.public): + self = .public + case .keyword(.open): + self = .open + default: + return nil + } + } + + /// The `TokenKind` corresponding to the `AccessModifier`. + public var rawValue: TokenKind { + switch self { + case .private: + return .keyword(.private) + case .fileprivate: + return .keyword(.fileprivate) + case .internal: + return .keyword(.internal) + case .package: + return .keyword(.package) + case .public: + return .keyword(.public) + case .open: + return .keyword(.open) + } + } + + /// The string name of the `AccessModifier`. + public var name: String { + switch self { + case .private: + return "private" + case .fileprivate: + return "fileprivate" + case .internal: + return "internal" + case .package: + return "package" + case .public: + return "public" + case .open: + return "open" + } + } +} diff --git a/Sources/MacroToolkit/Modifiers/DeclarationContextModifier.swift b/Sources/MacroToolkit/Modifiers/DeclarationContextModifier.swift new file mode 100644 index 0000000..ad8cbfb --- /dev/null +++ b/Sources/MacroToolkit/Modifiers/DeclarationContextModifier.swift @@ -0,0 +1,31 @@ +import SwiftSyntax + +/// Represents context-specific modifiers for declarations (e.g., static, class). +public enum DeclarationContextModifier: RawRepresentable, Modifier { + case `static` + case `class` + + /// Initializes a `DeclarationContextModifier` from a `TokenKind`. + /// + /// - Parameter rawValue: The `TokenKind` representing a context-specific keyword. + public init?(rawValue: TokenKind) { + switch rawValue { + case .keyword(.static): + self = .static + case .keyword(.class): + self = .class + default: + return nil + } + } + + /// The `TokenKind` corresponding to the `DeclarationContextModifier`. + public var rawValue: TokenKind { + switch self { + case .static: + return .keyword(.static) + case .class: + return .keyword(.class) + } + } +} diff --git a/Sources/MacroToolkit/Modifiers/Modifier.swift b/Sources/MacroToolkit/Modifiers/Modifier.swift new file mode 100644 index 0000000..9e6eb01 --- /dev/null +++ b/Sources/MacroToolkit/Modifiers/Modifier.swift @@ -0,0 +1,23 @@ +import SwiftSyntax + +/// A protocol for modifiers in Swift that are represented by `TokenKind`. +public protocol Modifier: RawRepresentable where RawValue == TokenKind { + /// Initializes a `Modifier` from a list of declaration modifiers. + /// + /// - Parameter modifiers: A list of declaration modifiers. + init?(modifiers: DeclModifierListSyntax) +} + +extension Modifier { + /// Default implementation for initializing a `Modifier` from a list of declaration modifiers. + /// + /// - Parameter modifiers: A list of declaration modifiers. + public init?(modifiers: DeclModifierListSyntax) { + for element in modifiers { + guard let modifier = Self(rawValue: element.name.tokenKind) else { continue } + self = modifier + return + } + return nil + } +} diff --git a/Sources/MacroToolkit/Struct.swift b/Sources/MacroToolkit/Struct.swift deleted file mode 100644 index b154e9d..0000000 --- a/Sources/MacroToolkit/Struct.swift +++ /dev/null @@ -1,14 +0,0 @@ -import SwiftSyntax - -/// Wraps a `struct` declaration. -public struct Struct: DeclGroupProtocol { - public var _syntax: StructDeclSyntax - - public var identifier: String { - _syntax.name.withoutTrivia().text - } - - public init(_ syntax: StructDeclSyntax) { - _syntax = syntax - } -} diff --git a/Sources/MacroToolkitExamplePlugin/MetaEnumMacro.swift b/Sources/MacroToolkitExamplePlugin/MetaEnumMacro.swift index ba2fa97..87e0505 100644 --- a/Sources/MacroToolkitExamplePlugin/MetaEnumMacro.swift +++ b/Sources/MacroToolkitExamplePlugin/MetaEnumMacro.swift @@ -21,8 +21,8 @@ public struct MetaEnumMacro { } parentTypeName = enumDecl.identifier - - access = enumDecl.isPublic ? "public " : "" + + access = enumDecl.accessLevel == .public ? "public " : "" metaCases = enumDecl.cases.map { case_ in case_.withoutValue() diff --git a/Sources/MacroToolkitExamplePlugin/OptionSetMacro.swift b/Sources/MacroToolkitExamplePlugin/OptionSetMacro.swift index 00d2361..835b586 100644 --- a/Sources/MacroToolkitExamplePlugin/OptionSetMacro.swift +++ b/Sources/MacroToolkitExamplePlugin/OptionSetMacro.swift @@ -149,8 +149,7 @@ extension OptionSetMacro: MemberMacro { let cases = optionsEnum.cases - // TODO: This seems wrong, surely other modifiers would also make sense to passthrough? - let access = structDecl.isPublic ? "public " : "" + let access = structDecl.accessLevel.map { "\($0) " } ?? "" let staticVars = cases.map { (case_) -> DeclSyntax in """ diff --git a/Tests/MacroToolkitTests/DeclGroupTests.swift b/Tests/MacroToolkitTests/DeclGroupTests.swift new file mode 100644 index 0000000..eaf04e3 --- /dev/null +++ b/Tests/MacroToolkitTests/DeclGroupTests.swift @@ -0,0 +1,161 @@ +import XCTest +import SwiftSyntax +@testable import MacroToolkit + +final class DeclGroupTests: XCTestCase { + + func testStructInitialization() throws { + let decl: DeclSyntax = """ + struct TestStruct { var value: Int } + """ + let structDecl = decl.as(StructDeclSyntax.self)! + let testStruct = Struct(structDecl) + + XCTAssertEqual(testStruct.identifier, "TestStruct") + XCTAssertEqual(testStruct.members.count, 1) + XCTAssertEqual(testStruct.properties.count, 1) + } + + func testEnumInitialization() throws { + let decl: DeclSyntax = """ + enum TestEnum { case caseOne, caseTwo } + """ + let enumDecl = decl.as(EnumDeclSyntax.self)! + let testEnum = Enum(enumDecl) + + XCTAssertEqual(testEnum.identifier, "TestEnum") + XCTAssertEqual(testEnum.members.count, 1) + XCTAssertEqual(testEnum.cases.count, 2) + } + + func testClassInitialization() throws { + let decl: DeclSyntax = """ + class TestClass { var value: Int } + """ + let classDecl = decl.as(ClassDeclSyntax.self)! + let testClass = Class(classDecl) + + XCTAssertEqual(testClass.identifier, "TestClass") + XCTAssertEqual(testClass.members.count, 1) + XCTAssertEqual(testClass.properties.count, 1) + } + + func testActorInitialization() throws { + let decl: DeclSyntax = """ + actor TestActor { var value: Int } + """ + let actorDecl = decl.as(ActorDeclSyntax.self)! + let testActor = Actor(actorDecl) + + XCTAssertEqual(testActor.identifier, "TestActor") + XCTAssertEqual(testActor.members.count, 1) + XCTAssertEqual(testActor.properties.count, 1) + } + + func testExtensionInitialization() throws { + let decl: DeclSyntax = """ + extension TestStruct { func testMethod() {} } + """ + let extensionDecl = decl.as(ExtensionDeclSyntax.self)! + let testExtension = Extension(extensionDecl) + + XCTAssertEqual(testExtension.identifier, "TestStruct") + XCTAssertEqual(testExtension.members.count, 1) + } + + func testDeclGroupInitialization() throws { + let structDecl: DeclSyntax = """ + struct TestStruct { var value: Int } + """ + let structSyntax = structDecl.as(StructDeclSyntax.self)! + let structDeclGroup = DeclGroup(structSyntax) + + switch structDeclGroup { + case .struct(let testStruct): + XCTAssertEqual(testStruct.identifier, "TestStruct") + XCTAssertEqual(testStruct.members.count, 1) + XCTAssertEqual(testStruct.properties.count, 1) + default: + XCTFail("Expected .struct case") + } + + let enumDecl: DeclSyntax = """ + enum TestEnum { case caseOne, caseTwo } + """ + let enumSyntax = enumDecl.as(EnumDeclSyntax.self)! + let enumDeclGroup = DeclGroup(enumSyntax) + + switch enumDeclGroup { + case .enum(let testEnum): + XCTAssertEqual(testEnum.identifier, "TestEnum") + XCTAssertEqual(testEnum.members.count, 1) + XCTAssertEqual(testEnum.cases.count, 2) + default: + XCTFail("Expected .enum case") + } + + let classDecl: DeclSyntax = """ + class TestClass { var value: Int } + """ + let classSyntax = classDecl.as(ClassDeclSyntax.self)! + let classDeclGroup = DeclGroup(classSyntax) + + switch classDeclGroup { + case .class(let testClass): + XCTAssertEqual(testClass.identifier, "TestClass") + XCTAssertEqual(testClass.members.count, 1) + XCTAssertEqual(testClass.properties.count, 1) + default: + XCTFail("Expected .class case") + } + + let actorDecl: DeclSyntax = """ + actor TestActor { var value: Int } + """ + let actorSyntax = actorDecl.as(ActorDeclSyntax.self)! + let actorDeclGroup = DeclGroup(actorSyntax) + + switch actorDeclGroup { + case .actor(let testActor): + XCTAssertEqual(testActor.identifier, "TestActor") + XCTAssertEqual(testActor.members.count, 1) + XCTAssertEqual(testActor.properties.count, 1) + default: + XCTFail("Expected .actor case") + } + + let extensionDecl: DeclSyntax = """ + extension TestStruct { func testMethod() {} } + """ + let extensionSyntax = extensionDecl.as(ExtensionDeclSyntax.self)! + let extensionDeclGroup = DeclGroup(extensionSyntax) + + switch extensionDeclGroup { + case .extension(let testExtension): + XCTAssertEqual(testExtension.identifier, "TestStruct") + XCTAssertEqual(testExtension.members.count, 1) + default: + XCTFail("Expected .extension case") + } + } + + func testDeclGroupProtocolExtension() throws { + let decl: DeclSyntax = """ + public class TestClass: SuperClass, ProtocolOne, ProtocolTwo { + public var a: Int + var b: Int + public static var c: Int + func method() {} + } + """ + let classDecl = decl.as(ClassDeclSyntax.self)! + let testClass = Class(classDecl) + + XCTAssertEqual(testClass.identifier, "TestClass") + XCTAssertEqual(testClass.members.count, 4) + XCTAssertEqual(testClass.properties.count, 3) + XCTAssertEqual(testClass.inheritedTypes.map { $0.description }, ["SuperClass", "ProtocolOne", "ProtocolTwo"]) + XCTAssertEqual(testClass.accessLevel, .public) + XCTAssertEqual(testClass.declarationContext, nil) + } +} diff --git a/Tests/MacroToolkitTests/ModifierTests.swift b/Tests/MacroToolkitTests/ModifierTests.swift new file mode 100644 index 0000000..f680b94 --- /dev/null +++ b/Tests/MacroToolkitTests/ModifierTests.swift @@ -0,0 +1,54 @@ +import XCTest +import SwiftSyntax +import SwiftSyntaxBuilder +@testable import MacroToolkit + +final class ModifierTests: XCTestCase { + func testAccessModifierInit() { + XCTAssertEqual(AccessModifier(rawValue: .keyword(.private)), .private) + XCTAssertEqual(AccessModifier(rawValue: .keyword(.fileprivate)), .fileprivate) + XCTAssertEqual(AccessModifier(rawValue: .keyword(.internal)), .internal) + XCTAssertEqual(AccessModifier(rawValue: .keyword(.package)), .package) + XCTAssertEqual(AccessModifier(rawValue: .keyword(.public)), .public) + XCTAssertEqual(AccessModifier(rawValue: .keyword(.open)), .open) + XCTAssertNil(AccessModifier(rawValue: .identifier("custom"))) + } + + func testAccessModifierRawValue() { + XCTAssertEqual(AccessModifier.private.rawValue, .keyword(.private)) + XCTAssertEqual(AccessModifier.fileprivate.rawValue, .keyword(.fileprivate)) + XCTAssertEqual(AccessModifier.internal.rawValue, .keyword(.internal)) + XCTAssertEqual(AccessModifier.package.rawValue, .keyword(.package)) + XCTAssertEqual(AccessModifier.public.rawValue, .keyword(.public)) + XCTAssertEqual(AccessModifier.open.rawValue, .keyword(.open)) + } + + func testAccessModifierName() { + XCTAssertEqual(AccessModifier.private.name, "private") + XCTAssertEqual(AccessModifier.fileprivate.name, "fileprivate") + XCTAssertEqual(AccessModifier.internal.name, "internal") + XCTAssertEqual(AccessModifier.package.name, "package") + XCTAssertEqual(AccessModifier.public.name, "public") + XCTAssertEqual(AccessModifier.open.name, "open") + } + + func testAccessModifierInitWithModifiers() throws { + let decl: DeclSyntax = """ + private struct Test { } + """ + let structDecl = decl.as(StructDeclSyntax.self) + let structObj = Struct.init(rawValue: structDecl!) + XCTAssertEqual(structObj?.accessLevel, .private) + } + + func testDeclarationContextModifierInit() { + XCTAssertEqual(DeclarationContextModifier(rawValue: .keyword(.static)), .static) + XCTAssertEqual(DeclarationContextModifier(rawValue: .keyword(.class)), .class) + XCTAssertNil(DeclarationContextModifier(rawValue: .identifier("custom"))) + } + + func testDeclarationContextModifierRawValue() { + XCTAssertEqual(DeclarationContextModifier.static.rawValue, .keyword(.static)) + XCTAssertEqual(DeclarationContextModifier.class.rawValue, .keyword(.class)) + } +} From fc363e228c11cdfbf12de8ab2fae5c42ed776dbe Mon Sep 17 00:00:00 2001 From: Andy Kolean Date: Wed, 22 May 2024 23:58:04 -0400 Subject: [PATCH 2/8] swift-format --- Sources/MacroToolkit/DeclGroup/Actor.swift | 4 +- Sources/MacroToolkit/DeclGroup/Class.swift | 4 +- .../MacroToolkit/DeclGroup/DeclGroup.swift | 56 ++++++------- .../DeclGroup/DeclGroupProtocol.swift | 39 ++++++--- Sources/MacroToolkit/DeclGroup/Enum.swift | 6 +- .../MacroToolkit/DeclGroup/Extension.swift | 4 +- Sources/MacroToolkit/DeclGroup/Struct.swift | 4 +- .../MacroToolkit/Modifiers/AccessLevel.swift | 82 +++++++++---------- .../DeclarationContextModifier.swift | 24 +++--- 9 files changed, 120 insertions(+), 103 deletions(-) diff --git a/Sources/MacroToolkit/DeclGroup/Actor.swift b/Sources/MacroToolkit/DeclGroup/Actor.swift index 101c6c6..7a20cf8 100644 --- a/Sources/MacroToolkit/DeclGroup/Actor.swift +++ b/Sources/MacroToolkit/DeclGroup/Actor.swift @@ -4,12 +4,12 @@ import SwiftSyntax public struct Actor: DeclGroupProtocol { /// The underlying syntax node for the `actor` declaration. public var rawValue: ActorDeclSyntax - + /// The identifier (name) of the `actor`. public var identifier: String { _syntax.name.withoutTrivia().text } - + /// Initializes an `Actor` instance with the given syntax node. /// /// - Parameter syntax: The syntax node representing the `actor` declaration. diff --git a/Sources/MacroToolkit/DeclGroup/Class.swift b/Sources/MacroToolkit/DeclGroup/Class.swift index b0feac7..1204b53 100644 --- a/Sources/MacroToolkit/DeclGroup/Class.swift +++ b/Sources/MacroToolkit/DeclGroup/Class.swift @@ -4,12 +4,12 @@ import SwiftSyntax public struct Class: DeclGroupProtocol { /// The underlying syntax node for the `class` declaration. public var rawValue: ClassDeclSyntax - + /// The identifier (name) of the `class`. public var identifier: String { _syntax.name.withoutTrivia().text } - + /// Initializes a `Class` instance with the given syntax node. /// /// - Parameter syntax: The syntax node representing the `class` declaration. diff --git a/Sources/MacroToolkit/DeclGroup/DeclGroup.swift b/Sources/MacroToolkit/DeclGroup/DeclGroup.swift index b7bc1a2..e3b78ab 100644 --- a/Sources/MacroToolkit/DeclGroup/DeclGroup.swift +++ b/Sources/MacroToolkit/DeclGroup/DeclGroup.swift @@ -8,56 +8,56 @@ public enum DeclGroup: DeclGroupProtocol { case `class`(Class) case actor(Actor) case `extension`(Extension) - + /// A private computed property that returns the wrapped `DeclGroupProtocol` instance. private var wrapped: any DeclGroupProtocol { switch self { - case .struct(let wrapped): return wrapped - case .enum(let wrapped): return wrapped - case .class(let wrapped): return wrapped - case .actor(let wrapped): return wrapped - case .extension(let wrapped): return wrapped + case .struct(let wrapped): return wrapped + case .enum(let wrapped): return wrapped + case .class(let wrapped): return wrapped + case .actor(let wrapped): return wrapped + case .extension(let wrapped): return wrapped } } - + /// Initializes a `DeclGroup` instance from a `DeclGroupSyntax`. /// /// - Parameter rawValue: The syntax node representing the declaration group. /// - Note: This initializer will fatalError if the syntax node does not match any known declaration group type. public init(_ rawValue: any DeclGroupSyntax) { switch rawValue { - case let syntax as ActorDeclSyntax: - self = .actor(Actor(syntax)) - case let syntax as ClassDeclSyntax: - self = .class(Class(syntax)) - case let syntax as EnumDeclSyntax: - self = .enum(Enum(syntax)) - case let syntax as ExtensionDeclSyntax: - self = .extension(Extension(syntax)) - case let syntax as StructDeclSyntax: - self = .struct(Struct(syntax)) - default: - fatalError("Unhandled decl group type '\(type(of: rawValue))'") + case let syntax as ActorDeclSyntax: + self = .actor(Actor(syntax)) + case let syntax as ClassDeclSyntax: + self = .class(Class(syntax)) + case let syntax as EnumDeclSyntax: + self = .enum(Enum(syntax)) + case let syntax as ExtensionDeclSyntax: + self = .extension(Extension(syntax)) + case let syntax as StructDeclSyntax: + self = .struct(Struct(syntax)) + default: + fatalError("Unhandled decl group type '\(type(of: rawValue))'") } } - + /// The underlying syntax node for the declaration group. public var rawValue: any DeclGroupSyntax { switch self { - case .struct(let wrapped): return wrapped.rawValue - case .enum(let wrapped): return wrapped.rawValue - case .class(let wrapped): return wrapped.rawValue - case .actor(let wrapped): return wrapped.rawValue - case .extension(let wrapped): return wrapped.rawValue + case .struct(let wrapped): return wrapped.rawValue + case .enum(let wrapped): return wrapped.rawValue + case .class(let wrapped): return wrapped.rawValue + case .actor(let wrapped): return wrapped.rawValue + case .extension(let wrapped): return wrapped.rawValue } } - + /// The identifier of the declaration group. public var identifier: String { wrapped.identifier } - + /// The members of the declaration group. public var members: [Decl] { wrapped.members } - + /// The properties declared in the declaration group. public var properties: [Property] { wrapped.properties } } diff --git a/Sources/MacroToolkit/DeclGroup/DeclGroupProtocol.swift b/Sources/MacroToolkit/DeclGroup/DeclGroupProtocol.swift index 4913ca6..df1bdc6 100644 --- a/Sources/MacroToolkit/DeclGroup/DeclGroupProtocol.swift +++ b/Sources/MacroToolkit/DeclGroup/DeclGroupProtocol.swift @@ -5,46 +5,58 @@ import SwiftSyntax public protocol DeclGroupProtocol: RawRepresentable { /// The underlying syntax node for the declaration group. var rawValue: RawValue { get } - + /// The declaration's identifier. /// /// Note: SwiftSyntax's `DeclGroupSyntax` protocol does not include the declaration's identifier. /// This must be implemented manually for each declaration wrapper. This omission might be due to /// the fact that extensions technically do not have a name, even though they are always attached to a specific identifier. var identifier: String { get } - + /// The members of the declaration group. var members: [Decl] { get } - + /// The properties declared in the declaration group. var properties: [Property] { get } - + /// Initializes the declaration group with the given syntax node. /// /// - Parameter syntax: The underlying syntax node representing the declaration group. init(_ syntax: RawValue) } +/// Default implementations and helper initializers for `DeclGroupProtocol`. extension DeclGroupProtocol { + /// The underlying syntax node for the declaration group. public var _syntax: RawValue { - get { rawValue } + rawValue } - + + /// Initializes the declaration group with the given raw value. + /// + /// - Parameter rawValue: The raw value representing the declaration group. public init?(rawValue: RawValue) { self.init(rawValue) } } +/// Additional functionality for `DeclGroupProtocol` where the raw value conforms to `DeclGroupSyntax`. extension DeclGroupProtocol where RawValue: DeclGroupSyntax { + /// Attempts to initialize the wrapper from an arbitrary declaration group. + /// + /// - Parameter syntax: The syntax node representing the declaration group. + /// - Note: This initializer will return `nil` if the syntax node does not match the expected type. public init?(_ syntax: any DeclGroupSyntax) { guard let rawValue = syntax as? RawValue else { return nil } self.init(rawValue: rawValue) } - + + /// The members of the declaration group. public var members: [Decl] { _syntax.memberBlock.members.map(\.decl).map(Decl.init) } - + + /// The properties declared in the declaration group. public var properties: [Property] { members.compactMap(\.asVariable).flatMap { variable in var bindings = variable._syntax.bindings.flatMap { binding in @@ -64,15 +76,20 @@ extension DeclGroupProtocol where RawValue: DeclGroupSyntax { return bindings } } - + + /// The types inherited from or conformed to by the declaration group. + /// + /// - Note: This does not include conformances added by other declaration groups such as extensions. public var inheritedTypes: [Type] { _syntax.inheritanceClause?.inheritedTypes.map(\.type).map(Type.init) ?? [] } - + + /// The access level of the declaration group. public var accessLevel: AccessModifier? { AccessModifier(modifiers: _syntax.modifiers) } - + + /// The context-specific modifiers of the declaration group. public var declarationContext: DeclarationContextModifier? { DeclarationContextModifier(modifiers: _syntax.modifiers) } diff --git a/Sources/MacroToolkit/DeclGroup/Enum.swift b/Sources/MacroToolkit/DeclGroup/Enum.swift index 9c05678..05a1c7a 100644 --- a/Sources/MacroToolkit/DeclGroup/Enum.swift +++ b/Sources/MacroToolkit/DeclGroup/Enum.swift @@ -4,19 +4,19 @@ import SwiftSyntax public struct Enum: DeclGroupProtocol { /// The underlying syntax node for the `enum` declaration. public var rawValue: EnumDeclSyntax - + /// The identifier (name) of the `enum`. public var identifier: String { _syntax.name.withoutTrivia().text } - + /// Initializes an `Enum` instance with the given syntax node. /// /// - Parameter syntax: The syntax node representing the `enum` declaration. public init(_ syntax: EnumDeclSyntax) { rawValue = syntax } - + /// The `enum`'s cases. public var cases: [EnumCase] { _syntax.memberBlock.members diff --git a/Sources/MacroToolkit/DeclGroup/Extension.swift b/Sources/MacroToolkit/DeclGroup/Extension.swift index 573b3ee..6f51a92 100644 --- a/Sources/MacroToolkit/DeclGroup/Extension.swift +++ b/Sources/MacroToolkit/DeclGroup/Extension.swift @@ -4,12 +4,12 @@ import SwiftSyntax public struct Extension: DeclGroupProtocol { /// The underlying syntax node for the `extension` declaration. public var rawValue: ExtensionDeclSyntax - + /// The identifier (extended type) of the `extension`. public var identifier: String { _syntax.extendedType.withoutTrivia().description } - + /// Initializes an `Extension` instance with the given syntax node. /// /// - Parameter syntax: The syntax node representing the `extension` declaration. diff --git a/Sources/MacroToolkit/DeclGroup/Struct.swift b/Sources/MacroToolkit/DeclGroup/Struct.swift index 4e98d49..8d27f5c 100644 --- a/Sources/MacroToolkit/DeclGroup/Struct.swift +++ b/Sources/MacroToolkit/DeclGroup/Struct.swift @@ -4,12 +4,12 @@ import SwiftSyntax public struct Struct: DeclGroupProtocol { /// The underlying syntax node for the `struct` declaration. public var rawValue: StructDeclSyntax - + /// The identifier (name) of the `struct`. public var identifier: String { _syntax.name.withoutTrivia().text } - + /// Initializes a `Struct` instance with the given syntax node. /// /// - Parameter syntax: The syntax node representing the `struct` declaration. diff --git a/Sources/MacroToolkit/Modifiers/AccessLevel.swift b/Sources/MacroToolkit/Modifiers/AccessLevel.swift index eebed4a..5fbbb19 100644 --- a/Sources/MacroToolkit/Modifiers/AccessLevel.swift +++ b/Sources/MacroToolkit/Modifiers/AccessLevel.swift @@ -8,62 +8,62 @@ public enum AccessModifier: RawRepresentable, Modifier, Comparable { case `package` case `public` case `open` - + /// Initializes an `AccessModifier` from a `TokenKind`. /// /// - Parameter rawValue: The `TokenKind` representing an access control keyword. public init?(rawValue: TokenKind) { switch rawValue { - case .keyword(.private): - self = .private - case .keyword(.fileprivate): - self = .fileprivate - case .keyword(.internal): - self = .internal - case .keyword(.package): - self = .package - case .keyword(.public): - self = .public - case .keyword(.open): - self = .open - default: - return nil + case .keyword(.private): + self = .private + case .keyword(.fileprivate): + self = .fileprivate + case .keyword(.internal): + self = .internal + case .keyword(.package): + self = .package + case .keyword(.public): + self = .public + case .keyword(.open): + self = .open + default: + return nil } } - + /// The `TokenKind` corresponding to the `AccessModifier`. public var rawValue: TokenKind { switch self { - case .private: - return .keyword(.private) - case .fileprivate: - return .keyword(.fileprivate) - case .internal: - return .keyword(.internal) - case .package: - return .keyword(.package) - case .public: - return .keyword(.public) - case .open: - return .keyword(.open) + case .private: + return .keyword(.private) + case .fileprivate: + return .keyword(.fileprivate) + case .internal: + return .keyword(.internal) + case .package: + return .keyword(.package) + case .public: + return .keyword(.public) + case .open: + return .keyword(.open) } } - + /// The string name of the `AccessModifier`. public var name: String { switch self { - case .private: - return "private" - case .fileprivate: - return "fileprivate" - case .internal: - return "internal" - case .package: - return "package" - case .public: - return "public" - case .open: - return "open" + case .private: + return "private" + case .fileprivate: + return "fileprivate" + case .internal: + return "internal" + case .package: + return "package" + case .public: + return "public" + case .open: + return "open" } } } diff --git a/Sources/MacroToolkit/Modifiers/DeclarationContextModifier.swift b/Sources/MacroToolkit/Modifiers/DeclarationContextModifier.swift index ad8cbfb..c25786b 100644 --- a/Sources/MacroToolkit/Modifiers/DeclarationContextModifier.swift +++ b/Sources/MacroToolkit/Modifiers/DeclarationContextModifier.swift @@ -4,28 +4,28 @@ import SwiftSyntax public enum DeclarationContextModifier: RawRepresentable, Modifier { case `static` case `class` - + /// Initializes a `DeclarationContextModifier` from a `TokenKind`. /// /// - Parameter rawValue: The `TokenKind` representing a context-specific keyword. public init?(rawValue: TokenKind) { switch rawValue { - case .keyword(.static): - self = .static - case .keyword(.class): - self = .class - default: - return nil + case .keyword(.static): + self = .static + case .keyword(.class): + self = .class + default: + return nil } } - + /// The `TokenKind` corresponding to the `DeclarationContextModifier`. public var rawValue: TokenKind { switch self { - case .static: - return .keyword(.static) - case .class: - return .keyword(.class) + case .static: + return .keyword(.static) + case .class: + return .keyword(.class) } } } From 473a533927927aa58b8d4f9de117bbaac1a1e5b9 Mon Sep 17 00:00:00 2001 From: Andy Kolean Date: Thu, 23 May 2024 00:02:41 -0400 Subject: [PATCH 3/8] add inherited types --- Sources/MacroToolkit/DeclGroup/DeclGroup.swift | 3 +++ Sources/MacroToolkit/DeclGroup/DeclGroupProtocol.swift | 3 +++ 2 files changed, 6 insertions(+) diff --git a/Sources/MacroToolkit/DeclGroup/DeclGroup.swift b/Sources/MacroToolkit/DeclGroup/DeclGroup.swift index e3b78ab..66de6df 100644 --- a/Sources/MacroToolkit/DeclGroup/DeclGroup.swift +++ b/Sources/MacroToolkit/DeclGroup/DeclGroup.swift @@ -60,4 +60,7 @@ public enum DeclGroup: DeclGroupProtocol { /// The properties declared in the declaration group. public var properties: [Property] { wrapped.properties } + + /// The types inherited in the declaration group. + public var inheritedTypes: [Type] { wrapped.inheritedTypes } } diff --git a/Sources/MacroToolkit/DeclGroup/DeclGroupProtocol.swift b/Sources/MacroToolkit/DeclGroup/DeclGroupProtocol.swift index df1bdc6..e8a8fec 100644 --- a/Sources/MacroToolkit/DeclGroup/DeclGroupProtocol.swift +++ b/Sources/MacroToolkit/DeclGroup/DeclGroupProtocol.swift @@ -19,6 +19,9 @@ public protocol DeclGroupProtocol: RawRepresentable { /// The properties declared in the declaration group. var properties: [Property] { get } + /// The inherited types of the declaration group. + var inheritedTypes: [Type] { get } + /// Initializes the declaration group with the given syntax node. /// /// - Parameter syntax: The underlying syntax node representing the declaration group. From a3551fb06016201a4e08038919afb5e9d9dbf27c Mon Sep 17 00:00:00 2001 From: Andy Kolean Date: Thu, 23 May 2024 00:07:30 -0400 Subject: [PATCH 4/8] swift-format --- Sources/MacroToolkitExamplePlugin/MetaEnumMacro.swift | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/Sources/MacroToolkitExamplePlugin/MetaEnumMacro.swift b/Sources/MacroToolkitExamplePlugin/MetaEnumMacro.swift index 87e0505..9a0f2c1 100644 --- a/Sources/MacroToolkitExamplePlugin/MetaEnumMacro.swift +++ b/Sources/MacroToolkitExamplePlugin/MetaEnumMacro.swift @@ -21,7 +21,7 @@ public struct MetaEnumMacro { } parentTypeName = enumDecl.identifier - + access = enumDecl.accessLevel == .public ? "public " : "" metaCases = enumDecl.cases.map { case_ in @@ -90,7 +90,11 @@ enum CaseMacroDiagnostic { func diagnose(at node: Syntax) -> Diagnostic { DiagnosticBuilder(for: node) .message(message) - .messageID(MessageID(domain: "MetaEnum", id: Mirror(reflecting: self).children.first?.label ?? "\(self)")) + .messageID( + MessageID( + domain: "MetaEnum", + id: Mirror(reflecting: self).children.first?.label ?? "\(self)") + ) .build() } } From 95b8c295f7d6d482d079dc7f4e5062eba53beaeb Mon Sep 17 00:00:00 2001 From: Andy Kolean Date: Fri, 24 May 2024 22:04:11 -0400 Subject: [PATCH 5/8] Remove RawRepresentable Conformance --- Sources/MacroToolkit/DeclGroup/Actor.swift | 4 +- .../{DeclGroup.swift => AnyDeclGroup.swift} | 31 ++-- Sources/MacroToolkit/DeclGroup/Class.swift | 4 +- .../DeclGroup/DeclGroupProtocol.swift | 63 +++---- Sources/MacroToolkit/DeclGroup/Enum.swift | 5 +- .../MacroToolkit/DeclGroup/Extension.swift | 4 +- Sources/MacroToolkit/DeclGroup/Struct.swift | 4 +- .../MacroToolkit/Modifiers/AccessLevel.swift | 2 +- .../DeclarationContextModifier.swift | 2 +- ...{Modifier.swift => ModifierProtocol.swift} | 10 +- .../MacroToolkit/RepresentableBySyntax.swift | 19 ++ .../CaseDetectionMacro.swift | 6 +- .../CustomCodableMacro.swift | 2 +- Tests/MacroToolkitTests/DeclGroupTests.swift | 172 +++++++++--------- Tests/MacroToolkitTests/ModifierTests.swift | 33 ++-- 15 files changed, 179 insertions(+), 182 deletions(-) rename Sources/MacroToolkit/DeclGroup/{DeclGroup.swift => AnyDeclGroup.swift} (65%) rename Sources/MacroToolkit/Modifiers/{Modifier.swift => ModifierProtocol.swift} (67%) create mode 100644 Sources/MacroToolkit/RepresentableBySyntax.swift diff --git a/Sources/MacroToolkit/DeclGroup/Actor.swift b/Sources/MacroToolkit/DeclGroup/Actor.swift index 7a20cf8..ae39feb 100644 --- a/Sources/MacroToolkit/DeclGroup/Actor.swift +++ b/Sources/MacroToolkit/DeclGroup/Actor.swift @@ -3,7 +3,7 @@ import SwiftSyntax /// Wraps an `actor` declaration. public struct Actor: DeclGroupProtocol { /// The underlying syntax node for the `actor` declaration. - public var rawValue: ActorDeclSyntax + public var _syntax: ActorDeclSyntax /// The identifier (name) of the `actor`. public var identifier: String { @@ -14,6 +14,6 @@ public struct Actor: DeclGroupProtocol { /// /// - Parameter syntax: The syntax node representing the `actor` declaration. public init(_ syntax: ActorDeclSyntax) { - rawValue = syntax + _syntax = syntax } } diff --git a/Sources/MacroToolkit/DeclGroup/DeclGroup.swift b/Sources/MacroToolkit/DeclGroup/AnyDeclGroup.swift similarity index 65% rename from Sources/MacroToolkit/DeclGroup/DeclGroup.swift rename to Sources/MacroToolkit/DeclGroup/AnyDeclGroup.swift index 66de6df..6f1fc2f 100644 --- a/Sources/MacroToolkit/DeclGroup/DeclGroup.swift +++ b/Sources/MacroToolkit/DeclGroup/AnyDeclGroup.swift @@ -1,15 +1,18 @@ import SwiftSyntax -/// An enumeration representing different types of declaration groups (e.g., `struct`, `class`, `enum`, `actor`, `extension`). -/// This enum conforms to `DeclGroupProtocol` and wraps specific declaration group types. -public enum DeclGroup: DeclGroupProtocol { +/// An enum that encapsulates various types of declaration groups (`struct`, `class`, `enum`, `actor`, `extension`) +/// and provides a unified interface for interacting with them. This enum conforms to `AnyDeclGroupProtocol`, +/// allowing access to common properties of declaration groups. +public enum AnyDeclGroup: AnyDeclGroupProtocol { case `struct`(Struct) case `enum`(Enum) case `class`(Class) - case actor(Actor) + case `actor`(Actor) case `extension`(Extension) /// A private computed property that returns the wrapped `DeclGroupProtocol` instance. + /// + /// This property is used internally to access the underlying implementation of the declaration group. private var wrapped: any DeclGroupProtocol { switch self { case .struct(let wrapped): return wrapped @@ -20,12 +23,12 @@ public enum DeclGroup: DeclGroupProtocol { } } - /// Initializes a `DeclGroup` instance from a `DeclGroupSyntax`. + /// Initializes an `AnyDeclGroup` instance from a `DeclGroupSyntax`. /// - /// - Parameter rawValue: The syntax node representing the declaration group. + /// - Parameter syntax: The syntax node representing the declaration group. /// - Note: This initializer will fatalError if the syntax node does not match any known declaration group type. - public init(_ rawValue: any DeclGroupSyntax) { - switch rawValue { + public init(_ syntax: DeclGroupSyntax) { + switch syntax { case let syntax as ActorDeclSyntax: self = .actor(Actor(syntax)) case let syntax as ClassDeclSyntax: @@ -37,20 +40,12 @@ public enum DeclGroup: DeclGroupProtocol { case let syntax as StructDeclSyntax: self = .struct(Struct(syntax)) default: - fatalError("Unhandled decl group type '\(type(of: rawValue))'") + fatalError("Unhandled decl group type '\(type(of: syntax))'") } } /// The underlying syntax node for the declaration group. - public var rawValue: any DeclGroupSyntax { - switch self { - case .struct(let wrapped): return wrapped.rawValue - case .enum(let wrapped): return wrapped.rawValue - case .class(let wrapped): return wrapped.rawValue - case .actor(let wrapped): return wrapped.rawValue - case .extension(let wrapped): return wrapped.rawValue - } - } + public var _syntax: DeclGroupSyntax { wrapped._syntax } /// The identifier of the declaration group. public var identifier: String { wrapped.identifier } diff --git a/Sources/MacroToolkit/DeclGroup/Class.swift b/Sources/MacroToolkit/DeclGroup/Class.swift index 1204b53..31121f7 100644 --- a/Sources/MacroToolkit/DeclGroup/Class.swift +++ b/Sources/MacroToolkit/DeclGroup/Class.swift @@ -3,7 +3,7 @@ import SwiftSyntax /// Wraps a `class` declaration. public struct Class: DeclGroupProtocol { /// The underlying syntax node for the `class` declaration. - public var rawValue: ClassDeclSyntax + public var _syntax: ClassDeclSyntax /// The identifier (name) of the `class`. public var identifier: String { @@ -14,6 +14,6 @@ public struct Class: DeclGroupProtocol { /// /// - Parameter syntax: The syntax node representing the `class` declaration. public init(_ syntax: ClassDeclSyntax) { - rawValue = syntax + _syntax = syntax } } diff --git a/Sources/MacroToolkit/DeclGroup/DeclGroupProtocol.swift b/Sources/MacroToolkit/DeclGroup/DeclGroupProtocol.swift index e8a8fec..4b5ea6d 100644 --- a/Sources/MacroToolkit/DeclGroup/DeclGroupProtocol.swift +++ b/Sources/MacroToolkit/DeclGroup/DeclGroupProtocol.swift @@ -1,57 +1,48 @@ import SwiftSyntax -/// A protocol for declaration groups (e.g., `struct`, `class`, `enum`) that can contain members and properties. -/// Declaration groups are higher-level constructs compared to regular declarations such as `var`. -public protocol DeclGroupProtocol: RawRepresentable { - /// The underlying syntax node for the declaration group. - var rawValue: RawValue { get } - - /// The declaration's identifier. - /// - /// Note: SwiftSyntax's `DeclGroupSyntax` protocol does not include the declaration's identifier. - /// This must be implemented manually for each declaration wrapper. This omission might be due to - /// the fact that extensions technically do not have a name, even though they are always attached to a specific identifier. +/// A protocol that represents a declaration group, such as a `struct`, `class`, `enum`, or `protocol`. +/// This protocol defines common properties that all declaration groups should have. +/// +/// Conforming types should provide the following properties: +/// - `identifier`: The identifier of the declaration group. +/// - `members`: The members of the declaration group. +/// - `properties`: The properties declared within the declaration group. +/// - `inheritedTypes`: The types that the declaration group inherits from or conforms to. +public protocol AnyDeclGroupProtocol { + /// The identifier of the declaration group. var identifier: String { get } /// The members of the declaration group. + /// This array contains all the members declared within the declaration group. var members: [Decl] { get } /// The properties declared in the declaration group. + /// This array contains all the properties declared within the declaration group. var properties: [Property] { get } /// The inherited types of the declaration group. + /// This array contains all the types that the declaration group inherits from or conforms to. var inheritedTypes: [Type] { get } - - /// Initializes the declaration group with the given syntax node. - /// - /// - Parameter syntax: The underlying syntax node representing the declaration group. - init(_ syntax: RawValue) } -/// Default implementations and helper initializers for `DeclGroupProtocol`. -extension DeclGroupProtocol { - /// The underlying syntax node for the declaration group. - public var _syntax: RawValue { - rawValue - } +/// A protocol that represents a declaration group with an underlying syntax node. +/// This protocol extends `AnyDeclGroupProtocol` and `RepresentableBySyntax` to provide additional +/// functionality specific to declaration groups in Swift syntax. +/// +/// Conforming types must: +/// - Conform to `AnyDeclGroupProtocol`, providing properties for `identifier`, `members`, `properties`, and `inheritedTypes`. +/// - Conform to `RepresentableBySyntax`, providing an underlying syntax node of type `DeclGroupSyntax`. +public protocol DeclGroupProtocol: AnyDeclGroupProtocol, RepresentableBySyntax +where UnderlyingSyntax: DeclGroupSyntax {} - /// Initializes the declaration group with the given raw value. - /// - /// - Parameter rawValue: The raw value representing the declaration group. - public init?(rawValue: RawValue) { - self.init(rawValue) - } -} - -/// Additional functionality for `DeclGroupProtocol` where the raw value conforms to `DeclGroupSyntax`. -extension DeclGroupProtocol where RawValue: DeclGroupSyntax { +extension DeclGroupProtocol { /// Attempts to initialize the wrapper from an arbitrary declaration group. /// /// - Parameter syntax: The syntax node representing the declaration group. /// - Note: This initializer will return `nil` if the syntax node does not match the expected type. public init?(_ syntax: any DeclGroupSyntax) { - guard let rawValue = syntax as? RawValue else { return nil } - self.init(rawValue: rawValue) + guard let syntax = syntax as? UnderlyingSyntax else { return nil } + self.init(syntax) } /// The members of the declaration group. @@ -89,11 +80,11 @@ extension DeclGroupProtocol where RawValue: DeclGroupSyntax { /// The access level of the declaration group. public var accessLevel: AccessModifier? { - AccessModifier(modifiers: _syntax.modifiers) + AccessModifier(firstModifierOfKindIn: _syntax.modifiers) } /// The context-specific modifiers of the declaration group. public var declarationContext: DeclarationContextModifier? { - DeclarationContextModifier(modifiers: _syntax.modifiers) + DeclarationContextModifier(firstModifierOfKindIn: _syntax.modifiers) } } diff --git a/Sources/MacroToolkit/DeclGroup/Enum.swift b/Sources/MacroToolkit/DeclGroup/Enum.swift index 05a1c7a..35e5e9a 100644 --- a/Sources/MacroToolkit/DeclGroup/Enum.swift +++ b/Sources/MacroToolkit/DeclGroup/Enum.swift @@ -2,8 +2,9 @@ import SwiftSyntax /// Wraps an `enum` declaration. public struct Enum: DeclGroupProtocol { + /// The underlying syntax node for the `enum` declaration. - public var rawValue: EnumDeclSyntax + public var _syntax: EnumDeclSyntax /// The identifier (name) of the `enum`. public var identifier: String { @@ -14,7 +15,7 @@ public struct Enum: DeclGroupProtocol { /// /// - Parameter syntax: The syntax node representing the `enum` declaration. public init(_ syntax: EnumDeclSyntax) { - rawValue = syntax + _syntax = syntax } /// The `enum`'s cases. diff --git a/Sources/MacroToolkit/DeclGroup/Extension.swift b/Sources/MacroToolkit/DeclGroup/Extension.swift index 6f51a92..c499805 100644 --- a/Sources/MacroToolkit/DeclGroup/Extension.swift +++ b/Sources/MacroToolkit/DeclGroup/Extension.swift @@ -3,7 +3,7 @@ import SwiftSyntax /// Wraps an `extension` declaration. public struct Extension: DeclGroupProtocol { /// The underlying syntax node for the `extension` declaration. - public var rawValue: ExtensionDeclSyntax + public var _syntax: ExtensionDeclSyntax /// The identifier (extended type) of the `extension`. public var identifier: String { @@ -14,6 +14,6 @@ public struct Extension: DeclGroupProtocol { /// /// - Parameter syntax: The syntax node representing the `extension` declaration. public init(_ syntax: ExtensionDeclSyntax) { - rawValue = syntax + _syntax = syntax } } diff --git a/Sources/MacroToolkit/DeclGroup/Struct.swift b/Sources/MacroToolkit/DeclGroup/Struct.swift index 8d27f5c..576ed9b 100644 --- a/Sources/MacroToolkit/DeclGroup/Struct.swift +++ b/Sources/MacroToolkit/DeclGroup/Struct.swift @@ -3,7 +3,7 @@ import SwiftSyntax /// Wraps a `struct` declaration. public struct Struct: DeclGroupProtocol { /// The underlying syntax node for the `struct` declaration. - public var rawValue: StructDeclSyntax + public var _syntax: StructDeclSyntax /// The identifier (name) of the `struct`. public var identifier: String { @@ -14,6 +14,6 @@ public struct Struct: DeclGroupProtocol { /// /// - Parameter syntax: The syntax node representing the `struct` declaration. public init(_ syntax: StructDeclSyntax) { - rawValue = syntax + _syntax = syntax } } diff --git a/Sources/MacroToolkit/Modifiers/AccessLevel.swift b/Sources/MacroToolkit/Modifiers/AccessLevel.swift index 5fbbb19..09b0a7e 100644 --- a/Sources/MacroToolkit/Modifiers/AccessLevel.swift +++ b/Sources/MacroToolkit/Modifiers/AccessLevel.swift @@ -1,7 +1,7 @@ import SwiftSyntax /// Represents access control levels in Swift (e.g., private, public). -public enum AccessModifier: RawRepresentable, Modifier, Comparable { +public enum AccessModifier: RawRepresentable, ModifierProtocol, Comparable { case `private` case `fileprivate` case `internal` diff --git a/Sources/MacroToolkit/Modifiers/DeclarationContextModifier.swift b/Sources/MacroToolkit/Modifiers/DeclarationContextModifier.swift index c25786b..4bcb81f 100644 --- a/Sources/MacroToolkit/Modifiers/DeclarationContextModifier.swift +++ b/Sources/MacroToolkit/Modifiers/DeclarationContextModifier.swift @@ -1,7 +1,7 @@ import SwiftSyntax /// Represents context-specific modifiers for declarations (e.g., static, class). -public enum DeclarationContextModifier: RawRepresentable, Modifier { +public enum DeclarationContextModifier: RawRepresentable, ModifierProtocol { case `static` case `class` diff --git a/Sources/MacroToolkit/Modifiers/Modifier.swift b/Sources/MacroToolkit/Modifiers/ModifierProtocol.swift similarity index 67% rename from Sources/MacroToolkit/Modifiers/Modifier.swift rename to Sources/MacroToolkit/Modifiers/ModifierProtocol.swift index 9e6eb01..47b7608 100644 --- a/Sources/MacroToolkit/Modifiers/Modifier.swift +++ b/Sources/MacroToolkit/Modifiers/ModifierProtocol.swift @@ -1,19 +1,19 @@ import SwiftSyntax /// A protocol for modifiers in Swift that are represented by `TokenKind`. -public protocol Modifier: RawRepresentable where RawValue == TokenKind { +public protocol ModifierProtocol: RawRepresentable where RawValue == TokenKind { /// Initializes a `Modifier` from a list of declaration modifiers. /// /// - Parameter modifiers: A list of declaration modifiers. - init?(modifiers: DeclModifierListSyntax) + init?(firstModifierOfKindIn: DeclModifierListSyntax) } -extension Modifier { +extension ModifierProtocol { /// Default implementation for initializing a `Modifier` from a list of declaration modifiers. /// /// - Parameter modifiers: A list of declaration modifiers. - public init?(modifiers: DeclModifierListSyntax) { - for element in modifiers { + public init?(firstModifierOfKindIn: DeclModifierListSyntax) { + for element in firstModifierOfKindIn { guard let modifier = Self(rawValue: element.name.tokenKind) else { continue } self = modifier return diff --git a/Sources/MacroToolkit/RepresentableBySyntax.swift b/Sources/MacroToolkit/RepresentableBySyntax.swift new file mode 100644 index 0000000..50fd3b2 --- /dev/null +++ b/Sources/MacroToolkit/RepresentableBySyntax.swift @@ -0,0 +1,19 @@ +import SwiftSyntax + +/// A protocol that provides a consistent interface for types that are represented by an underlying syntax node. +/// This protocol is useful for working with various SwiftSyntax types in a unified manner. +/// +/// Types conforming to this protocol must define an associated `UnderlyingSyntax` type that conforms to `SyntaxProtocol`. +/// They must also provide a `_syntax` property to access the underlying syntax node and an initializer to create an instance from the syntax node. +public protocol RepresentableBySyntax { + /// The type of the underlying syntax node that this type represents. + associatedtype UnderlyingSyntax: SyntaxProtocol + + /// The underlying syntax node for this type. + var _syntax: UnderlyingSyntax { get set } + + /// Initializes an instance with the given underlying syntax node. + /// + /// - Parameter syntax: The underlying syntax node to represent. + init(_ syntax: UnderlyingSyntax) +} diff --git a/Sources/MacroToolkitExamplePlugin/CaseDetectionMacro.swift b/Sources/MacroToolkitExamplePlugin/CaseDetectionMacro.swift index 80c1892..24670f4 100644 --- a/Sources/MacroToolkitExamplePlugin/CaseDetectionMacro.swift +++ b/Sources/MacroToolkitExamplePlugin/CaseDetectionMacro.swift @@ -1,6 +1,6 @@ +import MacroToolkit import SwiftSyntax import SwiftSyntaxMacros -import MacroToolkit // Modified from: https://github.com/DougGregor/swift-macro-examples/blob/f61ac7cdca8dc3557e53f86e7e03df1353908d3e/MacroExamplesPlugin/CaseDetectionMacro.swift public struct CaseDetectionMacro: MemberMacro { @@ -13,7 +13,7 @@ public struct CaseDetectionMacro: MemberMacro { ) throws -> [DeclSyntax] { guard let enum_ = Enum(declaration) else { throw MacroError("@CaseDetectionMacro can only be attached to enum declarations") - } + } return enum_.cases .map { ($0.identifier, $0.identifier.initialUppercased) } @@ -29,4 +29,4 @@ public struct CaseDetectionMacro: MemberMacro { """ } } -} \ No newline at end of file +} diff --git a/Sources/MacroToolkitExamplePlugin/CustomCodableMacro.swift b/Sources/MacroToolkitExamplePlugin/CustomCodableMacro.swift index 268ceda..df1ffb5 100644 --- a/Sources/MacroToolkitExamplePlugin/CustomCodableMacro.swift +++ b/Sources/MacroToolkitExamplePlugin/CustomCodableMacro.swift @@ -9,7 +9,7 @@ public struct CustomCodableMacro: MemberMacro { providingMembersOf declaration: some DeclGroupSyntax, in context: some MacroExpansionContext ) throws -> [DeclSyntax] { - let decl = DeclGroup(declaration) + let decl = AnyDeclGroup(declaration) let cases = decl.members.compactMap(\.asVariable).compactMap { (variable) -> String? in guard let propertyName = destructureSingle(variable.identifiers) else { diff --git a/Tests/MacroToolkitTests/DeclGroupTests.swift b/Tests/MacroToolkitTests/DeclGroupTests.swift index eaf04e3..0c9051a 100644 --- a/Tests/MacroToolkitTests/DeclGroupTests.swift +++ b/Tests/MacroToolkitTests/DeclGroupTests.swift @@ -1,160 +1,162 @@ -import XCTest import SwiftSyntax +import XCTest + @testable import MacroToolkit final class DeclGroupTests: XCTestCase { - func testStructInitialization() throws { let decl: DeclSyntax = """ - struct TestStruct { var value: Int } - """ + struct TestStruct { var value: Int } + """ let structDecl = decl.as(StructDeclSyntax.self)! let testStruct = Struct(structDecl) - + XCTAssertEqual(testStruct.identifier, "TestStruct") XCTAssertEqual(testStruct.members.count, 1) XCTAssertEqual(testStruct.properties.count, 1) } - + func testEnumInitialization() throws { let decl: DeclSyntax = """ - enum TestEnum { case caseOne, caseTwo } - """ + enum TestEnum { case caseOne, caseTwo } + """ let enumDecl = decl.as(EnumDeclSyntax.self)! let testEnum = Enum(enumDecl) - + XCTAssertEqual(testEnum.identifier, "TestEnum") XCTAssertEqual(testEnum.members.count, 1) XCTAssertEqual(testEnum.cases.count, 2) } - + func testClassInitialization() throws { let decl: DeclSyntax = """ - class TestClass { var value: Int } - """ + class TestClass { var value: Int } + """ let classDecl = decl.as(ClassDeclSyntax.self)! let testClass = Class(classDecl) - + XCTAssertEqual(testClass.identifier, "TestClass") XCTAssertEqual(testClass.members.count, 1) XCTAssertEqual(testClass.properties.count, 1) } - + func testActorInitialization() throws { let decl: DeclSyntax = """ - actor TestActor { var value: Int } - """ + actor TestActor { var value: Int } + """ let actorDecl = decl.as(ActorDeclSyntax.self)! let testActor = Actor(actorDecl) - + XCTAssertEqual(testActor.identifier, "TestActor") XCTAssertEqual(testActor.members.count, 1) XCTAssertEqual(testActor.properties.count, 1) } - + func testExtensionInitialization() throws { let decl: DeclSyntax = """ - extension TestStruct { func testMethod() {} } - """ + extension TestStruct { func testMethod() {} } + """ let extensionDecl = decl.as(ExtensionDeclSyntax.self)! let testExtension = Extension(extensionDecl) - + XCTAssertEqual(testExtension.identifier, "TestStruct") XCTAssertEqual(testExtension.members.count, 1) } - + func testDeclGroupInitialization() throws { let structDecl: DeclSyntax = """ - struct TestStruct { var value: Int } - """ + struct TestStruct { var value: Int } + """ let structSyntax = structDecl.as(StructDeclSyntax.self)! - let structDeclGroup = DeclGroup(structSyntax) - + let structDeclGroup = AnyDeclGroup(structSyntax) + switch structDeclGroup { - case .struct(let testStruct): - XCTAssertEqual(testStruct.identifier, "TestStruct") - XCTAssertEqual(testStruct.members.count, 1) - XCTAssertEqual(testStruct.properties.count, 1) - default: - XCTFail("Expected .struct case") + case .struct(let testStruct): + XCTAssertEqual(testStruct.identifier, "TestStruct") + XCTAssertEqual(testStruct.members.count, 1) + XCTAssertEqual(testStruct.properties.count, 1) + default: + XCTFail("Expected .struct case") } - + let enumDecl: DeclSyntax = """ - enum TestEnum { case caseOne, caseTwo } - """ + enum TestEnum { case caseOne, caseTwo } + """ let enumSyntax = enumDecl.as(EnumDeclSyntax.self)! - let enumDeclGroup = DeclGroup(enumSyntax) - + let enumDeclGroup = AnyDeclGroup(enumSyntax) + switch enumDeclGroup { - case .enum(let testEnum): - XCTAssertEqual(testEnum.identifier, "TestEnum") - XCTAssertEqual(testEnum.members.count, 1) - XCTAssertEqual(testEnum.cases.count, 2) - default: - XCTFail("Expected .enum case") + case .enum(let testEnum): + XCTAssertEqual(testEnum.identifier, "TestEnum") + XCTAssertEqual(testEnum.members.count, 1) + XCTAssertEqual(testEnum.cases.count, 2) + default: + XCTFail("Expected .enum case") } - + let classDecl: DeclSyntax = """ - class TestClass { var value: Int } - """ + class TestClass { var value: Int } + """ let classSyntax = classDecl.as(ClassDeclSyntax.self)! - let classDeclGroup = DeclGroup(classSyntax) - + let classDeclGroup = AnyDeclGroup(classSyntax) + switch classDeclGroup { - case .class(let testClass): - XCTAssertEqual(testClass.identifier, "TestClass") - XCTAssertEqual(testClass.members.count, 1) - XCTAssertEqual(testClass.properties.count, 1) - default: - XCTFail("Expected .class case") + case .class(let testClass): + XCTAssertEqual(testClass.identifier, "TestClass") + XCTAssertEqual(testClass.members.count, 1) + XCTAssertEqual(testClass.properties.count, 1) + default: + XCTFail("Expected .class case") } - + let actorDecl: DeclSyntax = """ - actor TestActor { var value: Int } - """ + actor TestActor { var value: Int } + """ let actorSyntax = actorDecl.as(ActorDeclSyntax.self)! - let actorDeclGroup = DeclGroup(actorSyntax) - + let actorDeclGroup = AnyDeclGroup(actorSyntax) + switch actorDeclGroup { - case .actor(let testActor): - XCTAssertEqual(testActor.identifier, "TestActor") - XCTAssertEqual(testActor.members.count, 1) - XCTAssertEqual(testActor.properties.count, 1) - default: - XCTFail("Expected .actor case") + case .actor(let testActor): + XCTAssertEqual(testActor.identifier, "TestActor") + XCTAssertEqual(testActor.members.count, 1) + XCTAssertEqual(testActor.properties.count, 1) + default: + XCTFail("Expected .actor case") } - + let extensionDecl: DeclSyntax = """ - extension TestStruct { func testMethod() {} } - """ + extension TestStruct { func testMethod() {} } + """ let extensionSyntax = extensionDecl.as(ExtensionDeclSyntax.self)! - let extensionDeclGroup = DeclGroup(extensionSyntax) - + let extensionDeclGroup = AnyDeclGroup(extensionSyntax) + switch extensionDeclGroup { - case .extension(let testExtension): - XCTAssertEqual(testExtension.identifier, "TestStruct") - XCTAssertEqual(testExtension.members.count, 1) - default: - XCTFail("Expected .extension case") + case .extension(let testExtension): + XCTAssertEqual(testExtension.identifier, "TestStruct") + XCTAssertEqual(testExtension.members.count, 1) + default: + XCTFail("Expected .extension case") } } - + func testDeclGroupProtocolExtension() throws { let decl: DeclSyntax = """ - public class TestClass: SuperClass, ProtocolOne, ProtocolTwo { - public var a: Int - var b: Int - public static var c: Int - func method() {} - } - """ + public class TestClass: SuperClass, ProtocolOne, ProtocolTwo { + public var a: Int + var b: Int + public static var c: Int + func method() {} + } + """ let classDecl = decl.as(ClassDeclSyntax.self)! let testClass = Class(classDecl) - + XCTAssertEqual(testClass.identifier, "TestClass") XCTAssertEqual(testClass.members.count, 4) XCTAssertEqual(testClass.properties.count, 3) - XCTAssertEqual(testClass.inheritedTypes.map { $0.description }, ["SuperClass", "ProtocolOne", "ProtocolTwo"]) + XCTAssertEqual( + testClass.inheritedTypes.map { $0.description }, + ["SuperClass", "ProtocolOne", "ProtocolTwo"]) XCTAssertEqual(testClass.accessLevel, .public) XCTAssertEqual(testClass.declarationContext, nil) } diff --git a/Tests/MacroToolkitTests/ModifierTests.swift b/Tests/MacroToolkitTests/ModifierTests.swift index f680b94..67a273c 100644 --- a/Tests/MacroToolkitTests/ModifierTests.swift +++ b/Tests/MacroToolkitTests/ModifierTests.swift @@ -1,52 +1,41 @@ -import XCTest import SwiftSyntax import SwiftSyntaxBuilder +import XCTest + @testable import MacroToolkit final class ModifierTests: XCTestCase { func testAccessModifierInit() { XCTAssertEqual(AccessModifier(rawValue: .keyword(.private)), .private) - XCTAssertEqual(AccessModifier(rawValue: .keyword(.fileprivate)), .fileprivate) - XCTAssertEqual(AccessModifier(rawValue: .keyword(.internal)), .internal) - XCTAssertEqual(AccessModifier(rawValue: .keyword(.package)), .package) XCTAssertEqual(AccessModifier(rawValue: .keyword(.public)), .public) - XCTAssertEqual(AccessModifier(rawValue: .keyword(.open)), .open) XCTAssertNil(AccessModifier(rawValue: .identifier("custom"))) } - + func testAccessModifierRawValue() { XCTAssertEqual(AccessModifier.private.rawValue, .keyword(.private)) - XCTAssertEqual(AccessModifier.fileprivate.rawValue, .keyword(.fileprivate)) - XCTAssertEqual(AccessModifier.internal.rawValue, .keyword(.internal)) - XCTAssertEqual(AccessModifier.package.rawValue, .keyword(.package)) XCTAssertEqual(AccessModifier.public.rawValue, .keyword(.public)) - XCTAssertEqual(AccessModifier.open.rawValue, .keyword(.open)) } - + func testAccessModifierName() { XCTAssertEqual(AccessModifier.private.name, "private") - XCTAssertEqual(AccessModifier.fileprivate.name, "fileprivate") - XCTAssertEqual(AccessModifier.internal.name, "internal") - XCTAssertEqual(AccessModifier.package.name, "package") - XCTAssertEqual(AccessModifier.public.name, "public") XCTAssertEqual(AccessModifier.open.name, "open") } - + func testAccessModifierInitWithModifiers() throws { let decl: DeclSyntax = """ - private struct Test { } - """ + private struct Test { } + """ let structDecl = decl.as(StructDeclSyntax.self) - let structObj = Struct.init(rawValue: structDecl!) - XCTAssertEqual(structObj?.accessLevel, .private) + let structObj = Struct(structDecl!) + XCTAssertEqual(structObj.accessLevel, .private) } - + func testDeclarationContextModifierInit() { XCTAssertEqual(DeclarationContextModifier(rawValue: .keyword(.static)), .static) XCTAssertEqual(DeclarationContextModifier(rawValue: .keyword(.class)), .class) XCTAssertNil(DeclarationContextModifier(rawValue: .identifier("custom"))) } - + func testDeclarationContextModifierRawValue() { XCTAssertEqual(DeclarationContextModifier.static.rawValue, .keyword(.static)) XCTAssertEqual(DeclarationContextModifier.class.rawValue, .keyword(.class)) From f0d003ece1d261ce5877ebd9ce4512085b392599 Mon Sep 17 00:00:00 2001 From: Andy Kolean Date: Mon, 27 May 2024 18:19:39 -0400 Subject: [PATCH 6/8] remove AnyDeclProtocol --- Sources/MacroToolkit/DeclGroup/Actor.swift | 2 +- .../MacroToolkit/DeclGroup/AnyDeclGroup.swift | 61 ------------------- Sources/MacroToolkit/DeclGroup/Class.swift | 2 +- .../MacroToolkit/DeclGroup/DeclGroup.swift | 57 +++++++++++++++++ .../DeclGroup/DeclGroupProtocol.swift | 52 ++++------------ Sources/MacroToolkit/DeclGroup/Enum.swift | 3 +- .../MacroToolkit/DeclGroup/Extension.swift | 2 +- Sources/MacroToolkit/DeclGroup/Struct.swift | 2 +- .../MacroToolkit/RepresentableBySyntax.swift | 4 +- .../CustomCodableMacro.swift | 2 +- Tests/MacroToolkitTests/DeclGroupTests.swift | 10 +-- 11 files changed, 84 insertions(+), 113 deletions(-) delete mode 100644 Sources/MacroToolkit/DeclGroup/AnyDeclGroup.swift create mode 100644 Sources/MacroToolkit/DeclGroup/DeclGroup.swift diff --git a/Sources/MacroToolkit/DeclGroup/Actor.swift b/Sources/MacroToolkit/DeclGroup/Actor.swift index ae39feb..c33e03c 100644 --- a/Sources/MacroToolkit/DeclGroup/Actor.swift +++ b/Sources/MacroToolkit/DeclGroup/Actor.swift @@ -1,7 +1,7 @@ import SwiftSyntax /// Wraps an `actor` declaration. -public struct Actor: DeclGroupProtocol { +public struct Actor: DeclGroupProtocol, RepresentableBySyntax { /// The underlying syntax node for the `actor` declaration. public var _syntax: ActorDeclSyntax diff --git a/Sources/MacroToolkit/DeclGroup/AnyDeclGroup.swift b/Sources/MacroToolkit/DeclGroup/AnyDeclGroup.swift deleted file mode 100644 index 6f1fc2f..0000000 --- a/Sources/MacroToolkit/DeclGroup/AnyDeclGroup.swift +++ /dev/null @@ -1,61 +0,0 @@ -import SwiftSyntax - -/// An enum that encapsulates various types of declaration groups (`struct`, `class`, `enum`, `actor`, `extension`) -/// and provides a unified interface for interacting with them. This enum conforms to `AnyDeclGroupProtocol`, -/// allowing access to common properties of declaration groups. -public enum AnyDeclGroup: AnyDeclGroupProtocol { - case `struct`(Struct) - case `enum`(Enum) - case `class`(Class) - case `actor`(Actor) - case `extension`(Extension) - - /// A private computed property that returns the wrapped `DeclGroupProtocol` instance. - /// - /// This property is used internally to access the underlying implementation of the declaration group. - private var wrapped: any DeclGroupProtocol { - switch self { - case .struct(let wrapped): return wrapped - case .enum(let wrapped): return wrapped - case .class(let wrapped): return wrapped - case .actor(let wrapped): return wrapped - case .extension(let wrapped): return wrapped - } - } - - /// Initializes an `AnyDeclGroup` instance from a `DeclGroupSyntax`. - /// - /// - Parameter syntax: The syntax node representing the declaration group. - /// - Note: This initializer will fatalError if the syntax node does not match any known declaration group type. - public init(_ syntax: DeclGroupSyntax) { - switch syntax { - case let syntax as ActorDeclSyntax: - self = .actor(Actor(syntax)) - case let syntax as ClassDeclSyntax: - self = .class(Class(syntax)) - case let syntax as EnumDeclSyntax: - self = .enum(Enum(syntax)) - case let syntax as ExtensionDeclSyntax: - self = .extension(Extension(syntax)) - case let syntax as StructDeclSyntax: - self = .struct(Struct(syntax)) - default: - fatalError("Unhandled decl group type '\(type(of: syntax))'") - } - } - - /// The underlying syntax node for the declaration group. - public var _syntax: DeclGroupSyntax { wrapped._syntax } - - /// The identifier of the declaration group. - public var identifier: String { wrapped.identifier } - - /// The members of the declaration group. - public var members: [Decl] { wrapped.members } - - /// The properties declared in the declaration group. - public var properties: [Property] { wrapped.properties } - - /// The types inherited in the declaration group. - public var inheritedTypes: [Type] { wrapped.inheritedTypes } -} diff --git a/Sources/MacroToolkit/DeclGroup/Class.swift b/Sources/MacroToolkit/DeclGroup/Class.swift index 31121f7..0b7f44e 100644 --- a/Sources/MacroToolkit/DeclGroup/Class.swift +++ b/Sources/MacroToolkit/DeclGroup/Class.swift @@ -1,7 +1,7 @@ import SwiftSyntax /// Wraps a `class` declaration. -public struct Class: DeclGroupProtocol { +public struct Class: DeclGroupProtocol, RepresentableBySyntax { /// The underlying syntax node for the `class` declaration. public var _syntax: ClassDeclSyntax diff --git a/Sources/MacroToolkit/DeclGroup/DeclGroup.swift b/Sources/MacroToolkit/DeclGroup/DeclGroup.swift new file mode 100644 index 0000000..100c08e --- /dev/null +++ b/Sources/MacroToolkit/DeclGroup/DeclGroup.swift @@ -0,0 +1,57 @@ +import SwiftSyntax + +/// An enum that encapsulates various types of declaration groups (`struct`, `class`, `enum`, `actor`, `extension`) +/// and provides a unified interface for interacting with them. This enum conforms to `DeclGroupProtocol`, +/// allowing access to common properties of declaration groups. +public enum DeclGroup: DeclGroupProtocol { + case `struct`(Struct) + case `enum`(Enum) + case `class`(Class) + case `actor`(Actor) + case `extension`(Extension) + + /// A private computed property that returns the wrapped `DeclGroupProtocol` instance. + /// + /// This property is used internally to access the underlying implementation of the declaration group. + private var wrapped: any DeclGroupProtocol { + switch self { + case .struct(let wrapped): return wrapped + case .enum(let wrapped): return wrapped + case .class(let wrapped): return wrapped + case .actor(let wrapped): return wrapped + case .extension(let wrapped): return wrapped + } + } + + /// Initializes a `DeclGroup` instance from a `DeclGroupSyntax`. + /// + /// - Parameter syntax: The syntax node representing the declaration group. + /// - Note: This initializer will fatalError if the syntax node does not match any known declaration group type. + public init(_ syntax: DeclGroupSyntax) { + if let syntax = syntax.as(ActorDeclSyntax.self) { + self = .actor(Actor(syntax)) + } else if let syntax = syntax.as(ClassDeclSyntax.self) { + self = .class(Class(syntax)) + } else if let syntax = syntax.as(EnumDeclSyntax.self) { + self = .enum(Enum(syntax)) + } else if let syntax = syntax.as(ExtensionDeclSyntax.self) { + self = .extension(Extension(syntax)) + } else if let syntax = syntax.as(StructDeclSyntax.self) { + self = .struct(Struct(syntax)) + } else { + fatalError("Unhandled decl group type '\(type(of: syntax))'") + } + } + + /// The identifier of the declaration group. + public var identifier: String { wrapped.identifier } + + /// All members declared within the declaration group. + public var members: [Decl] { wrapped.members } + + /// All properties declared within the declaration group. + public var properties: [Property] { wrapped.properties } + + /// All types that the declaration group inherits from or conforms to. + public var inheritedTypes: [Type] { wrapped.inheritedTypes } +} diff --git a/Sources/MacroToolkit/DeclGroup/DeclGroupProtocol.swift b/Sources/MacroToolkit/DeclGroup/DeclGroupProtocol.swift index 4b5ea6d..ff7cac9 100644 --- a/Sources/MacroToolkit/DeclGroup/DeclGroupProtocol.swift +++ b/Sources/MacroToolkit/DeclGroup/DeclGroupProtocol.swift @@ -2,40 +2,21 @@ import SwiftSyntax /// A protocol that represents a declaration group, such as a `struct`, `class`, `enum`, or `protocol`. /// This protocol defines common properties that all declaration groups should have. -/// -/// Conforming types should provide the following properties: -/// - `identifier`: The identifier of the declaration group. -/// - `members`: The members of the declaration group. -/// - `properties`: The properties declared within the declaration group. -/// - `inheritedTypes`: The types that the declaration group inherits from or conforms to. -public protocol AnyDeclGroupProtocol { +public protocol DeclGroupProtocol { /// The identifier of the declaration group. var identifier: String { get } - - /// The members of the declaration group. - /// This array contains all the members declared within the declaration group. + + /// All members declared within the declaration group. var members: [Decl] { get } - - /// The properties declared in the declaration group. - /// This array contains all the properties declared within the declaration group. + + /// All properties declared within the declaration group. var properties: [Property] { get } - - /// The inherited types of the declaration group. - /// This array contains all the types that the declaration group inherits from or conforms to. + + /// All types that the declaration group inherits from or conforms to. var inheritedTypes: [Type] { get } } -/// A protocol that represents a declaration group with an underlying syntax node. -/// This protocol extends `AnyDeclGroupProtocol` and `RepresentableBySyntax` to provide additional -/// functionality specific to declaration groups in Swift syntax. -/// -/// Conforming types must: -/// - Conform to `AnyDeclGroupProtocol`, providing properties for `identifier`, `members`, `properties`, and `inheritedTypes`. -/// - Conform to `RepresentableBySyntax`, providing an underlying syntax node of type `DeclGroupSyntax`. -public protocol DeclGroupProtocol: AnyDeclGroupProtocol, RepresentableBySyntax -where UnderlyingSyntax: DeclGroupSyntax {} - -extension DeclGroupProtocol { +extension DeclGroupProtocol where UnderlyingSyntax: DeclGroupSyntax, Self: RepresentableBySyntax { /// Attempts to initialize the wrapper from an arbitrary declaration group. /// /// - Parameter syntax: The syntax node representing the declaration group. @@ -44,13 +25,11 @@ extension DeclGroupProtocol { guard let syntax = syntax as? UnderlyingSyntax else { return nil } self.init(syntax) } - - /// The members of the declaration group. + public var members: [Decl] { _syntax.memberBlock.members.map(\.decl).map(Decl.init) } - - /// The properties declared in the declaration group. + public var properties: [Property] { members.compactMap(\.asVariable).flatMap { variable in var bindings = variable._syntax.bindings.flatMap { binding in @@ -70,20 +49,15 @@ extension DeclGroupProtocol { return bindings } } - - /// The types inherited from or conformed to by the declaration group. - /// - /// - Note: This does not include conformances added by other declaration groups such as extensions. + public var inheritedTypes: [Type] { _syntax.inheritanceClause?.inheritedTypes.map(\.type).map(Type.init) ?? [] } - - /// The access level of the declaration group. + public var accessLevel: AccessModifier? { AccessModifier(firstModifierOfKindIn: _syntax.modifiers) } - - /// The context-specific modifiers of the declaration group. + public var declarationContext: DeclarationContextModifier? { DeclarationContextModifier(firstModifierOfKindIn: _syntax.modifiers) } diff --git a/Sources/MacroToolkit/DeclGroup/Enum.swift b/Sources/MacroToolkit/DeclGroup/Enum.swift index 35e5e9a..c1c7e3f 100644 --- a/Sources/MacroToolkit/DeclGroup/Enum.swift +++ b/Sources/MacroToolkit/DeclGroup/Enum.swift @@ -1,8 +1,7 @@ import SwiftSyntax /// Wraps an `enum` declaration. -public struct Enum: DeclGroupProtocol { - +public struct Enum: DeclGroupProtocol, RepresentableBySyntax { /// The underlying syntax node for the `enum` declaration. public var _syntax: EnumDeclSyntax diff --git a/Sources/MacroToolkit/DeclGroup/Extension.swift b/Sources/MacroToolkit/DeclGroup/Extension.swift index c499805..d415d40 100644 --- a/Sources/MacroToolkit/DeclGroup/Extension.swift +++ b/Sources/MacroToolkit/DeclGroup/Extension.swift @@ -1,7 +1,7 @@ import SwiftSyntax /// Wraps an `extension` declaration. -public struct Extension: DeclGroupProtocol { +public struct Extension: DeclGroupProtocol, RepresentableBySyntax { /// The underlying syntax node for the `extension` declaration. public var _syntax: ExtensionDeclSyntax diff --git a/Sources/MacroToolkit/DeclGroup/Struct.swift b/Sources/MacroToolkit/DeclGroup/Struct.swift index 576ed9b..5eafdfb 100644 --- a/Sources/MacroToolkit/DeclGroup/Struct.swift +++ b/Sources/MacroToolkit/DeclGroup/Struct.swift @@ -1,7 +1,7 @@ import SwiftSyntax /// Wraps a `struct` declaration. -public struct Struct: DeclGroupProtocol { +public struct Struct: DeclGroupProtocol, RepresentableBySyntax { /// The underlying syntax node for the `struct` declaration. public var _syntax: StructDeclSyntax diff --git a/Sources/MacroToolkit/RepresentableBySyntax.swift b/Sources/MacroToolkit/RepresentableBySyntax.swift index 50fd3b2..d68e405 100644 --- a/Sources/MacroToolkit/RepresentableBySyntax.swift +++ b/Sources/MacroToolkit/RepresentableBySyntax.swift @@ -10,10 +10,12 @@ public protocol RepresentableBySyntax { associatedtype UnderlyingSyntax: SyntaxProtocol /// The underlying syntax node for this type. - var _syntax: UnderlyingSyntax { get set } + var _syntax: UnderlyingSyntax { get } /// Initializes an instance with the given underlying syntax node. /// /// - Parameter syntax: The underlying syntax node to represent. init(_ syntax: UnderlyingSyntax) } + + diff --git a/Sources/MacroToolkitExamplePlugin/CustomCodableMacro.swift b/Sources/MacroToolkitExamplePlugin/CustomCodableMacro.swift index df1ffb5..268ceda 100644 --- a/Sources/MacroToolkitExamplePlugin/CustomCodableMacro.swift +++ b/Sources/MacroToolkitExamplePlugin/CustomCodableMacro.swift @@ -9,7 +9,7 @@ public struct CustomCodableMacro: MemberMacro { providingMembersOf declaration: some DeclGroupSyntax, in context: some MacroExpansionContext ) throws -> [DeclSyntax] { - let decl = AnyDeclGroup(declaration) + let decl = DeclGroup(declaration) let cases = decl.members.compactMap(\.asVariable).compactMap { (variable) -> String? in guard let propertyName = destructureSingle(variable.identifiers) else { diff --git a/Tests/MacroToolkitTests/DeclGroupTests.swift b/Tests/MacroToolkitTests/DeclGroupTests.swift index 0c9051a..6d22fa2 100644 --- a/Tests/MacroToolkitTests/DeclGroupTests.swift +++ b/Tests/MacroToolkitTests/DeclGroupTests.swift @@ -68,7 +68,7 @@ final class DeclGroupTests: XCTestCase { struct TestStruct { var value: Int } """ let structSyntax = structDecl.as(StructDeclSyntax.self)! - let structDeclGroup = AnyDeclGroup(structSyntax) + let structDeclGroup = DeclGroup(structSyntax) switch structDeclGroup { case .struct(let testStruct): @@ -83,7 +83,7 @@ final class DeclGroupTests: XCTestCase { enum TestEnum { case caseOne, caseTwo } """ let enumSyntax = enumDecl.as(EnumDeclSyntax.self)! - let enumDeclGroup = AnyDeclGroup(enumSyntax) + let enumDeclGroup = DeclGroup(enumSyntax) switch enumDeclGroup { case .enum(let testEnum): @@ -98,7 +98,7 @@ final class DeclGroupTests: XCTestCase { class TestClass { var value: Int } """ let classSyntax = classDecl.as(ClassDeclSyntax.self)! - let classDeclGroup = AnyDeclGroup(classSyntax) + let classDeclGroup = DeclGroup(classSyntax) switch classDeclGroup { case .class(let testClass): @@ -113,7 +113,7 @@ final class DeclGroupTests: XCTestCase { actor TestActor { var value: Int } """ let actorSyntax = actorDecl.as(ActorDeclSyntax.self)! - let actorDeclGroup = AnyDeclGroup(actorSyntax) + let actorDeclGroup = DeclGroup(actorSyntax) switch actorDeclGroup { case .actor(let testActor): @@ -128,7 +128,7 @@ final class DeclGroupTests: XCTestCase { extension TestStruct { func testMethod() {} } """ let extensionSyntax = extensionDecl.as(ExtensionDeclSyntax.self)! - let extensionDeclGroup = AnyDeclGroup(extensionSyntax) + let extensionDeclGroup = DeclGroup(extensionSyntax) switch extensionDeclGroup { case .extension(let testExtension): From 9b1200975e4b677551d8b2c55486cb9f8079ed1e Mon Sep 17 00:00:00 2001 From: Andy Kolean Date: Mon, 27 May 2024 18:25:50 -0400 Subject: [PATCH 7/8] swift-format --- .../MacroToolkit/ClassRestrictionType.swift | 3 +- .../MacroToolkit/DeclGroup/DeclGroup.swift | 26 ++-- .../DeclGroup/DeclGroupProtocol.swift | 16 +-- Sources/MacroToolkit/FunctionParameter.swift | 3 +- .../ImplicitlyUnwrappedOptionalType.swift | 5 +- Sources/MacroToolkit/Property.swift | 122 +++++++++--------- .../MacroToolkit/RepresentableBySyntax.swift | 2 - Sources/MacroToolkit/Type.swift | 74 +++++------ .../AddAsyncAllMembersMacro.swift | 7 +- .../AddAsyncMacroCore.swift | 44 ++++--- .../DictionaryStorageMacro.swift | 2 +- 11 files changed, 159 insertions(+), 145 deletions(-) diff --git a/Sources/MacroToolkit/ClassRestrictionType.swift b/Sources/MacroToolkit/ClassRestrictionType.swift index d90097d..1884092 100644 --- a/Sources/MacroToolkit/ClassRestrictionType.swift +++ b/Sources/MacroToolkit/ClassRestrictionType.swift @@ -5,7 +5,8 @@ public struct ClassRestrictionType: TypeProtocol { public var _baseSyntax: ClassRestrictionTypeSyntax public var _attributedSyntax: AttributedTypeSyntax? - public init(_ syntax: ClassRestrictionTypeSyntax, attributedSyntax: AttributedTypeSyntax? = nil) { + public init(_ syntax: ClassRestrictionTypeSyntax, attributedSyntax: AttributedTypeSyntax? = nil) + { _baseSyntax = syntax _attributedSyntax = attributedSyntax } diff --git a/Sources/MacroToolkit/DeclGroup/DeclGroup.swift b/Sources/MacroToolkit/DeclGroup/DeclGroup.swift index 100c08e..837d244 100644 --- a/Sources/MacroToolkit/DeclGroup/DeclGroup.swift +++ b/Sources/MacroToolkit/DeclGroup/DeclGroup.swift @@ -9,20 +9,20 @@ public enum DeclGroup: DeclGroupProtocol { case `class`(Class) case `actor`(Actor) case `extension`(Extension) - + /// A private computed property that returns the wrapped `DeclGroupProtocol` instance. /// /// This property is used internally to access the underlying implementation of the declaration group. private var wrapped: any DeclGroupProtocol { - switch self { - case .struct(let wrapped): return wrapped - case .enum(let wrapped): return wrapped - case .class(let wrapped): return wrapped - case .actor(let wrapped): return wrapped - case .extension(let wrapped): return wrapped - } + switch self { + case .struct(let wrapped): return wrapped + case .enum(let wrapped): return wrapped + case .class(let wrapped): return wrapped + case .actor(let wrapped): return wrapped + case .extension(let wrapped): return wrapped + } } - + /// Initializes a `DeclGroup` instance from a `DeclGroupSyntax`. /// /// - Parameter syntax: The syntax node representing the declaration group. @@ -42,16 +42,16 @@ public enum DeclGroup: DeclGroupProtocol { fatalError("Unhandled decl group type '\(type(of: syntax))'") } } - + /// The identifier of the declaration group. public var identifier: String { wrapped.identifier } - + /// All members declared within the declaration group. public var members: [Decl] { wrapped.members } - + /// All properties declared within the declaration group. public var properties: [Property] { wrapped.properties } - + /// All types that the declaration group inherits from or conforms to. public var inheritedTypes: [Type] { wrapped.inheritedTypes } } diff --git a/Sources/MacroToolkit/DeclGroup/DeclGroupProtocol.swift b/Sources/MacroToolkit/DeclGroup/DeclGroupProtocol.swift index ff7cac9..10a1495 100644 --- a/Sources/MacroToolkit/DeclGroup/DeclGroupProtocol.swift +++ b/Sources/MacroToolkit/DeclGroup/DeclGroupProtocol.swift @@ -5,13 +5,13 @@ import SwiftSyntax public protocol DeclGroupProtocol { /// The identifier of the declaration group. var identifier: String { get } - + /// All members declared within the declaration group. var members: [Decl] { get } - + /// All properties declared within the declaration group. var properties: [Property] { get } - + /// All types that the declaration group inherits from or conforms to. var inheritedTypes: [Type] { get } } @@ -25,11 +25,11 @@ extension DeclGroupProtocol where UnderlyingSyntax: DeclGroupSyntax, Self: Repre guard let syntax = syntax as? UnderlyingSyntax else { return nil } self.init(syntax) } - + public var members: [Decl] { _syntax.memberBlock.members.map(\.decl).map(Decl.init) } - + public var properties: [Property] { members.compactMap(\.asVariable).flatMap { variable in var bindings = variable._syntax.bindings.flatMap { binding in @@ -49,15 +49,15 @@ extension DeclGroupProtocol where UnderlyingSyntax: DeclGroupSyntax, Self: Repre return bindings } } - + public var inheritedTypes: [Type] { _syntax.inheritanceClause?.inheritedTypes.map(\.type).map(Type.init) ?? [] } - + public var accessLevel: AccessModifier? { AccessModifier(firstModifierOfKindIn: _syntax.modifiers) } - + public var declarationContext: DeclarationContextModifier? { DeclarationContextModifier(firstModifierOfKindIn: _syntax.modifiers) } diff --git a/Sources/MacroToolkit/FunctionParameter.swift b/Sources/MacroToolkit/FunctionParameter.swift index 9199955..baa475c 100644 --- a/Sources/MacroToolkit/FunctionParameter.swift +++ b/Sources/MacroToolkit/FunctionParameter.swift @@ -60,7 +60,8 @@ extension Sequence where Element == FunctionParameter { let parameters = Array(self) for (index, parameter) in parameters.enumerated() { let isLast = index == parameters.count - 1 - let syntax = parameter._syntax.with(\.trailingComma, isLast ? nil : TokenSyntax.commaToken()) + let syntax = parameter._syntax.with( + \.trailingComma, isLast ? nil : TokenSyntax.commaToken()) list += [syntax] } return list diff --git a/Sources/MacroToolkit/ImplicitlyUnwrappedOptionalType.swift b/Sources/MacroToolkit/ImplicitlyUnwrappedOptionalType.swift index 9d51c1c..9f3e1b3 100644 --- a/Sources/MacroToolkit/ImplicitlyUnwrappedOptionalType.swift +++ b/Sources/MacroToolkit/ImplicitlyUnwrappedOptionalType.swift @@ -5,7 +5,10 @@ public struct ImplicitlyUnwrappedOptionalType: TypeProtocol { public var _baseSyntax: ImplicitlyUnwrappedOptionalTypeSyntax public var _attributedSyntax: AttributedTypeSyntax? - public init(_ syntax: ImplicitlyUnwrappedOptionalTypeSyntax, attributedSyntax: AttributedTypeSyntax? = nil) { + public init( + _ syntax: ImplicitlyUnwrappedOptionalTypeSyntax, + attributedSyntax: AttributedTypeSyntax? = nil + ) { _baseSyntax = syntax _attributedSyntax = attributedSyntax } diff --git a/Sources/MacroToolkit/Property.swift b/Sources/MacroToolkit/Property.swift index 3aa2e30..da9ec23 100644 --- a/Sources/MacroToolkit/Property.swift +++ b/Sources/MacroToolkit/Property.swift @@ -32,19 +32,21 @@ public struct Property { static func properties(from binding: PatternBindingSyntax, in decl: Variable) -> [Property] { - let accessors: [AccessorDeclSyntax] = switch binding.accessorBlock?.accessors { - case .accessors(let block): - Array(block) - case .getter(let getter): - [AccessorDeclSyntax(accessorSpecifier: .keyword(.get)) { getter }] - case .none: + let accessors: [AccessorDeclSyntax] = + switch binding.accessorBlock?.accessors { + case .accessors(let block): + Array(block) + case .getter(let getter): + [AccessorDeclSyntax(accessorSpecifier: .keyword(.get)) { getter }] + case .none: + [] + } + let attributes: [AttributeListElement] = + if decl.bindings.count == 1 { + decl.attributes + } else { [] - } - let attributes: [AttributeListElement] = if decl.bindings.count == 1 { - decl.attributes - } else { - [] - } + } return properties( pattern: binding.pattern, initialValue: (binding.initializer?.value).map(Expr.init), @@ -67,25 +69,26 @@ public struct Property { ) -> [Property] { switch pattern.asProtocol(PatternSyntaxProtocol.self) { case let pattern as IdentifierPatternSyntax: - let type: Type? = if let type { - type - } else { - if initialValue?.asIntegerLiteral != nil { - Type("Int") - } else if initialValue?.asFloatLiteral != nil { - Type("Double") - } else if initialValue?.asStringLiteral != nil { - Type("String") - } else if initialValue?.asBooleanLiteral != nil { - Type("Bool") - } else if initialValue?.asRegexLiteral != nil { - Type("Regex") - } else if let array = initialValue?._syntax.as(ArrayExprSyntax.self) { - inferArrayLiteralType(array) + let type: Type? = + if let type { + type } else { - nil + if initialValue?.asIntegerLiteral != nil { + Type("Int") + } else if initialValue?.asFloatLiteral != nil { + Type("Double") + } else if initialValue?.asStringLiteral != nil { + Type("String") + } else if initialValue?.asBooleanLiteral != nil { + Type("Bool") + } else if initialValue?.asRegexLiteral != nil { + Type("Regex") + } else if let array = initialValue?._syntax.as(ArrayExprSyntax.self) { + inferArrayLiteralType(array) + } else { + nil + } } - } return [ Property( _syntax: pattern.identifier, @@ -101,46 +104,47 @@ public struct Property { case let pattern as TuplePatternSyntax: let tupleInitialValue: TupleExprSyntax? = if let initialValue, let tuple = initialValue._syntax.as(TupleExprSyntax.self), - tuple.elements.count == pattern.elements.count - { - tuple - } else { - nil - } + tuple.elements.count == pattern.elements.count + { + tuple + } else { + nil + } let tupleType: TupleType? = if let type, - let tuple = TupleType(type), - tuple.elements.count == pattern.elements.count - { - tuple - } else { - nil - } - return pattern.elements.enumerated().flatMap { (index, element) in - let initialValue = if let tupleInitialValue { - Expr(Array(tupleInitialValue.elements)[index].expression) + let tuple = TupleType(type), + tuple.elements.count == pattern.elements.count + { + tuple } else { - initialValue.map { expr in - Expr( - MemberAccessExprSyntax( - leadingTrivia: nil, base: expr._syntax.parenthesized, - period: .periodToken(), - name: .identifier(String(index)), trailingTrivia: nil + nil + } + return pattern.elements.enumerated().flatMap { (index, element) in + let initialValue = + if let tupleInitialValue { + Expr(Array(tupleInitialValue.elements)[index].expression) + } else { + initialValue.map { expr in + Expr( + MemberAccessExprSyntax( + leadingTrivia: nil, base: expr._syntax.parenthesized, + period: .periodToken(), + name: .identifier(String(index)), trailingTrivia: nil + ) ) - ) + } } - } // If in a tuple initial value expression, an empty array literal is inferred to have // type `Array`, unlike with regular initial value expressions. let type = if let arrayLiteral = initialValue?._syntax.as(ArrayExprSyntax.self), - arrayLiteral.elements.isEmpty - { - Type("Array") - } else { - tupleType?.elements[index] - } + arrayLiteral.elements.isEmpty + { + Type("Array") + } else { + tupleType?.elements[index] + } // Tuple bindings can't have accessors or attributes (i.e. property wrappers or macros) return properties( diff --git a/Sources/MacroToolkit/RepresentableBySyntax.swift b/Sources/MacroToolkit/RepresentableBySyntax.swift index d68e405..176aaff 100644 --- a/Sources/MacroToolkit/RepresentableBySyntax.swift +++ b/Sources/MacroToolkit/RepresentableBySyntax.swift @@ -17,5 +17,3 @@ public protocol RepresentableBySyntax { /// - Parameter syntax: The underlying syntax node to represent. init(_ syntax: UnderlyingSyntax) } - - diff --git a/Sources/MacroToolkit/Type.swift b/Sources/MacroToolkit/Type.swift index d4bd394..746205b 100644 --- a/Sources/MacroToolkit/Type.swift +++ b/Sources/MacroToolkit/Type.swift @@ -39,46 +39,48 @@ public enum `Type`: TypeProtocol, SyntaxExpressibleByStringInterpolation { case tuple(TupleType) public var _baseSyntax: TypeSyntax { - let type: any TypeProtocol = switch self { - case .array(let type): type - case .classRestriction(let type): type - case .composition(let type): type - case .someOrAny(let type): type - case .dictionary(let type): type - case .function(let type): type - case .implicitlyUnwrappedOptional(let type): type - case .member(let type): type - case .metatype(let type): type - case .missing(let type): type - case .optional(let type): type - case .packExpansion(let type): type - case .packReference(let type): type - case .simple(let type): type - case .suppressed(let type): type - case .tuple(let type): type - } + let type: any TypeProtocol = + switch self { + case .array(let type): type + case .classRestriction(let type): type + case .composition(let type): type + case .someOrAny(let type): type + case .dictionary(let type): type + case .function(let type): type + case .implicitlyUnwrappedOptional(let type): type + case .member(let type): type + case .metatype(let type): type + case .missing(let type): type + case .optional(let type): type + case .packExpansion(let type): type + case .packReference(let type): type + case .simple(let type): type + case .suppressed(let type): type + case .tuple(let type): type + } return TypeSyntax(type._baseSyntax) } public var _attributedSyntax: AttributedTypeSyntax? { - let type: any TypeProtocol = switch self { - case .array(let type): type - case .classRestriction(let type): type - case .composition(let type): type - case .someOrAny(let type): type - case .dictionary(let type): type - case .function(let type): type - case .implicitlyUnwrappedOptional(let type): type - case .member(let type): type - case .metatype(let type): type - case .missing(let type): type - case .optional(let type): type - case .packExpansion(let type): type - case .packReference(let type): type - case .simple(let type): type - case .suppressed(let type): type - case .tuple(let type): type - } + let type: any TypeProtocol = + switch self { + case .array(let type): type + case .classRestriction(let type): type + case .composition(let type): type + case .someOrAny(let type): type + case .dictionary(let type): type + case .function(let type): type + case .implicitlyUnwrappedOptional(let type): type + case .member(let type): type + case .metatype(let type): type + case .missing(let type): type + case .optional(let type): type + case .packExpansion(let type): type + case .packReference(let type): type + case .simple(let type): type + case .suppressed(let type): type + case .tuple(let type): type + } return type._attributedSyntax } diff --git a/Sources/MacroToolkitExamplePlugin/AddAsyncAllMembersMacro.swift b/Sources/MacroToolkitExamplePlugin/AddAsyncAllMembersMacro.swift index 15cd34a..9bf7182 100644 --- a/Sources/MacroToolkitExamplePlugin/AddAsyncAllMembersMacro.swift +++ b/Sources/MacroToolkitExamplePlugin/AddAsyncAllMembersMacro.swift @@ -1,9 +1,12 @@ -import SwiftSyntax import MacroToolkit +import SwiftSyntax import SwiftSyntaxMacros public enum AddAsyncAllMembersMacro: MemberMacro { - public static func expansion(of node: AttributeSyntax, providingMembersOf declaration: some DeclGroupSyntax, in context: some MacroExpansionContext) throws -> [DeclSyntax] { + public static func expansion( + of node: AttributeSyntax, providingMembersOf declaration: some DeclGroupSyntax, + in context: some MacroExpansionContext + ) throws -> [DeclSyntax] { declaration.memberBlock.members.map(\.decl).compactMap { try? AddAsyncMacroCore.expansion(of: nil, providingFunctionOf: $0) } diff --git a/Sources/MacroToolkitExamplePlugin/AddAsyncMacroCore.swift b/Sources/MacroToolkitExamplePlugin/AddAsyncMacroCore.swift index 25e64eb..9088843 100644 --- a/Sources/MacroToolkitExamplePlugin/AddAsyncMacroCore.swift +++ b/Sources/MacroToolkitExamplePlugin/AddAsyncMacroCore.swift @@ -1,11 +1,13 @@ -import SwiftSyntax import MacroToolkit +import SwiftSyntax import SwiftSyntaxMacros // Modified from: https://github.com/DougGregor/swift-macro-examples/blob/f61ac7cdca8dc3557e53f86e7e03df1353908d3e/MacroExamplesPlugin/AddAsyncMacro.swift enum AddAsyncMacroCore { - static func expansion(of node: AttributeSyntax?, providingFunctionOf declaration: some DeclSyntaxProtocol) throws -> DeclSyntax { + static func expansion( + of node: AttributeSyntax?, providingFunctionOf declaration: some DeclSyntaxProtocol + ) throws -> DeclSyntax { // Only on functions at the moment. guard let function = Function(declaration) else { throw MacroError("@AddAsync only works on functions") @@ -65,28 +67,28 @@ enum AddAsyncMacroCore { let newBody = function._syntax.body.map { _ in let switchBody: ExprSyntax = - """ - switch returnValue { - case .success(let value): - continuation.resume(returning: value) - case .failure(let error): - continuation.resume(throwing: error) - } - """ - + """ + switch returnValue { + case .success(let value): + continuation.resume(returning: value) + case .failure(let error): + continuation.resume(throwing: error) + } + """ + let continuationExpr = - isResultReturn - ? "try await withCheckedThrowingContinuation { continuation in" - : "await withCheckedContinuation { continuation in" - + isResultReturn + ? "try await withCheckedThrowingContinuation { continuation in" + : "await withCheckedContinuation { continuation in" + let newBody: ExprSyntax = - """ - \(raw: continuationExpr) - \(raw: function.identifier)(\(raw: callArguments.joined(separator: ", "))) { returnValue in - \(isResultReturn ? switchBody : "continuation.resume(returning: returnValue)") + """ + \(raw: continuationExpr) + \(raw: function.identifier)(\(raw: callArguments.joined(separator: ", "))) { returnValue in + \(isResultReturn ? switchBody : "continuation.resume(returning: returnValue)") + } } - } - """ + """ return CodeBlockSyntax([newBody]) } // TODO: Make better codeblock init diff --git a/Sources/MacroToolkitExamplePlugin/DictionaryStorageMacro.swift b/Sources/MacroToolkitExamplePlugin/DictionaryStorageMacro.swift index 0bb677e..c9960b8 100644 --- a/Sources/MacroToolkitExamplePlugin/DictionaryStorageMacro.swift +++ b/Sources/MacroToolkitExamplePlugin/DictionaryStorageMacro.swift @@ -1,6 +1,6 @@ +import MacroToolkit import SwiftSyntax import SwiftSyntaxMacros -import MacroToolkit public struct DictionaryStorageMacro {} From 3a18d7786dbac1dc5e720494aef0e21e094eac1f Mon Sep 17 00:00:00 2001 From: Andy Kolean Date: Tue, 28 May 2024 00:03:49 -0400 Subject: [PATCH 8/8] swift-format is whack --- Sources/MacroToolkit/ClassRestrictionType.swift | 6 ++++-- Sources/MacroToolkit/FunctionParameter.swift | 4 ++-- Sources/MacroToolkitExamplePlugin/AddAsyncMacroCore.swift | 3 ++- 3 files changed, 8 insertions(+), 5 deletions(-) diff --git a/Sources/MacroToolkit/ClassRestrictionType.swift b/Sources/MacroToolkit/ClassRestrictionType.swift index 1884092..3177256 100644 --- a/Sources/MacroToolkit/ClassRestrictionType.swift +++ b/Sources/MacroToolkit/ClassRestrictionType.swift @@ -5,8 +5,10 @@ public struct ClassRestrictionType: TypeProtocol { public var _baseSyntax: ClassRestrictionTypeSyntax public var _attributedSyntax: AttributedTypeSyntax? - public init(_ syntax: ClassRestrictionTypeSyntax, attributedSyntax: AttributedTypeSyntax? = nil) - { + public init( + _ syntax: ClassRestrictionTypeSyntax, + attributedSyntax: AttributedTypeSyntax? = nil + ) { _baseSyntax = syntax _attributedSyntax = attributedSyntax } diff --git a/Sources/MacroToolkit/FunctionParameter.swift b/Sources/MacroToolkit/FunctionParameter.swift index baa475c..da08c4d 100644 --- a/Sources/MacroToolkit/FunctionParameter.swift +++ b/Sources/MacroToolkit/FunctionParameter.swift @@ -60,8 +60,8 @@ extension Sequence where Element == FunctionParameter { let parameters = Array(self) for (index, parameter) in parameters.enumerated() { let isLast = index == parameters.count - 1 - let syntax = parameter._syntax.with( - \.trailingComma, isLast ? nil : TokenSyntax.commaToken()) + let syntax = parameter._syntax + .with(\.trailingComma, isLast ? nil : TokenSyntax.commaToken()) list += [syntax] } return list diff --git a/Sources/MacroToolkitExamplePlugin/AddAsyncMacroCore.swift b/Sources/MacroToolkitExamplePlugin/AddAsyncMacroCore.swift index 9088843..496717d 100644 --- a/Sources/MacroToolkitExamplePlugin/AddAsyncMacroCore.swift +++ b/Sources/MacroToolkitExamplePlugin/AddAsyncMacroCore.swift @@ -6,7 +6,8 @@ import SwiftSyntaxMacros enum AddAsyncMacroCore { static func expansion( - of node: AttributeSyntax?, providingFunctionOf declaration: some DeclSyntaxProtocol + of node: AttributeSyntax?, + providingFunctionOf declaration: some DeclSyntaxProtocol ) throws -> DeclSyntax { // Only on functions at the moment. guard let function = Function(declaration) else {