diff --git a/Sources/SwiftLexicalLookup/Configurations/FileScopeNameIntroductionStrategy.swift b/Sources/SwiftLexicalLookup/Configurations/FileScopeHandlingConfig.swift similarity index 68% rename from Sources/SwiftLexicalLookup/Configurations/FileScopeNameIntroductionStrategy.swift rename to Sources/SwiftLexicalLookup/Configurations/FileScopeHandlingConfig.swift index ca0c6642d8d..34c0861a91e 100644 --- a/Sources/SwiftLexicalLookup/Configurations/FileScopeNameIntroductionStrategy.swift +++ b/Sources/SwiftLexicalLookup/Configurations/FileScopeHandlingConfig.swift @@ -14,12 +14,10 @@ import SwiftSyntax /// Specifies how names should be introduced at the file scope. @_spi(Experimental) public enum FileScopeHandlingConfig { - /// Default behavior. Names introduced sequentially like in member block - /// scope up to the first non-declaration after and including which, - /// the declarations are treated like in code block scope. + /// This is the behavior that is being used + /// for Swift files with top-level code. case memberBlockUpToLastDecl - /// File scope behaves like member block scope. + /// This is the behavior that is being used + /// for Swift files that don’t allow top-level code. case memberBlock - /// File scope behaves like code block scope. - case codeBlock } diff --git a/Sources/SwiftLexicalLookup/Configurations/LookupConfig.swift b/Sources/SwiftLexicalLookup/Configurations/LookupConfig.swift index d96093037ca..973c9ea940d 100644 --- a/Sources/SwiftLexicalLookup/Configurations/LookupConfig.swift +++ b/Sources/SwiftLexicalLookup/Configurations/LookupConfig.swift @@ -13,11 +13,16 @@ import Foundation @_spi(Experimental) public struct LookupConfig { - /// Specifies behaviour of file scope. - /// `memberBlockUpToLastDecl` by default. - public var fileScopeHandling: FileScopeHandlingConfig = .memberBlockUpToLastDecl + /// Specifies behavior of file scope. + @_spi(Experimental) public var fileScopeHandling: FileScopeHandlingConfig - public init(fileScopeHandling: FileScopeHandlingConfig = .memberBlockUpToLastDecl) { + /// Creates a new lookup configuration. + /// + /// - `fileScopeHandling` - specifies behavior of file scope. + /// `memberBlockUpToLastDecl` by default. + @_spi(Experimental) public init( + fileScopeHandling: FileScopeHandlingConfig = .memberBlockUpToLastDecl + ) { self.fileScopeHandling = fileScopeHandling } } diff --git a/Sources/SwiftLexicalLookup/IdentifiableSyntax.swift b/Sources/SwiftLexicalLookup/IdentifiableSyntax.swift index 67e6de6b4e9..004e3f36b4d 100644 --- a/Sources/SwiftLexicalLookup/IdentifiableSyntax.swift +++ b/Sources/SwiftLexicalLookup/IdentifiableSyntax.swift @@ -13,25 +13,25 @@ import SwiftSyntax /// Syntax node that can be refered to with an identifier. -public protocol IdentifiableSyntax: SyntaxProtocol { +@_spi(Experimental) public protocol IdentifiableSyntax: SyntaxProtocol { var identifier: TokenSyntax { get } } -extension IdentifierPatternSyntax: IdentifiableSyntax {} +@_spi(Experimental) extension IdentifierPatternSyntax: IdentifiableSyntax {} -extension ClosureParameterSyntax: IdentifiableSyntax { +@_spi(Experimental) extension ClosureParameterSyntax: IdentifiableSyntax { @_spi(Experimental) public var identifier: TokenSyntax { secondName ?? firstName } } -extension ClosureShorthandParameterSyntax: IdentifiableSyntax { +@_spi(Experimental) extension ClosureShorthandParameterSyntax: IdentifiableSyntax { @_spi(Experimental) public var identifier: TokenSyntax { name } } -extension ClosureCaptureSyntax: IdentifiableSyntax { +@_spi(Experimental) extension ClosureCaptureSyntax: IdentifiableSyntax { @_spi(Experimental) public var identifier: TokenSyntax { /* Doesn't work with closures like: _ = { [y=1+2] in diff --git a/Sources/SwiftLexicalLookup/LookupName.swift b/Sources/SwiftLexicalLookup/LookupName.swift index da7338a46f5..868cfd1f0d6 100644 --- a/Sources/SwiftLexicalLookup/LookupName.swift +++ b/Sources/SwiftLexicalLookup/LookupName.swift @@ -14,19 +14,19 @@ import SwiftSyntax @_spi(Experimental) public enum LookupName { /// Identifier associated with the name. - /// Could be an identifier of a variable, function or closure parameter and more + /// Could be an identifier of a variable, function or closure parameter and more. case identifier(IdentifiableSyntax, accessibleAfter: AbsolutePosition?) /// Declaration associated with the name. - /// Could be class, struct, actor, protocol, function and more - case declaration(NamedDeclSyntax, accessibleAfter: AbsolutePosition?) + /// Could be class, struct, actor, protocol, function and more. + case declaration(NamedDeclSyntax) /// Syntax associated with this name. @_spi(Experimental) public var syntax: SyntaxProtocol { switch self { case .identifier(let syntax, _): - syntax - case .declaration(let syntax, _): - syntax + return syntax + case .declaration(let syntax): + return syntax } } @@ -34,9 +34,9 @@ import SwiftSyntax @_spi(Experimental) public var identifier: Identifier? { switch self { case .identifier(let syntax, _): - Identifier(syntax.identifier) - case .declaration(let syntax, _): - Identifier(syntax.name) + return Identifier(syntax.identifier) + case .declaration(let syntax): + return Identifier(syntax.name) } } @@ -44,8 +44,10 @@ import SwiftSyntax /// If set to `nil`, the name is available at any point in scope. var accessibleAfter: AbsolutePosition? { switch self { - case .identifier(_, let absolutePosition), .declaration(_, let absolutePosition): - absolutePosition + case .identifier(_, let absolutePosition): + return absolutePosition + default: + return nil } } @@ -61,46 +63,59 @@ import SwiftSyntax return name == lookedUpName } - /// Extracts names introduced by the given `from` structure. - static func getNames(from syntax: SyntaxProtocol, accessibleAfter: AbsolutePosition? = nil) -> [LookupName] { + /// Extracts names introduced by the given `syntax` structure. + /// + /// When e.g. looking up a variable declaration like `let a = a`, + /// we expect `a` to be visible after the whole declaration. + /// That's why we can't just use `syntax.endPosition` for the `a` identifier pattern, + /// as the name would already be visible at the `a` reference withing the declaration. + /// That’s why code block and file scopes have to set + /// `accessibleAfter` to be the end position of the entire declaration syntax node. + static func getNames( + from syntax: SyntaxProtocol, + accessibleAfter: AbsolutePosition? = nil + ) -> [LookupName] { switch Syntax(syntax).as(SyntaxEnum.self) { case .variableDecl(let variableDecl): - variableDecl.bindings.flatMap { binding in - getNames(from: binding.pattern, accessibleAfter: accessibleAfter) + return variableDecl.bindings.flatMap { binding in + getNames( + from: binding.pattern, + accessibleAfter: accessibleAfter != nil ? binding.endPositionBeforeTrailingTrivia : nil + ) } case .tuplePattern(let tuplePattern): - tuplePattern.elements.flatMap { tupleElement in + return tuplePattern.elements.flatMap { tupleElement in getNames(from: tupleElement.pattern, accessibleAfter: accessibleAfter) } case .valueBindingPattern(let valueBindingPattern): - getNames(from: valueBindingPattern.pattern, accessibleAfter: accessibleAfter) + return getNames(from: valueBindingPattern.pattern, accessibleAfter: accessibleAfter) case .expressionPattern(let expressionPattern): - getNames(from: expressionPattern.expression, accessibleAfter: accessibleAfter) + return getNames(from: expressionPattern.expression, accessibleAfter: accessibleAfter) case .sequenceExpr(let sequenceExpr): - sequenceExpr.elements.flatMap { expression in + return sequenceExpr.elements.flatMap { expression in getNames(from: expression, accessibleAfter: accessibleAfter) } case .patternExpr(let patternExpr): - getNames(from: patternExpr.pattern, accessibleAfter: accessibleAfter) + return getNames(from: patternExpr.pattern, accessibleAfter: accessibleAfter) case .optionalBindingCondition(let optionalBinding): - getNames(from: optionalBinding.pattern, accessibleAfter: accessibleAfter) + return getNames(from: optionalBinding.pattern, accessibleAfter: accessibleAfter) case .matchingPatternCondition(let matchingPatternCondition): - getNames(from: matchingPatternCondition.pattern, accessibleAfter: accessibleAfter) + return getNames(from: matchingPatternCondition.pattern, accessibleAfter: accessibleAfter) case .functionCallExpr(let functionCallExpr): - functionCallExpr.arguments.flatMap { argument in + return functionCallExpr.arguments.flatMap { argument in getNames(from: argument.expression, accessibleAfter: accessibleAfter) } case .guardStmt(let guardStmt): - guardStmt.conditions.flatMap { cond in + return guardStmt.conditions.flatMap { cond in getNames(from: cond.condition, accessibleAfter: cond.endPosition) } default: if let namedDecl = Syntax(syntax).asProtocol(SyntaxProtocol.self) as? NamedDeclSyntax { - handle(namedDecl: namedDecl, accessibleAfter: accessibleAfter) + return handle(namedDecl: namedDecl, accessibleAfter: accessibleAfter) } else if let identifiable = Syntax(syntax).asProtocol(SyntaxProtocol.self) as? IdentifiableSyntax { - handle(identifiable: identifiable, accessibleAfter: accessibleAfter) + return handle(identifiable: identifiable, accessibleAfter: accessibleAfter) } else { - [] + return [] } } } @@ -108,7 +123,7 @@ import SwiftSyntax /// Extracts name introduced by `IdentifiableSyntax` node. private static func handle(identifiable: IdentifiableSyntax, accessibleAfter: AbsolutePosition? = nil) -> [LookupName] { - if identifiable.identifier.text != "_" { + if identifiable.identifier.tokenKind != .wildcard { return [.identifier(identifiable, accessibleAfter: accessibleAfter)] } else { return [] @@ -116,7 +131,10 @@ import SwiftSyntax } /// Extracts name introduced by `NamedDeclSyntax` node. - private static func handle(namedDecl: NamedDeclSyntax, accessibleAfter: AbsolutePosition? = nil) -> [LookupName] { - [.declaration(namedDecl, accessibleAfter: accessibleAfter)] + private static func handle( + namedDecl: NamedDeclSyntax, + accessibleAfter: AbsolutePosition? = nil + ) -> [LookupName] { + [.declaration(namedDecl)] } } diff --git a/Sources/SwiftLexicalLookup/LookupResult.swift b/Sources/SwiftLexicalLookup/LookupResult.swift index 4e5cc04f97b..3fb4c30b046 100644 --- a/Sources/SwiftLexicalLookup/LookupResult.swift +++ b/Sources/SwiftLexicalLookup/LookupResult.swift @@ -12,7 +12,7 @@ import SwiftSyntax -/// Represents resul +/// Represents result from a specific scope. @_spi(Experimental) public enum LookupResult { /// Scope and the names that matched lookup. case fromScope(ScopeSyntax, withNames: [LookupName]) @@ -23,9 +23,9 @@ import SwiftSyntax @_spi(Experimental) public var scope: ScopeSyntax? { switch self { case .fromScope(let scopeSyntax, _): - scopeSyntax + return scopeSyntax case .fromFileScope(let fileScopeSyntax, _): - fileScopeSyntax + return fileScopeSyntax } } @@ -33,7 +33,7 @@ import SwiftSyntax @_spi(Experimental) public var names: [LookupName] { switch self { case .fromScope(_, let names), .fromFileScope(_, let names): - names + return names } } } diff --git a/Sources/SwiftLexicalLookup/ScopeImplementations.swift b/Sources/SwiftLexicalLookup/ScopeImplementations.swift index 06eb4da0a0e..9671ce176a0 100644 --- a/Sources/SwiftLexicalLookup/ScopeImplementations.swift +++ b/Sources/SwiftLexicalLookup/ScopeImplementations.swift @@ -12,13 +12,13 @@ import SwiftSyntax -extension SyntaxProtocol { +@_spi(Experimental) extension SyntaxProtocol { /// Parent scope of this syntax node, or scope introduced by this syntax node. var scope: ScopeSyntax? { if let scopeSyntax = Syntax(self).asProtocol(SyntaxProtocol.self) as? ScopeSyntax { - scopeSyntax + return scopeSyntax } else { - self.parent?.scope + return self.parent?.scope } } } @@ -26,7 +26,7 @@ extension SyntaxProtocol { @_spi(Experimental) extension SourceFileSyntax: ScopeSyntax { /// All names introduced in the file scope /// according to the default strategy: `memberBlockUpToLastDecl`. - public var introducedNames: [LookupName] { + @_spi(Experimental) public var introducedNames: [LookupName] { introducedNames(using: .memberBlockUpToLastDecl) } @@ -47,10 +47,10 @@ extension SyntaxProtocol { /// ``` /// During lookup, according to different configurations, /// names available at the marked place are: - /// - for `fileScopeNameIntroductionStrategy` - a, b, c, d + /// - for `memberBlockUpToLastDecl` - a, b, c, d /// - for `memberBlock` - a, b, c, d, e, f /// - for `codeBlock` - a - public func introducedNames(using fileScopeHandling: FileScopeHandlingConfig) -> [LookupName] { + @_spi(Experimental) public func introducedNames(using fileScopeHandling: FileScopeHandlingConfig) -> [LookupName] { switch fileScopeHandling { case .memberBlockUpToLastDecl: var encounteredNonDeclaration = false @@ -61,7 +61,7 @@ extension SyntaxProtocol { if encounteredNonDeclaration { return LookupName.getNames(from: item, accessibleAfter: codeBlockItem.endPosition) } else { - if item.is(DeclSyntax.self) || item.is(VariableDeclSyntax.self) { + if item.is(DeclSyntax.self) { return LookupName.getNames(from: item) } else { encounteredNonDeclaration = true @@ -69,10 +69,6 @@ extension SyntaxProtocol { } } } - case .codeBlock: - return statements.flatMap { codeBlockItem in - LookupName.getNames(from: codeBlockItem.item, accessibleAfter: codeBlockItem.endPosition) - } case .memberBlock: return statements.flatMap { codeBlockItem in LookupName.getNames(from: codeBlockItem.item) @@ -81,7 +77,7 @@ extension SyntaxProtocol { } /// Returns names matching lookup using provided file - /// scope handling configuration (by default: `memberBlockUpToLastDecl`). + /// scope handling configuration. /// /// Example usage: /// ```swift @@ -97,10 +93,10 @@ extension SyntaxProtocol { /// ``` /// According to different configurations, /// names available at the marked place are: - /// - for `fileScopeNameIntroductionStrategy` - a, b, c, d + /// - for `memberBlockUpToLastDecl` - a, b, c, d /// - for `memberBlock` - a, b, c, d, e, f /// - for `codeBlock` - a - public func lookup( + @_spi(Experimental) public func lookup( for name: String?, at syntax: SyntaxProtocol, with config: LookupConfig @@ -117,7 +113,7 @@ extension SyntaxProtocol { @_spi(Experimental) extension CodeBlockSyntax: ScopeSyntax { /// Names introduced in the code block scope /// accessible after their declaration. - public var introducedNames: [LookupName] { + @_spi(Experimental) public var introducedNames: [LookupName] { statements.flatMap { codeBlockItem in LookupName.getNames(from: codeBlockItem.item, accessibleAfter: codeBlockItem.endPosition) } @@ -126,7 +122,7 @@ extension SyntaxProtocol { @_spi(Experimental) extension ForStmtSyntax: ScopeSyntax { /// Names introduced in the `for` body. - public var introducedNames: [LookupName] { + @_spi(Experimental) public var introducedNames: [LookupName] { LookupName.getNames(from: pattern) } } @@ -143,26 +139,20 @@ extension SyntaxProtocol { /// ``` /// During lookup, names available at the marked place are: /// `self`, a, b. - public var introducedNames: [LookupName] { + @_spi(Experimental) public var introducedNames: [LookupName] { let captureNames = - signature?.capture?.children(viewMode: .sourceAccurate).flatMap { child in - if let captureList = child.as(ClosureCaptureListSyntax.self) { - captureList.children(viewMode: .sourceAccurate).flatMap { capture in - LookupName.getNames(from: capture) - } - } else { - LookupName.getNames(from: child) - } + signature?.capture?.items.flatMap { element in + LookupName.getNames(from: element) } ?? [] let parameterNames = signature?.parameterClause?.children(viewMode: .sourceAccurate).flatMap { parameter in if let parameterList = parameter.as(ClosureParameterListSyntax.self) { - parameterList.children(viewMode: .sourceAccurate).flatMap { parameter in + return parameterList.children(viewMode: .sourceAccurate).flatMap { parameter in LookupName.getNames(from: parameter) } } else { - LookupName.getNames(from: parameter) + return LookupName.getNames(from: parameter) } } ?? [] @@ -172,7 +162,7 @@ extension SyntaxProtocol { @_spi(Experimental) extension WhileStmtSyntax: ScopeSyntax { /// Names introduced by the `while` loop by its conditions. - public var introducedNames: [LookupName] { + @_spi(Experimental) public var introducedNames: [LookupName] { conditions.flatMap { element in LookupName.getNames(from: element.condition) } @@ -181,7 +171,7 @@ extension SyntaxProtocol { @_spi(Experimental) extension IfExprSyntax: ScopeSyntax { /// Parent scope, omitting ancestor `if` statements if part of their `else if` clause. - public var parentScope: ScopeSyntax? { + @_spi(Experimental) public var parentScope: ScopeSyntax? { getParent(for: self.parent, previousIfElse: self.elseKeyword == nil) } @@ -217,7 +207,7 @@ extension SyntaxProtocol { } /// Names introduced by the `if` optional binding conditions. - public var introducedNames: [LookupName] { + @_spi(Experimental) public var introducedNames: [LookupName] { conditions.flatMap { element in LookupName.getNames(from: element.condition, accessibleAfter: element.endPosition) } @@ -235,22 +225,22 @@ extension SyntaxProtocol { /// // <-- a is not visible here /// } /// ``` - public func lookup( + @_spi(Experimental) public func lookup( for name: String?, at syntax: SyntaxProtocol, with config: LookupConfig ) -> [LookupResult] { if let elseBody, elseBody.position <= syntax.position, elseBody.endPosition >= syntax.position { - lookupInParent(for: name, at: syntax, with: config) + return lookupInParent(for: name, at: syntax, with: config) } else { - defaultLookupImplementation(for: name, at: syntax, with: config) + return defaultLookupImplementation(for: name, at: syntax, with: config) } } } @_spi(Experimental) extension MemberBlockSyntax: ScopeSyntax { /// All names introduced by members of this member scope. - public var introducedNames: [LookupName] { + @_spi(Experimental) public var introducedNames: [LookupName] { members.flatMap { member in LookupName.getNames(from: member.decl) } @@ -260,7 +250,7 @@ extension SyntaxProtocol { @_spi(Experimental) extension GuardStmtSyntax: ScopeSyntax { /// Guard doesn't introduce any names to its children. /// It's always empty. - public var introducedNames: [LookupName] { + @_spi(Experimental) public var introducedNames: [LookupName] { [] } @@ -275,15 +265,15 @@ extension SyntaxProtocol { /// } /// // a is visible here /// ``` - public func lookup( + @_spi(Experimental) public func lookup( for name: String?, at syntax: SyntaxProtocol, with config: LookupConfig ) -> [LookupResult] { if body.position <= syntax.position && body.endPosition >= syntax.position { - lookupInParent(for: name, at: self, with: config) + return lookupInParent(for: name, at: self, with: config) } else { - defaultLookupImplementation(for: name, at: syntax, with: config) + return defaultLookupImplementation(for: name, at: syntax, with: config) } } } diff --git a/Sources/SwiftLexicalLookup/ScopeSyntax.swift b/Sources/SwiftLexicalLookup/ScopeSyntax.swift index 55bebd9ef78..a1d426ea84b 100644 --- a/Sources/SwiftLexicalLookup/ScopeSyntax.swift +++ b/Sources/SwiftLexicalLookup/ScopeSyntax.swift @@ -14,10 +14,10 @@ import SwiftSyntax extension SyntaxProtocol { /// Returns all names that `for` refers to at this syntax node. - /// Optional configuration can be passed as `with` to customize the lookup behavior. + /// Optional configuration can be passed as `config` to customize the lookup behavior. /// - /// - Returns: An array of `LookupResult` for name `for` at this syntax node, - /// ordered by visibility. If set to `nil`, returns all available names ordered by visibility. + /// - Returns: An array of `LookupResult` for `name` at this syntax node, + /// ordered by visibility. If `name` is set to `nil`, returns all available names ordered by visibility. /// The order is from the innermost to the outermost scope, /// and within each result, names are ordered by their introduction /// in the source code. @@ -56,20 +56,24 @@ extension SyntaxProtocol { var parentScope: ScopeSyntax? { get } /// Names found in this scope. Ordered from first to last introduced. var introducedNames: [LookupName] { get } - /// Finds all declarations `name` refers to. `at` specifies the node lookup was triggered with. + /// Finds all declarations `name` refers to. `syntax` specifies the node lookup was triggered with. /// If `name` set to `nil`, returns all available names at the given node. - func lookup(for name: String?, at syntax: SyntaxProtocol, with config: LookupConfig) -> [LookupResult] + func lookup( + for name: String?, + at syntax: SyntaxProtocol, + with config: LookupConfig + ) -> [LookupResult] } @_spi(Experimental) extension ScopeSyntax { - public var parentScope: ScopeSyntax? { + @_spi(Experimental) public var parentScope: ScopeSyntax? { self.parent?.scope } /// Returns `LookupResult` of all names introduced in this scope that `name` /// refers to and is accessible at given syntax node then passes lookup to the parent. /// If `name` set to `nil`, returns all available names at the given node. - public func lookup( + @_spi(Experimental) public func lookup( for name: String?, at syntax: SyntaxProtocol, with config: LookupConfig @@ -88,7 +92,7 @@ extension SyntaxProtocol { let filteredNames = introducedNames .filter { introducedName in - introducedName.isAccessible(at: syntax) && (name == nil || introducedName.refersTo(name!)) + checkName(name, refersTo: introducedName, at: syntax) } if filteredNames.isEmpty { @@ -106,4 +110,8 @@ extension SyntaxProtocol { ) -> [LookupResult] { parentScope?.lookup(for: name, at: syntax, with: config) ?? [] } + + func checkName(_ name: String?, refersTo introducedName: LookupName, at syntax: SyntaxProtocol) -> Bool { + introducedName.isAccessible(at: syntax) && (name == nil || introducedName.refersTo(name!)) + } } diff --git a/Tests/SwiftLexicalLookupTest/Assertions.swift b/Tests/SwiftLexicalLookupTest/Assertions.swift index 76106fb1453..56161b8e43a 100644 --- a/Tests/SwiftLexicalLookupTest/Assertions.swift +++ b/Tests/SwiftLexicalLookupTest/Assertions.swift @@ -10,7 +10,6 @@ // //===----------------------------------------------------------------------===// -import Foundation @_spi(Experimental) import SwiftLexicalLookup import SwiftParser import SwiftSyntax @@ -53,7 +52,7 @@ enum MarkerExpectation { } } -/// Used to define +/// Used to define result assertion. enum ResultExpectation { case fromScope(ScopeSyntax.Type, expectedNames: [String]) case fromFileScope(expectedNames: [String]) @@ -61,9 +60,9 @@ enum ResultExpectation { var expectedNames: [String] { switch self { case .fromScope(_, let expectedNames): - expectedNames + return expectedNames case .fromFileScope(expectedNames: let expectedNames): - expectedNames + return expectedNames } } } @@ -73,7 +72,7 @@ enum ResultExpectation { /// It also checks whether result types match rules specified in `expectedResultTypes`. func assertLexicalScopeQuery( source: String, - methodUnderTest: (String, TokenSyntax) -> ([SyntaxProtocol?]), + methodUnderTest: (_ marker: String, _ tokenAtMarker: TokenSyntax) -> ([SyntaxProtocol?]), expected: [String: [String?]], expectedResultTypes: MarkerExpectation = .none ) { @@ -118,27 +117,15 @@ func assertLexicalScopeQuery( } // Assert validity of the output - for (actual, expected) in zip(result, zip(expectedMarkers, expectedPositions)) { - if actual == nil && expected.1 == nil { continue } - - guard let actual else { - XCTFail( - "For marker \(marker), actual is nil while expected is \(sourceFileSyntax.token(at: expected.1!)?.description ?? "nil")" - ) - continue - } - - guard let expectedPosition = expected.1 else { - XCTFail("For marker \(marker), actual is \(actual) while expected position is nil") - continue - } + for (actual, (expectedMarker, expectedPosition)) in zip(result, zip(expectedMarkers, expectedPositions)) { + guard let actual, let expectedPosition else { continue } XCTAssert( actual.positionAfterSkippingLeadingTrivia == expectedPosition, - "For marker \(marker), actual result: \(actual) doesn't match expected value: \(sourceFileSyntax.token(at: expected.1!)?.description ?? "nil")" + "For marker \(marker), actual result: \(actual) doesn't match expected value: \(sourceFileSyntax.token(at: expectedPosition)?.description ?? "nil")" ) - if let expectedMarker = expected.0 { + if let expectedMarker { expectedResultTypes.assertMarkerType(marker: expectedMarker, actual: actual) } } @@ -157,8 +144,8 @@ func assertLexicalNameLookup( ) { assertLexicalScopeQuery( source: source, - methodUnderTest: { marker, argument in - let result = argument.lookup(for: useNilAsTheParameter ? nil : argument.text, with: config) + methodUnderTest: { marker, tokenAtMarker in + let result = tokenAtMarker.lookup(for: useNilAsTheParameter ? nil : tokenAtMarker.text, with: config) guard let expectedValues = references[marker] else { XCTFail("For marker \(marker), couldn't find result expectation") diff --git a/Tests/SwiftLexicalLookupTest/NameLookupTests.swift b/Tests/SwiftLexicalLookupTest/NameLookupTests.swift index a04c35382e7..4b232686c5a 100644 --- a/Tests/SwiftLexicalLookupTest/NameLookupTests.swift +++ b/Tests/SwiftLexicalLookupTest/NameLookupTests.swift @@ -50,7 +50,7 @@ final class testNameLookup: XCTestCase { source: """ for i in 1..<4 { let (1️⃣a, 2️⃣b) = (1, 2) - let 3️⃣c = 3, 4️⃣d = 4 + let 3️⃣c = 3, 4️⃣d = 9️⃣c 5️⃣a 6️⃣b @@ -63,6 +63,7 @@ final class testNameLookup: XCTestCase { "6️⃣": [.fromScope(CodeBlockSyntax.self, expectedNames: ["2️⃣"])], "7️⃣": [.fromScope(CodeBlockSyntax.self, expectedNames: ["3️⃣"])], "8️⃣": [.fromScope(CodeBlockSyntax.self, expectedNames: ["4️⃣"])], + "9️⃣": [.fromScope(CodeBlockSyntax.self, expectedNames: ["3️⃣"])], ], expectedResultTypes: .all(IdentifierPatternSyntax.self) ) @@ -73,8 +74,8 @@ final class testNameLookup: XCTestCase { source: """ for 1️⃣i in 1..<4 { let (a, b) = (2️⃣i, 3️⃣j) - for (4️⃣i, 5️⃣j) in foo { - let (c, d) = (6️⃣i, 7️⃣j) + for (4️⃣i, (5️⃣j, 8️⃣k)) in foo { + let (c, d, e) = (6️⃣i, 7️⃣j, 9️⃣k) } } """, @@ -86,6 +87,7 @@ final class testNameLookup: XCTestCase { .fromScope(ForStmtSyntax.self, expectedNames: ["1️⃣"]), ], "7️⃣": [.fromScope(ForStmtSyntax.self, expectedNames: ["5️⃣"])], + "9️⃣": [.fromScope(ForStmtSyntax.self, expectedNames: ["8️⃣"])], ], expectedResultTypes: .all(IdentifierPatternSyntax.self) ) @@ -484,13 +486,13 @@ final class testNameLookup: XCTestCase { 9️⃣class d {} - let x = 0️⃣d + let 🔟a = 0️⃣d """, references: [ "3️⃣": [.fromFileScope(expectedNames: ["1️⃣", "8️⃣"])], "4️⃣": [.fromFileScope(expectedNames: ["2️⃣"])], "5️⃣": [.fromFileScope(expectedNames: ["7️⃣"])], - "6️⃣": [], + "6️⃣": [.fromFileScope(expectedNames: ["9️⃣"])], "0️⃣": [.fromFileScope(expectedNames: ["9️⃣"])], ], expectedResultTypes: .all(ClassDeclSyntax.self, except: ["8️⃣": IdentifierPatternSyntax.self]) @@ -514,48 +516,41 @@ final class testNameLookup: XCTestCase { 9️⃣class d {} - let x = 0️⃣d + let 🔟a = 0️⃣d """, references: [ - "3️⃣": [.fromFileScope(expectedNames: ["1️⃣", "8️⃣"])], + "3️⃣": [.fromFileScope(expectedNames: ["1️⃣", "8️⃣", "🔟"])], "4️⃣": [.fromFileScope(expectedNames: ["2️⃣"])], "5️⃣": [.fromFileScope(expectedNames: ["7️⃣"])], "6️⃣": [.fromFileScope(expectedNames: ["9️⃣"])], "0️⃣": [.fromFileScope(expectedNames: ["9️⃣"])], ], - expectedResultTypes: .all(ClassDeclSyntax.self, except: ["8️⃣": IdentifierPatternSyntax.self]), + expectedResultTypes: .all( + ClassDeclSyntax.self, + except: [ + "8️⃣": IdentifierPatternSyntax.self, + "🔟": IdentifierPatternSyntax.self, + ] + ), config: LookupConfig(fileScopeHandling: .memberBlock) ) } - func testFileScopeAsCodeBlock() { + func testDeclarationAvailabilityInCodeBlock() { assertLexicalNameLookup( source: """ - 1️⃣class a {} - - 2️⃣class b { - let x = 3️⃣a + 4️⃣b + 5️⃣c + 6️⃣d - } - - let 8️⃣a = 0 + func x { + 1️⃣class A {} - 7️⃣class c {} - - if a == 0 {} + let a = 2️⃣A() - 9️⃣class d {} - - let x = 0️⃣d + 3️⃣class A {} + } """, references: [ - "3️⃣": [.fromFileScope(expectedNames: ["1️⃣"])], - "4️⃣": [], - "5️⃣": [], - "6️⃣": [], - "0️⃣": [.fromFileScope(expectedNames: ["9️⃣"])], + "2️⃣": [.fromScope(CodeBlockSyntax.self, expectedNames: ["1️⃣", "3️⃣"])] ], - expectedResultTypes: .all(ClassDeclSyntax.self, except: ["8️⃣": IdentifierPatternSyntax.self]), - config: LookupConfig(fileScopeHandling: .codeBlock) + expectedResultTypes: .all(ClassDeclSyntax.self) ) } }