Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 4 additions & 1 deletion .github/workflows/Build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,9 @@ on:
- preview/version-8
pull_request:

env:
DEVELOPER_DIR: /Applications/Xcode_15.0.app/Contents/Developer

jobs:

build_iOS:
Expand Down Expand Up @@ -72,7 +75,7 @@ jobs:
run: make build_example_tvOS

build_SPM:
runs-on: macOS-12
runs-on: macOS-13
steps:
- uses: actions/checkout@v1
- name: Build
Expand Down
5 changes: 4 additions & 1 deletion .github/workflows/Lint.yml
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,9 @@ name: Lint

on: [pull_request]

env:
DEVELOPER_DIR: /Applications/Xcode_15.0.app/Contents/Developer

jobs:
danger:
runs-on: ubuntu-latest
Expand All @@ -16,7 +19,7 @@ jobs:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}

pod:
runs-on: macOS-12
runs-on: macOS-13
steps:
- uses: actions/checkout@v1
- name: Generate projects
Expand Down
12 changes: 6 additions & 6 deletions Brewfile.lock.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
"entries": {
"tap": {
"homebrew/cask": {
"revision": "266747f8b172dd47e6c80ce2590889a8a7c96501"
"revision": "c896fe86403b4515fddc15421cce660a306dc3f0"
},
"yonaskolb/xcodegen": {
"revision": "372f20fe5a3823dc398f52cb6cc1af5003d452f7",
Expand All @@ -19,7 +19,7 @@
},
"brew": {
"xcodegen": {
"version": "2.25.0",
"version": "2.35.0",
"bottle": {
"rebuild": 0,
"root_url": "https://ghcr.io/v2/homebrew/core",
Expand Down Expand Up @@ -58,7 +58,7 @@
}
},
"sourcery": {
"version": "1.8.1",
"version": "2.0.2",
"bottle": {
"rebuild": 0,
"root_url": "https://ghcr.io/v2/homebrew/core",
Expand Down Expand Up @@ -107,11 +107,11 @@
"macOS": "11.5.1"
},
"ventura": {
"HOMEBREW_VERSION": "4.0.19",
"HOMEBREW_PREFIX": "/opt/homebrew",
"HOMEBREW_VERSION": "4.0.23-10-g6abf680",
"HOMEBREW_PREFIX": "/usr/local",
"Homebrew/homebrew-core": "api",
"CLT": "",
"Xcode": "14.3",
"Xcode": "15.0",
"macOS": "13.4"
}
}
Expand Down
8 changes: 8 additions & 0 deletions Components/Macro/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
.DS_Store
/.build
/Packages
xcuserdata/
DerivedData/
.swiftpm/configuration/registries.json
.swiftpm/xcode/package.xcworkspace/contents.xcworkspacedata
.netrc
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>IDEDidComputeMac32BitWarning</key>
<true/>
</dict>
</plist>
14 changes: 14 additions & 0 deletions Components/Macro/Package.resolved

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

57 changes: 57 additions & 0 deletions Components/Macro/Package.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
// swift-tools-version: 5.9
// The swift-tools-version declares the minimum version of Swift required to build this package.

import PackageDescription
import CompilerPluginSupport

let package = Package(
name: "Macro",
Copy link
Contributor

@Ikeret Ikeret Jun 21, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Какое то уникальное название надо придумать)

platforms: [.macOS(.v10_15), .iOS(.v13), .tvOS(.v13), .watchOS(.v6), .macCatalyst(.v13)],
products: [
// Products define the executables and libraries a package produces, making them visible to other packages.
.library(
name: "Macro",
type: .dynamic,
targets: ["Macro"]
),
.executable(
name: "MacroClient",
targets: ["MacroClient"]
)
],
dependencies: [
// Depend on the latest Swift 5.9 prerelease of SwiftSyntax
.package(url: "https://github.com/apple/swift-syntax.git", from: "509.0.0-swift-5.9-DEVELOPMENT-SNAPSHOT-2023-04-25-b")
],
targets: [
// Targets are the basic building blocks of a package, defining a module or a test suite.
// Targets can depend on other targets in this package and products from dependencies.
// Macro implementation that performs the source transformation of a macro.
.macro(
name: "Macros",
dependencies: [
.product(name: "SwiftSyntaxMacros", package: "swift-syntax"),
.product(name: "SwiftCompilerPlugin", package: "swift-syntax")
]
),

// Library that exposes a macro as part of its API, which is used in client programs.
.target(name: "Macro",
dependencies: [
"Macros"
]
),

// A client of the library, which is able to use the macro in its own code.
.executableTarget(name: "MacroClient", dependencies: ["Macro"]),

// A test target used to develop the macro implementation.
.testTarget(
name: "MacroTests",
dependencies: [
"Macro",
.product(name: "SwiftSyntaxMacrosTestSupport", package: "swift-syntax")
]
)
]
)
10 changes: 10 additions & 0 deletions Components/Macro/Sources/Macro/MacroDefinition.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
// The Swift Programming Language
// https://docs.swift.org/swift-book

/// A macro which generate `mutating func` for all variables inside struct.
@attached(member, names: named(set))
public macro Mutable() = #externalMacro(module: "Macros", type: "MutableMacro")

@attached(conformance)
@attached(member, names: named(create), named(Property))
public macro Buildable() = #externalMacro(module: "Macros", type: "BuildableMacro")
37 changes: 37 additions & 0 deletions Components/Macro/Sources/MacroClient/main.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
import Macro

// MARK: - Precondition

@Mutable
struct SomeStruct {
let id: Int = Int.random(in: Int.min...Int.max)
var someFlag: Bool = false
var someString: String = ""
}

// MARK: - Helpers

extension SomeStruct {

func debugPrintAll() {
debugPrint(someFlag)
debugPrint(someString)
}

}

// MARK: - Debugging

var someStruct = SomeStruct()

someStruct.debugPrintAll()

someStruct.set(someFlag: true)
someStruct.set(someString: "echo")

someStruct.debugPrintAll()

someStruct.set(someFlag: false)
someStruct.set(someString: "horray")

someStruct.debugPrintAll()
110 changes: 110 additions & 0 deletions Components/Macro/Sources/Macros/BuildableMacro.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,110 @@
//
// BuildableMacro.swift
//
//
// Created by Никита Коробейников on 25.07.2023.
//

import SwiftSyntax
import SwiftSyntaxBuilder
import SwiftSyntaxMacros

/// A macro which generate `func build` for all variables inside struct.
/// - Note: builder is based on resultBuilder from Swift 5.4
public struct BuildableMacro: MemberMacro, ConformanceMacro {

public static func expansion(of node: SwiftSyntax.AttributeSyntax, providingConformancesOf declaration: some SwiftSyntax.DeclGroupSyntax, in context: some SwiftSyntaxMacros.MacroExpansionContext) throws -> [(SwiftSyntax.TypeSyntax, SwiftSyntax.GenericWhereClauseSyntax?)] {
return [(TypeSyntax(stringLiteral: "EditorWrapper"), nil)]
}

public static func expansion(of node: SwiftSyntax.AttributeSyntax, providingMembersOf declaration: some SwiftSyntax.DeclGroupSyntax, in context: some SwiftSyntaxMacros.MacroExpansionContext) throws -> [SwiftSyntax.DeclSyntax] {
guard let baseStruct = declaration.as(StructDeclSyntax.self) else {
throw MacroError.onlyApplicableToStruct
}

let variables = baseStruct.variables

let editors = try prepareEditorDeclarations(for: variables)

let propertyStruct = preparePropertyStruct(for: baseStruct, with: editors)

let createFunc = try prepareCreateFunction(for: baseStruct)

var result: [DeclSyntaxProtocol] = []

result.append(createFunc)
result.append(propertyStruct)

return result.compactMap { $0.as(DeclSyntax.self) }
}

}

// MARK: - Private

private extension BuildableMacro {

static func prepareCreateFunction(for baseStruct: StructDeclSyntax) throws -> FunctionDeclSyntax {
guard let baseStuctType = TypeSyntax(baseStruct) else {
throw MacroError.failedToExtractTypeOfBaseStruct
}

return .init(modifiers: .init(itemsBuilder: {
DeclModifierSyntax(name: .keyword(.public))
DeclModifierSyntax(name: .keyword(.static))
}),
identifier: .identifier("create"),
signature: .init(input: .init(parameterListBuilder: { }),
output: .init(returnType: baseStuctType))
)
}

static func preparePropertyStruct(for baseStruct: StructDeclSyntax, with editors: [FunctionDeclSyntax]) -> StructDeclSyntax {
StructDeclSyntax(modifiers: .init(itemsBuilder: {
DeclModifierSyntax(name: .keyword(.public))
}),
identifier: .identifier("Property"),
memberBlock: .init(membersBuilder: {
.init(itemsBuilder: {
TypealiasDeclSyntax(
modifiers: .init(itemsBuilder: {
DeclModifierSyntax(name: .keyword(.public))
}),
identifier:
.identifier("Model"),
initializer: .init(baseStruct)!)

// VariableDeclSyntax(modifiers: .init(itemsBuilder: {
// DeclModifierSyntax(name: .keyword(.private))
// }), name: .init(stringLiteral: "closure"), .keyword(.let))
})
}))
}

static func prepareEditorDeclarations(for variables: [VariableDeclSyntax]) throws -> [FunctionDeclSyntax] {
try variables.compactMap { variableDecl -> FunctionDeclSyntax? in

guard let variable = try variableDecl.parseNameAndType() else {
return nil
}

return FunctionDeclSyntax(leadingTrivia: .newlines(2),
modifiers: .init(itemsBuilder: {
DeclModifierSyntax(name: .keyword(.static))
}),
identifier: .identifier(variable.name.text),
signature: .init(input: .init(parameterListBuilder: {
FunctionParameterSyntax(leadingTrivia: .space,
firstName: .identifier("value"),
type: variable.type)
})),
body: CodeBlockSyntax(statementsBuilder: {
StmtSyntax(stringLiteral: "var model = model")
StmtSyntax(stringLiteral: "model.set(\(variable.name.text):value)")
StmtSyntax(stringLiteral: "return model")
})
)
}
}

}
51 changes: 51 additions & 0 deletions Components/Macro/Sources/Macros/Decl+Extensions.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
//
// Decl+Extensions.swift
//
//
// Created by Никита Коробейников on 25.07.2023.
//

import SwiftSyntax
import SwiftSyntaxBuilder
import SwiftSyntaxMacros

// MARK: - Struct

extension StructDeclSyntax {

var variables: [VariableDeclSyntax] {
return memberBlock.members
.compactMap { $0.decl.as(VariableDeclSyntax.self) }
.filter { $0.bindingKeyword.text == "var" }
}


}

// MARK: - Variable

extension VariableDeclSyntax {

func parseNameAndType() throws -> (name: TokenSyntax, type: TypeSyntax)? {
guard let variableBinding = bindings.first,
let variableType = variableBinding.typeAnnotation?.type.trimmed else {
let variableName = bindings.first?.pattern.trimmedDescription
throw MacroError.typeAnnotationRequiredFor(variableName: variableName ?? "unknown")
}

let variableName = TokenSyntax(stringLiteral: variableBinding.pattern.description)

return (name: variableName, type: variableType)
}

}

// MARK: - CodeBlockExpression

extension CodeBlockItemSyntax {

static func stringItem(_ string: String) -> CodeBlockItemSyntax.Item {
CodeBlockItemSyntax.Item.expr(.init(stringLiteral: string))
}

}
14 changes: 14 additions & 0 deletions Components/Macro/Sources/Macros/MacroError.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
//
// MacroError.swift
//
//
// Created by Никита Коробейников on 16.06.2023.
//

public enum MacroError: Error {

case onlyApplicableToStruct
case typeAnnotationRequiredFor(variableName: String)
case failedToExtractTypeOfBaseStruct

}
Loading