diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md
new file mode 100644
index 0000000..8dc7e75
--- /dev/null
+++ b/.github/ISSUE_TEMPLATE/bug_report.md
@@ -0,0 +1,41 @@
+---
+name: "🐛 Bug Report"
+about: Report a reproducible bug or regression.
+title: 'Bug: '
+labels: 'bug'
+
+---
+
+
+
+Application version:
+
+## Steps To Reproduce
+
+1.
+2.
+
+
+
+Link to code example:
+
+
+
+## The current behavior
+
+
+## The expected behavior
\ No newline at end of file
diff --git a/.github/ISSUE_TEMPLATE/feature_request.md b/.github/ISSUE_TEMPLATE/feature_request.md
new file mode 100644
index 0000000..3e6b15f
--- /dev/null
+++ b/.github/ISSUE_TEMPLATE/feature_request.md
@@ -0,0 +1,11 @@
+---
+name: 🛠 Feature request
+about: If you have a feature request for the builder-macro, file it here.
+labels: 'type: enhancement'
+---
+
+**Feature description**
+Clearly and concisely describe the feature.
+
+**Describe the solution you'd like**
+A clear and concise description of what you want to happen.
\ No newline at end of file
diff --git a/.github/PULL_REQUEST_TEMPLATE/bug_template.md b/.github/PULL_REQUEST_TEMPLATE/bug_template.md
new file mode 100644
index 0000000..7d6a149
--- /dev/null
+++ b/.github/PULL_REQUEST_TEMPLATE/bug_template.md
@@ -0,0 +1,9 @@
+## Bug description
+Clearly and concisely describe the problem.
+
+## Solution description
+Describe your code changes in detail for reviewers. Explain the technical solution you have provided and how it fixes the issue case.
+
+## Covered unit test cases
+- [x] yes
+- [x] no
\ No newline at end of file
diff --git a/.github/PULL_REQUEST_TEMPLATE/feature_template.md b/.github/PULL_REQUEST_TEMPLATE/feature_template.md
new file mode 100644
index 0000000..ab3978b
--- /dev/null
+++ b/.github/PULL_REQUEST_TEMPLATE/feature_template.md
@@ -0,0 +1,12 @@
+## Feature description
+Clearly and concisely describe the feature.
+
+## Solution description
+Describe your code changes in detail for reviewers.
+
+## Areas affected and ensured
+List out the areas affected by your code changes.
+
+## Covered unit test cases
+- [x] yes
+- [x] no
\ No newline at end of file
diff --git a/.github/dependabot.yml b/.github/dependabot.yml
new file mode 100644
index 0000000..c3e658c
--- /dev/null
+++ b/.github/dependabot.yml
@@ -0,0 +1,34 @@
+# To get started with Dependabot version updates, you'll need to specify which
+# package ecosystems to update and where the package manifests are located.
+# Please see the documentation for all configuration options:
+# https://docs.github.com/github/administering-a-repository/configuration-options-for-dependency-updates
+
+version: 2
+updates:
+ - package-ecosystem: github-actions
+ directory: /
+ open-pull-requests-limit: 10
+ schedule:
+ interval: daily
+ time: '07:00'
+ timezone: Europe/Berlin
+
+ assignees:
+ - nik3212
+ reviewers:
+ - nik3212
+
+
+ - package-ecosystem: swift
+ directory: /
+ open-pull-requests-limit: 10
+ schedule:
+ interval: daily
+ time: '07:00'
+ timezone: Europe/Berlin
+
+ assignees:
+ - nik3212
+ reviewers:
+ - nik3212
+
\ No newline at end of file
diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
new file mode 100644
index 0000000..64e6ca9
--- /dev/null
+++ b/.github/workflows/ci.yml
@@ -0,0 +1,221 @@
+name: "builder-macro"
+
+on:
+ push:
+ branches:
+ - main
+ - dev
+ pull_request:
+ paths:
+ - '.swiftlint.yml'
+ - ".github/workflows/**"
+ - "Package.swift"
+ - "Source/**"
+ - "Tests/**"
+jobs:
+ SwiftLint:
+ runs-on: ubuntu-latest
+ steps:
+ - uses: actions/checkout@v4
+ - name: GitHub Action for SwiftLint
+ uses: norio-nomura/action-swiftlint@3.2.1
+ with:
+ args: --strict
+ env:
+ DIFF_BASE: ${{ github.base_ref }}
+ macOS:
+ name: ${{ matrix.name }}
+ runs-on: ${{ matrix.runsOn }}
+ env:
+ DEVELOPER_DIR: "/Applications/${{ matrix.xcode }}.app/Contents/Developer"
+ timeout-minutes: 20
+ strategy:
+ fail-fast: false
+ matrix:
+ include:
+ - xcode: "Xcode_16.0"
+ runsOn: macos-14
+ name: "macOS 14, Xcode 16.0, Swift 6.0"
+ - xcode: "Xcode_15.4"
+ runsOn: macos-14
+ name: "macOS 14, Xcode 15.4, Swift 5.10"
+ - xcode: "Xcode_15.2"
+ runsOn: macos-13
+ name: "macOS 14, Xcode 15.2, Swift 5.9.2"
+ - xcode: "Xcode_15.1"
+ runsOn: macOS-13
+ name: "macOS 14, Xcode 15.1, Swift 5.9.1"
+ - xcode: "Xcode_15.0"
+ runsOn: macos-13
+ name: "macOS 13, Xcode 15.0, Swift 5.9.0"
+ steps:
+ - uses: actions/checkout@v4
+ - name: ${{ matrix.name }}
+ run: xcodebuild test -scheme "BuilderMacro-Package" -destination "platform=macOS" clean -enableCodeCoverage YES -resultBundlePath "test_output/${{ matrix.name }}.xcresult" || exit 1
+ - name: Upload coverage reports to Codecov
+ uses: codecov/codecov-action@v4.5.0
+ with:
+ token: ${{ secrets.CODECOV_TOKEN }}
+ xcode: true
+ xcode_archive_path: test_output/${{ matrix.name }}.xcresult
+ - uses: actions/upload-artifact@v4
+ with:
+ name: ${{ matrix.name }}
+ path: test_output
+ iOS:
+ name: ${{ matrix.name }}
+ runs-on: ${{ matrix.runsOn }}
+ env:
+ DEVELOPER_DIR: "/Applications/${{ matrix.xcode }}.app/Contents/Developer"
+ timeout-minutes: 20
+ strategy:
+ fail-fast: false
+ matrix:
+ include:
+ - destination: "OS=17.5,name=iPhone 15 Pro"
+ name: "iOS 17.5"
+ xcode: "Xcode_15.4"
+ runsOn: macOS-14
+ - destination: "OS=17.2,name=iPhone 15 Pro"
+ name: "iOS 17.2"
+ xcode: "Xcode_15.2"
+ runsOn: macOS-14
+ - destination: "OS=17.0,name=iPhone 15 Pro"
+ name: "iOS 17.0"
+ xcode: "Xcode_15.0.1"
+ runsOn: macOS-14
+ steps:
+ - uses: actions/checkout@v4
+ - name: ${{ matrix.name }}
+ run: xcodebuild test -scheme "BuilderMacro-Package" -destination "${{ matrix.destination }}" clean -enableCodeCoverage YES -resultBundlePath "test_output/${{ matrix.name }}.xcresult" || exit 1
+ - uses: actions/upload-artifact@v4
+ with:
+ name: ${{ matrix.name }}
+ path: test_output
+ tvOS:
+ name: ${{ matrix.name }}
+ runs-on: ${{ matrix.runsOn }}
+ env:
+ DEVELOPER_DIR: "/Applications/${{ matrix.xcode }}.app/Contents/Developer"
+ timeout-minutes: 20
+ strategy:
+ fail-fast: false
+ matrix:
+ include:
+ - destination: "OS=17.5,name=Apple TV"
+ name: "tvOS 17.5"
+ xcode: "Xcode_15.4"
+ runsOn: macOS-14
+ - destination: "OS=17.2,name=Apple TV"
+ name: "tvOS 17.2"
+ xcode: "Xcode_15.2"
+ runsOn: macOS-14
+ - destination: "OS=17.0,name=Apple TV"
+ name: "tvOS 17.0"
+ xcode: "Xcode_15.0.1"
+ runsOn: macOS-14
+ steps:
+ - uses: actions/checkout@v4
+ - name: ${{ matrix.name }}
+ run: xcodebuild test -scheme "BuilderMacro-Package" -destination "${{ matrix.destination }}" clean -enableCodeCoverage YES -resultBundlePath "test_output/${{ matrix.name }}.xcresult" || exit 1
+ - name: Upload coverage reports to Codecov
+ uses: codecov/codecov-action@v4.5.0
+ with:
+ token: ${{ secrets.CODECOV_TOKEN }}
+ xcode: true
+ xcode_archive_path: test_output/${{ matrix.name }}.xcresult
+ - uses: actions/upload-artifact@v4
+ with:
+ name: ${{ matrix.name }}
+ path: test_output
+ watchOS:
+ name: ${{ matrix.name }}
+ runs-on: ${{ matrix.runsOn }}
+ env:
+ DEVELOPER_DIR: "/Applications/${{ matrix.xcode }}.app/Contents/Developer"
+ timeout-minutes: 20
+ strategy:
+ fail-fast: false
+ matrix:
+ include:
+ - destination: "OS=10.5,name=Apple Watch Series 9 (45mm)"
+ name: "watchOS 10.5"
+ xcode: "Xcode_15.4"
+ runsOn: macOS-14
+ - destination: "OS=10.2,name=Apple Watch Series 9 (45mm)"
+ name: "watchOS 10.2"
+ xcode: "Xcode_15.2"
+ runsOn: macOS-14
+ - destination: "OS=10.0,name=Apple Watch Series 9 (45mm)"
+ name: "watchOS 10.0"
+ xcode: "Xcode_15.0.1"
+ runsOn: macOS-14
+ steps:
+ - uses: actions/checkout@v4
+ - name: ${{ matrix.name }}
+ run: xcodebuild test -scheme "BuilderMacro-Package" -destination "${{ matrix.destination }}" clean -enableCodeCoverage YES -resultBundlePath "test_output/${{ matrix.name }}.xcresult" || exit 1
+ - name: Upload coverage reports to Codecov
+ uses: codecov/codecov-action@v4.5.0
+ with:
+ token: ${{ secrets.CODECOV_TOKEN }}
+ xcode: true
+ xcode_archive_path: test_output/${{ matrix.name }}.xcresult
+ - uses: actions/upload-artifact@v4
+ with:
+ name: ${{ matrix.name }}
+ path: test_output
+ spm:
+ name: ${{ matrix.name }}
+ runs-on: ${{ matrix.runsOn }}
+ env:
+ DEVELOPER_DIR: "/Applications/${{ matrix.xcode }}.app/Contents/Developer"
+ timeout-minutes: 20
+ strategy:
+ fail-fast: false
+ matrix:
+ include:
+ - xcode: "Xcode_15.4"
+ runsOn: macOS-14
+ name: "macOS 14, SPM 5.10 Test"
+ - xcode: "Xcode_15.2"
+ runsOn: macOS-14
+ name: "macOS 14, SPM 5.9.2 Test"
+ - xcode: "Xcode_15.1"
+ runsOn: macOS-14
+ name: "macOS 14, SPM 5.9.1 Test"
+ - xcode: "Xcode_15.0.1"
+ runsOn: macOS-14
+ name: "macOS 14, SPM 5.9.0 Test"
+ steps:
+ - uses: actions/checkout@v4
+ - name: ${{ matrix.name }}
+ run: swift build -c release --target "BuilderMacro"
+
+ merge-test-reports:
+ needs: [iOS, macOS, watchOS, tvOS]
+ runs-on: macos-13
+ steps:
+ - name: Download artifacts
+ uses: actions/download-artifact@v4
+ with:
+ path: test_output
+ - run: xcrun xcresulttool merge test_output/**/*.xcresult --output-path test_output/final/final.xcresult
+ - name: Upload Merged Artifact
+ uses: actions/upload-artifact@v4
+ with:
+ name: MergedResult
+ path: test_output/final
+
+ discover-typos:
+ name: Discover Typos
+ runs-on: macOS-12
+ env:
+ DEVELOPER_DIR: /Applications/Xcode_14.1.app/Contents/Developer
+ steps:
+ - uses: actions/checkout@v4
+ - name: Discover typos
+ run: |
+ export PATH="$PATH:/Library/Frameworks/Python.framework/Versions/3.11/bin"
+ python3 -m pip install --upgrade pip
+ python3 -m pip install codespell
+ codespell --ignore-words-list="hart,inout,msdos,sur,commend" --skip="./.build/*,./.git/*"
diff --git a/.github/workflows/danger.yml b/.github/workflows/danger.yml
new file mode 100644
index 0000000..55a4794
--- /dev/null
+++ b/.github/workflows/danger.yml
@@ -0,0 +1,31 @@
+name: Danger
+
+on:
+ pull_request:
+ types: [synchronize, opened, reopened, labeled, unlabeled, edited]
+
+env:
+ LC_CTYPE: en_US.UTF-8
+ LANG: en_US.UTF-8
+
+jobs:
+ run-danger:
+ runs-on: ubuntu-latest
+ steps:
+ - name: ruby setup
+ uses: ruby/setup-ruby@v1
+ with:
+ ruby-version: 3.1.4
+ bundler-cache: true
+ - name: Checkout code
+ uses: actions/checkout@v4
+ - name: Setup gems
+ run: |
+ gem install bundler
+ bundle install --clean --path vendor/bundle
+ - name: danger
+ env:
+
+ DANGER_GITHUB_API_TOKEN: ${{ secrets.DANGER_GITHUB_API_TOKEN }}
+
+ run: bundle exec danger --verbose
\ No newline at end of file
diff --git a/.swiftformat b/.swiftformat
new file mode 100644
index 0000000..97a3629
--- /dev/null
+++ b/.swiftformat
@@ -0,0 +1,64 @@
+# Stream rules
+
+--swiftversion 5.3
+
+# Use 'swiftformat --options' to list all of the possible options
+
+--header "\nbuilder-macro\nCopyright © {created.year} Space Code. All rights reserved.\n//"
+
+--enable blankLinesBetweenScopes
+--enable blankLinesAtStartOfScope
+--enable blankLinesAtEndOfScope
+--enable blankLinesAroundMark
+--enable anyObjectProtocol
+--enable consecutiveBlankLines
+--enable consecutiveSpaces
+--enable duplicateImports
+--enable elseOnSameLine
+--enable emptyBraces
+--enable initCoderUnavailable
+--enable leadingDelimiters
+--enable numberFormatting
+--enable preferKeyPath
+--enable redundantBreak
+--enable redundantExtensionACL
+--enable redundantFileprivate
+--enable redundantGet
+--enable redundantInit
+--enable redundantLet
+--enable redundantLetError
+--enable redundantNilInit
+--enable redundantObjc
+--enable redundantParens
+--enable redundantPattern
+--enable redundantRawValues
+--enable redundantReturn
+--enable redundantSelf
+--enable redundantVoidReturnType
+--enable semicolons
+--enable sortImports
+--enable sortSwitchCases
+--enable spaceAroundBraces
+--enable spaceAroundBrackets
+--enable spaceAroundComments
+--enable spaceAroundGenerics
+--enable spaceAroundOperators
+--enable spaceInsideBraces
+--enable spaceInsideBrackets
+--enable spaceInsideComments
+--enable spaceInsideGenerics
+--enable spaceInsideParens
+--enable strongOutlets
+--enable strongifiedSelf
+--enable todos
+--enable trailingClosures
+--enable unusedArguments
+--enable void
+--enable markTypes
+--enable isEmpty
+
+# format options
+
+--wraparguments before-first
+--wrapcollections before-first
+--maxwidth 140
\ No newline at end of file
diff --git a/.swiftlint.yml b/.swiftlint.yml
new file mode 100644
index 0000000..89efd09
--- /dev/null
+++ b/.swiftlint.yml
@@ -0,0 +1,135 @@
+excluded:
+ - Tests
+ - Package.swift
+ - .build
+
+# Rules
+
+disabled_rules:
+ - trailing_comma
+ - todo
+ - opening_brace
+
+opt_in_rules: # some rules are only opt-in
+ - anyobject_protocol
+ - array_init
+ - attributes
+ - closure_body_length
+ - closure_end_indentation
+ - closure_spacing
+ - collection_alignment
+ - conditional_returns_on_newline
+ - contains_over_filter_count
+ - contains_over_filter_is_empty
+ - contains_over_first_not_nil
+ - contains_over_range_nil_comparison
+ - convenience_type
+ - discouraged_object_literal
+ - discouraged_optional_boolean
+ - empty_collection_literal
+ - empty_count
+ - empty_string
+ - empty_xctest_method
+ - enum_case_associated_values_count
+ - explicit_init
+ - fallthrough
+ - fatal_error_message
+ - file_name
+ - file_types_order
+ - first_where
+ - flatmap_over_map_reduce
+ - force_unwrapping
+ - ibinspectable_in_extension
+ - identical_operands
+ - implicit_return
+ - inert_defer
+ - joined_default_parameter
+ - last_where
+ - legacy_multiple
+ - legacy_random
+ - literal_expression_end_indentation
+ - lower_acl_than_parent
+ - multiline_arguments
+ - multiline_function_chains
+ - multiline_literal_brackets
+ - multiline_parameters
+ - multiline_parameters_brackets
+ - no_space_in_method_call
+ - operator_usage_whitespace
+ - optional_enum_case_matching
+ - orphaned_doc_comment
+ - overridden_super_call
+ - override_in_extension
+ - pattern_matching_keywords
+ - prefer_self_type_over_type_of_self
+ - prefer_zero_over_explicit_init
+ - prefixed_toplevel_constant
+ - private_action
+ - prohibited_super_call
+ - quick_discouraged_call
+ - quick_discouraged_focused_test
+ - quick_discouraged_pending_test
+ - reduce_into
+ - redundant_nil_coalescing
+ - redundant_objc_attribute
+ - redundant_type_annotation
+ - required_enum_case
+ - single_test_class
+ - sorted_first_last
+ - sorted_imports
+ - static_operator
+ - strict_fileprivate
+ - switch_case_on_newline
+ - toggle_bool
+ - unavailable_function
+ - unneeded_parentheses_in_closure_argument
+ - unowned_variable_capture
+ - untyped_error_in_catch
+ - vertical_parameter_alignment_on_call
+ - vertical_whitespace_closing_braces
+ - vertical_whitespace_opening_braces
+ - xct_specific_matcher
+ - yoda_condition
+
+force_cast: warning
+force_try: warning
+
+identifier_name:
+ excluded:
+ - id
+ - URL
+
+analyzer_rules:
+ - unused_import
+ - unused_declaration
+
+line_length:
+ warning: 130
+ error: 200
+
+type_body_length:
+ warning: 300
+ error: 400
+
+file_length:
+ warning: 500
+ error: 1200
+
+function_body_length:
+ warning: 30
+ error: 50
+
+large_tuple:
+ error: 3
+
+nesting:
+ type_level:
+ warning: 2
+ statement_level:
+ warning: 10
+
+
+type_name:
+ max_length:
+ warning: 40
+ error: 50
\ No newline at end of file
diff --git a/.swiftpm/xcode/package.xcworkspace/contents.xcworkspacedata b/.swiftpm/xcode/package.xcworkspace/contents.xcworkspacedata
new file mode 100644
index 0000000..919434a
--- /dev/null
+++ b/.swiftpm/xcode/package.xcworkspace/contents.xcworkspacedata
@@ -0,0 +1,7 @@
+
+
+
+
+
diff --git a/.swiftpm/xcode/xcshareddata/xcschemes/BuilderMacro-Package.xcscheme b/.swiftpm/xcode/xcshareddata/xcschemes/BuilderMacro-Package.xcscheme
new file mode 100644
index 0000000..d370188
--- /dev/null
+++ b/.swiftpm/xcode/xcshareddata/xcschemes/BuilderMacro-Package.xcscheme
@@ -0,0 +1,102 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/.swiftpm/xcode/xcshareddata/xcschemes/BuilderMacro.xcscheme b/.swiftpm/xcode/xcshareddata/xcschemes/BuilderMacro.xcscheme
new file mode 100644
index 0000000..b1b7327
--- /dev/null
+++ b/.swiftpm/xcode/xcshareddata/xcschemes/BuilderMacro.xcscheme
@@ -0,0 +1,67 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/.swiftpm/xcode/xcshareddata/xcschemes/BuilderMacroClient.xcscheme b/.swiftpm/xcode/xcshareddata/xcschemes/BuilderMacroClient.xcscheme
new file mode 100644
index 0000000..fc65e63
--- /dev/null
+++ b/.swiftpm/xcode/xcshareddata/xcschemes/BuilderMacroClient.xcscheme
@@ -0,0 +1,90 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/CHANGELOG.md b/CHANGELOG.md
new file mode 100644
index 0000000..2e9885a
--- /dev/null
+++ b/CHANGELOG.md
@@ -0,0 +1,2 @@
+# Change Log
+All notable changes to this project will be documented in this file.
\ No newline at end of file
diff --git a/CODE_OF_CONDUCT.md b/CODE_OF_CONDUCT.md
new file mode 100644
index 0000000..56c1661
--- /dev/null
+++ b/CODE_OF_CONDUCT.md
@@ -0,0 +1,74 @@
+# Contributor Covenant Code of Conduct
+
+## Our Pledge
+
+In the interest of fostering an open and welcoming environment, we as
+contributors and maintainers pledge to making participation in our project and
+our community a harassment-free experience for everyone, regardless of age, body
+size, disability, ethnicity, gender identity and expression, level of experience,
+nationality, personal appearance, race, religion, or sexual identity and
+orientation.
+
+## Our Standards
+
+Examples of behavior that contributes to creating a positive environment
+include:
+
+* Using welcoming and inclusive language
+* Being respectful of differing viewpoints and experiences
+* Gracefully accepting constructive criticism
+* Focusing on what is best for the community
+* Showing empathy towards other community members
+
+Examples of unacceptable behavior by participants include:
+
+* The use of sexualized language or imagery and unwelcome sexual attention or
+advances
+* Trolling, insulting/derogatory comments, and personal or political attacks
+* Public or private harassment
+* Publishing others' private information, such as a physical or electronic
+ address, without explicit permission
+* Other conduct which could reasonably be considered inappropriate in a
+ professional setting
+
+## Our Responsibilities
+
+Project maintainers are responsible for clarifying the standards of acceptable
+behavior and are expected to take appropriate and fair corrective action in
+response to any instances of unacceptable behavior.
+
+Project maintainers have the right and responsibility to remove, edit, or
+reject comments, commits, code, wiki edits, issues, and other contributions
+that are not aligned to this Code of Conduct, or to ban temporarily or
+permanently any contributor for other behaviors that they deem inappropriate,
+threatening, offensive, or harmful.
+
+## Scope
+
+This Code of Conduct applies both within project spaces and in public spaces
+when an individual is representing the project or its community. Examples of
+representing a project or community include using an official project e-mail
+address, posting via an official social media account, or acting as an appointed
+representative at an online or offline event. Representation of a project may be
+further defined and clarified by project maintainers.
+
+## Enforcement
+
+Instances of abusive, harassing, or otherwise unacceptable behavior may be
+reported by contacting one of the project maintainers https://github.com/orgs/space-code/people. All
+complaints will be reviewed and investigated and will result in a response that
+is deemed necessary and appropriate to the circumstances. The project team is
+obligated to maintain confidentiality with regard to the reporter of an incident.
+Further details of specific enforcement policies may be posted separately.
+
+Project maintainers who do not follow or enforce the Code of Conduct in good
+faith may face temporary or permanent repercussions as determined by other
+members of the project's leadership.
+
+## Attribution
+
+This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4,
+available at [http://contributor-covenant.org/version/1/4][version]
+
+[homepage]: http://contributor-covenant.org
+[version]: http://contributor-covenant.org/version/1/4/
\ No newline at end of file
diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md
new file mode 100644
index 0000000..1458dc6
--- /dev/null
+++ b/CONTRIBUTING.md
@@ -0,0 +1,61 @@
+# Contributing Guidelines
+
+This document contains information and guidelines about contributing to this project.
+Please read it before you start participating.
+
+**Topics**
+
+* [Reporting Issues](#reporting-issues)
+* [Submitting Pull Requests](#submitting-pull-requests)
+* [Developers Certificate of Origin](#developers-certificate-of-origin)
+* [Code of Conduct](#code-of-conduct)
+
+## Reporting Issues
+
+A great way to contribute to the project is to send a detailed issue when you encounter a problem. We always appreciate a well-written, thorough bug report.
+
+Check that the project issues database doesn't already include that problem or suggestion before submitting an issue. If you find a match, feel free to vote for the issue by adding a reaction. Doing this helps prioritize the most common problems and requests.
+
+When reporting issues, please fill out our issue template. The information the template asks for will help us review and fix your issue faster.
+
+## Submitting Pull Requests
+
+You can contribute by fixing bugs or adding new features. For larger code changes, we recommend first discussing your ideas on our [GitHub Discussions](https://github.com/space-code/builder-macro/discussions). When submitting a pull request, please add relevant tests and ensure your changes don't break any existing tests.
+
+## Developer's Certificate of Origin 1.1
+
+By making a contribution to this project, I certify that:
+
+- (a) The contribution was created in whole or in part by me and I
+ have the right to submit it under the open source license
+ indicated in the file; or
+
+- (b) The contribution is based upon previous work that, to the best
+ of my knowledge, is covered under an appropriate open source
+ license and I have the right under that license to submit that
+ work with modifications, whether created in whole or in part
+ by me, under the same open source license (unless I am
+ permitted to submit under a different license), as indicated
+ in the file; or
+
+- (c) The contribution was provided directly to me by some other
+ person who certified (a), (b) or (c) and I have not modified
+ it.
+
+- (d) I understand and agree that this project and the contribution
+ are public and that a record of the contribution (including all
+ personal information I submit with it, including my sign-off) is
+ maintained indefinitely and may be redistributed consistent with
+ this project or the open source license(s) involved.
+
+## Code of Conduct
+
+The Code of Conduct governs how we behave in public or in private
+whenever the project will be judged by our actions.
+We expect it to be honored by everyone who contributes to this project.
+
+See [CODE_OF_CONDUCT.md](https://github.com/space-code/builder-macro/blob/master/CODE_OF_CONDUCT.md) for details.
+
+---
+
+*Some of the ideas and wording for the statements above were based on work by the [Docker](https://github.com/docker/docker/blob/master/CONTRIBUTING.md) and [Linux](https://elinux.org/Developer_Certificate_Of_Origin) communities. We commend them for their efforts to facilitate collaboration in their projects.*
\ No newline at end of file
diff --git a/Dangerfile b/Dangerfile
new file mode 100644
index 0000000..b266982
--- /dev/null
+++ b/Dangerfile
@@ -0,0 +1 @@
+danger.import_dangerfile(github: 'space-code/dangerfile')
\ No newline at end of file
diff --git a/Gemfile b/Gemfile
new file mode 100644
index 0000000..20dff64
--- /dev/null
+++ b/Gemfile
@@ -0,0 +1,3 @@
+source "https://rubygems.org"
+
+gem 'danger'
\ No newline at end of file
diff --git a/Makefile b/Makefile
new file mode 100644
index 0000000..de1897f
--- /dev/null
+++ b/Makefile
@@ -0,0 +1,19 @@
+all: bootstrap
+
+bootstrap: hook
+ mint bootstrap
+
+hook:
+ ln -sf ../../hooks/pre-commit .git/hooks/pre-commit
+ chmod +x .git/hooks/pre-commit
+
+mint:
+ mint bootstrap
+
+lint:
+ mint run swiftlint
+
+fmt:
+ mint run swiftformat Sources Tests
+
+.PHONY: all bootstrap hook mint lint fmt
diff --git a/Mintfile b/Mintfile
new file mode 100644
index 0000000..e2cdefa
--- /dev/null
+++ b/Mintfile
@@ -0,0 +1,2 @@
+nicklockwood/SwiftFormat@0.52.7
+realm/SwiftLint@0.53.0
\ No newline at end of file
diff --git a/Package.resolved b/Package.resolved
new file mode 100644
index 0000000..1809cd0
--- /dev/null
+++ b/Package.resolved
@@ -0,0 +1,14 @@
+{
+ "pins" : [
+ {
+ "identity" : "swift-syntax",
+ "kind" : "remoteSourceControl",
+ "location" : "https://github.com/apple/swift-syntax.git",
+ "state" : {
+ "revision" : "2bc86522d115234d1f588efe2bcb4ce4be8f8b82",
+ "version" : "510.0.3"
+ }
+ }
+ ],
+ "version" : 2
+}
diff --git a/Package.swift b/Package.swift
new file mode 100644
index 0000000..ce36c63
--- /dev/null
+++ b/Package.swift
@@ -0,0 +1,50 @@
+// swift-tools-version: 5.9
+// The swift-tools-version declares the minimum version of Swift required to build this package.
+
+import CompilerPluginSupport
+import PackageDescription
+
+let package = Package(
+ name: "BuilderMacro",
+ platforms: [.macOS(.v10_15), .iOS(.v13), .tvOS(.v13), .watchOS(.v6), .macCatalyst(.v13)],
+ products: [
+ .library(
+ name: "BuilderMacro",
+ targets: ["BuilderMacro"]
+ ),
+ .executable(
+ name: "BuilderMacroClient",
+ targets: ["BuilderMacroClient"]
+ ),
+ ],
+ dependencies: [
+ .package(url: "https://github.com/apple/swift-syntax.git", from: "510.0.0"),
+ ],
+ 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: "BuilderMacroMacros",
+ 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: "BuilderMacro", dependencies: ["BuilderMacroMacros"]),
+
+ // A client of the library, which is able to use the macro in its own code.
+ .executableTarget(name: "BuilderMacroClient", dependencies: ["BuilderMacro"]),
+
+ // A test target used to develop the macro implementation.
+ .testTarget(
+ name: "BuilderMacroTests",
+ dependencies: [
+ "BuilderMacroMacros",
+ .product(name: "SwiftSyntaxMacrosTestSupport", package: "swift-syntax"),
+ ]
+ ),
+ ]
+)
diff --git a/README.md b/README.md
index 2d3e32f..9316dde 100644
--- a/README.md
+++ b/README.md
@@ -1 +1,167 @@
-# builder-macro
\ No newline at end of file
+
+
+
builder-macro
+
+
+
+
+
+
+
+
+
+
+
+## Description
+`builder-macro` is a macro that implements the Builder design pattern in Swift.
+
+- [Usage](#usage)
+- [Requirements](#requirements)
+- [Installation](#installation)
+- [Communication](#communication)
+- [Contributing](#contributing)
+- [Author](#author)
+- [License](#license)
+
+## Usage
+```swift
+import BuilderMacro
+
+@Builder
+struct Person {
+ let id: UUID
+ let name: String
+ let bday: Date
+}
+
+// Expanded
+
+struct Person {
+ let firstName: String, let lastName: String, let middleName: String
+ let birthday: Date
+ let city: String
+
+ struct PersonBuilder {
+ var firstName: String?
+ var lastName: String?
+ var middleName: String?
+ var birthday: Date?
+ var city: String?
+
+ func firstName(_ firstName: String) -> Self {
+ var copy = self
+ copy.firstName = firstName
+ return copy
+ }
+
+ func lastName(_ lastName: String) -> Self {
+ var copy = self
+ copy.lastName = lastName
+ return copy
+ }
+
+ func middleName(_ middleName: String) -> Self {
+ var copy = self
+ copy.middleName = middleName
+ return copy
+ }
+
+ func birthday(_ birthday: Date) -> Self {
+ var copy = self
+ copy.birthday = birthday
+ return copy
+ }
+
+ func city(_ city: String) -> Self {
+ var copy = self
+ copy.city = city
+ return copy
+ }
+
+ enum BuildError: Swift.Error {
+ case missingRequiredField(description: String)
+ }
+
+ func build() throws -> Person {
+ guard let firstName = firstName else {
+ throw BuildError.missingRequiredField(description: "firstName")
+ }
+
+ guard let lastName = lastName else {
+ throw BuildError.missingRequiredField(description: "lastName")
+ }
+
+ guard let middleName = middleName else {
+ throw BuildError.missingRequiredField(description: "middleName")
+ }
+
+ guard let birthday = birthday else {
+ throw BuildError.missingRequiredField(description: "birthday")
+ }
+
+ guard let city = city else {
+ throw BuildError.missingRequiredField(description: "city")
+ }
+
+ return Person(
+ firstName: firstName,
+ lastName: lastName,
+ middleName: middleName,
+ birthday: birthday,
+ city: city
+ )
+ }
+ }
+}
+```
+
+If a property type is optional, you can force validation of nil values by passing the addGuards parameter to the builder macro like this::
+
+```swift
+import BuilderMacro
+
+@Builder(addGuards: true)
+struct Person {
+ let id: UUID
+ let name: String
+ let bday: Date
+}
+```
+
+## Requirements
+- iOS 13.0+ / macOS 10.15+ / tvOS 13.0+ / watchOS 6.0+
+- Xcode 15.0
+- Swift 5.9
+
+## Installation
+### Swift Package Manager
+
+The [Swift Package Manager](https://swift.org/package-manager/) is a tool for automating the distribution of Swift code and is integrated into the `swift` compiler. It is in early development, but `builder-macro` does support its use on supported platforms.
+
+Once you have your Swift package set up, adding `builder-macro` as a dependency is as easy as adding it to the `dependencies` value of your `Package.swift`.
+
+```swift
+dependencies: [
+ .package(url: "https://github.com/space-code/builder-macro.git", .upToNextMajor(from: "1.0.0"))
+]
+```
+
+## Communication
+- If you **found a bug**, open an issue.
+- If you **have a feature request**, open an issue.
+- If you **want to contribute**, submit a pull request.
+
+## Contributing
+Bootstrapping development environment
+
+```
+make bootstrap
+```
+
+Please feel free to help out with this project! If you see something that could be made better or want a new feature, open up an issue or send a Pull Request!
+
+## Author
+Nikita Vasilev, nv3212@gmail.com
+
+## License
+builder-macro is available under the MIT license. See the LICENSE file for more info.
\ No newline at end of file
diff --git a/Resources/builder-macro.png b/Resources/builder-macro.png
new file mode 100644
index 0000000..dd8d9e7
Binary files /dev/null and b/Resources/builder-macro.png differ
diff --git a/SEQURITY.md b/SEQURITY.md
new file mode 100644
index 0000000..20dffca
--- /dev/null
+++ b/SEQURITY.md
@@ -0,0 +1,7 @@
+# Reporting Security Vulnerabilities
+
+This software is built with security and data privacy in mind to ensure your data is safe. We are grateful for security researchers and users reporting a vulnerability to us, first. To ensure that your request is handled in a timely manner and non-disclosure of vulnerabilities can be assured, please follow the below guideline.
+
+**Please do not report security vulnerabilities directly on GitHub. GitHub Issues can be publicly seen and therefore would result in a direct disclosure.**
+
+* Please address questions about data privacy, security concepts, and other media requests to the nv3212@gmail.com mailbox.
\ No newline at end of file
diff --git a/Sources/BuilderMacro/Classes/BuilderMacro.swift b/Sources/BuilderMacro/Classes/BuilderMacro.swift
new file mode 100644
index 0000000..c46f14d
--- /dev/null
+++ b/Sources/BuilderMacro/Classes/BuilderMacro.swift
@@ -0,0 +1,10 @@
+//
+// builder-macro
+// Copyright © 2024 Space Code. All rights reserved.
+//
+
+@attached(member, names: arbitrary)
+public macro Builder() = #externalMacro(
+ module: "BuilderMacroMacros",
+ type: "BuilderMacro"
+)
diff --git a/Sources/BuilderMacroClient/main.swift b/Sources/BuilderMacroClient/main.swift
new file mode 100644
index 0000000..a30d83d
--- /dev/null
+++ b/Sources/BuilderMacroClient/main.swift
@@ -0,0 +1,14 @@
+//
+// builder-macro
+// Copyright © 2024 Space Code. All rights reserved.
+//
+
+import BuilderMacro
+import Foundation
+
+@Builder
+struct Person {
+ let id: UUID
+ let name: String
+ let bday: Date
+}
diff --git a/Sources/BuilderMacroMacros/BuilderMacro.swift b/Sources/BuilderMacroMacros/BuilderMacro.swift
new file mode 100644
index 0000000..04f33e4
--- /dev/null
+++ b/Sources/BuilderMacroMacros/BuilderMacro.swift
@@ -0,0 +1,40 @@
+//
+// builder-macro
+// Copyright © 2024 Space Code. All rights reserved.
+//
+
+import SwiftCompilerPlugin
+import SwiftSyntax
+import SwiftSyntaxBuilder
+import SwiftSyntaxMacros
+
+// MARK: - BuilderMacro
+
+public struct BuilderMacro: MemberMacro {
+ enum Error: Swift.Error {
+ case wrongDeclarationSyntax
+ }
+
+ public static func expansion(
+ of attribute: AttributeSyntax,
+ providingMembersOf declaration: some DeclGroupSyntax,
+ in _: some MacroExpansionContext
+ ) throws -> [DeclSyntax] {
+ guard let structDecl = declaration.as(StructDeclSyntax.self) else {
+ throw Error.wrongDeclarationSyntax
+ }
+
+ return try Generator().generate(structDecl, attribute: attribute)
+ }
+}
+
+// MARK: - BuilderMacro.Error + CustomStringConvertible
+
+extension BuilderMacro.Error: CustomStringConvertible {
+ var description: String {
+ switch self {
+ case .wrongDeclarationSyntax:
+ return "Builder Macro supports only structs"
+ }
+ }
+}
diff --git a/Sources/BuilderMacroMacros/BuilderMacroPlugin.swift b/Sources/BuilderMacroMacros/BuilderMacroPlugin.swift
new file mode 100644
index 0000000..3a01144
--- /dev/null
+++ b/Sources/BuilderMacroMacros/BuilderMacroPlugin.swift
@@ -0,0 +1,18 @@
+//
+// builder-macro
+// Copyright © 2024 Space Code. All rights reserved.
+//
+
+import SwiftCompilerPlugin
+import SwiftSyntax
+import SwiftSyntaxBuilder
+import SwiftSyntaxMacros
+
+// MARK: - BuilderMacroPlugin
+
+@main
+struct BuilderMacroPlugin: CompilerPlugin {
+ let providingMacros: [Macro.Type] = [
+ BuilderMacro.self,
+ ]
+}
diff --git a/Sources/BuilderMacroMacros/Core/AttributeExtractor.swift b/Sources/BuilderMacroMacros/Core/AttributeExtractor.swift
new file mode 100644
index 0000000..4503ac4
--- /dev/null
+++ b/Sources/BuilderMacroMacros/Core/AttributeExtractor.swift
@@ -0,0 +1,25 @@
+//
+// builder-macro
+// Copyright © 2024 Space Code. All rights reserved.
+//
+
+import SwiftCompilerPlugin
+import SwiftSyntax
+import SwiftSyntaxBuilder
+import SwiftSyntaxMacros
+
+enum AttributeExtractor {
+ static func extractBool(from attribute: AttributeSyntax, id: String) -> Bool {
+ guard let argumentList = attribute.arguments?.as(LabeledExprListSyntax.self) else {
+ return false
+ }
+
+ for argument in argumentList {
+ if argument.label?.text == id, let boolExpr = argument.expression.as(BooleanLiteralExprSyntax.self) {
+ return boolExpr.literal.text == "true"
+ }
+ }
+
+ return false
+ }
+}
diff --git a/Sources/BuilderMacroMacros/Core/Generator.swift b/Sources/BuilderMacroMacros/Core/Generator.swift
new file mode 100644
index 0000000..f66586f
--- /dev/null
+++ b/Sources/BuilderMacroMacros/Core/Generator.swift
@@ -0,0 +1,147 @@
+//
+// builder-macro
+// Copyright © 2024 Space Code. All rights reserved.
+//
+
+import SwiftCompilerPlugin
+import SwiftSyntax
+import SwiftSyntaxBuilder
+import SwiftSyntaxMacros
+
+// MARK: - Generator
+
+struct Generator {
+ // MARK: Types
+
+ enum Error: Swift.Error {
+ case wrongDeclarationSyntax
+ }
+
+ // MARK: Internal
+
+ func generate(_ declaration: some DeclGroupSyntax, attribute: AttributeSyntax) throws -> [DeclSyntax] {
+ guard let structDecl = declaration.as(StructDeclSyntax.self) else {
+ throw Error.wrongDeclarationSyntax
+ }
+
+ let addGuards = AttributeExtractor.extractBool(from: attribute, id: .addGuards)
+
+ let name = structDecl.name.text
+
+ var items: [BodyItem?] = []
+
+ for member in structDecl.memberBlock.members {
+ if let variableDecl = member.decl.as(VariableDeclSyntax.self) {
+ for binding in variableDecl.bindings {
+ if let pattern = identifierPatternSyntax(from: binding.pattern),
+ let type = binding.typeAnnotation?.type
+ {
+ items.append(generate(pattern: pattern, type: type))
+ }
+ }
+ }
+ }
+
+ let builderStruct = generateBuilderMethod(from: items.compactMap { $0 }, name: name, addGuards: addGuards)
+ return [DeclSyntax(stringLiteral: builderStruct)]
+ }
+
+ // MARK: Private
+
+ private func identifierPatternSyntax(from patternSyntax: PatternSyntax) -> IdentifierPatternSyntax? {
+ if let valuePattern = patternSyntax.as(ValueBindingPatternSyntax.self) {
+ return valuePattern.pattern.as(IdentifierPatternSyntax.self)
+ } else {
+ return patternSyntax.as(IdentifierPatternSyntax.self)
+ }
+ }
+
+ private func generate(pattern: IdentifierPatternSyntax, type: TypeSyntax) -> BodyItem {
+ var builderProperty = ""
+ var requiredProperty: TypedVariable
+ var isOptional = false
+
+ let propertyName = pattern.identifier.text
+ let propertyType = type.description.trimmingCharacters(in: .whitespaces)
+
+ if type.as(OptionalTypeSyntax.self) != nil {
+ builderProperty = """
+ var \(propertyName): \(propertyType)
+ """
+ } else {
+ builderProperty = """
+ var \(propertyName): \(propertyType)?
+ """
+
+ isOptional = true
+ }
+
+ requiredProperty = TypedVariable(name: propertyName, type: .init(value: propertyType, isOptional: isOptional))
+
+ let builderMethod = """
+ func \(propertyName)(_ \(propertyName): \(propertyType)) -> Self {
+ var copy = self
+ copy.\(propertyName) = \(propertyName)
+ return copy
+ }
+ """
+
+ return BodyItem(
+ builderProperty: builderProperty,
+ builderMethod: builderMethod,
+ requiredProperty: requiredProperty
+ )
+ }
+
+ private func generateBuilderMethod(from bodyItems: [BodyItem], name: String, addGuards: Bool) -> String {
+ let builderName = "\(name)Builder"
+
+ let guardAssignment: String
+
+ if addGuards {
+ guardAssignment = (bodyItems.map(\.requiredProperty).guardAssignment)
+ } else {
+ guardAssignment = (bodyItems.map(\.requiredProperty).filter(\.type.isOptional).guardAssignment)
+ }
+
+ let buildMethod = """
+ enum BuildError: Swift.Error {
+ case missingRequiredField(description: String)
+ }
+
+ func build() throws -> \(name) {
+ \(guardAssignment)
+
+ return \(name)(
+ \(bodyItems.map(\.requiredProperty).initAssignments)
+ )
+ }
+ """
+
+ let builderStruct = """
+ struct \(builderName) {
+ \(bodyItems.map(\.builderProperty).joined(separator: "\n"))
+
+ \(bodyItems.map(\.builderMethod).joined(separator: "\n\n"))
+
+ \(buildMethod)
+ }
+ """
+
+ return builderStruct
+ }
+}
+
+// MARK: Generator.BodyItem
+
+private extension Generator {
+ struct BodyItem {
+ let builderProperty: String
+ let builderMethod: String
+ let requiredProperty: TypedVariable
+ }
+}
+
+private extension String {
+ static let addGuards = "addGuards"
+}
diff --git a/Sources/BuilderMacroMacros/Model/TypedVariable.swift b/Sources/BuilderMacroMacros/Model/TypedVariable.swift
new file mode 100644
index 0000000..7bd32ca
--- /dev/null
+++ b/Sources/BuilderMacroMacros/Model/TypedVariable.swift
@@ -0,0 +1,46 @@
+//
+// builder-macro
+// Copyright © 2024 Space Code. All rights reserved.
+//
+
+import Foundation
+
+// MARK: - TypedVariable
+
+struct TypedVariable {
+ let name: String
+ let type: VariableType
+
+ var initAssignment: String {
+ "\(name): \(name)"
+ }
+
+ var guardAssignment: String {
+ """
+ guard let \(name) = \(name) else {
+ throw BuildError.missingRequiredField(description: \"\(name)\")
+ }
+ """
+ }
+}
+
+extension [TypedVariable] {
+ var initAssignments: String {
+ map(\.initAssignment)
+ .joined(separator: ",\n")
+ }
+
+ var guardAssignment: String {
+ map(\.guardAssignment)
+ .joined(separator: "\n\n")
+ }
+}
+
+// MARK: - TypedVariable.VariableType
+
+extension TypedVariable {
+ struct VariableType {
+ let value: String
+ let isOptional: Bool
+ }
+}
diff --git a/Tests/BuilderMacroTests/BuilderMacroTests.swift b/Tests/BuilderMacroTests/BuilderMacroTests.swift
new file mode 100644
index 0000000..23e4e19
--- /dev/null
+++ b/Tests/BuilderMacroTests/BuilderMacroTests.swift
@@ -0,0 +1,287 @@
+//
+// builder-macro
+// Copyright © 2024 Space Code. All rights reserved.
+//
+
+import SwiftSyntax
+import SwiftSyntaxBuilder
+import SwiftSyntaxMacros
+import SwiftSyntaxMacrosTestSupport
+import XCTest
+
+#if canImport(BuilderMacroMacros)
+ import BuilderMacroMacros
+
+ let testMacros: [String: Macro.Type] = [
+ "Builder": BuilderMacro.self,
+ ]
+#endif
+
+// MARK: - BuilderMacroTests
+
+final class BuilderMacroTests: XCTestCase {
+ func testBuilderMacroOptionals() throws {
+ #if canImport(BuilderMacroMacros)
+ assertMacroExpansion("""
+ @Builder
+ struct Person {
+ let firstName: String, let lastName: String, let middleName: String
+ let birthday: Date?
+ let city: String?
+ }
+ """, expandedSource: """
+ struct Person {
+ let firstName: String, let lastName: String, let middleName: String
+ let birthday: Date?
+ let city: String?
+
+ struct PersonBuilder {
+ var firstName: String?
+ var lastName: String?
+ var middleName: String?
+ var birthday: Date?
+ var city: String?
+
+ func firstName(_ firstName: String) -> Self {
+ var copy = self
+ copy.firstName = firstName
+ return copy
+ }
+
+ func lastName(_ lastName: String) -> Self {
+ var copy = self
+ copy.lastName = lastName
+ return copy
+ }
+
+ func middleName(_ middleName: String) -> Self {
+ var copy = self
+ copy.middleName = middleName
+ return copy
+ }
+
+ func birthday(_ birthday: Date?) -> Self {
+ var copy = self
+ copy.birthday = birthday
+ return copy
+ }
+
+ func city(_ city: String?) -> Self {
+ var copy = self
+ copy.city = city
+ return copy
+ }
+
+ enum BuildError: Swift.Error {
+ case missingRequiredField(description: String)
+ }
+
+ func build() throws -> Person {
+ guard let firstName = firstName else {
+ throw BuildError.missingRequiredField(description: "firstName")
+ }
+
+ guard let lastName = lastName else {
+ throw BuildError.missingRequiredField(description: "lastName")
+ }
+
+ guard let middleName = middleName else {
+ throw BuildError.missingRequiredField(description: "middleName")
+ }
+
+ return Person(
+ firstName: firstName,
+ lastName: lastName,
+ middleName: middleName,
+ birthday: birthday,
+ city: city
+ )
+ }
+ }
+ }
+ """, macros: testMacros)
+ #endif
+ }
+
+ func testBuilderMacro() throws {
+ #if canImport(BuilderMacroMacros)
+ assertMacroExpansion("""
+ @Builder
+ struct Person {
+ let firstName: String, let lastName: String, let middleName: String
+ let birthday: Date
+ let city: String
+ }
+ """, expandedSource: """
+ struct Person {
+ let firstName: String, let lastName: String, let middleName: String
+ let birthday: Date
+ let city: String
+
+ struct PersonBuilder {
+ var firstName: String?
+ var lastName: String?
+ var middleName: String?
+ var birthday: Date?
+ var city: String?
+
+ func firstName(_ firstName: String) -> Self {
+ var copy = self
+ copy.firstName = firstName
+ return copy
+ }
+
+ func lastName(_ lastName: String) -> Self {
+ var copy = self
+ copy.lastName = lastName
+ return copy
+ }
+
+ func middleName(_ middleName: String) -> Self {
+ var copy = self
+ copy.middleName = middleName
+ return copy
+ }
+
+ func birthday(_ birthday: Date) -> Self {
+ var copy = self
+ copy.birthday = birthday
+ return copy
+ }
+
+ func city(_ city: String) -> Self {
+ var copy = self
+ copy.city = city
+ return copy
+ }
+
+ enum BuildError: Swift.Error {
+ case missingRequiredField(description: String)
+ }
+
+ func build() throws -> Person {
+ guard let firstName = firstName else {
+ throw BuildError.missingRequiredField(description: "firstName")
+ }
+
+ guard let lastName = lastName else {
+ throw BuildError.missingRequiredField(description: "lastName")
+ }
+
+ guard let middleName = middleName else {
+ throw BuildError.missingRequiredField(description: "middleName")
+ }
+
+ guard let birthday = birthday else {
+ throw BuildError.missingRequiredField(description: "birthday")
+ }
+
+ guard let city = city else {
+ throw BuildError.missingRequiredField(description: "city")
+ }
+
+ return Person(
+ firstName: firstName,
+ lastName: lastName,
+ middleName: middleName,
+ birthday: birthday,
+ city: city
+ )
+ }
+ }
+ }
+ """, macros: testMacros)
+ #endif
+ }
+
+ func testBuilderMacroIgnore() throws {
+ #if canImport(BuilderMacroMacros)
+ assertMacroExpansion("""
+ @Builder(addGuards: true)
+ struct Person {
+ let firstName: String, let lastName: String, let middleName: String
+ let birthday: Date
+ let city: String
+ }
+ """, expandedSource: """
+ struct Person {
+ let firstName: String, let lastName: String, let middleName: String
+ let birthday: Date
+ let city: String
+
+ struct PersonBuilder {
+ var firstName: String?
+ var lastName: String?
+ var middleName: String?
+ var birthday: Date?
+ var city: String?
+
+ func firstName(_ firstName: String) -> Self {
+ var copy = self
+ copy.firstName = firstName
+ return copy
+ }
+
+ func lastName(_ lastName: String) -> Self {
+ var copy = self
+ copy.lastName = lastName
+ return copy
+ }
+
+ func middleName(_ middleName: String) -> Self {
+ var copy = self
+ copy.middleName = middleName
+ return copy
+ }
+
+ func birthday(_ birthday: Date) -> Self {
+ var copy = self
+ copy.birthday = birthday
+ return copy
+ }
+
+ func city(_ city: String) -> Self {
+ var copy = self
+ copy.city = city
+ return copy
+ }
+
+ enum BuildError: Swift.Error {
+ case missingRequiredField(description: String)
+ }
+
+ func build() throws -> Person {
+ guard let firstName = firstName else {
+ throw BuildError.missingRequiredField(description: "firstName")
+ }
+
+ guard let lastName = lastName else {
+ throw BuildError.missingRequiredField(description: "lastName")
+ }
+
+ guard let middleName = middleName else {
+ throw BuildError.missingRequiredField(description: "middleName")
+ }
+
+ guard let birthday = birthday else {
+ throw BuildError.missingRequiredField(description: "birthday")
+ }
+
+ guard let city = city else {
+ throw BuildError.missingRequiredField(description: "city")
+ }
+
+ return Person(
+ firstName: firstName,
+ lastName: lastName,
+ middleName: middleName,
+ birthday: birthday,
+ city: city
+ )
+ }
+ }
+ }
+ """, macros: testMacros)
+ #endif
+ }
+}
diff --git a/codecov.yml b/codecov.yml
new file mode 100644
index 0000000..8bb858a
--- /dev/null
+++ b/codecov.yml
@@ -0,0 +1,48 @@
+codecov:
+ # Require CI to pass to show coverage, default yes
+ require_ci_to_pass: yes
+ notify:
+ # Codecov should wait for all CI statuses to complete, default yes
+ wait_for_ci: yes
+
+coverage:
+ # Coverage precision range 0-5, default 2
+ precision: 2
+
+ # Direction to round the coverage value - up, down, nearest, default down
+ round: nearest
+
+ # Value range for red...green, default 70...100
+ range: "70...90"
+
+ status:
+ # Overall project coverage, compare against pull request base
+ project:
+ default:
+ # The required coverage value
+ target: 50%
+
+ # The leniency in hitting the target. Allow coverage to drop by X%
+ threshold: 5%
+
+ # Only measure lines adjusted in the pull request or single commit, if the commit in not in the pr
+ patch:
+ default:
+ # The required coverage value
+ target: 85%
+
+ # Allow coverage to drop by X%
+ threshold: 50%
+ changes: no
+
+comment:
+ # Pull request Codecov comment format.
+ # diff: coverage diff of the pull request
+ # files: a list of files impacted by the pull request (coverage changes, file is new or removed)
+ layout: "diff, files"
+
+ # Update Codecov comment, if exists. Otherwise post new
+ behavior: default
+
+ # If true, only post the Codecov comment if coverage changes
+ require_changes: false
\ No newline at end of file
diff --git a/hooks/pre-commit b/hooks/pre-commit
new file mode 100755
index 0000000..956fdcb
--- /dev/null
+++ b/hooks/pre-commit
@@ -0,0 +1,38 @@
+#!/bin/bash
+git diff --diff-filter=d --staged --name-only | grep -e '\.swift$' | while read line; do
+ if [[ $line == *"/Generated"* ]]; then
+ echo "IGNORING GENERATED FILE: " "$line";
+ else
+ mint run swiftformat swiftformat "${line}";
+ git add "$line";
+ fi
+done
+
+LINT=$(which mint)
+if [[ -e "${LINT}" ]]; then
+ # Export files in SCRIPT_INPUT_FILE_$count to lint against later
+ count=0
+ while IFS= read -r file_path; do
+ export SCRIPT_INPUT_FILE_$count="$file_path"
+ count=$((count + 1))
+ done < <(git diff --name-only --cached --diff-filter=d | grep ".swift$")
+ export SCRIPT_INPUT_FILE_COUNT=$count
+
+ if [ "$count" -eq 0 ]; then
+ echo "No files to lint!"
+ exit 0
+ fi
+
+ echo "Found $count lintable files! Linting now.."
+ mint run swiftlint --use-script-input-files --strict --config .swiftlint.yml
+ RESULT=$? # swiftline exit value is number of errors
+
+ if [ $RESULT -eq 0 ]; then
+ echo "🎉 Well done. No violation."
+ fi
+ exit $RESULT
+else
+ echo "⚠️ WARNING: SwiftLint not found"
+ echo "⚠️ You might want to edit .git/hooks/pre-commit to locate your swiftlint"
+ exit 0
+fi
\ No newline at end of file