-
Notifications
You must be signed in to change notification settings - Fork 498
Expand file tree
/
Copy pathMoveMembersToExtension.swift
More file actions
101 lines (84 loc) · 3.23 KB
/
MoveMembersToExtension.swift
File metadata and controls
101 lines (84 loc) · 3.23 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
//===----------------------------------------------------------------------===//
//
// This source file is part of the Swift.org open source project
//
// Copyright (c) 2014 - 2026 Apple Inc. and the Swift project authors
// Licensed under Apache License v2.0 with Runtime Library Exception
//
// See https://swift.org/LICENSE.txt for license information
// See https://swift.org/CONTRIBUTORS.txt for the list of Swift project authors
//
//===----------------------------------------------------------------------===//
#if compiler(>=6)
public import SwiftSyntax
#else
import SwiftSyntax
#endif
public struct MoveMembersToExtension: SyntaxRefactoringProvider {
public struct Context {
public let range: Range<AbsolutePosition>
public init(range: Range<AbsolutePosition>) {
self.range = range
}
}
public static func refactor(syntax: SourceFileSyntax, in context: Context) throws -> SourceFileSyntax {
guard
let statement = syntax.statements.first(where: { $0.item.range.contains(context.range) }),
let decl = statement.item.asProtocol(NamedDeclSyntax.self),
let declGroup = statement.item.asProtocol(DeclGroupSyntax.self),
let statementIndex = syntax.statements.index(of: statement)
else {
throw RefactoringNotApplicableError("Type declaration not found")
}
var selectedMembers = [MemberBlockItemSyntax]()
var selectedIdentifiers = [SyntaxIdentifier]()
var notMovedMembers: [MemberBlockItemSyntax] = []
declGroup.memberBlock.members.forEach {
if context.range.overlaps($0.trimmedRange) {
if validateMember($0) {
selectedMembers.append($0)
selectedIdentifiers.append($0.id)
} else {
notMovedMembers.append($0)
}
}
}
guard !selectedMembers.isEmpty else {
throw RefactoringNotApplicableError("No members to move")
}
var updatedDeclGroup = declGroup
updatedDeclGroup.memberBlock.members = declGroup.memberBlock.members.filter { !selectedIdentifiers.contains($0.id) }
let updatedItem = statement.with(\.item, .decl(DeclSyntax(updatedDeclGroup)))
let extensionMemberBlockSyntax = declGroup.memberBlock.with(\.members, MemberBlockItemListSyntax(selectedMembers))
var declName = decl.name
declName.trailingTrivia = declName.trailingTrivia.merging(.space)
let extensionDecl = ExtensionDeclSyntax(
leadingTrivia: .newlines(2),
extendedType: IdentifierTypeSyntax(
leadingTrivia: .space,
name: declName
),
memberBlock: extensionMemberBlockSyntax
)
var syntax = syntax
syntax.statements[statementIndex] = updatedItem
syntax.statements.insert(
CodeBlockItemSyntax(item: .decl(DeclSyntax(extensionDecl))),
at: syntax.statements.index(after: statementIndex)
)
return syntax
}
private static func validateMember(_ member: MemberBlockItemSyntax) -> Bool {
if member.decl.is(AccessorDeclSyntax.self) || member.decl.is(DeinitializerDeclSyntax.self)
|| member.decl.is(EnumCaseDeclSyntax.self)
{
return false
}
if let varDecl = member.decl.as(VariableDeclSyntax.self),
varDecl.bindings.contains(where: { $0.accessorBlock == nil || $0.initializer != nil })
{
return false
}
return true
}
}