diff --git a/.github/workflows/pull-request.yml b/.github/workflows/pull-request.yml index 5a877bb..da19e5e 100644 --- a/.github/workflows/pull-request.yml +++ b/.github/workflows/pull-request.yml @@ -12,11 +12,11 @@ concurrency: cancel-in-progress: true env: - XCODE_VERSION: "16.3" + XCODE_VERSION: "26.1" jobs: prepare: - runs-on: macos-15 + runs-on: macos-26 outputs: platforms: ${{ steps.platforms.outputs.platforms }} scheme: ${{ steps.scheme.outputs.scheme }} @@ -62,7 +62,7 @@ jobs: build-and-test: needs: prepare - runs-on: macos-15 + runs-on: macos-26 strategy: fail-fast: false matrix: @@ -81,16 +81,16 @@ jobs: destination="platform=macOS,variant=Mac Catalyst" ;; ios) - destination="platform=iOS Simulator,name=iPhone 16 Pro Max,OS=latest" + destination="platform=iOS Simulator,name=iPhone 17 Pro Max,OS=26.1" ;; tvos) - destination="platform=tvOS Simulator,name=Apple TV 4K (3rd generation),OS=latest" + destination="platform=tvOS Simulator,name=Apple TV 4K (3rd generation),OS=26.1" ;; watchos) - destination="platform=watchOS Simulator,name=Apple Watch Series 10 (46mm),OS=latest" + destination="platform=watchOS Simulator,name=Apple Watch Series 11 (46mm),OS=26.1" ;; visionos) - destination="platform=visionOS Simulator,name=Apple Vision Pro,OS=latest" + destination="platform=visionOS Simulator,name=Apple Vision Pro,OS=26.1" ;; *) echo "Unknown platform: ${{ matrix.platform }}" @@ -136,4 +136,4 @@ jobs: if: ${{ matrix.platform == 'macos' }} uses: codecov/codecov-action@v5 with: - fail_ci_if_error: true \ No newline at end of file + fail_ci_if_error: true diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index d109093..5431cec 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -6,11 +6,11 @@ on: - '*' env: - XCODE_VERSION: "16.3" + XCODE_VERSION: "26.1" jobs: release: - runs-on: macos-15 + runs-on: macos-26 steps: - uses: actions/checkout@v4 with: diff --git a/.mise.toml b/.mise.toml index 513f2cf..7811bca 100644 --- a/.mise.toml +++ b/.mise.toml @@ -5,8 +5,8 @@ swiftlint = '~/.local/bin/mise x -- swiftlint' swiftformat = '~/.local/bin/mise x -- swiftformat' [tools] -swiftlint = "0.58.2" -swiftformat = "0.55.5" +swiftlint = "0.62.2" +swiftformat = "0.58.5" [tasks.lint] description = 'Run all linters' diff --git a/.swift-version b/.swift-version index e8f1734..913671c 100644 --- a/.swift-version +++ b/.swift-version @@ -1 +1 @@ -6.1 \ No newline at end of file +6.2 \ No newline at end of file diff --git a/.swiftformat b/.swiftformat index 25c4fbb..85fe404 100644 --- a/.swiftformat +++ b/.swiftformat @@ -1,106 +1,125 @@ --acronyms ID,URL,UUID --allman false ---anonymousforeach convert ---assetliterals visual-width ---asynccapturing ---beforemarks ---binarygrouping 4,8 ---callsiteparen default ---categorymark "MARK: %c" ---classthreshold 0 ---closingparen default ---closurevoid remove ---commas inline ---complexattrs prev-line ---computedvarattrs prev-line ---condassignment always ---conflictmarkers reject ---dateformat system ---decimalgrouping 3,6 ---doccomments before-declarations ---elseposition same-line ---emptybraces no-space ---enumnamespaces always ---enumthreshold 0 ---exponentcase lowercase ---exponentgrouping disabled ---extensionacl on-declarations ---extensionlength 0 ---extensionmark "MARK: - %t + %c" ---fractiongrouping disabled +--allow-partial-wrapping true +--anonymous-for-each convert +--asset-literals visual-width +--async-capturing +--before-marks +--binary-grouping 4,8 +--blank-line-after-switch-case multiline-only +--call-site-paren default +--category-mark "MARK: %c" +--class-threshold 0 +--closing-paren default +--closure-void remove +--complex-attributes prev-line +--computed-var-attributes prev-line +--conditional-assignment always +--conflict-markers reject +--date-format system +--decimal-grouping 3,6 +--default-test-suite-attributes +--doc-comments before-declarations +--else-position same-line +--empty-braces no-space +--enum-namespaces always +--enum-threshold 0 +--equatable-macro none +--exponent-case lowercase +--exponent-grouping disabled +--extension-acl on-declarations +--extension-mark "MARK: - %t + %c" +--extension-threshold 0 +--file-macro "#file" +--fraction-grouping disabled --fragment false ---funcattributes prev-line ---generictypes ---groupblanklines true ---groupedextension "MARK: %c" ---guardelse next-line +--func-attributes prev-line +--generic-types +--group-blank-lines true +--grouped-extension "MARK: %c" +--guard-else next-line --header ignore ---hexgrouping 4,8 ---hexliteralcase uppercase +--hex-grouping 4,8 +--hex-literal-case uppercase --ifdef indent ---importgrouping testable-first +--import-grouping testable-first --indent 4 ---indentcase false ---indentstrings false ---inferredtypes always ---initcodernil false ---inlinedforeach ignore +--indent-case false +--indent-strings false +--inferred-types always +--init-coder-nil false --lifecycle ---lineaftermarks true +--line-after-marks true +--line-between-guards false --linebreaks lf ---markcategories true ---markextensions always ---marktypes always ---maxwidth none ---modifierorder ---nevertrailing ---nilinit remove ---noncomplexattrs ---nospaceoperators ---nowrapoperators ---octalgrouping 4,8 ---operatorfunc spaced ---organizationmode visibility ---organizetypes actor,class,enum,struct ---patternlet hoist ---preservedecls ---preservedsymbols Package ---propertytypes inferred +--mark-categories true +--mark-class-threshold 0 +--mark-enum-threshold 0 +--mark-extension-threshold 0 +--mark-extensions always +--mark-struct-threshold 0 +--mark-types always +--markdown-files ignore +--max-width none +--modifier-order +--never-trailing +--nil-init remove +--no-space-operators +--no-wrap-operators +--non-complex-attributes +--octal-grouping 4,8 +--operator-func spaced +--organization-mode visibility +--organize-types actor,class,enum,struct +--pattern-let hoist +--preserve-acronyms +--preserve-decls +--preserved-property-types Package +--property-types inferred --ranges spaced +--redundant-async always +--redundant-throws always --self init-only ---selfrequired ---semicolons inline ---shortoptionals always ---smarttabs enabled ---someany true ---sortedpatterns ---storedvarattrs prev-line ---stripunusedargs always ---structthreshold 0 ---tabwidth unspecified ---throwcapturing +--self-required +--semicolons inline-only +--short-optionals always +--single-line-for-each ignore +--smart-tabs enabled +--some-any true +--sort-swiftui-properties none +--sorted-patterns +--stored-var-attributes prev-line +--strip-unused-args always +--struct-threshold 0 +--tab-width unspecified +--throw-capturing --timezone system ---trailingclosures ---trimwhitespace always ---typeattributes prev-line ---typeblanklines preserve ---typedelimiter space-after ---typemark "MARK: - %t" ---typemarks ---typeorder ---visibilitymarks ---visibilityorder ---voidtype void ---wraparguments before-first ---wrapcollections before-first ---wrapconditions after-first ---wrapeffects preserve ---wrapenumcases always ---wrapparameters before-first ---wrapreturntype preserve ---wrapternary before-operators ---wraptypealiases after-first ---xcodeindentation enabled ---yodaswap always ---disable enumNamespaces,fileHeader,headerFileName,redundantInternal,wrap,wrapMultilineStatementBraces,wrapSingleLineComments ---enable acronyms,blankLinesBetweenImports,blockComments,docComments,isEmpty,propertyTypes,redundantProperty,sortSwitchCases,unusedPrivateDeclarations,wrapConditionalBodies,wrapEnumCases +--trailing-closures +--trailing-commas never +--trim-whitespace always +--type-attributes prev-line +--type-blank-lines preserve +--type-body-marks preserve +--type-delimiter space-after +--type-mark "MARK: - %t" +--type-marks +--type-order +--url-macro none +--visibility-marks +--visibility-order +--void-type Void +--wrap-arguments before-first +--wrap-collections before-first +--wrap-conditions after-first +--wrap-effects preserve +--wrap-enum-cases always +--wrap-parameters before-first +--wrap-return-type preserve +--wrap-string-interpolation default +--wrap-ternary before-operators +--wrap-type-aliases after-first +--xcode-indentation enabled +--xctest-symbols +--yoda-swap always +--disable fileHeader,headerFileName,redundantInternal,wrap,wrapMultilineStatementBraces,wrapSingleLineComments +--enable acronyms,blankLinesBetweenImports,blockComments,docComments,emptyExtensions,environmentEntry,isEmpty,noForceTryInTests,noForceUnwrapInTests,noGuardInTests,propertyTypes,redundantAsync,redundantMemberwiseInit,redundantProperty,redundantThrows,singlePropertyPerLine,sortSwitchCases,unusedPrivateDeclarations,wrapConditionalBodies,wrapEnumCases,wrapMultilineFunctionChains diff --git a/.swiftlint.yml b/.swiftlint.yml index 7724a67..865f9ac 100644 --- a/.swiftlint.yml +++ b/.swiftlint.yml @@ -7,7 +7,7 @@ opt_in_rules: - accessibility_trait_for_button - anonymous_argument_in_multiline_closure - array_init - # async_without_await - not recognized + - async_without_await # attributes # balanced_xctest_lifecycle # closure_body_length @@ -21,7 +21,7 @@ opt_in_rules: - contains_over_first_not_nil - contains_over_range_nil_comparison # contrasted_opening_brace - # convenience_type - not working with Testing framework + # convenience_type - direct_return - discarded_notification_center_observer - discouraged_assert @@ -56,6 +56,7 @@ opt_in_rules: - identical_operands - implicit_return # implicitly_unwrapped_optional + # incompatible_concurrency_annotation # indentation_width - joined_default_parameter - last_where @@ -66,7 +67,7 @@ opt_in_rules: - local_doc_comment - lower_acl_than_parent # missing_docs - # modifier_order + - modifier_order - multiline_arguments - multiline_arguments_brackets - multiline_function_chains @@ -90,6 +91,8 @@ opt_in_rules: - override_in_extension - pattern_matching_keywords - period_spacing + - prefer_asset_symbols + - prefer_condition_list - prefer_key_path # prefer_nimble - prefer_self_in_static_references @@ -123,7 +126,7 @@ opt_in_rules: - static_operator # strict_fileprivate - strong_iboutlet - - superfluous_else + # superfluous_else - switch_case_on_newline - test_case_accessibility - toggle_bool @@ -164,7 +167,7 @@ file_length: warning: 500 identifier_name: - excluded: [id, x, y, z] + excluded: [id, ui, x, y, z, dx, dy, dz] line_length: ignores_comments: true @@ -172,6 +175,9 @@ line_length: nesting: type_level: 2 +no_magic_numbers: + allowed_numbers: [0.0, 1.0, 2.0, 100.0] + type_name: allowed_symbols: ["_"] max_length: 50 @@ -183,11 +189,11 @@ custom_rules: global_actor_attribute_order: name: "Global actor attribute order" message: "Global actor should be the first attribute." - regex: "(?-s)(@.+[^,\\s]\\s+@.*Actor)" + regex: "(?-s)(@.+[^,\\s]\\s+@.*Actor\\s)" sendable_attribute_order: name: "Sendable attribute order" message: "Sendable should be the first attribute." - regex: "(?-s)(@.+[^,\\s]\\s+@Sendable)" + regex: "(?-s)(@.+[^,\\s]\\s+@Sendable\\s)" autoclosure_attribute_order: name: "Autoclosure attribute order" message: "Autoclosure should be the last attribute." diff --git a/Package.resolved b/Package.resolved index a508748..2d95f47 100644 --- a/Package.resolved +++ b/Package.resolved @@ -1,13 +1,13 @@ { - "originHash" : "f7222f8d74cbdc2dcf2a590f0c4dec1650949abd03d2a9a80da83fa4e4950ccb", + "originHash" : "c8b1d213d5994fb29d9aa9f375cbcecbb5eef9ce380a6acd772a74a80d6f143b", "pins" : [ { "identity" : "swift-syntax", "kind" : "remoteSourceControl", "location" : "https://github.com/swiftlang/swift-syntax", "state" : { - "revision" : "f99ae8aa18f0cf0d53481901f88a0991dc3bd4a2", - "version" : "601.0.1" + "revision" : "4799286537280063c85a32f09884cfbca301b1a1", + "version" : "602.0.0" } } ], diff --git a/Package.swift b/Package.swift index fd2ada5..6eae19f 100644 --- a/Package.swift +++ b/Package.swift @@ -1,4 +1,4 @@ -// swift-tools-version: 6.0 +// swift-tools-version: 6.2 // The swift-tools-version declares the minimum version of Swift required to build this package. import PackageDescription @@ -17,12 +17,20 @@ let package = Package( .library( name: "PrincipleMacros", targets: ["PrincipleMacros"] + ), + .library( + name: "PrincipleMacrosTestSupport", + targets: ["PrincipleMacrosTestSupport"] + ), + .library( + name: "PrincipleMacrosClientSupport", + targets: ["PrincipleMacrosClientSupport"] ) ], dependencies: [ .package( url: "https://github.com/swiftlang/swift-syntax", - "600.0.0" ..< "604.0.0" + "602.0.0" ..< "603.0.0" ) ], targets: [ @@ -35,6 +43,19 @@ let package = Package( ) ] ), + .target( + name: "PrincipleMacrosTestSupport", + dependencies: [ + "PrincipleMacros", + .product( + name: "SwiftSyntaxMacrosTestSupport", + package: "swift-syntax" + ) + ] + ), + .target( + name: "PrincipleMacrosClientSupport" + ), .testTarget( name: "PrincipleMacrosTests", dependencies: [ @@ -51,6 +72,8 @@ let package = Package( for target in package.targets { target.swiftSettings = (target.swiftSettings ?? []) + [ .swiftLanguageMode(.v6), - .enableUpcomingFeature("ExistentialAny") + .enableUpcomingFeature("ExistentialAny"), + .enableUpcomingFeature("MemberImportVisibility"), + .enableUpcomingFeature("NonisolatedNonsendingByDefault") ] } diff --git a/Sources/PrincipleMacros/Builders/Declarations/Common/DeclBuilder.swift b/Sources/PrincipleMacros/Builders/Declarations/Common/DeclBuilder.swift index a836cf0..fc0dda1 100644 --- a/Sources/PrincipleMacros/Builders/Declarations/Common/DeclBuilder.swift +++ b/Sources/PrincipleMacros/Builders/Declarations/Common/DeclBuilder.swift @@ -6,40 +6,35 @@ // Copyright © 2025 Kamil Strzelecki. All rights reserved. // -import SwiftSyntax +import SwiftSyntaxMacros public protocol DeclBuilder { var basicDeclaration: any BasicDeclSyntax { get } - var settings: DeclBuilderSettings { get } + var lexicalContext: [Syntax] { get } + + var preferredGlobalActorIsolation: GlobalActorIsolation? { get } + var preferredAccessControlLevel: AccessControlLevel? { get } + var maxAllowedAccessControlLevel: AccessControlLevel { get } func build() throws -> [DeclSyntax] } extension DeclBuilder { - public var inheritedAccessControlLevel: TokenSyntax? { - let settings = settings.accessControlLevel - return basicDeclaration.inlinableAccessControlLevel( - inheritedBy: settings.inheritingDeclaration, - maxAllowed: settings.maxAllowed - ) + public var lexicalContext: [Syntax] { + [] } -} -extension DeclBuilder { + public var preferredGlobalActorIsolation: GlobalActorIsolation? { + nil + } - public var inheritedGlobalActorIsolation: GlobalActorIsolation? { - if let explicit = settings.explicitGlobalActorIsolation { - return explicit - } - if let inherited = basicDeclaration.globalActor?.attributeName { - return .isolated(trimmedType: inherited.trimmed) - } - return .nonisolated + public var preferredAccessControlLevel: AccessControlLevel? { + nil } - public var inheritedGlobalActorAttribute: AttributeSyntax? { - inheritedGlobalActorIsolation?.inlinableAttribute + public var maxAllowedAccessControlLevel: AccessControlLevel { + .public } } diff --git a/Sources/PrincipleMacros/Builders/Declarations/Common/DeclBuilderSettings.swift b/Sources/PrincipleMacros/Builders/Declarations/Common/DeclBuilderSettings.swift deleted file mode 100644 index bc48e4a..0000000 --- a/Sources/PrincipleMacros/Builders/Declarations/Common/DeclBuilderSettings.swift +++ /dev/null @@ -1,40 +0,0 @@ -// -// DeclBuilderSettings.swift -// PrincipleMacros -// -// Created by Kamil Strzelecki on 26/01/2025. -// Copyright © 2025 Kamil Strzelecki. All rights reserved. -// - -import SwiftSyntax - -public struct DeclBuilderSettings { - - public var accessControlLevel: AccessControlLevel - public var explicitGlobalActorIsolation: GlobalActorIsolation? - - public init( - accessControlLevel: AccessControlLevel, - explicitGlobalActorIsolation: GlobalActorIsolation? = nil - ) { - self.accessControlLevel = accessControlLevel - self.explicitGlobalActorIsolation = explicitGlobalActorIsolation - } -} - -extension DeclBuilderSettings { - - public struct AccessControlLevel { - - public var inheritingDeclaration: InheritingDeclaration - public var maxAllowed: Keyword - - public init( - inheritingDeclaration: InheritingDeclaration, - maxAllowed: Keyword = .public - ) { - self.inheritingDeclaration = inheritingDeclaration - self.maxAllowed = maxAllowed - } - } -} diff --git a/Sources/PrincipleMacros/Builders/Declarations/Common/MemberBuilding.swift b/Sources/PrincipleMacros/Builders/Declarations/Common/MemberBuilding.swift new file mode 100644 index 0000000..2eae3db --- /dev/null +++ b/Sources/PrincipleMacros/Builders/Declarations/Common/MemberBuilding.swift @@ -0,0 +1,20 @@ +// +// MemberBuilding.swift +// PrincipleMacros +// +// Created by Kamil Strzelecki on 13/11/2025. +// Copyright © 2025 Kamil Strzelecki. All rights reserved. +// + +public protocol MemberBuilding {} + +extension MemberBuilding where Self: TypeDeclBuilder { + + public var inheritedAccessControlLevel: AccessControlLevel? { + .forMember( + of: typeDeclaration, + preferred: preferredAccessControlLevel, + maxAllowed: maxAllowedAccessControlLevel + ) + } +} diff --git a/Sources/PrincipleMacros/Builders/Declarations/Common/PeerBuilding.swift b/Sources/PrincipleMacros/Builders/Declarations/Common/PeerBuilding.swift new file mode 100644 index 0000000..17d6ece --- /dev/null +++ b/Sources/PrincipleMacros/Builders/Declarations/Common/PeerBuilding.swift @@ -0,0 +1,20 @@ +// +// PeerBuilding.swift +// PrincipleMacros +// +// Created by Kamil Strzelecki on 13/11/2025. +// Copyright © 2025 Kamil Strzelecki. All rights reserved. +// + +public protocol PeerBuilding {} + +extension PeerBuilding where Self: DeclBuilder { + + public var inheritedAccessControlLevel: AccessControlLevel? { + .forPeer( + of: basicDeclaration, + preferred: preferredAccessControlLevel, + maxAllowed: maxAllowedAccessControlLevel + ) + } +} diff --git a/Sources/PrincipleMacros/Builders/Declarations/Members/FunctionDeclBuilder.swift b/Sources/PrincipleMacros/Builders/Declarations/Members/FunctionDeclBuilder.swift new file mode 100644 index 0000000..a278df2 --- /dev/null +++ b/Sources/PrincipleMacros/Builders/Declarations/Members/FunctionDeclBuilder.swift @@ -0,0 +1,21 @@ +// +// FunctionDeclBuilder.swift +// PrincipleMacros +// +// Created by Kamil Strzelecki on 13/11/2025. +// Copyright © 2025 Kamil Strzelecki. All rights reserved. +// + +import SwiftSyntaxMacros + +public protocol FunctionDeclBuilder: MemberDeclBuilder { + + var declaration: FunctionDeclSyntax { get } +} + +extension FunctionDeclBuilder { + + public var basicDeclaration: any BasicDeclSyntax { + declaration + } +} diff --git a/Sources/PrincipleMacros/Builders/Declarations/Members/MemberDeclBuilder.swift b/Sources/PrincipleMacros/Builders/Declarations/Members/MemberDeclBuilder.swift index a14c26e..afa299a 100644 --- a/Sources/PrincipleMacros/Builders/Declarations/Members/MemberDeclBuilder.swift +++ b/Sources/PrincipleMacros/Builders/Declarations/Members/MemberDeclBuilder.swift @@ -6,6 +6,17 @@ // Copyright © 2025 Kamil Strzelecki. All rights reserved. // -import SwiftSyntax +import SwiftSyntaxMacros public protocol MemberDeclBuilder: DeclBuilder {} + +extension MemberDeclBuilder { + + public var inheritedGlobalActorIsolation: GlobalActorIsolation? { + .resolved( + for: basicDeclaration, + in: lexicalContext, + preferred: preferredGlobalActorIsolation + ) + } +} diff --git a/Sources/PrincipleMacros/Builders/Declarations/Members/PropertyDeclBuilder.swift b/Sources/PrincipleMacros/Builders/Declarations/Members/PropertyDeclBuilder.swift index 0db4a28..d6287ef 100644 --- a/Sources/PrincipleMacros/Builders/Declarations/Members/PropertyDeclBuilder.swift +++ b/Sources/PrincipleMacros/Builders/Declarations/Members/PropertyDeclBuilder.swift @@ -6,16 +6,16 @@ // Copyright © 2025 Kamil Strzelecki. All rights reserved. // -import SwiftSyntax +import SwiftSyntaxMacros public protocol PropertyDeclBuilder: MemberDeclBuilder { - var property: Property { get } + var declaration: Property { get } } extension PropertyDeclBuilder { public var basicDeclaration: any BasicDeclSyntax { - property.declaration + declaration.underlying } } diff --git a/Sources/PrincipleMacros/Builders/Declarations/Types/ActorDeclBuilder.swift b/Sources/PrincipleMacros/Builders/Declarations/Types/ActorDeclBuilder.swift index b0eed62..d50b880 100644 --- a/Sources/PrincipleMacros/Builders/Declarations/Types/ActorDeclBuilder.swift +++ b/Sources/PrincipleMacros/Builders/Declarations/Types/ActorDeclBuilder.swift @@ -6,7 +6,7 @@ // Copyright © 2025 Kamil Strzelecki. All rights reserved. // -import SwiftSyntax +import SwiftSyntaxMacros public protocol ActorDeclBuilder: TypeDeclBuilder { diff --git a/Sources/PrincipleMacros/Builders/Declarations/Types/ClassDeclBuilder.swift b/Sources/PrincipleMacros/Builders/Declarations/Types/ClassDeclBuilder.swift index 627bd2b..e45daf4 100644 --- a/Sources/PrincipleMacros/Builders/Declarations/Types/ClassDeclBuilder.swift +++ b/Sources/PrincipleMacros/Builders/Declarations/Types/ClassDeclBuilder.swift @@ -6,7 +6,7 @@ // Copyright © 2025 Kamil Strzelecki. All rights reserved. // -import SwiftSyntax +import SwiftSyntaxMacros public protocol ClassDeclBuilder: TypeDeclBuilder { diff --git a/Sources/PrincipleMacros/Builders/Declarations/Types/EnumDeclBuilder.swift b/Sources/PrincipleMacros/Builders/Declarations/Types/EnumDeclBuilder.swift index 99de26a..0951186 100644 --- a/Sources/PrincipleMacros/Builders/Declarations/Types/EnumDeclBuilder.swift +++ b/Sources/PrincipleMacros/Builders/Declarations/Types/EnumDeclBuilder.swift @@ -6,7 +6,7 @@ // Copyright © 2025 Kamil Strzelecki. All rights reserved. // -import SwiftSyntax +import SwiftSyntaxMacros public protocol EnumDeclBuilder: TypeDeclBuilder { diff --git a/Sources/PrincipleMacros/Builders/Declarations/Types/StatefulDeclBuilder.swift b/Sources/PrincipleMacros/Builders/Declarations/Types/StatefulDeclBuilder.swift index cbb744b..4d378cc 100644 --- a/Sources/PrincipleMacros/Builders/Declarations/Types/StatefulDeclBuilder.swift +++ b/Sources/PrincipleMacros/Builders/Declarations/Types/StatefulDeclBuilder.swift @@ -6,7 +6,7 @@ // Copyright © 2025 Kamil Strzelecki. All rights reserved. // -import SwiftSyntax +import SwiftSyntaxMacros public protocol StatefulDeclBuilder: TypeDeclBuilder { diff --git a/Sources/PrincipleMacros/Builders/Declarations/Types/StructDeclBuilder.swift b/Sources/PrincipleMacros/Builders/Declarations/Types/StructDeclBuilder.swift index ab93d72..a209688 100644 --- a/Sources/PrincipleMacros/Builders/Declarations/Types/StructDeclBuilder.swift +++ b/Sources/PrincipleMacros/Builders/Declarations/Types/StructDeclBuilder.swift @@ -6,7 +6,7 @@ // Copyright © 2025 Kamil Strzelecki. All rights reserved. // -import SwiftSyntax +import SwiftSyntaxMacros public protocol StructDeclBuilder: TypeDeclBuilder { diff --git a/Sources/PrincipleMacros/Builders/Declarations/Types/TypeDeclBuilder.swift b/Sources/PrincipleMacros/Builders/Declarations/Types/TypeDeclBuilder.swift index 10bf2ba..652a1ac 100644 --- a/Sources/PrincipleMacros/Builders/Declarations/Types/TypeDeclBuilder.swift +++ b/Sources/PrincipleMacros/Builders/Declarations/Types/TypeDeclBuilder.swift @@ -6,7 +6,7 @@ // Copyright © 2025 Kamil Strzelecki. All rights reserved. // -import SwiftSyntax +import SwiftSyntaxMacros public protocol TypeDeclBuilder: DeclBuilder { @@ -18,6 +18,13 @@ extension TypeDeclBuilder { public var basicDeclaration: any BasicDeclSyntax { typeDeclaration } + + public var inheritedGlobalActorIsolation: GlobalActorIsolation? { + .resolved( + for: typeDeclaration, + preferred: preferredGlobalActorIsolation + ) + } } extension TypeDeclBuilder { @@ -31,18 +38,19 @@ extension TypeDeclBuilder { } } - public func buildExtension(of extendedType: some TypeSyntaxProtocol) throws -> MemberBlockSyntax { + public func buildExtension( + of extendedType: some TypeSyntaxProtocol + ) throws -> MemberBlockSyntax { try TypeDeclBuilderContext.$current.withValue( - .extension(trimmedType: TypeSyntax(extendedType.trimmed)), - operation: { - try MemberBlockSyntax( - members: MemberBlockItemListSyntax( - build().map { decl in - MemberBlockItemSyntax(decl: decl).withLeadingNewlines() - } - ) + .extension(trimmedType: TypeSyntax(extendedType.trimmed)) + ) { + try MemberBlockSyntax( + members: MemberBlockItemListSyntax( + build().map { decl in + MemberBlockItemSyntax(decl: decl).withLeadingNewlines() + } ) - } - ) + ) + } } } diff --git a/Sources/PrincipleMacros/Builders/Declarations/Types/TypeDeclBuilderContext.swift b/Sources/PrincipleMacros/Builders/Declarations/Types/TypeDeclBuilderContext.swift index 19fa5d3..a0d2df1 100644 --- a/Sources/PrincipleMacros/Builders/Declarations/Types/TypeDeclBuilderContext.swift +++ b/Sources/PrincipleMacros/Builders/Declarations/Types/TypeDeclBuilderContext.swift @@ -6,7 +6,7 @@ // Copyright © 2025 Kamil Strzelecki. All rights reserved. // -import SwiftSyntax +import SwiftSyntaxMacros internal enum TypeDeclBuilderContext { diff --git a/Sources/PrincipleMacros/Builders/Expressions/AssociatedValuesListExprBuilder.swift b/Sources/PrincipleMacros/Builders/Expressions/AssociatedValuesListExprBuilder.swift index 26df787..2cf0155 100644 --- a/Sources/PrincipleMacros/Builders/Expressions/AssociatedValuesListExprBuilder.swift +++ b/Sources/PrincipleMacros/Builders/Expressions/AssociatedValuesListExprBuilder.swift @@ -6,7 +6,7 @@ // Copyright © 2025 Kamil Strzelecki. All rights reserved. // -import SwiftSyntax +import SwiftSyntaxMacros internal struct AssociatedValuesListExprBuilder: ExprBuilder where Expr: ExprSyntaxProtocol { diff --git a/Sources/PrincipleMacros/Builders/Expressions/EnumCaseCallExprBuilder.swift b/Sources/PrincipleMacros/Builders/Expressions/EnumCaseCallExprBuilder.swift index 1ea7abf..aba1f27 100644 --- a/Sources/PrincipleMacros/Builders/Expressions/EnumCaseCallExprBuilder.swift +++ b/Sources/PrincipleMacros/Builders/Expressions/EnumCaseCallExprBuilder.swift @@ -6,7 +6,7 @@ // Copyright © 2025 Kamil Strzelecki. All rights reserved. // -import SwiftSyntax +import SwiftSyntaxMacros public struct EnumCaseCallExprBuilder: ExprBuilder where Expr: ExprSyntaxProtocol { diff --git a/Sources/PrincipleMacros/Builders/Expressions/ExprBuilder.swift b/Sources/PrincipleMacros/Builders/Expressions/ExprBuilder.swift index 61c89bb..c905848 100644 --- a/Sources/PrincipleMacros/Builders/Expressions/ExprBuilder.swift +++ b/Sources/PrincipleMacros/Builders/Expressions/ExprBuilder.swift @@ -6,7 +6,7 @@ // Copyright © 2025 Kamil Strzelecki. All rights reserved. // -import SwiftSyntax +import SwiftSyntaxMacros public protocol ExprBuilder { diff --git a/Sources/PrincipleMacros/Builders/Expressions/SwitchExprBuilder.swift b/Sources/PrincipleMacros/Builders/Expressions/SwitchExprBuilder.swift index 9814a87..8f29700 100644 --- a/Sources/PrincipleMacros/Builders/Expressions/SwitchExprBuilder.swift +++ b/Sources/PrincipleMacros/Builders/Expressions/SwitchExprBuilder.swift @@ -6,7 +6,7 @@ // Copyright © 2025 Kamil Strzelecki. All rights reserved. // -import SwiftSyntax +import SwiftSyntaxMacros public struct SwitchExprBuilder: ExprBuilder { diff --git a/Sources/PrincipleMacros/ExpansionContext/MacroExpansionContext.swift b/Sources/PrincipleMacros/ExpansionContext/MacroExpansionContext.swift index bb686d4..3e3b5cc 100644 --- a/Sources/PrincipleMacros/ExpansionContext/MacroExpansionContext.swift +++ b/Sources/PrincipleMacros/ExpansionContext/MacroExpansionContext.swift @@ -18,4 +18,13 @@ extension MacroExpansionContext { let diagnostic = Diagnostic(node: node, message: message) diagnose(diagnostic) } + + public func diagnose( + node: some SyntaxProtocol, + warningMessage: String + ) { + let message = MacroExpansionWarningMessage(warningMessage) + let diagnostic = Diagnostic(node: node, message: message) + diagnose(diagnostic) + } } diff --git a/Sources/PrincipleMacros/Imports.swift b/Sources/PrincipleMacros/Imports.swift index f98437a..b486647 100644 --- a/Sources/PrincipleMacros/Imports.swift +++ b/Sources/PrincipleMacros/Imports.swift @@ -11,3 +11,4 @@ @_documentation(visibility: private) @_exported import SwiftSyntax @_documentation(visibility: private) @_exported import SwiftSyntaxBuilder @_documentation(visibility: private) @_exported import SwiftSyntaxMacros +@_documentation(visibility: private) @_exported import SwiftParser diff --git a/Sources/PrincipleMacros/Parameters/ParameterExtractionError.swift b/Sources/PrincipleMacros/Parameters/ParameterExtractionError.swift index 5cc07cf..11ddb9d 100644 --- a/Sources/PrincipleMacros/Parameters/ParameterExtractionError.swift +++ b/Sources/PrincipleMacros/Parameters/ParameterExtractionError.swift @@ -8,6 +8,6 @@ public enum ParameterExtractionError: Error { - case notFound + case missingRequirement case unexpectedSyntaxType } diff --git a/Sources/PrincipleMacros/Parameters/ParameterExtractor.swift b/Sources/PrincipleMacros/Parameters/ParameterExtractor.swift index 08256fb..2b16262 100644 --- a/Sources/PrincipleMacros/Parameters/ParameterExtractor.swift +++ b/Sources/PrincipleMacros/Parameters/ParameterExtractor.swift @@ -6,12 +6,12 @@ // Copyright © 2025 Kamil Strzelecki. All rights reserved. // -import SwiftSyntax +import SwiftSyntaxMacros -public struct ParameterExtractor { +public final class ParameterExtractor { - private let arguments: LabeledExprListSyntax? - private let trailingClosure: ClosureExprSyntax? + private var arguments: LabeledExprListSyntax? + private var trailingClosure: ClosureExprSyntax? public init(from node: some FreestandingMacroExpansionSyntax) { self.arguments = node.arguments @@ -27,25 +27,102 @@ public struct ParameterExtractor { } self.trailingClosure = nil } +} + +extension ParameterExtractor { public func expression( withLabel label: TokenSyntax? ) -> ExprSyntax? { - let match = arguments?.first { element in - element.label?.trimmedDescription == label?.trimmedDescription + guard let arguments else { + return nil + } + + for (index, element) in zip(arguments.indices, arguments) + where element.label?.trimmedDescription == label?.trimmedDescription { + self.arguments?.remove(at: index) + return element.expression.trimmed + } + + return nil + } + + public func requiredExpression( + withLabel label: TokenSyntax? + ) throws -> ExprSyntax { + guard let expression = expression(withLabel: label) else { + throw ParameterExtractionError.missingRequirement } - return match?.expression.trimmed + return expression } +} + +extension ParameterExtractor { public func trailingClosure( withLabel label: TokenSyntax? - ) throws -> ExprSyntax? { - if let trailingClosure { + ) -> ExprSyntax? { + if let trailingClosure = trailingClosure.take() { return ExprSyntax(trailingClosure) } return expression(withLabel: label) } + public func requiredTrailingClosure( + withLabel label: TokenSyntax? + ) throws -> ExprSyntax { + guard let trailingClosure = trailingClosure(withLabel: label) else { + throw ParameterExtractionError.missingRequirement + } + return trailingClosure + } +} + +extension ParameterExtractor { + + public func accessControlLevel( + withLabel label: TokenSyntax? + ) throws -> AccessControlLevel? { + guard let expression = expression(withLabel: label) else { + return nil + } + + let baseName = expression + .as(MemberAccessExprSyntax.self)? + .declName + .baseName + .trimmedDescription + + switch baseName { + case "private": + return .private + case "fileprivate": + return .fileprivate + case "internal": + return .internal + case "package": + return .package + case "public": + return .public + case "open": + return .open + default: + throw ParameterExtractionError.unexpectedSyntaxType + } + } + + public func requiredAccessControlLevel( + withLabel label: TokenSyntax? + ) throws -> AccessControlLevel { + guard let level = try accessControlLevel(withLabel: label) else { + throw ParameterExtractionError.missingRequirement + } + return level + } +} + +extension ParameterExtractor { + public func rawString( withLabel label: TokenSyntax? ) throws -> String? { @@ -64,6 +141,18 @@ public struct ParameterExtractor { return rawString } + public func requiredRawString( + withLabel label: TokenSyntax? + ) throws -> String { + guard let rawString = try rawString(withLabel: label) else { + throw ParameterExtractionError.missingRequirement + } + return rawString + } +} + +extension ParameterExtractor { + public func globalActorIsolation( withLabel label: TokenSyntax? ) throws -> GlobalActorIsolation? { @@ -72,15 +161,25 @@ public struct ParameterExtractor { } if NilLiteralExprSyntax(expression) != nil { - return .nonisolated + let isolation = DeclModifierSyntax(name: .keyword(.nonisolated)) + return .nonisolated(trimmedModifer: isolation) } if let memberAccessExpression = MemberAccessExprSyntax(expression), - memberAccessExpression.declName.baseName.tokenKind == .keyword(.self), - let explicitType = memberAccessExpression.base?.trimmed { - return .isolated(trimmedType: "\(explicitType)") + let explicitType = memberAccessExpression.base?.inferredType, + memberAccessExpression.referencesBaseType { + return .isolated(standardizedType: explicitType.standardized) } throw ParameterExtractionError.unexpectedSyntaxType } + + public func requiredGlobalActorIsolation( + withLabel label: TokenSyntax? + ) throws -> GlobalActorIsolation { + guard let isolation = try globalActorIsolation(withLabel: label) else { + throw ParameterExtractionError.missingRequirement + } + return isolation + } } diff --git a/Sources/PrincipleMacros/Parsers/Common/Parser.swift b/Sources/PrincipleMacros/Parsers/Common/Parser.swift index 7843fb7..5e450e5 100644 --- a/Sources/PrincipleMacros/Parsers/Common/Parser.swift +++ b/Sources/PrincipleMacros/Parsers/Common/Parser.swift @@ -6,7 +6,6 @@ // Copyright © 2025 Kamil Strzelecki. All rights reserved. // -import SwiftSyntax import SwiftSyntaxMacros public protocol Parser { diff --git a/Sources/PrincipleMacros/Parsers/Common/_Parser.swift b/Sources/PrincipleMacros/Parsers/Common/_Parser.swift index cb89772..4b16f81 100644 --- a/Sources/PrincipleMacros/Parsers/Common/_Parser.swift +++ b/Sources/PrincipleMacros/Parsers/Common/_Parser.swift @@ -6,7 +6,6 @@ // Copyright © 2025 Kamil Strzelecki. All rights reserved. // -import SwiftSyntax import SwiftSyntaxMacros internal protocol _Parser: Parser diff --git a/Sources/PrincipleMacros/Parsers/EnumCases/EnumCase.swift b/Sources/PrincipleMacros/Parsers/EnumCases/EnumCase.swift index ac943d5..26d1b96 100644 --- a/Sources/PrincipleMacros/Parsers/EnumCases/EnumCase.swift +++ b/Sources/PrincipleMacros/Parsers/EnumCases/EnumCase.swift @@ -6,12 +6,12 @@ // Copyright © 2025 Kamil Strzelecki. All rights reserved. // -import SwiftSyntax +import SwiftSyntaxMacros @dynamicMemberLookup public final class EnumCase: ParserResult { - public let declaration: EnumCaseDeclSyntax + public let underlying: EnumCaseDeclSyntax public let element: EnumCaseElementSyntax public let trimmedName: TokenSyntax public let associatedValues: [AssociatedValue] @@ -20,18 +20,20 @@ public final class EnumCase: ParserResult { declaration: EnumCaseDeclSyntax, element: EnumCaseElementSyntax ) { - self.declaration = declaration + self.underlying = declaration self.element = element self.trimmedName = element.name.trimmed - self.associatedValues = element.parameterClause? - .parameters.enumerated() + self.associatedValues = element + .parameterClause? + .parameters + .enumerated() .map { .init($1, index: $0) } ?? [] } public subscript(dynamicMember keyPath: KeyPath) -> T { - declaration[keyPath: keyPath] + underlying[keyPath: keyPath] } } diff --git a/Sources/PrincipleMacros/Parsers/EnumCases/EnumCasesList.swift b/Sources/PrincipleMacros/Parsers/EnumCases/EnumCasesList.swift index eec788d..cb84d39 100644 --- a/Sources/PrincipleMacros/Parsers/EnumCases/EnumCasesList.swift +++ b/Sources/PrincipleMacros/Parsers/EnumCases/EnumCasesList.swift @@ -6,7 +6,7 @@ // Copyright © 2025 Kamil Strzelecki. All rights reserved. // -import SwiftSyntax +import SwiftSyntaxMacros public struct EnumCasesList: _ParserResultsCollection { diff --git a/Sources/PrincipleMacros/Parsers/EnumCases/EnumCasesParser.swift b/Sources/PrincipleMacros/Parsers/EnumCases/EnumCasesParser.swift index 3ae92ed..b9cd9d3 100644 --- a/Sources/PrincipleMacros/Parsers/EnumCases/EnumCasesParser.swift +++ b/Sources/PrincipleMacros/Parsers/EnumCases/EnumCasesParser.swift @@ -6,7 +6,6 @@ // Copyright © 2025 Kamil Strzelecki. All rights reserved. // -import SwiftSyntax import SwiftSyntaxMacros public enum EnumCasesParser: _Parser { diff --git a/Sources/PrincipleMacros/Parsers/Properties/PropertiesList.swift b/Sources/PrincipleMacros/Parsers/Properties/PropertiesList.swift index d996294..65d1a7b 100644 --- a/Sources/PrincipleMacros/Parsers/Properties/PropertiesList.swift +++ b/Sources/PrincipleMacros/Parsers/Properties/PropertiesList.swift @@ -6,7 +6,7 @@ // Copyright © 2025 Kamil Strzelecki. All rights reserved. // -import SwiftSyntax +import SwiftSyntaxMacros public struct PropertiesList: _ParserResultsCollection { diff --git a/Sources/PrincipleMacros/Parsers/Properties/PropertiesParser.swift b/Sources/PrincipleMacros/Parsers/Properties/PropertiesParser.swift index 9670fc3..797ac3a 100644 --- a/Sources/PrincipleMacros/Parsers/Properties/PropertiesParser.swift +++ b/Sources/PrincipleMacros/Parsers/Properties/PropertiesParser.swift @@ -6,7 +6,6 @@ // Copyright © 2025 Kamil Strzelecki. All rights reserved. // -import SwiftSyntax import SwiftSyntaxMacros public enum PropertiesParser: _Parser { diff --git a/Sources/PrincipleMacros/Parsers/Properties/Property.swift b/Sources/PrincipleMacros/Parsers/Properties/Property.swift index 8da68dc..871706a 100644 --- a/Sources/PrincipleMacros/Parsers/Properties/Property.swift +++ b/Sources/PrincipleMacros/Parsers/Properties/Property.swift @@ -6,12 +6,12 @@ // Copyright © 2025 Kamil Strzelecki. All rights reserved. // -import SwiftSyntax +import SwiftSyntaxMacros @dynamicMemberLookup public final class Property: ParserResult { - public let declaration: VariableDeclSyntax + public let underlying: VariableDeclSyntax public let binding: PatternBindingSyntax public let trimmedName: TokenSyntax public let inferredType: TypeSyntax @@ -27,7 +27,7 @@ public final class Property: ParserResult { name: TokenSyntax, inferredType: TypeSyntax ) { - self.declaration = declaration + self.underlying = declaration self.binding = binding self.trimmedName = name.trimmed self.inferredType = inferredType @@ -74,7 +74,7 @@ public final class Property: ParserResult { } public subscript(dynamicMember keyPath: KeyPath) -> T { - declaration[keyPath: keyPath] + underlying[keyPath: keyPath] } } diff --git a/Sources/PrincipleMacros/Syntax/Concepts/AccessControlLevel.swift b/Sources/PrincipleMacros/Syntax/Concepts/AccessControlLevel.swift new file mode 100644 index 0000000..cdae34f --- /dev/null +++ b/Sources/PrincipleMacros/Syntax/Concepts/AccessControlLevel.swift @@ -0,0 +1,141 @@ +// +// AccessControlLevel.swift +// PrincipleMacros +// +// Created by Kamil Strzelecki on 13/11/2025. +// Copyright © 2025 Kamil Strzelecki. All rights reserved. +// + +public enum AccessControlLevel: Int, Hashable, CaseIterable { + + case `private` + case `fileprivate` + case `internal` + case package + case `public` + case open + + public var keyword: Keyword { + switch self { + case .private: + .private + case .fileprivate: + .fileprivate + case .internal: + .internal + case .package: + .package + case .public: + .public + case .open: + .open + } + } + + public var tokenSyntax: TokenSyntax { + TokenSyntax( + .keyword(keyword), + presence: .present + ) + } + + public init?(keyword: Keyword) { + switch keyword { + case .private: + self = .private + case .fileprivate: + self = .fileprivate + case .internal: + self = .internal + case .package: + self = .package + case .public: + self = .public + case .open: + self = .open + default: + return nil + } + } + + public init?(tokenSyntax: TokenSyntax) { + switch tokenSyntax.tokenKind { + case let .keyword(keyword): + self.init(keyword: keyword) + default: + return nil + } + } +} + +extension AccessControlLevel: Comparable { + + public static func < (lhs: Self, rhs: Self) -> Bool { + lhs.rawValue < rhs.rawValue + } +} + +extension AccessControlLevel { + + public static func forMember( + of declaration: some TypeDeclSyntax, + preferred: Self? = nil, + maxAllowed: Self = .public + ) -> Self? { + _resolved( + from: declaration.accessControlLevel, + preferred: preferred, + maxAllowed: maxAllowed, + transform: { $0 == .private ? nil : $0 } + ) + } + + public static func forSibling( + of syntax: some WithModifiersSyntax, + preferred: Self? = nil, + maxAllowed: Self = .public + ) -> Self? { + _resolved( + from: syntax.accessControlLevel, + preferred: preferred, + maxAllowed: maxAllowed, + transform: { $0 == .private ? .fileprivate : $0 } + ) + } + + public static func forPeer( + of syntax: some WithModifiersSyntax, + preferred: Self? = nil, + maxAllowed: Self = .public + ) -> Self? { + _resolved( + from: syntax.accessControlLevel, + preferred: preferred, + maxAllowed: maxAllowed + ) + } + + private static func _resolved( + from attached: Self?, + preferred: Self?, + maxAllowed: Self, + transform: (Self) -> Self? = \.self + ) -> Self? { + if let preferred { + return min(preferred, maxAllowed) + } + if var attached { + attached = min(attached, maxAllowed) + return transform(attached) + } + return nil + } +} + +extension SyntaxStringInterpolation { + + public mutating func appendInterpolation(_ accessControlLevel: AccessControlLevel?) { + let node = accessControlLevel?.tokenSyntax.withTrailingSpace + appendInterpolation(node) + } +} diff --git a/Sources/PrincipleMacros/Syntax/Concepts/CamelCaseNotation.swift b/Sources/PrincipleMacros/Syntax/Concepts/CamelCaseNotation.swift new file mode 100644 index 0000000..27beba2 --- /dev/null +++ b/Sources/PrincipleMacros/Syntax/Concepts/CamelCaseNotation.swift @@ -0,0 +1,144 @@ +// +// CamelCaseNotation.swift +// PrincipleMacros +// +// Created by Kamil Strzelecki on 14/11/2025. +// Copyright © 2025 Kamil Strzelecki. All rights reserved. +// + +import Foundation +import SwiftSyntaxMacros + +public struct CamelCaseNotation { + + public var segments: [Segment] + + public init(segments: some Sequence) { + self.segments = Array(segments) + } + + public init(string: String) { + var segments = [Segment]() + var segment: MutableSegment? + + func appendSegment(_ segment: MutableSegment) { + segments.append(Segment(from: segment, in: string)) + } + + for (index, character) in zip(string.indices.reversed(), string.reversed()) { + let characterSpelling = Segment.Spelling(character) + + guard var oldSegment = segment else { + segment = MutableSegment(index: index, spelling: characterSpelling) + continue + } + + switch (characterSpelling, oldSegment.spelling) { + case (.lowercase, .uppercase): // a|B + appendSegment(oldSegment) + segment = MutableSegment(index: index, spelling: .lowercase) + + case (.uppercase, .lowercase): // |Ab + oldSegment.expand(to: index) + oldSegment.spelling = .capitalized + appendSegment(oldSegment) + segment = nil + + default: + segment?.expand(to: index) + } + } + + if let segment { + appendSegment(segment) + } + + self.segments = segments.reversed() + } + + public mutating func removeFirst(_ count: Int = 1) { + segments.removeFirst(min(count, segments.count)) + } + + public mutating func removeLast(_ count: Int = 1) { + segments.removeLast(min(count, segments.count)) + } + + public func joined(as spelling: Spelling) -> String { + var joined = segments.first? + .string(as: spelling) + ?? "" + joined += segments.dropFirst() + .map { $0.string(as: .upperCamelCase) } + .joined() + return joined + } +} + +extension CamelCaseNotation { + + public enum Spelling: Hashable { + + case lowerCamelCase + case upperCamelCase + } +} + +extension CamelCaseNotation { + + fileprivate struct MutableSegment: Hashable { + + var range: ClosedRange + var spelling: Segment.Spelling + + init(index: String.Index, spelling: Segment.Spelling) { + self.range = index ... index + self.spelling = spelling + } + + mutating func expand(to index: String.Index) { + range = index ... range.upperBound + } + } + + public struct Segment: Hashable { + + public let string: String + public let spelling: Spelling + + public init(string: String, spelling: Spelling) { + self.string = string + self.spelling = spelling + } + + fileprivate init(from segment: MutableSegment, in string: String) { + self.string = String(string[segment.range]) + self.spelling = segment.spelling + } + + public func string(as transform: CamelCaseNotation.Spelling) -> String { + switch transform { + case .lowerCamelCase: + string.lowercased() + case .upperCamelCase: + spelling == .uppercase + ? string.uppercased() + : string.capitalized + } + } + } +} + +extension CamelCaseNotation.Segment { + + public enum Spelling: Hashable { + + case lowercase + case capitalized + case uppercase + + fileprivate init(_ character: Character) { + self = character.isUppercase ? .uppercase : .lowercase + } + } +} diff --git a/Sources/PrincipleMacros/Syntax/Concepts/GlobalActorIsolation.swift b/Sources/PrincipleMacros/Syntax/Concepts/GlobalActorIsolation.swift new file mode 100644 index 0000000..ed5a70b --- /dev/null +++ b/Sources/PrincipleMacros/Syntax/Concepts/GlobalActorIsolation.swift @@ -0,0 +1,100 @@ +// +// GlobalActorIsolation.swift +// PrincipleMacros +// +// Created by Kamil Strzelecki on 18/08/2025. +// Copyright © 2025 Kamil Strzelecki. All rights reserved. +// + +import SwiftSyntaxMacros + +public enum GlobalActorIsolation { + + case nonisolated(trimmedModifer: DeclModifierSyntax) + case isolated(standardizedType: TypeSyntax) + + public var trimmedNonisolatedModifier: DeclModifierSyntax? { + switch self { + case let .nonisolated(trimmedModifer): + trimmedModifer + default: + nil + } + } + + public var standardizedIsolationType: TypeSyntax? { + switch self { + case let .isolated(standardizedType): + standardizedType + default: + nil + } + } + + public var standardizedIsolationAttribute: AttributeSyntax? { + guard let standardizedIsolationType else { + return nil + } + return AttributeSyntax(attributeName: standardizedIsolationType) + } +} + +extension GlobalActorIsolation { + + public static func resolved( + for declaration: some TypeDeclSyntax, + preferred: Self? = nil + ) -> Self? { + _resolved( + in: CollectionOfOne(Syntax(declaration)), + preferred: preferred + ) + } + + public static func resolved( + for declaration: some BasicDeclSyntax, + in lexicalContext: [Syntax], + preferred: Self? = nil + ) -> Self? { + _resolved( + in: CollectionOfOne(Syntax(declaration)) + lexicalContext, + preferred: preferred + ) + } + + private static func _resolved( + in fullContext: some Collection, + preferred: Self? + ) -> Self? { + if let preferred { + return preferred + } + + for syntax in fullContext { + if let attributedSyntax = syntax.asProtocol((any WithAttributesSyntax).self), + let inherited = attributedSyntax.globalActorIsolation { + return inherited + } + if let modifiedSyntax = syntax.asProtocol((any WithModifiersSyntax).self), + let inherited = modifiedSyntax.globalActorIsolation { + return inherited + } + if syntax.isProtocol((any DeclGroupSyntax).self) { + break + } + } + + return nil + } +} + +extension SyntaxStringInterpolation { + + public mutating func appendInterpolation(_ isolation: GlobalActorIsolation?) { + if let attribute = isolation?.standardizedIsolationAttribute { + appendInterpolation(attribute.withTrailingSpace) + } else if let modifier = isolation?.trimmedNonisolatedModifier { + appendInterpolation(modifier.withTrailingSpace) + } + } +} diff --git a/Sources/PrincipleMacros/Syntax/Custom/BasicDeclSyntax.swift b/Sources/PrincipleMacros/Syntax/Custom/BasicDeclSyntax.swift index 6f9dee2..7fee7aa 100644 --- a/Sources/PrincipleMacros/Syntax/Custom/BasicDeclSyntax.swift +++ b/Sources/PrincipleMacros/Syntax/Custom/BasicDeclSyntax.swift @@ -6,7 +6,7 @@ // Copyright © 2025 Kamil Strzelecki. All rights reserved. // -import SwiftSyntax +import SwiftSyntaxMacros public typealias BasicDeclSyntax = DeclSyntaxProtocol & WithAttributesSyntax diff --git a/Sources/PrincipleMacros/Transformers/ClosureType.swift b/Sources/PrincipleMacros/Syntax/Custom/ClosureTypeSyntax.swift similarity index 94% rename from Sources/PrincipleMacros/Transformers/ClosureType.swift rename to Sources/PrincipleMacros/Syntax/Custom/ClosureTypeSyntax.swift index 6040566..1517dff 100644 --- a/Sources/PrincipleMacros/Transformers/ClosureType.swift +++ b/Sources/PrincipleMacros/Syntax/Custom/ClosureTypeSyntax.swift @@ -1,15 +1,15 @@ // -// ClosureType.swift +// ClosureTypeSyntax.swift // PrincipleMacros // // Created by Kamil Strzelecki on 26/01/2025. // Copyright © 2025 Kamil Strzelecki. All rights reserved. // -import SwiftSyntax +import SwiftSyntaxMacros @dynamicMemberLookup -public struct ClosureType { +public struct ClosureTypeSyntax { public let attributes: AttributeListSyntax public let function: FunctionTypeSyntax @@ -49,7 +49,7 @@ public struct ClosureType { } } -extension ClosureType { +extension ClosureTypeSyntax { public struct Parameter { diff --git a/Sources/PrincipleMacros/Syntax/Custom/StatefulDeclSyntax.swift b/Sources/PrincipleMacros/Syntax/Custom/StatefulDeclSyntax.swift index f8a2ce0..46765f4 100644 --- a/Sources/PrincipleMacros/Syntax/Custom/StatefulDeclSyntax.swift +++ b/Sources/PrincipleMacros/Syntax/Custom/StatefulDeclSyntax.swift @@ -6,7 +6,7 @@ // Copyright © 2025 Kamil Strzelecki. All rights reserved. // -import SwiftSyntax +import SwiftSyntaxMacros public protocol StatefulDeclSyntax: TypeDeclSyntax {} diff --git a/Sources/PrincipleMacros/Syntax/Custom/TypeDeclSyntax.swift b/Sources/PrincipleMacros/Syntax/Custom/TypeDeclSyntax.swift index c3b7e45..48a995a 100644 --- a/Sources/PrincipleMacros/Syntax/Custom/TypeDeclSyntax.swift +++ b/Sources/PrincipleMacros/Syntax/Custom/TypeDeclSyntax.swift @@ -6,7 +6,7 @@ // Copyright © 2025 Kamil Strzelecki. All rights reserved. // -import SwiftSyntax +import SwiftSyntaxMacros public protocol TypeDeclSyntax: DeclGroupSyntax, NamedDeclSyntax, BasicDeclSyntax { diff --git a/Sources/PrincipleMacros/Syntax/Extensions/AccessorDeclListSyntax.swift b/Sources/PrincipleMacros/Syntax/Extensions/AccessorDeclListSyntax.swift index d31c7d3..5e3f851 100644 --- a/Sources/PrincipleMacros/Syntax/Extensions/AccessorDeclListSyntax.swift +++ b/Sources/PrincipleMacros/Syntax/Extensions/AccessorDeclListSyntax.swift @@ -6,7 +6,7 @@ // Copyright © 2025 Kamil Strzelecki. All rights reserved. // -import SwiftSyntax +import SwiftSyntaxMacros extension AccessorDeclListSyntax { diff --git a/Sources/PrincipleMacros/Syntax/Extensions/AttributeListSyntax.swift b/Sources/PrincipleMacros/Syntax/Extensions/AttributeListSyntax.swift new file mode 100644 index 0000000..b24ed65 --- /dev/null +++ b/Sources/PrincipleMacros/Syntax/Extensions/AttributeListSyntax.swift @@ -0,0 +1,37 @@ +// +// AttributeListSyntax.swift +// PrincipleMacros +// +// Created by Kamil Strzelecki on 14/11/2025. +// Copyright © 2025 Kamil Strzelecki. All rights reserved. +// + +import SwiftSyntaxMacros + +extension AttributeListSyntax { + + public var attributeElements: some Collection { + lazy.compactMap { element in + switch element { + case let .attribute(attribute): + attribute + default: + nil + } + } + } + + public func first(like other: AttributeSyntax) -> AttributeSyntax? { + attributeElements.first { $0.isLike(other) } + } + + public func contains(like other: AttributeSyntax) -> Bool { + first(like: other) != nil + } + + public func contains(likeOneOf other: AttributeSyntax...) -> Bool { + attributeElements.contains { attribute in + other.contains { attribute.isLike($0) } + } + } +} diff --git a/Sources/PrincipleMacros/Syntax/Extensions/AttributeSyntax.swift b/Sources/PrincipleMacros/Syntax/Extensions/AttributeSyntax.swift new file mode 100644 index 0000000..abddf7d --- /dev/null +++ b/Sources/PrincipleMacros/Syntax/Extensions/AttributeSyntax.swift @@ -0,0 +1,16 @@ +// +// AttributeSyntax.swift +// PrincipleMacros +// +// Created by Kamil Strzelecki on 15/11/2025. +// Copyright © 2025 Kamil Strzelecki. All rights reserved. +// + +import SwiftSyntaxMacros + +extension AttributeSyntax { + + public func isLike(_ other: AttributeSyntax) -> Bool { + attributeName.isLike(other.attributeName) + } +} diff --git a/Sources/PrincipleMacros/Syntax/Extensions/AttributedTypeSyntax.swift b/Sources/PrincipleMacros/Syntax/Extensions/AttributedTypeSyntax.swift new file mode 100644 index 0000000..b2593da --- /dev/null +++ b/Sources/PrincipleMacros/Syntax/Extensions/AttributedTypeSyntax.swift @@ -0,0 +1,38 @@ +// +// AttributedTypeSyntax.swift +// PrincipleMacros +// +// Created by Kamil Strzelecki on 14/11/2025. +// Copyright © 2025 Kamil Strzelecki. All rights reserved. +// + +import SwiftSyntaxMacros + +extension AttributedTypeSyntax { + + public init( + globalActorIsolation: GlobalActorIsolation?, + baseType: some TypeSyntaxProtocol + ) { + let specifiers: TypeSpecifierListSyntax = + switch globalActorIsolation { + case .nonisolated: + [.nonisolatedTypeSpecifier(.init())] + default: + [] + } + + let attributes: AttributeListSyntax = + if let attribute = globalActorIsolation?.standardizedIsolationAttribute { + [.attribute(attribute)] + } else { + [] + } + + self.init( + specifiers: specifiers, + attributes: attributes, + baseType: baseType + ) + } +} diff --git a/Sources/PrincipleMacros/Syntax/Extensions/ClosureExprSyntax.swift b/Sources/PrincipleMacros/Syntax/Extensions/ClosureExprSyntax.swift index 467a7af..79e9b7b 100644 --- a/Sources/PrincipleMacros/Syntax/Extensions/ClosureExprSyntax.swift +++ b/Sources/PrincipleMacros/Syntax/Extensions/ClosureExprSyntax.swift @@ -6,8 +6,7 @@ // Copyright © 2025 Kamil Strzelecki. All rights reserved. // -import SwiftBasicFormat -import SwiftSyntax +import SwiftSyntaxMacros extension ClosureExprSyntax { diff --git a/Sources/PrincipleMacros/Syntax/Extensions/ExprSyntax.swift b/Sources/PrincipleMacros/Syntax/Extensions/ExprSyntax.swift index 4e1a02c..0153a3d 100644 --- a/Sources/PrincipleMacros/Syntax/Extensions/ExprSyntax.swift +++ b/Sources/PrincipleMacros/Syntax/Extensions/ExprSyntax.swift @@ -6,7 +6,7 @@ // Copyright © 2025 Kamil Strzelecki. All rights reserved. // -import SwiftSyntax +import SwiftSyntaxMacros extension ExprSyntax { @@ -134,6 +134,10 @@ extension GenericSpecializationExprSyntax { extension MemberAccessExprSyntax { + public var referencesBaseType: Bool { + declName.baseName.tokenKind == .keyword(.self) + } + public var inferredType: TypeSyntax? { guard let first = base?.inferredType else { return nil diff --git a/Sources/PrincipleMacros/Syntax/Extensions/PatternBindingSyntax.swift b/Sources/PrincipleMacros/Syntax/Extensions/PatternBindingSyntax.swift index d8d2da9..99dc835 100644 --- a/Sources/PrincipleMacros/Syntax/Extensions/PatternBindingSyntax.swift +++ b/Sources/PrincipleMacros/Syntax/Extensions/PatternBindingSyntax.swift @@ -6,7 +6,7 @@ // Copyright © 2025 Kamil Strzelecki. All rights reserved. // -import SwiftSyntax +import SwiftSyntaxMacros extension PatternBindingSyntax { diff --git a/Sources/PrincipleMacros/Syntax/Extensions/SyntaxProtocol.swift b/Sources/PrincipleMacros/Syntax/Extensions/SyntaxProtocol.swift index 7c16789..86e6fdd 100644 --- a/Sources/PrincipleMacros/Syntax/Extensions/SyntaxProtocol.swift +++ b/Sources/PrincipleMacros/Syntax/Extensions/SyntaxProtocol.swift @@ -6,7 +6,7 @@ // Copyright © 2025 Kamil Strzelecki. All rights reserved. // -import SwiftSyntax +import SwiftSyntaxMacros extension SyntaxProtocol { diff --git a/Sources/PrincipleMacros/Syntax/Extensions/TypeSyntax.swift b/Sources/PrincipleMacros/Syntax/Extensions/TypeSyntax.swift index d588ccc..cfdcc3e 100644 --- a/Sources/PrincipleMacros/Syntax/Extensions/TypeSyntax.swift +++ b/Sources/PrincipleMacros/Syntax/Extensions/TypeSyntax.swift @@ -6,7 +6,7 @@ // Copyright © 2025 Kamil Strzelecki. All rights reserved. // -import SwiftSyntax +import SwiftSyntaxMacros extension TypeSyntax { @@ -114,25 +114,18 @@ extension GenericArgumentClauseSyntax { GenericArgumentClauseSyntax( arguments: GenericArgumentListSyntax( arguments.map { element in - #if canImport(SwiftSyntax601) - switch element.argument { - case let .type(type): - GenericArgumentSyntax( - argument: .type(type.standardized), - trailingComma: element.trailingComma?.trimmed.withTrailingSpace - ) - default: - GenericArgumentSyntax( - argument: element.argument, - trailingComma: element.trailingComma?.trimmed.withTrailingSpace - ) - } - #else + switch element.argument { + case let .type(type): GenericArgumentSyntax( - argument: element.argument.standardized, + argument: .type(type.standardized), trailingComma: element.trailingComma?.trimmed.withTrailingSpace ) - #endif + default: + GenericArgumentSyntax( + argument: element.argument, + trailingComma: element.trailingComma?.trimmed.withTrailingSpace + ) + } } ) ) diff --git a/Sources/PrincipleMacros/Syntax/Extensions/WithAttributesSyntax.swift b/Sources/PrincipleMacros/Syntax/Extensions/WithAttributesSyntax.swift index d13a73a..e148e03 100644 --- a/Sources/PrincipleMacros/Syntax/Extensions/WithAttributesSyntax.swift +++ b/Sources/PrincipleMacros/Syntax/Extensions/WithAttributesSyntax.swift @@ -6,44 +6,18 @@ // Copyright © 2025 Kamil Strzelecki. All rights reserved. // -import SwiftSyntax +import SwiftSyntaxMacros extension WithAttributesSyntax { - public var globalActor: AttributeSyntax? { - attributes.attributeElements.first { attribute in + public var globalActorIsolation: GlobalActorIsolation? { + let attribute = attributes.attributeElements.first { attribute in attribute.attributeName.trimmedDescription.hasSuffix("Actor") } - } -} - -extension AttributeListSyntax { - - public var attributeElements: some Collection { - lazy.compactMap { element in - switch element { - case let .attribute(attribute): - attribute - default: - nil - } - } - } - - public func contains(likeOneOf someAttributes: AttributeSyntax...) -> Bool { - attributeElements.contains { attribute in - someAttributes.contains { attribute.isLike($0) } + if let attribute { + let standardizedType = attribute.attributeName.standardized + return .isolated(standardizedType: standardizedType) } - } - - public func contains(like someAttribute: AttributeSyntax) -> Bool { - contains(likeOneOf: someAttribute) - } -} - -extension AttributeSyntax { - - public func isLike(_ other: AttributeSyntax) -> Bool { - attributeName.isLike(other.attributeName) + return nil } } diff --git a/Sources/PrincipleMacros/Syntax/Extensions/WithModifiersSyntax.swift b/Sources/PrincipleMacros/Syntax/Extensions/WithModifiersSyntax.swift index a0779cf..5c81264 100644 --- a/Sources/PrincipleMacros/Syntax/Extensions/WithModifiersSyntax.swift +++ b/Sources/PrincipleMacros/Syntax/Extensions/WithModifiersSyntax.swift @@ -6,95 +6,56 @@ // Copyright © 2025 Kamil Strzelecki. All rights reserved. // -import SwiftSyntax +import SwiftSyntaxMacros extension WithModifiersSyntax { - public var finalSpecifier: TokenSyntax? { - modifiers.lazy.map(\.name).first { name in - name.tokenKind == .keyword(.final) + public var globalActorIsolation: GlobalActorIsolation? { + let modifier = modifiers.first { modifier in + modifier.name.tokenKind == .keyword(.nonisolated) } - } - - public var typeScopeSpecifier: TokenSyntax? { - modifiers.lazy.map(\.name).first { name in - TokenKind.typeScopeSpecifiers.contains(name.tokenKind) + if let modifier { + return .nonisolated(trimmedModifer: modifier.trimmed) } + return nil } } extension WithModifiersSyntax { - public var accessControlLevel: TokenSyntax? { + public var accessControlLevel: AccessControlLevel? { accessControlLevel(detail: nil) } - public var setterAccessControlLevel: TokenSyntax? { - accessControlLevel(detail: .identifier("set")) ?? accessControlLevel + public var setterAccessControlLevel: AccessControlLevel? { + accessControlLevel(detail: .identifier("set")) + ?? accessControlLevel } - private func accessControlLevel(detail: TokenKind?) -> TokenSyntax? { + private func accessControlLevel(detail: TokenKind?) -> AccessControlLevel? { modifiers.lazy .filter { $0.detail?.detail.tokenKind == detail } - .map(\.name) - .first { TokenKind.accessControlLevels.contains($0.tokenKind) } + .compactMap { AccessControlLevel(tokenSyntax: $0.name) } + .first } } extension WithModifiersSyntax { - public func inlinableAccessControlLevel( - inheritedBy inheritingDeclaration: InheritingDeclaration, - maxAllowed: Keyword - ) -> TokenSyntax? { - guard let accessControlLevel, - let index = TokenKind.accessControlLevels.firstIndex(of: accessControlLevel.tokenKind), - let maxAllowedIndex = Keyword.accessControlLevels.firstIndex(of: maxAllowed) - else { - return nil - } - - guard index <= maxAllowedIndex else { - let tokenKind = TokenKind.accessControlLevels[maxAllowedIndex] - return TokenSyntax(tokenKind, presence: .present).withTrailingSpace + public var finalSpecifier: TokenSyntax? { + modifiers.lazy.map(\.name).first { name in + name.tokenKind == .keyword(.final) } + } - switch inheritingDeclaration { - case .member: - if let internalIndex = Keyword.accessControlLevels.firstIndex(of: .internal), - index <= internalIndex { - return nil + public var typeScopeSpecifier: TokenSyntax? { + modifiers.lazy.map(\.name).first { name in + switch name.tokenKind { + case .keyword(.class), .keyword(.static): + true + default: + false } - case .peer: - break } - - return accessControlLevel.trimmed.withTrailingSpace } } - -extension TokenKind { - - fileprivate static let typeScopeSpecifiers = Keyword.typeScopeSpecifiers - .map(TokenKind.keyword) - - fileprivate static let accessControlLevels = Keyword.accessControlLevels - .map(TokenKind.keyword) -} - -extension Keyword { - - fileprivate static let typeScopeSpecifiers: [Keyword] = [ - .static, - .class - ] - - fileprivate static let accessControlLevels: [Keyword] = [ - .private, - .fileprivate, - .internal, - .package, - .public, - .open - ] -} diff --git a/Sources/PrincipleMacros/Syntax/Helpers/GlobalActorIsolation.swift b/Sources/PrincipleMacros/Syntax/Helpers/GlobalActorIsolation.swift deleted file mode 100644 index e8a60fa..0000000 --- a/Sources/PrincipleMacros/Syntax/Helpers/GlobalActorIsolation.swift +++ /dev/null @@ -1,32 +0,0 @@ -// -// GlobalActorIsolation.swift -// PrincipleMacros -// -// Created by Kamil Strzelecki on 18/08/2025. -// Copyright © 2025 Kamil Strzelecki. All rights reserved. -// - -import SwiftSyntax - -public enum GlobalActorIsolation: Hashable { - - case nonisolated - case isolated(trimmedType: TypeSyntax) - - public var trimmedType: TypeSyntax? { - switch self { - case let .isolated(trimmedType): - trimmedType - case .nonisolated: - nil - } - } - - public var inlinableAttribute: AttributeSyntax? { - guard let trimmedType else { - return nil - } - let attribute = AttributeSyntax(attributeName: trimmedType) - return attribute.withTrailingSpace - } -} diff --git a/Sources/PrincipleMacros/Syntax/Helpers/InheritingDeclaration.swift b/Sources/PrincipleMacros/Syntax/Helpers/InheritingDeclaration.swift deleted file mode 100644 index e9679ae..0000000 --- a/Sources/PrincipleMacros/Syntax/Helpers/InheritingDeclaration.swift +++ /dev/null @@ -1,13 +0,0 @@ -// -// InheritingDeclaration.swift -// PrincipleMacros -// -// Created by Kamil Strzelecki on 17/01/2025. -// Copyright © 2025 Kamil Strzelecki. All rights reserved. -// - -public enum InheritingDeclaration: Hashable { - - case member - case peer -} diff --git a/Sources/PrincipleMacrosClientSupport/AccessControlLevel.swift b/Sources/PrincipleMacrosClientSupport/AccessControlLevel.swift new file mode 100644 index 0000000..603b57b --- /dev/null +++ b/Sources/PrincipleMacrosClientSupport/AccessControlLevel.swift @@ -0,0 +1,17 @@ +// +// AccessControlLevel.swift +// PrincipleMacros +// +// Created by Kamil Strzelecki on 13/11/2025. +// Copyright © 2025 Kamil Strzelecki. All rights reserved. +// + +public enum AccessControlLevel: Hashable { + + case `private` + case `fileprivate` + case `internal` + case package + case `public` + case open +} diff --git a/Sources/PrincipleMacrosTestSupport/Imports.swift b/Sources/PrincipleMacrosTestSupport/Imports.swift new file mode 100644 index 0000000..5ef5866 --- /dev/null +++ b/Sources/PrincipleMacrosTestSupport/Imports.swift @@ -0,0 +1,10 @@ +// +// Imports.swift +// PrincipleMacros +// +// Created by Kamil Strzelecki on 12/11/2025. +// Copyright © 2025 Kamil Strzelecki. All rights reserved. +// + +@_documentation(visibility: private) @_exported import PrincipleMacros +@_documentation(visibility: private) @_exported import SwiftSyntaxMacrosTestSupport diff --git a/Tests/PrincipleMacrosTests/Builders/EnumCaseCallExprBuilderTests.swift b/Tests/PrincipleMacrosTests/Builders/EnumCaseCallExprBuilderTests.swift index bafe81d..da06d9b 100644 --- a/Tests/PrincipleMacrosTests/Builders/EnumCaseCallExprBuilderTests.swift +++ b/Tests/PrincipleMacrosTests/Builders/EnumCaseCallExprBuilderTests.swift @@ -18,7 +18,7 @@ internal struct EnumCaseCallExprBuilderTests { } @Test - func testCallWithoutAssociatedValues() throws { + func callWithoutAssociatedValues() throws { let enumCase = try makeEnumCase(from: "case first") let builder = EnumCaseCallExprBuilder(for: enumCase) { _ in Issue.record() @@ -28,7 +28,7 @@ internal struct EnumCaseCallExprBuilderTests { } @Test - func testCallWithUnnamedAssociatedValue() throws { + func callWithUnnamedAssociatedValue() throws { let enumCase = try makeEnumCase(from: "case second(Int)") let builder = EnumCaseCallExprBuilder(for: enumCase) { _ in "123" as ExprSyntax @@ -37,7 +37,7 @@ internal struct EnumCaseCallExprBuilderTests { } @Test - func testCallWithMultipleAssociatedValues() throws { + func callWithMultipleAssociatedValues() throws { let enumCase = try makeEnumCase(from: "case third(arg: String, Int)") let builder = EnumCaseCallExprBuilder(for: enumCase) { associatedValue in if associatedValue.standardizedName.description == "arg" { diff --git a/Tests/PrincipleMacrosTests/Builders/SwitchExprBuilderTests.swift b/Tests/PrincipleMacrosTests/Builders/SwitchExprBuilderTests.swift index 3a71932..a627982 100644 --- a/Tests/PrincipleMacrosTests/Builders/SwitchExprBuilderTests.swift +++ b/Tests/PrincipleMacrosTests/Builders/SwitchExprBuilderTests.swift @@ -18,7 +18,7 @@ internal struct SwitchExprBuilderTests { } @Test - func testSwitchExpression() throws { + func switchExpression() throws { let enumCases = try EnumCasesList([ makeEnumCase(from: "case first"), makeEnumCase(from: "case second(Int)"), diff --git a/Tests/PrincipleMacrosTests/Parameters/ParameterExtractorTests.swift b/Tests/PrincipleMacrosTests/Parameters/ParameterExtractorTests.swift index f25ff03..b7cda1b 100644 --- a/Tests/PrincipleMacrosTests/Parameters/ParameterExtractorTests.swift +++ b/Tests/PrincipleMacrosTests/Parameters/ParameterExtractorTests.swift @@ -17,7 +17,7 @@ internal struct ParameterExtractorTests { } @Test - func testExpressionExtraction() throws { + func expressionExtraction() throws { let extractor = try makeExtractor(from: "#MyMacro(value: Type.make())") let extracted = extractor.expression(withLabel: "value") let expected: ExprSyntax = "Type.make()" @@ -25,7 +25,7 @@ internal struct ParameterExtractorTests { } @Test - func testUnnamedExpressionExtraction() throws { + func unnamedExpressionExtraction() throws { let extractor = try makeExtractor(from: "#MyMacro(value: Type.make(), 123)") let extracted = extractor.expression(withLabel: nil) let expected: ExprSyntax = "123" @@ -33,7 +33,19 @@ internal struct ParameterExtractorTests { } @Test - func testMissingExpressionExtraction() throws { + func overlappingExpressionExtraction() throws { + let extractor = try makeExtractor(from: "#MyMacro(Type.make(), 123)") + let firstExtracted = extractor.expression(withLabel: nil) + let firstExpected: ExprSyntax = "Type.make()" + #expect(firstExtracted?.description == firstExpected.description) + + let secondExtracted = extractor.expression(withLabel: nil) + let secondExpected: ExprSyntax = "123" + #expect(secondExtracted?.description == secondExpected.description) + } + + @Test + func missingExpressionExtraction() throws { let extractor = try makeExtractor(from: #"#MyMacro(arg: Type.make())"#) let extracted = extractor.expression(withLabel: "value") #expect(extracted == nil) @@ -43,31 +55,58 @@ internal struct ParameterExtractorTests { extension ParameterExtractorTests { @Test - func testTrailingClosureExtraction() throws { + func trailingClosureExtraction() throws { let extractor = try makeExtractor(from: "#MyMacro { _ in }") - let extracted = try extractor.trailingClosure(withLabel: "operation") + let extracted = extractor.trailingClosure(withLabel: "operation") #expect(extracted?.description == "{ _ in }") } @Test - func testTrailingClosureReferenceExtraction() throws { + func trailingClosureReferenceExtraction() throws { let extractor = try makeExtractor(from: "#MyMacro(operation: perform)") - let extracted = try extractor.trailingClosure(withLabel: "operation") + let extracted = extractor.trailingClosure(withLabel: "operation") #expect(extracted?.description == "perform") } } extension ParameterExtractorTests { + @Test( + arguments: [ + "private", + "fileprivate", + "internal", + "package", + "public", + "open" + ] + ) + func accessControlLevelExtraction(_ level: String) throws { + let extractor = try makeExtractor(from: "#MyMacro(.\(raw: level))") + let extracted = try #require(try extractor.accessControlLevel(withLabel: nil)) + #expect(String(describing: extracted).hasSuffix(level)) + } + @Test - func testRawStringExtraction() throws { + func unexpectedSyntaxWhenPerformingAccessControlLevelExtraction() throws { + let extractor = try makeExtractor(from: "#MyMacro(.didSet)") + #expect(throws: ParameterExtractionError.unexpectedSyntaxType) { + try extractor.accessControlLevel(withLabel: nil) + } + } +} + +extension ParameterExtractorTests { + + @Test + func rawStringExtraction() throws { let extractor = try makeExtractor(from: #"#MyMacro(string: "arg")"#) let extracted = try extractor.rawString(withLabel: "string") #expect(extracted == "arg") } @Test - func testUnexpectedSyntaxWhenPerformingRawStringExtraction() throws { + func unexpectedSyntaxWhenPerformingRawStringExtraction() throws { let extractor = try makeExtractor(from: #"#MyMacro(string: reference.arg)"#) #expect(throws: ParameterExtractionError.unexpectedSyntaxType) { try extractor.rawString(withLabel: "string") @@ -77,34 +116,34 @@ extension ParameterExtractorTests { extension ParameterExtractorTests { + @Test( + arguments: [ + "MainActor", + "SomeType.SomeActor" + ] + ) + func globalActorExtraction(_ isolation: String) throws { + let extractor = try makeExtractor(from: "#MyMacro(isolation: \(raw: isolation).self)") + let extracted = try extractor.globalActorIsolation(withLabel: "isolation") + #expect(extracted?.standardizedIsolationType?.trimmedDescription == isolation) + } + @Test - func testMissingGlobalActorExtraction() throws { + func missingGlobalActorExtraction() throws { let extractor = try makeExtractor(from: "#MyMacro()") let extracted = try extractor.globalActorIsolation(withLabel: "isolation") #expect(extracted == nil) } @Test - func testExplicitNilGlobalActorExtraction() throws { + func explicitNilGlobalActorExtraction() throws { let extractor = try makeExtractor(from: "#MyMacro(isolation: nil)") let extracted = try extractor.globalActorIsolation(withLabel: "isolation") - #expect(extracted == .nonisolated) - } - - @Test( - arguments: [ - "MainActor", - "SomeType.SomeActor" - ] - ) - func testGlobalActorExtraction(isolation: String) throws { - let extractor = try makeExtractor(from: "#MyMacro(isolation: \(raw: isolation).self)") - let extracted = try extractor.globalActorIsolation(withLabel: "isolation") - #expect(extracted?.trimmedType?.description == isolation) + #expect(extracted?.trimmedNonisolatedModifier?.trimmedDescription == "nonisolated") } @Test - func testUnexpectedSyntaxWhenPerformingGlobalActorExtraction() throws { + func unexpectedSyntaxWhenPerformingGlobalActorExtraction() throws { let extractor = try makeExtractor(from: #"#MyMacro(isolation: MainActor.Type)"#) #expect(throws: ParameterExtractionError.unexpectedSyntaxType) { try extractor.globalActorIsolation(withLabel: "isolation") diff --git a/Tests/PrincipleMacrosTests/Parsers/EnumCasesParserTests.swift b/Tests/PrincipleMacrosTests/Parsers/EnumCasesParserTests.swift index d637c7c..98a46a1 100644 --- a/Tests/PrincipleMacrosTests/Parsers/EnumCasesParserTests.swift +++ b/Tests/PrincipleMacrosTests/Parsers/EnumCasesParserTests.swift @@ -15,7 +15,7 @@ internal struct EnumCasesParserTests { private let context = BasicMacroExpansionContext() @Test - func testEnumCase() throws { + func enumCase() throws { let decl: DeclSyntax = """ case myCase """ @@ -25,7 +25,7 @@ internal struct EnumCasesParserTests { } @Test - func testEnumCaseWithUnnamedAssociatedValue() throws { + func enumCaseWithUnnamedAssociatedValue() throws { let decl: DeclSyntax = """ case myCase(Int?) """ @@ -38,7 +38,7 @@ internal struct EnumCasesParserTests { } @Test - func testEnumCaseWithNamedAssociatedValue() throws { + func enumCaseWithNamedAssociatedValue() throws { let decl: DeclSyntax = """ case myCase(values: [String]) """ @@ -51,7 +51,7 @@ internal struct EnumCasesParserTests { } @Test - func testEnumCaseWithManyAssociatedValues() throws { + func enumCaseWithManyAssociatedValues() throws { let decl: DeclSyntax = """ case myCase(value: Int?, [String]) """ diff --git a/Tests/PrincipleMacrosTests/Parsers/PropertiesListTests.swift b/Tests/PrincipleMacrosTests/Parsers/PropertiesListTests.swift index a7808f1..a30ad93 100644 --- a/Tests/PrincipleMacrosTests/Parsers/PropertiesListTests.swift +++ b/Tests/PrincipleMacrosTests/Parsers/PropertiesListTests.swift @@ -40,7 +40,7 @@ internal struct PropertiesListTests { } @Test - func testImmutable() throws { + func immutable() { #expect( list.immutable.map(\.trimmedName.description) == [ "letProp", @@ -52,7 +52,7 @@ internal struct PropertiesListTests { } @Test - func testMutable() throws { + func mutable() { #expect( list.mutable.map(\.trimmedName.description) == [ "varProp", @@ -64,7 +64,7 @@ internal struct PropertiesListTests { } @Test - func testInstance() throws { + func instance() { #expect( list.instance.map(\.trimmedName.description) == [ "letProp", @@ -77,7 +77,7 @@ internal struct PropertiesListTests { } @Test - func testType() throws { + func type() { #expect( list.type.map(\.trimmedName.description) == [ "staticLetProp", @@ -88,7 +88,7 @@ internal struct PropertiesListTests { } @Test - func testStored() throws { + func stored() { #expect( list.stored.map(\.trimmedName.description) == [ "letProp", @@ -101,7 +101,7 @@ internal struct PropertiesListTests { } @Test - func testComputed() throws { + func computed() { #expect( list.computed.map(\.trimmedName.description) == [ "classVarProp", @@ -112,7 +112,7 @@ internal struct PropertiesListTests { } @Test - func testUniqueInferredTypes() throws { + func uniqueInferredTypes() { #expect( list.uniqueInferredTypes.map(\.description) == [ "Optional", @@ -122,7 +122,7 @@ internal struct PropertiesListTests { } @Test - func testWithInferredType() throws { + func withInferredType() { #expect( list.withInferredType(like: "Int?").map(\.trimmedName.description) == [ "letProp", diff --git a/Tests/PrincipleMacrosTests/Parsers/PropertiesParserTests.swift b/Tests/PrincipleMacrosTests/Parsers/PropertiesParserTests.swift index f2c54dc..b5e0b85 100644 --- a/Tests/PrincipleMacrosTests/Parsers/PropertiesParserTests.swift +++ b/Tests/PrincipleMacrosTests/Parsers/PropertiesParserTests.swift @@ -15,15 +15,15 @@ internal struct PropertiesParserTests { private let context = BasicMacroExpansionContext() @Test - func testStoredLet() throws { + func storedLet() throws { let decl: DeclSyntax = """ public internal(set) static let myLet: Int? """ let property = try #require(PropertiesParser.parse(declaration: decl, in: context).first) #expect(property.kind == .stored) #expect(property.mutability == .immutable) - #expect(property.accessControlLevel?.trimmedDescription == "public") - #expect(property.setterAccessControlLevel?.trimmedDescription == "internal") + #expect(property.accessControlLevel == .public) + #expect(property.setterAccessControlLevel == .internal) #expect(property.typeScopeSpecifier?.trimmedDescription == "static") #expect(property.trimmedName.description == "myLet") #expect(property.inferredType.description == "Optional") @@ -32,7 +32,7 @@ internal struct PropertiesParserTests { } @Test - func testStoredVar() throws { + func storedVar() throws { let decl: DeclSyntax = """ private(set) var myVar = "Hello, world!" """ @@ -40,7 +40,7 @@ internal struct PropertiesParserTests { #expect(property.kind == .stored) #expect(property.mutability == .mutable) #expect(property.accessControlLevel == nil) - #expect(property.setterAccessControlLevel?.trimmedDescription == "private") + #expect(property.setterAccessControlLevel == .private) #expect(property.typeScopeSpecifier == nil) #expect(property.trimmedName.description == "myVar") #expect(property.inferredType.description == "String") @@ -49,7 +49,7 @@ internal struct PropertiesParserTests { } @Test - func testStoredVarWithObservers() throws { + func storedVarWithObservers() throws { let decl: DeclSyntax = """ class var myObservedVar = UIView.Constraint.make() { willSet { print("willSet", newValue) } @@ -70,7 +70,7 @@ internal struct PropertiesParserTests { } @Test - func testComputedVar() throws { + func computedVar() throws { let decl: DeclSyntax = """ fileprivate var myComputedVar: [Model] { [1, 2, 3] @@ -79,8 +79,8 @@ internal struct PropertiesParserTests { let property = try #require(PropertiesParser.parse(declaration: decl, in: context).first) #expect(property.kind == .computed) #expect(property.mutability == .immutable) - #expect(property.accessControlLevel?.trimmedDescription == "fileprivate") - #expect(property.setterAccessControlLevel?.trimmedDescription == "fileprivate") + #expect(property.accessControlLevel == .fileprivate) + #expect(property.setterAccessControlLevel == .fileprivate) #expect(property.typeScopeSpecifier == nil) #expect(property.trimmedName.description == "myComputedVar") #expect(property.inferredType.description == "Array") @@ -89,7 +89,7 @@ internal struct PropertiesParserTests { } @Test - func testComputedVarWithSetter() throws { + func computedVarWithSetter() throws { let decl: DeclSyntax = """ static var mySettableVar: [Model]! { get { _storage } diff --git a/Tests/PrincipleMacrosTests/Syntax/CamelCaseNotationTests.swift b/Tests/PrincipleMacrosTests/Syntax/CamelCaseNotationTests.swift new file mode 100644 index 0000000..7f2b0da --- /dev/null +++ b/Tests/PrincipleMacrosTests/Syntax/CamelCaseNotationTests.swift @@ -0,0 +1,73 @@ +// +// CamelCaseNotationTests.swift +// PrincipleMacros +// +// Created by Kamil Strzelecki on 14/11/2025. +// Copyright © 2025 Kamil Strzelecki. All rights reserved. +// + +@testable import PrincipleMacros +import Testing + +internal struct CamelCaseNotationTests { + + @Test( + arguments: [ + ("", []), + ("x", ["x"]), + ("X", ["X"]), + ("URL", ["URL"]), + ("name", ["name"]), + ("Name", ["Name"]), + ("longName", ["long", "Name"]), + ("LongName", ["Long", "Name"]), + ("VERYLongName", ["VERY", "Long", "Name"]), + ("VeryLONGName", ["Very", "LONG", "Name"]), + ("veryLongNAME", ["very", "Long", "NAME"]) + ] + ) + func segments(from string: String, expectation: [String]) { + let notation = CamelCaseNotation(string: string) + #expect(notation.segments.map(\.string) == expectation) + } + + @Test( + arguments: [ + ("", ""), + ("x", "X"), + ("X", "X"), + ("URL", "URL"), + ("name", "Name"), + ("Name", "Name"), + ("longName", "LongName"), + ("LongName", "LongName"), + ("VERYLongName", "VERYLongName"), + ("VeryLONGName", "VeryLONGName"), + ("veryLongNAME", "VeryLongNAME") + ] + ) + func upperCamelCase(from string: String, expectation: String) { + let notation = CamelCaseNotation(string: string) + #expect(notation.joined(as: .upperCamelCase) == expectation) + } + + @Test( + arguments: [ + ("", ""), + ("x", "x"), + ("X", "x"), + ("URL", "url"), + ("name", "name"), + ("Name", "name"), + ("longName", "longName"), + ("LongName", "longName"), + ("VERYLongName", "veryLongName"), + ("VeryLONGName", "veryLONGName"), + ("veryLongNAME", "veryLongNAME") + ] + ) + func lowerCamelCase(from string: String, expectation: String) { + let notation = CamelCaseNotation(string: string) + #expect(notation.joined(as: .lowerCamelCase) == expectation) + } +} diff --git a/Tests/PrincipleMacrosTests/Syntax/ClosureExprSyntaxTests.swift b/Tests/PrincipleMacrosTests/Syntax/ClosureExprSyntaxTests.swift index e778ea5..14d84ff 100644 --- a/Tests/PrincipleMacrosTests/Syntax/ClosureExprSyntaxTests.swift +++ b/Tests/PrincipleMacrosTests/Syntax/ClosureExprSyntaxTests.swift @@ -9,9 +9,9 @@ @testable import PrincipleMacros import Testing -internal struct ClosureExprSyntaxTests { +internal enum ClosureExprSyntaxTests { - struct SingleLine { + enum SingleLine { struct WithoutSignature { @@ -20,7 +20,7 @@ internal struct ClosureExprSyntaxTests { """ @Test - func testExpansion() throws { + func expansion() { let closure = expr.expanded(nestingLevel: 2) let interpolation: ExprSyntax = """ .init( @@ -53,7 +53,7 @@ internal struct ClosureExprSyntaxTests { """ @Test - func testExpansion() throws { + func expansion() { let closure = expr.expanded(nestingLevel: 2) let interpolation: ExprSyntax = """ .init( @@ -94,7 +94,7 @@ internal struct ClosureExprSyntaxTests { """ @Test - func testExpansion() throws { + func expansion() { let closure = expr.expanded(nestingLevel: 2) let interpolation: ExprSyntax = """ .init( diff --git a/Tests/PrincipleMacrosTests/Transformers/ClosureTypeTests.swift b/Tests/PrincipleMacrosTests/Syntax/ClosureTypeSyntaxTests.swift similarity index 84% rename from Tests/PrincipleMacrosTests/Transformers/ClosureTypeTests.swift rename to Tests/PrincipleMacrosTests/Syntax/ClosureTypeSyntaxTests.swift index 0a90d5e..a36dac7 100644 --- a/Tests/PrincipleMacrosTests/Transformers/ClosureTypeTests.swift +++ b/Tests/PrincipleMacrosTests/Syntax/ClosureTypeSyntaxTests.swift @@ -1,5 +1,5 @@ // -// ClosureTypeTests.swift +// ClosureTypeSyntaxTests.swift // PrincipleMacros // // Created by Kamil Strzelecki on 26/01/2025. @@ -9,12 +9,12 @@ @testable import PrincipleMacros import Testing -internal struct ClosureTypeTests { +internal struct ClosureTypeSyntaxTests { @Test - func testUnnamedClosure() throws { + func unnamedClosure() throws { let type: TypeSyntax = "(Int?, [Bool]) async throws -> ()" - let closure = try #require(ClosureType(type)) + let closure = try #require(ClosureTypeSyntax(type)) let parameter0 = try #require(closure.parameters.first) #expect(parameter0.standardizedName.description == "_0") @@ -30,9 +30,9 @@ internal struct ClosureTypeTests { } @Test - func testNamedClosure() throws { + func namedClosure() throws { let type: TypeSyntax = "(_ value: Int!, _ argument: (first: String, [Int])) throws(SomeError) -> String?" - let closure = try #require(ClosureType(type)) + let closure = try #require(ClosureTypeSyntax(type)) let parameter0 = try #require(closure.parameters.first) #expect(parameter0.standardizedName.description == "value") @@ -48,9 +48,9 @@ internal struct ClosureTypeTests { } @Test - func testMixedClosure() throws { + func mixedClosure() throws { let type: TypeSyntax = "(_ value: Bool, String.Key) -> Void" - let closure = try #require(ClosureType(type)) + let closure = try #require(ClosureTypeSyntax(type)) let parameter0 = try #require(closure.parameters.first) #expect(parameter0.standardizedName.description == "value") @@ -66,9 +66,9 @@ internal struct ClosureTypeTests { } @Test - func testAttributedClosure() throws { + func attributedClosure() throws { let type: TypeSyntax = "@Sendable () -> Void" - let closure = try #require(ClosureType(type)) + let closure = try #require(ClosureTypeSyntax(type)) let attribute = try #require(closure.attributes.first) #expect(attribute.trimmedDescription == "@Sendable") } diff --git a/Tests/PrincipleMacrosTests/Syntax/ExprSyntaxTests.swift b/Tests/PrincipleMacrosTests/Syntax/ExprSyntaxTests.swift index ac625c4..f26a851 100644 --- a/Tests/PrincipleMacrosTests/Syntax/ExprSyntaxTests.swift +++ b/Tests/PrincipleMacrosTests/Syntax/ExprSyntaxTests.swift @@ -9,84 +9,84 @@ @testable import PrincipleMacros import Testing -internal struct ExprSyntaxTests { +internal enum ExprSyntaxTests { struct Basic { @Test - func testOptionalLiteral() { + func optionalLiteral() { let expr: ExprSyntax = "Int?" #expect(expr.inferredType?.description == "Optional") } @Test - func testIntegerLiteral() { + func integerLiteral() { let expr: ExprSyntax = "123" #expect(expr.inferredType?.description == "Int") } @Test - func testFloatLiteral() { + func floatLiteral() { let expr: ExprSyntax = "1.23" #expect(expr.inferredType?.description == "Double") } @Test - func testBoolLiteral() { + func boolLiteral() { let expr: ExprSyntax = "false" #expect(expr.inferredType?.description == "Bool") } @Test - func testStringLiteral() { + func stringLiteral() { let expr: ExprSyntax = "\"Hello\"" #expect(expr.inferredType?.description == "String") } @Test - func testArrayLiteral() { + func arrayLiteral() { let expr: ExprSyntax = "[String]" #expect(expr.inferredType?.description == "Array") } @Test - func testDictionaryLiteral() { + func dictionaryLiteral() { let expr: ExprSyntax = "[String: Int]" #expect(expr.inferredType?.description == "Dictionary") } @Test - func testInitializer() { + func initializer() { let expr: ExprSyntax = "UIView()" #expect(expr.inferredType?.description == "UIView") } @Test - func testGenericInitializer() { + func genericInitializer() { let expr: ExprSyntax = "Dictionary()" #expect(expr.inferredType?.description == "Dictionary") } @Test - func testMemberAccess() { + func memberAccess() { let expr: ExprSyntax = "Options.first" #expect(expr.inferredType?.description == "Options") } @Test - func testFunctionCall() { + func functionCall() { let expr: ExprSyntax = "Model.create(arg: true)" #expect(expr.inferredType?.description == "Model") } @Test - func testNestedFunctionCall() { + func nestedFunctionCall() { let expr: ExprSyntax = "Model.Default.create()" #expect(expr.inferredType?.description == "Model.Default") } @Test - func testTypeReference() { + func typeReference() { let expr: ExprSyntax = "Model.self" #expect(expr.inferredType?.description == "Model.Type") } @@ -106,7 +106,7 @@ internal struct ExprSyntaxTests { ) ] ) - func testComposition(expr: String, expectation: String) { + func composition(expr: String, expectation: String) { let expr: ExprSyntax = "\(raw: expr)" #expect(expr.inferredType?.description == expectation) } diff --git a/Tests/PrincipleMacrosTests/Syntax/GlobalActorIsolationTests.swift b/Tests/PrincipleMacrosTests/Syntax/GlobalActorIsolationTests.swift new file mode 100644 index 0000000..f0feeb6 --- /dev/null +++ b/Tests/PrincipleMacrosTests/Syntax/GlobalActorIsolationTests.swift @@ -0,0 +1,142 @@ +// +// GlobalActorIsolationTests.swift +// PrincipleMacros +// +// Created by Kamil Strzelecki on 14/11/2025. +// Copyright © 2025 Kamil Strzelecki. All rights reserved. +// + +@testable import PrincipleMacros +import Testing + +internal enum GlobalActorIsolationTests { + + internal struct Function { + + @Test + func withNonisolatedNonsendingModifier() throws { + let decl: DeclSyntax = "nonisolated(nonsending) func test() {}" + let functionDecl = try #require(decl.as(FunctionDeclSyntax.self)) + let isolation = GlobalActorIsolation.resolved(for: functionDecl, in: []) + #expect(isolation?.trimmedNonisolatedModifier?.trimmedDescription == "nonisolated(nonsending)") + } + } + + internal struct Property { + + @Test + func withGlobalActor() throws { + let decl: DeclSyntax = "@MainActor var test = 123" + let propertyDecl = try #require(decl.as(VariableDeclSyntax.self)) + let isolation = GlobalActorIsolation.resolved(for: propertyDecl, in: []) + #expect(isolation?.standardizedIsolationType?.trimmedDescription == "MainActor") + } + + @Test + func withoutGlobalActor() throws { + let decl: DeclSyntax = "var test = 123" + let propertyDecl = try #require(decl.as(VariableDeclSyntax.self)) + let isolation = GlobalActorIsolation.resolved(for: propertyDecl, in: []) + #expect(isolation == nil) + } + + @Test + func withNonisolatedModifier() throws { + let decl: DeclSyntax = "nonisolated var test = 123" + let propertyDecl = try #require(decl.as(VariableDeclSyntax.self)) + let isolation = GlobalActorIsolation.resolved(for: propertyDecl, in: []) + #expect(isolation?.trimmedNonisolatedModifier?.trimmedDescription == "nonisolated") + } + + @Test + func withoutGlobalActorInStructWithGlobalActor() throws { + let decl: DeclSyntax = "var test = 123" + let propertyDecl = try #require(decl.as(VariableDeclSyntax.self)) + let structDecl: DeclSyntax = "@MainActor struct MyStruct {}" + let lexicalContext = [Syntax(structDecl)] + let isolation = GlobalActorIsolation.resolved(for: propertyDecl, in: lexicalContext) + #expect(isolation?.standardizedIsolationType?.trimmedDescription == "MainActor") + } + + @Test + func withGlobalActorInStructWithGlobalActor() throws { + let decl: DeclSyntax = "@MainActor var test = 123" + let propertyDecl = try #require(decl.as(VariableDeclSyntax.self)) + let structDecl: DeclSyntax = "@MyActor struct MyStruct {}" + let lexicalContext = [Syntax(structDecl)] + let isolation = GlobalActorIsolation.resolved(for: propertyDecl, in: lexicalContext) + #expect(isolation?.standardizedIsolationType?.trimmedDescription == "MainActor") + } + + @Test + func withNonisolatedModifierInStructWithGlobalActor() throws { + let decl: DeclSyntax = "nonisolated var test = 123" + let propertyDecl = try #require(decl.as(VariableDeclSyntax.self)) + let structDecl: DeclSyntax = "@MyActor struct MyStruct {}" + let lexicalContext = [Syntax(structDecl)] + let isolation = GlobalActorIsolation.resolved(for: propertyDecl, in: lexicalContext) + #expect(isolation?.trimmedNonisolatedModifier?.trimmedDescription == "nonisolated") + } + + @Test + func inNestedStructWithGlobalActor() throws { + let decl: DeclSyntax = "var test = 123" + let propertyDecl = try #require(decl.as(VariableDeclSyntax.self)) + let structDecl1: DeclSyntax = "@MainActor struct Inner {}" + let structDecl2: DeclSyntax = "@MyActor struct Outer {}" + let lexicalContext = [Syntax(structDecl1), Syntax(structDecl2)] + let isolation = GlobalActorIsolation.resolved(for: propertyDecl, in: lexicalContext) + #expect(isolation?.standardizedIsolationType?.trimmedDescription == "MainActor") + } + + @Test + func inNestedStructWithoutGlobalActor() throws { + let decl: DeclSyntax = "var test = 123" + let propertyDecl = try #require(decl.as(VariableDeclSyntax.self)) + let structDecl1: DeclSyntax = "struct Inner {}" + let structDecl2: DeclSyntax = "@MyActor struct Outer {}" + let lexicalContext = [Syntax(structDecl1), Syntax(structDecl2)] + let isolation = GlobalActorIsolation.resolved(for: propertyDecl, in: lexicalContext) + #expect(isolation == nil) + } + } + + internal struct Class { + + @Test + func withGlobalActor() throws { + let decl: DeclSyntax = "@MainActor class Model {}" + let classDecl = try #require(decl.as(ClassDeclSyntax.self)) + let isolation = GlobalActorIsolation.resolved(for: classDecl) + #expect(isolation?.standardizedIsolationType?.trimmedDescription == "MainActor") + } + + @Test + func withoutGlobalActor() throws { + let decl: DeclSyntax = "class Model {}" + let classDecl = try #require(decl.as(ClassDeclSyntax.self)) + let isolation = GlobalActorIsolation.resolved(for: classDecl) + #expect(isolation == nil) + } + + @Test + func withoutGlobalActorInStructWithGlobalActor() throws { + let decl: DeclSyntax = "class Model {}" + let classDecl = try #require(decl.as(ClassDeclSyntax.self)) + let structDecl: DeclSyntax = "@MyActor struct MyStruct {}" + let lexicalContext = [Syntax(structDecl)] + let isolation = GlobalActorIsolation.resolved(for: classDecl, in: lexicalContext) + #expect(isolation == nil) + } + + @Test + func withGlobalActorInStructWithGlobalActor() throws { + let decl: DeclSyntax = "@MainActor class Model {}" + let classDecl = try #require(decl.as(ClassDeclSyntax.self)) + let structDecl: DeclSyntax = "@MyActor struct MyStruct {}" + let lexicalContext = [Syntax(structDecl)] + let isolation = GlobalActorIsolation.resolved(for: classDecl, in: lexicalContext) + #expect(isolation?.standardizedIsolationType?.trimmedDescription == "MainActor") + } + } +} diff --git a/Tests/PrincipleMacrosTests/Syntax/TypeSyntaxTests.swift b/Tests/PrincipleMacrosTests/Syntax/TypeSyntaxTests.swift index e252843..a2ab13c 100644 --- a/Tests/PrincipleMacrosTests/Syntax/TypeSyntaxTests.swift +++ b/Tests/PrincipleMacrosTests/Syntax/TypeSyntaxTests.swift @@ -9,60 +9,60 @@ @testable import PrincipleMacros import Testing -internal struct TypeSyntaxTests { +internal enum TypeSyntaxTests { struct Basic { @Test - func testOptionalLiteral() { + func optionalLiteral() { let type: TypeSyntax = "Int?" #expect(type.standardized.description == "Optional") } @Test - func testImplicitlyUnwrappedOptionalLiteral() { + func implicitlyUnwrappedOptionalLiteral() { let type: TypeSyntax = "String!" #expect(type.standardized.description == "Optional") } @Test - func testArrayLiteral() { + func arrayLiteral() { let type: TypeSyntax = "[String]" #expect(type.standardized.description == "Array") } @Test - func testDictionaryLiteral() { + func dictionaryLiteral() { let type: TypeSyntax = "[String: Int]" #expect(type.standardized.description == "Dictionary") } @Test - func testBasicType() { + func basicType() { let type: TypeSyntax = "UIView" #expect(type.standardized.description == "UIView") } @Test - func testMemberType() { + func memberType() { let type: TypeSyntax = "UIView.Constraints" #expect(type.standardized.description == "UIView.Constraints") } @Test - func testGenericType() { + func genericType() { let type: TypeSyntax = "Cache" #expect(type.standardized.description == "Cache") } @Test - func testVoidType() { + func voidType() { let type: TypeSyntax = "()" #expect(type.standardized.description == "Void") } @Test - func testTupleType() { + func tupleType() { let type: TypeSyntax = "(_ first: String, second: Int, Bool)" #expect(type.standardized.description == "(_ first: String, second: Int, Bool)") } @@ -82,7 +82,7 @@ internal struct TypeSyntaxTests { ) ] ) - func testComposition(type: String, expectation: String) { + func composition(type: String, expectation: String) { let type: TypeSyntax = "\(raw: type)" #expect(type.standardized.description == expectation) }