Skip to content
Merged
Show file tree
Hide file tree
Changes from 8 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .swiftlint.tests.yml
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
disabled_rules:
- function_body_length
- type_body_length
- no_magic_numbers
17 changes: 7 additions & 10 deletions .swiftlint.yml
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ opt_in_rules:
- anonymous_argument_in_multiline_closure
- array_init
- async_without_await
# attributes
# attributes (swiftformat)
# balanced_xctest_lifecycle
# closure_body_length
- closure_end_indentation
Expand Down Expand Up @@ -55,9 +55,9 @@ opt_in_rules:
- ibinspectable_in_extension
- identical_operands
- implicit_return
# implicitly_unwrapped_optional
- implicitly_unwrapped_optional
# incompatible_concurrency_annotation
# indentation_width
# indentation_width (swiftformat)
- joined_default_parameter
- last_where
- legacy_multiple
Expand All @@ -67,7 +67,7 @@ opt_in_rules:
- local_doc_comment
- lower_acl_than_parent
# missing_docs
# modifier_order
# modifier_order (swiftformat)
- multiline_arguments
- multiline_arguments_brackets
- multiline_function_chains
Expand Down Expand Up @@ -144,7 +144,7 @@ opt_in_rules:
# vertical_whitespace_opening_braces
- weak_delegate
- xct_specific_matcher
# yoda_condition
# yoda_condition (swiftformat)

analyzer_rules:
- capture_variable
Expand Down Expand Up @@ -173,6 +173,7 @@ identifier_name:
excluded: [id, ui, x, y, z, dx, dy, dz]

line_length:
ignores_multiline_strings: true
ignores_comments: true

nesting:
Expand All @@ -189,10 +190,6 @@ type_contents_order:
order: [[case], [type_alias, associated_type], [subtype], [type_property], [instance_property], [ib_inspectable], [ib_outlet], [initializer], [deinitializer], [type_method], [view_life_cycle_method], [ib_action, ib_segue_action], [other_method], [subscript]]

custom_rules:
global_actor_attribute_order:
name: "Global actor attribute order"
message: "Global actor should be the first attribute."
regex: "(?-s)(@.+[^,\\s]\\s+@.*Actor\\s)"
sendable_attribute_order:
name: "Sendable attribute order"
message: "Sendable should be the first attribute."
Expand All @@ -204,4 +201,4 @@ custom_rules:
empty_line_after_type_declaration:
name: "Empty line after type declaration"
message: "Type declaration should start with an empty line."
regex: "( |^)(actor|class|struct|enum|protocol|extension) (?!var)[^\\{]*? \\{(?!\\s*\\}) *\\n? *\\S"
regex: "( |^)(actor|class|struct|enum|protocol|extension) (?!var)[^\\n\\{]*? \\{(?!\\s*\\}) *\\n? *\\S"
35 changes: 35 additions & 0 deletions Sources/PrincipleMacros/Diagnostics/FixIt.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
//
// FixIt.swift
// PrincipleMacros
//
// Created by Kamil Strzelecki on 30/11/2025.
// Copyright © 2025 Kamil Strzelecki. All rights reserved.
//

import SwiftSyntaxMacros

extension FixIt {

public static func remove(
message: String,
oldNode: some SyntaxProtocol
) -> Self {
.replace(
message: MacroExpansionFixItMessage(message),
oldNode: oldNode,
newNode: "\(oldNode.leadingTrivia)" as TokenSyntax
)
}

public static func replace(
message: String,
oldNode: some SyntaxProtocol,
newNode: some SyntaxProtocol
) -> Self {
.replace(
message: MacroExpansionFixItMessage(message),
oldNode: oldNode,
newNode: newNode.withTrivia(from: oldNode)
)
}
}
3 changes: 1 addition & 2 deletions Sources/PrincipleMacros/Parameters/ParameterExtractor.swift
Original file line number Diff line number Diff line change
Expand Up @@ -187,8 +187,7 @@ extension ParameterExtractor {
}

if expression.is(NilLiteralExprSyntax.self) {
let isolation = DeclModifierSyntax(name: .keyword(.nonisolated))
return .nonisolated(trimmedModifer: isolation)
return .nonisolated
}

guard let memberAccessExpression = MemberAccessExprSyntax(expression),
Expand Down
28 changes: 5 additions & 23 deletions Sources/PrincipleMacros/Parsers/Common/Parser.swift
Original file line number Diff line number Diff line change
Expand Up @@ -20,37 +20,19 @@ public protocol Parser {
extension Parser {

public static func parse(
ifConfig: IfConfigDeclSyntax
declarationGroup: some DeclGroupSyntax
) throws -> ResultsCollection {
try ResultsCollection(
ifConfig.clauses.flatMap { clause in
switch clause.elements {
case let .decls(members):
try parse(members: members)
default:
ResultsCollection()
}
}
)
let members = declarationGroup.memberBlock.members.flattened
return try parse(members: members)
}

public static func parse(
members: MemberBlockItemListSyntax
members: some Sequence<MemberBlockItemSyntax>
) throws -> ResultsCollection {
try ResultsCollection(
members.flatMap { member in
if let ifConfig = member.decl.as(IfConfigDeclSyntax.self) {
try parse(ifConfig: ifConfig)
} else {
try parse(declaration: member.decl)
}
try parse(declaration: member.decl)
}
)
}

public static func parse(
memberBlock: MemberBlockSyntax
) throws -> ResultsCollection {
try parse(members: memberBlock.members)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ public enum EnumCasesParser: Parser {
declaration: some DeclSyntaxProtocol
) -> EnumCasesList {
guard let declaration = EnumCaseDeclSyntax(declaration) else {
return .init()
return EnumCasesList()
}

return EnumCasesList(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ public enum PropertiesParser: Parser {
declaration: some DeclSyntaxProtocol
) throws -> PropertiesList {
guard let declaration = VariableDeclSyntax(declaration) else {
return .init()
return PropertiesList()
}

return try PropertiesList(
Expand Down Expand Up @@ -42,4 +42,22 @@ public enum PropertiesParser: Parser {
}
)
}

public static func parseStandalone(
declaration: some DeclSyntaxProtocol
) throws -> Property? {
let properties = try parse(declaration: declaration)
guard let first = properties.first else {
return nil
}

guard properties.count == 1 else {
throw DiagnosticsError(
node: declaration,
message: "Property must have only one binding"
)
}

return first
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
// Copyright © 2025 Kamil Strzelecki. All rights reserved.
//

public enum AccessControlLevel: Int, Hashable, CaseIterable {
public enum AccessControlLevel: Int, Hashable, CaseIterable, Sendable {

case `private`
case `fileprivate`
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,14 @@ public enum GlobalActorIsolation {
case nonisolated(trimmedModifer: DeclModifierSyntax)
case isolated(standardizedType: TypeSyntax)

public static var nonisolated: Self {
let modifier = DeclModifierSyntax(name: .keyword(.nonisolated))
return .nonisolated(trimmedModifer: modifier)
}
}

extension GlobalActorIsolation {

public var trimmedNonisolatedModifier: DeclModifierSyntax? {
switch self {
case let .nonisolated(trimmedModifer):
Expand Down Expand Up @@ -63,7 +71,7 @@ extension GlobalActorIsolation {
}

private static func _resolved(
in fullContext: some Collection<Syntax>,
in fullContext: some Sequence<Syntax>,
preferred: Self?
) -> Self? {
if let preferred {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ extension AttributedTypeSyntax {

public init(
globalActorIsolation: GlobalActorIsolation?,
baseType: some TypeSyntaxProtocol
baseType: TypeSyntax
) {
let specifiers: TypeSpecifierListSyntax =
switch globalActorIsolation {
Expand All @@ -35,4 +35,14 @@ extension AttributedTypeSyntax {
baseType: baseType
)
}

public init(
globalActorIsolation: GlobalActorIsolation?,
baseType: some TypeSyntaxProtocol
) {
self.init(
globalActorIsolation: globalActorIsolation,
baseType: TypeSyntax(baseType)
)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
// Copyright © 2025 Kamil Strzelecki. All rights reserved.
//

import SwiftSyntax
import SwiftSyntaxMacros

extension IfConfigDeclSyntax {

Expand Down Expand Up @@ -65,6 +65,17 @@ extension MemberBlockItemListSyntax {
}
return nil
}

@MemberBlockItemListBuilder
public func withIfConfigIfPresent(
from declaration: some DeclSyntaxProtocol
) -> Self {
if let ifConfig = declaration.applyEnclosingIfConfig(to: .decls(self)) {
ifConfig
} else {
self
}
}
}

extension MemberBlockItemSyntax {
Expand All @@ -83,6 +94,20 @@ extension MemberBlockItemSyntax {
}
}

extension CodeBlockItemListSyntax {

@CodeBlockItemListBuilder
public func withIfConfigIfPresent(
from declaration: some DeclSyntaxProtocol
) -> Self {
if let ifConfig = declaration.applyEnclosingIfConfig(to: .statements(self)) {
ifConfig
} else {
self
}
}
}

extension DeclSyntaxProtocol {

public var enclosingIfConfig: IfConfigDeclSyntax? {
Expand All @@ -92,23 +117,11 @@ extension DeclSyntaxProtocol {
return nil
}

public func applyingEnclosingIfConfig(
to members: MemberBlockItemListSyntax
) -> IfConfigDeclSyntax? {
applyingEnclosingIfConfig(to: .decls(members.withLeadingNewline))
}

public func applyingEnclosingIfConfig(
to statements: CodeBlockItemListSyntax
) -> IfConfigDeclSyntax? {
applyingEnclosingIfConfig(to: .statements(statements.withLeadingNewline))
}

private func applyingEnclosingIfConfig(
fileprivate func applyEnclosingIfConfig(
to elements: IfConfigClauseSyntax.Elements
) -> IfConfigDeclSyntax? {
if var ancestor = parent?.parent?.parent?.as(IfConfigClauseSyntax.self) {
ancestor = ancestor.with(\.elements, elements)
ancestor = ancestor.with(\.elements, elements.withLeadingNewline)
return ancestor.enclosingIfConfig
} else {
return nil
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
//
// MemberBlockItemListSyntax.swift
// PrincipleMacros
//
// Created by Kamil Strzelecki on 30/11/2025.
// Copyright © 2025 Kamil Strzelecki. All rights reserved.
//

import SwiftSyntaxMacros

extension MemberBlockItemListSyntax {

public var flattened: some Sequence<MemberBlockItemSyntax> {
lazy.flatMap { member in
if let ifConfig = member.decl.as(IfConfigDeclSyntax.self) {
AnySequence(ifConfig.flattenedMembers)
} else {
AnySequence(CollectionOfOne(member))
}
}
}
}

extension IfConfigDeclSyntax {

public var flattenedMembers: some Sequence<MemberBlockItemSyntax> {
clauses.lazy.flatMap(\.flattenedMembers)
}
}

extension IfConfigClauseSyntax {

public var flattenedMembers: some Sequence<MemberBlockItemSyntax> {
switch elements {
case let .decls(members):
AnySequence(members.flattened)
default:
AnySequence(EmptyCollection<MemberBlockItemSyntax>())
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,23 @@ extension WithModifiersSyntax {
}
}

extension DeclModifierListSyntax {

public func withAccessControlLevel(_ level: AccessControlLevel?) -> Self {
var modifiers = filter { modifier in
modifier.accessControlLevel == nil
&& modifier.setterAccessControlLevel == nil
}

if let level {
let modifier = DeclModifierSyntax(name: level.tokenSyntax)
modifiers.insert(modifier, at: modifiers.startIndex)
}

return modifiers
}
}

extension DeclModifierSyntax {

public var accessControlLevel: AccessControlLevel? {
Expand Down
Loading
Loading