@@ -34,7 +34,7 @@ protocol Scope {
3434 var sourceSyntax : SyntaxProtocol { get }
3535
3636 /// Returns the declaration `name` refers to at a particular syntax node location.
37- func getDeclarationFor ( name: String , at syntax: SyntaxProtocol ) -> Syntax ?
37+ func getDeclarationsFor ( name: String , at syntax: SyntaxProtocol ) -> [ Syntax ]
3838}
3939
4040extension Scope {
@@ -46,43 +46,33 @@ extension Scope {
4646 return syntax? . scope
4747 }
4848 }
49-
49+
5050 // MARK: - lookupLabeledStmts
51-
51+
5252 /// Given syntax node position, returns all available labeled statements.
5353 func lookupLabeledStmts( at syntax: SyntaxProtocol ) -> [ LabeledStmtSyntax ] {
54- return lookupLabeledStmtsHelper ( at: syntax. parent)
55- }
56-
57- /// Helper method to recursively collect labeled statements from the syntax node's parents.
58- private func lookupLabeledStmtsHelper( at syntax: Syntax ? ) -> [ LabeledStmtSyntax ] {
59- guard let syntax, !syntax. is ( MemberBlockSyntax . self) else { return [ ] }
60- if let labeledStmtSyntax = syntax. as ( LabeledStmtSyntax . self) {
61- return [ labeledStmtSyntax] + lookupLabeledStmtsHelper( at: labeledStmtSyntax. parent)
62- } else {
63- return lookupLabeledStmtsHelper ( at: syntax. parent)
64- }
54+ return walkParentTreeUpToFunctionBoundary ( at: syntax. parent, collect: LabeledStmtSyntax . self)
6555 }
66-
56+
6757 // MARK: - lookupFallthroughSourceAndDest
68-
58+
6959 /// Given syntax node position, returns the current switch case and it's fallthrough destination.
7060 func lookupFallthroughSourceAndDestination( at syntax: SyntaxProtocol ) -> ( SwitchCaseSyntax ? , SwitchCaseSyntax ? ) {
71- guard let originalSwitchCase = syntax . ancestorOrSelf ( mapping : { $0 . as ( SwitchCaseSyntax . self) } ) else {
61+ guard let originalSwitchCase = walkParentTreeUpToFunctionBoundary ( at : Syntax ( syntax ) , collect : SwitchCaseSyntax . self) else {
7262 return ( nil , nil )
7363 }
74-
64+
7565 let nextSwitchCase = lookupNextSwitchCase ( at: originalSwitchCase)
76-
66+
7767 return ( originalSwitchCase, nextSwitchCase)
7868 }
79-
69+
8070 /// Given a switch case, returns the case that follows according to the parent.
8171 private func lookupNextSwitchCase( at switchCaseSyntax: SwitchCaseSyntax ) -> SwitchCaseSyntax ? {
8272 guard let switchCaseListSyntax = switchCaseSyntax. parent? . as ( SwitchCaseListSyntax . self) else { return nil }
83-
73+
8474 var visitedOriginalCase = false
85-
75+
8676 for child in switchCaseListSyntax. children ( viewMode: . sourceAccurate) {
8777 if let thisCase = child. as ( SwitchCaseSyntax . self) {
8878 if thisCase. id == switchCaseSyntax. id {
@@ -92,21 +82,21 @@ extension Scope {
9282 }
9383 }
9484 }
95-
85+
9686 return nil
9787 }
98-
88+
9989 // MARK: - lookupCatchNode
100-
90+
10191 /// Given syntax node position, returns the closest ancestor catch node.
10292 func lookupCatchNode( at syntax: Syntax ) -> Syntax ? {
10393 return lookupCatchNodeHelper ( at: syntax, traversedCatchClause: false )
10494 }
105-
95+
10696 /// Given syntax node location, finds where an error could be caught. If set to `true`, `traverseCatchClause`lookup will skip the next do statement.
10797 private func lookupCatchNodeHelper( at syntax: Syntax ? , traversedCatchClause: Bool ) -> Syntax ? {
10898 guard let syntax else { return nil }
109-
99+
110100 switch syntax. as ( SyntaxEnum . self) {
111101 case . doStmt:
112102 if traversedCatchClause {
@@ -122,14 +112,47 @@ extension Scope {
122112 } else {
123113 return lookupCatchNodeHelper ( at: syntax. parent, traversedCatchClause: traversedCatchClause)
124114 }
125- case . functionDecl( let functionDecl) :
126- if functionDecl. signature. effectSpecifiers? . throwsClause != nil {
127- return syntax
128- } else {
129- return lookupCatchNodeHelper ( at: syntax. parent, traversedCatchClause: traversedCatchClause)
130- }
115+ case . functionDecl, . accessorDecl, . initializerDecl:
116+ return syntax
131117 default :
132118 return lookupCatchNodeHelper ( at: syntax. parent, traversedCatchClause: traversedCatchClause)
133119 }
134120 }
121+
122+ /// Callect the first syntax node matching the collection type up to a function boundary.
123+ func walkParentTreeUpToFunctionBoundary< T: SyntaxProtocol > ( at syntax: Syntax ? ,
124+ collect: T . Type ) -> T ? {
125+ walkParentTreeUpToFunctionBoundary ( at: syntax, collect: collect, stopWithFirstMatch: true ) . first
126+ }
127+
128+ /// Callect syntax nodes matching the collection type up to a function boundary.
129+ func walkParentTreeUpToFunctionBoundary< T: SyntaxProtocol > ( at syntax: Syntax ? ,
130+ collect: T . Type ,
131+ stopWithFirstMatch: Bool = false ) -> [ T ] {
132+ walkParentTree ( upTo: [ MemberBlockSyntax . self,
133+ FunctionDeclSyntax . self,
134+ InitializerDeclSyntax . self,
135+ ClosureExprSyntax . self] ,
136+ at: syntax,
137+ collect: collect,
138+ stopWithFirstMatch: stopWithFirstMatch
139+ )
140+ }
141+
142+ /// Callect syntax nodes matching the collection type up until encountering one of the specified syntax nodes.
143+ func walkParentTree< T: SyntaxProtocol > ( upTo stopAt: [ SyntaxProtocol . Type ] ,
144+ at syntax: Syntax ? ,
145+ collect: T . Type ,
146+ stopWithFirstMatch: Bool = false ) -> [ T ] {
147+ guard let syntax, !stopAt. contains ( where: { syntax. is ( $0) } ) else { return [ ] }
148+ if let matchedSyntax = syntax. as ( T . self) {
149+ if stopWithFirstMatch {
150+ return [ matchedSyntax]
151+ } else {
152+ return [ matchedSyntax] + walkParentTree( upTo: stopAt, at: syntax. parent, collect: collect, stopWithFirstMatch: stopWithFirstMatch)
153+ }
154+ } else {
155+ return walkParentTree ( upTo: stopAt, at: syntax. parent, collect: collect, stopWithFirstMatch: stopWithFirstMatch)
156+ }
157+ }
135158}
0 commit comments