-
Notifications
You must be signed in to change notification settings - Fork 441
/
Copy pathDeclNameLocation.swift
175 lines (151 loc) · 7.18 KB
/
DeclNameLocation.swift
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
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
//===----------------------------------------------------------------------===//
//
// This source file is part of the Swift.org open source project
//
// Copyright (c) 2014 - 2023 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
/// The location of a declaration name, resolved by ``NameMatcher`` and that can
/// be used to rename the declaration name and, if it is a function, its
/// argument labels.
@_spi(Compiler)
public struct DeclNameLocation: Equatable {
/// A single argument inside a ``DeclNameLocation``.
public enum Argument: Equatable {
/// The argument has an explicit label.
///
/// The ranges are the ranges of the first name and second name, both without trivia.
///
/// ## Examples
/// - `a` in `func foo(a: Int) {}`
/// - `a` and `b` in `func foo(a b: Int) {}`
/// - `additionalTrailingClosure:` in `foo {} additionalTrailingClosure: {}`
/// - We cannot use `labeledCall` here because when changing the parameter label to `_`, we need to change the
/// call to `foo {} _: {}`. If we used `labeledCall`, the label would be removed, leaving us with `foo {} {}`.
case labeled(firstName: Range<AbsolutePosition>, secondName: Range<AbsolutePosition>?)
/// The argument of a call.
///
/// The range of the label does not include trivia.
///
/// The range of the colon *does* include trivia. This is so we can remove the colon including the space after it
/// when changing a labeled parameter to an unlabeled parameter.
///
/// ## Examples
/// - `a` and `:` in `foo(a: 1)`
case labeledCall(label: Range<AbsolutePosition>, colon: Range<AbsolutePosition>)
/// The argument is unlabeled (like `foo(1)`).
///
/// The position points to the position of the argument after skipping leading trivia.
case unlabeled(argumentPosition: AbsolutePosition)
static func labeled(firstName: TokenSyntax, secondName: TokenSyntax?) -> Argument {
return .labeled(firstName: firstName.rangeWithoutTrivia, secondName: secondName?.rangeWithoutTrivia)
}
static func labeledCall(label: TokenSyntax, colon: TokenSyntax) -> Argument {
return .labeledCall(label: label.rangeWithoutTrivia, colon: colon.position..<colon.endPosition)
}
static func unlabeled(argument: some SyntaxProtocol) -> Argument {
return .unlabeled(argumentPosition: argument.positionAfterSkippingLeadingTrivia)
}
public var range: Range<AbsolutePosition> {
switch self {
case .labeled(let firstName, let secondName):
let endPosition = secondName?.upperBound ?? firstName.upperBound
return firstName.lowerBound..<endPosition
case .labeledCall(label: let label, colon: let colon):
return label.lowerBound..<colon.upperBound
case .unlabeled(argumentPosition: let argumentPosition):
return argumentPosition..<argumentPosition
}
}
/// Shift the ranges `utf8Offset` bytes to the right, ie. add `utf8Offset` to the upper and lower bound.
func advanced(by utf8Offset: Int) -> DeclNameLocation.Argument {
switch self {
case .labeled(firstName: let firstName, secondName: let secondName):
return .labeled(firstName: firstName.advanced(by: utf8Offset), secondName: secondName?.advanced(by: utf8Offset))
case .labeledCall(label: let label, colon: let colon):
return .labeledCall(label: label.advanced(by: utf8Offset), colon: colon.advanced(by: utf8Offset))
case .unlabeled(argumentPosition: let argumentPosition):
return .unlabeled(argumentPosition: argumentPosition.advanced(by: utf8Offset))
}
}
}
/// The arguments of a ``DeclNameLocation``.
public enum Arguments: Equatable {
/// The location doesn't have any arguments
case noArguments
/// A function call, like `foo(a: 2)`
///
/// If the call contains a trailing closure, `firstTrailingClosureIndex` is the index to the first argument that
/// is a trailing closure, otherwise `firstTrailingClosureIndex` is `nil`.
case call([Argument], firstTrailingClosureIndex: Int?)
/// The parameter of a function declaration, like `func foo(a b: Int)`
case parameters([Argument])
/// An enum case declaration like `case myCase(label: String)`.
///
/// For enum case parameters the argument label is removed when set to empty instead of being changed to eg.
/// `myCase(_ label: String)`
case enumCaseParameters([Argument])
/// Same as `param` but the parameters can't be collapsed if they are the same. This is the case for subscript
/// declarations.
///
/// For example, `subscript(a a: Int)` requires both the first and the second parameter label.
case noncollapsibleParameters([Argument])
/// The argument label to disambiguate multiple functions with the same base name, like `foo(a:)`.
case selector([Argument])
/// Shift the ranges `utf8Offset` bytes to the right, ie. add `utf8Offset` to the upper and lower bound.
func advanced(by utf8Offset: Int) -> DeclNameLocation.Arguments {
switch self {
case .noArguments:
return .noArguments
case .call(let arguments, firstTrailingClosureIndex: let firstTrailingClosureIndex):
return .call(
arguments.map { $0.advanced(by: utf8Offset) },
firstTrailingClosureIndex: firstTrailingClosureIndex
)
case .parameters(let parameters):
return .parameters(parameters.map { $0.advanced(by: utf8Offset) })
case .enumCaseParameters(let parameters):
return .enumCaseParameters(parameters.map { $0.advanced(by: utf8Offset) })
case .noncollapsibleParameters(let parameters):
return .noncollapsibleParameters(parameters.map { $0.advanced(by: utf8Offset) })
case .selector(let arguments):
return .selector(arguments.map { $0.advanced(by: utf8Offset) })
}
}
}
public enum Context {
/// The name occurs anywhere in normal Swift code.
case `default`
/// The name occurs inside a `#selector`.
case selector
/// The name occurs inside a comment.
case comment
/// The name occurs inside a string literal.
case stringLiteral
}
/// The range of the base name, without trivia.
///
/// ## Examples
/// - For `foo(a: 1)`, the range of `foo`.
/// - For `myArray[1]`, the base name range is `[`.
/// - For a variable that is a closure, and a call `closure(1)`, the call has the base name `(`.
public let baseNameRange: Range<AbsolutePosition>
public let arguments: Arguments
/// The context in which the name occurs, eg. in code, a comment or a string
/// literal.
public let context: Context
/// If the name occurs in an inactive `#if` region, `false`, otherwise `true`.
///
/// - Note: Currently `NameMatcher` does not evaluate `#if` conditions and all
/// occurances within `#if`, `#elseif` or `#else` blocks are considered inactive.
public let isActive: Bool
}