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

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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