Correctly handle static methods called on a generic type
This patch fixes syntax highlighting for the following scenario: ``` Type<GenericType>.call() ``` Highlighting generic types is especially tricky, since we want them to be highlighted when appearing at the call site, like above - but we don’t want to highlight them when they are being declared. Hopefully with this fix all/most edge cases are covered.
This commit is contained in:
parent
aea685e8b4
commit
be35cef1ea
@ -11,7 +11,7 @@ extension Equatable {
|
||||
return candidates.contains(self)
|
||||
}
|
||||
|
||||
func isAny(of candidates: [Self]) -> Bool {
|
||||
func isAny<S: Sequence>(of candidates: S) -> Bool where S.Element == Self {
|
||||
return candidates.contains(self)
|
||||
}
|
||||
}
|
||||
|
@ -214,12 +214,14 @@ private extension SwiftGrammar {
|
||||
struct TypeRule: SyntaxRule {
|
||||
var tokenType: TokenType { return .type }
|
||||
|
||||
private let declarationKeywords: Set<String> = [
|
||||
"class", "struct", "enum", "func",
|
||||
"protocol", "typealias", "import"
|
||||
]
|
||||
|
||||
func matches(_ segment: Segment) -> Bool {
|
||||
// Types should not be highlighted when declared
|
||||
if let previousToken = segment.tokens.previous {
|
||||
let declarationKeywords = ["class", "struct", "enum",
|
||||
"protocol", "typealias", "import"]
|
||||
|
||||
guard !previousToken.isAny(of: declarationKeywords) else {
|
||||
return false
|
||||
}
|
||||
@ -234,9 +236,21 @@ private extension SwiftGrammar {
|
||||
}
|
||||
|
||||
// In a generic declaration, only highlight constraints
|
||||
if !segment.tokens.onSameLine.contains(anyOf: "var", "let") {
|
||||
if !segment.tokens.containsBalancedOccurrences(of: "<", and: ">") {
|
||||
return !segment.tokens.previous.isAny(of: "<", ",")
|
||||
if segment.tokens.previous.isAny(of: "<", ",") {
|
||||
// Since the declaration might be on another line, we have to walk
|
||||
// backwards through all tokens until we've found enough information.
|
||||
for token in segment.tokens.all.reversed() {
|
||||
guard !declarationKeywords.contains(token) else {
|
||||
return false
|
||||
}
|
||||
|
||||
guard !keywords.contains(token) else {
|
||||
return true
|
||||
}
|
||||
|
||||
if token.isAny(of: ">", "=", "==", "(") {
|
||||
return true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -24,16 +24,18 @@ public struct Segment {
|
||||
public extension Segment {
|
||||
/// A collection of tokens included in a code segment
|
||||
struct Tokens {
|
||||
/// All tokens that have been found so far (excluding the current one)
|
||||
public var all: [String]
|
||||
/// The number of times a given token has been found up until this point
|
||||
var counts: [String : Int]
|
||||
public var counts: [String : Int]
|
||||
/// The tokens that were previously found on the same line as the current one
|
||||
var onSameLine: [String]
|
||||
public var onSameLine: [String]
|
||||
/// The token that was previously found (may be on a different line)
|
||||
var previous: String?
|
||||
public var previous: String?
|
||||
/// The current token which is currently being evaluated
|
||||
var current: String
|
||||
public var current: String
|
||||
/// Any upcoming token that will follow the current one
|
||||
var next: String?
|
||||
public var next: String?
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -43,6 +43,7 @@ private extension Tokenizer {
|
||||
private let delimiters: CharacterSet
|
||||
private var index: String.Index?
|
||||
private var tokenCounts = [String : Int]()
|
||||
private var allTokens = [String]()
|
||||
private var lineTokens = [String]()
|
||||
private var segments: (current: Segment?, previous: Segment?)
|
||||
|
||||
@ -132,6 +133,7 @@ private extension Tokenizer {
|
||||
|
||||
private func makeSegment(with component: Component, at index: String.Index) -> Segment {
|
||||
let tokens = Segment.Tokens(
|
||||
all: allTokens,
|
||||
counts: tokenCounts,
|
||||
onSameLine: lineTokens,
|
||||
previous: segments.current?.tokens.current,
|
||||
@ -155,6 +157,8 @@ private extension Tokenizer {
|
||||
count += 1
|
||||
tokenCounts[segment.tokens.current] = count
|
||||
|
||||
allTokens.append(segment.tokens.current)
|
||||
|
||||
if segment.isLastOnLine {
|
||||
lineTokens = []
|
||||
} else {
|
||||
|
@ -75,6 +75,19 @@ final class FunctionCallTests: SyntaxHighlighterTestCase {
|
||||
])
|
||||
}
|
||||
|
||||
func testCallingStaticMethodOnGenericType() {
|
||||
let components = highlighter.highlight("Array<String>.call()")
|
||||
|
||||
XCTAssertEqual(components, [
|
||||
.token("Array", .type),
|
||||
.plainText("<"),
|
||||
.token("String", .type),
|
||||
.plainText(">."),
|
||||
.token("call", .call),
|
||||
.plainText("()")
|
||||
])
|
||||
}
|
||||
|
||||
func testAllTestsRunOnLinux() {
|
||||
XCTAssertTrue(TestCaseVerifier.verifyLinuxTests((type(of: self)).allTests))
|
||||
}
|
||||
@ -87,7 +100,8 @@ extension FunctionCallTests {
|
||||
("testImplicitInitializerCall", testImplicitInitializerCall),
|
||||
("testExplicitInitializerCall", testExplicitInitializerCall),
|
||||
("testAccessingPropertyAfterFunctionCallWithoutArguments", testAccessingPropertyAfterFunctionCallWithoutArguments),
|
||||
("testAccessingPropertyAfterFunctionCallWithArguments", testAccessingPropertyAfterFunctionCallWithArguments)
|
||||
("testAccessingPropertyAfterFunctionCallWithArguments", testAccessingPropertyAfterFunctionCallWithArguments),
|
||||
("testCallingStaticMethodOnGenericType", testCallingStaticMethodOnGenericType)
|
||||
]
|
||||
}
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user