Skip to content

Commit 9cf1c7f

Browse files
committed
Rewrite NameMatcher in Swift
1 parent d9ae448 commit 9cf1c7f

File tree

5 files changed

+807
-1
lines changed

5 files changed

+807
-1
lines changed

Package.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -159,7 +159,7 @@ let package = Package(
159159

160160
.target(
161161
name: "SwiftIDEUtils",
162-
dependencies: ["SwiftSyntax"],
162+
dependencies: ["SwiftSyntax", "SwiftParser"],
163163
exclude: ["CMakeLists.txt"]
164164
),
165165

Sources/SwiftIDEUtils/CMakeLists.txt

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,11 +7,14 @@
77
# See http://swift.org/CONTRIBUTORS.txt for Swift project authors
88

99
add_swift_syntax_library(SwiftIDEUtils
10+
DeclNameLocation.swift
11+
NameMatcher.swift
1012
SwiftIDEUtilsCompatibility.swift
1113
Syntax+Classifications.swift
1214
SyntaxClassification.swift
1315
SyntaxClassifier.swift
1416
)
1517

1618
target_link_swift_syntax_libraries(SwiftIDEUtils PUBLIC
19+
SwiftParser
1720
SwiftSyntax)
Lines changed: 130 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,130 @@
1+
//===----------------------------------------------------------------------===//
2+
//
3+
// This source file is part of the Swift.org open source project
4+
//
5+
// Copyright (c) 2014 - 2023 Apple Inc. and the Swift project authors
6+
// Licensed under Apache License v2.0 with Runtime Library Exception
7+
//
8+
// See https://swift.org/LICENSE.txt for license information
9+
// See https://swift.org/CONTRIBUTORS.txt for the list of Swift project authors
10+
//
11+
//===----------------------------------------------------------------------===//
12+
13+
import SwiftSyntax
14+
15+
/// The location of a declaration name, resolved by ``NameMatcher`` and that can
16+
/// be used to rename the declaration name and, if it is a function, its
17+
/// argument labels.
18+
@_spi(Compiler)
19+
public struct DeclNameLocation: Equatable {
20+
/// A single argument inside a ``DeclNameLocation``.
21+
public enum Argument: Equatable {
22+
/// The argument has an explicit label.
23+
///
24+
/// The ranges are the ranges of the first name, second name and colon token, each without trivia.
25+
///
26+
/// ## Examples
27+
/// - `a` in `func foo(a: Int) {}`
28+
/// - `a` and `b` in `func foo(a b: Int) {}`
29+
case labeled(firstName: Range<AbsolutePosition>, secondName: Range<AbsolutePosition>?)
30+
31+
/// The argument of a call.
32+
///
33+
/// The range of the label does not include trivia. The range of the colon *does* include trivia.
34+
///
35+
/// ## Examples
36+
/// - `a` and `:` in `foo(a: 1)`
37+
case labeledCall(label: Range<AbsolutePosition>, colon: Range<AbsolutePosition>)
38+
39+
/// The argument is unlabeled (like `foo(1)`).
40+
///
41+
/// The position points to the position of the argument after skipping leading trivia.
42+
case unlabeled(argumentPosition: AbsolutePosition)
43+
44+
static func labeled(firstName: TokenSyntax, secondName: TokenSyntax?) -> Argument {
45+
return .labeled(firstName: firstName.rangeWithoutTrivia, secondName: secondName?.rangeWithoutTrivia)
46+
}
47+
48+
static func labeledCall(label: TokenSyntax, colon: TokenSyntax) -> Argument {
49+
// FIXME: (NameMatcher) The `labeledCall` case is problematic for two reasons
50+
// 1. The fact that `colon` includes trivia is inconsistent with the associated values in `label` and `labeledCall`
51+
// 2. If `colon` didn't need to contain trivia, we wouldn't need the `labeledCall` case at all.
52+
// See if we can unify `labeledCall` and `labeled`.
53+
return .labeledCall(label: label.rangeWithoutTrivia, colon: colon.position..<colon.endPosition)
54+
}
55+
56+
static func unlabeled(argument: some SyntaxProtocol) -> Argument {
57+
return .unlabeled(argumentPosition: argument.positionAfterSkippingLeadingTrivia)
58+
}
59+
60+
public var range: Range<AbsolutePosition> {
61+
switch self {
62+
case .labeled(let firstName, let secondName):
63+
let endPosition = secondName?.upperBound ?? firstName.upperBound
64+
return firstName.lowerBound..<endPosition
65+
case .labeledCall(label: let label, colon: let colon):
66+
return label.lowerBound..<colon.upperBound
67+
case .unlabeled(argumentPosition: let argumentPosition):
68+
return argumentPosition..<argumentPosition
69+
}
70+
}
71+
}
72+
73+
/// The arguments of a ``DeclNameLocation``.
74+
public enum Arguments: Equatable {
75+
/// The location doesn't have any arguments
76+
case noArguments
77+
78+
/// A function call, like `foo(a: 2)`
79+
///
80+
/// If the call contains a trailing closure, `firstTrailingClosureIndex` is the index to the first argument that
81+
/// is a trailing closure, otherwise `firstTrailingClosureIndex` is `nil`.
82+
case call([Argument], firstTrailingClosureIndex: Int?)
83+
84+
/// The parameter of a function declaration, like `func foo(a b: Int)`
85+
case parameters([Argument])
86+
87+
/// Same as `param` but the parameters can't be collapsed if they are the same. This is the case for subscript
88+
/// declarations.
89+
///
90+
/// For example, `subscript(a a: Int)` requires both the first and the second parameter label.
91+
case noncollapsibleParameters([Argument])
92+
93+
/// The argument label to disambiguate multiple functions with the same base name, like `foo(a:)`.
94+
case selector([Argument])
95+
}
96+
97+
public enum Context {
98+
/// The name occurs anywhere in normal Swift code.
99+
case `default`
100+
101+
/// The name occurs inside a `#selector`.
102+
case selector
103+
104+
/// The name occurs inside a comment.
105+
case comment
106+
107+
/// The name occurs inside a string literal.
108+
case stringLiteral
109+
}
110+
111+
/// The range of the base name, without trivia.
112+
///
113+
/// ## Examples
114+
/// - For `foo(a: 1)`, the range of `foo`.
115+
/// - For `myArray[1]`, the base name range is `[`.
116+
/// - For a variable that is a closure, and a call `closure(1)`, the call has the base name `(`.
117+
public let baseNameRange: Range<AbsolutePosition>
118+
119+
public let arguments: Arguments
120+
121+
/// The context in which the name occurs, eg. in code, a comment or a string
122+
/// literal.
123+
public let context: Context
124+
125+
/// If the name occurs in an inactive `#if` region, `false`, otherwise `true`.
126+
///
127+
/// - Note: Currently `NameMatcher` does not evaluate `#if` conditions and all
128+
/// occurances within `#if`, `#elseif` or `#else` blocks are considered inactive.
129+
public let isActive: Bool
130+
}

0 commit comments

Comments
 (0)