From c85d8d9d4d15c5f495cf5624a0ff08e5441021ef Mon Sep 17 00:00:00 2001 From: John Sundell Date: Fri, 8 Mar 2019 21:50:07 +0100 Subject: [PATCH] Support highlighting of setters with an explicit access level MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This change makes Splash correctly highlight property setters that have an explicit access level defined — such as `private(set) var`. --- Sources/Splash/Grammar/SwiftGrammar.swift | 37 ++++++++++++++++--- .../SplashTests/Tests/DeclarationTests.swift | 27 ++++++++++++++ 2 files changed, 58 insertions(+), 6 deletions(-) diff --git a/Sources/Splash/Grammar/SwiftGrammar.swift b/Sources/Splash/Grammar/SwiftGrammar.swift index 4951fff..5a170eb 100644 --- a/Sources/Splash/Grammar/SwiftGrammar.swift +++ b/Sources/Splash/Grammar/SwiftGrammar.swift @@ -36,12 +36,11 @@ public struct SwiftGrammar: Grammar { } private extension SwiftGrammar { - static let keywords: Set = [ + static let keywords = ([ "final", "class", "struct", "enum", "protocol", "extension", "let", "var", "func", "typealias", "init", "guard", "if", "else", "return", "get", "throw", "throws", "for", "in", "open", "weak", - "public", "internal", "private", "fileprivate", "import", "mutating", "associatedtype", "case", "switch", "static", "do", "try", "catch", "as", "super", "self", "set", "true", "false", "nil", @@ -49,6 +48,10 @@ private extension SwiftGrammar { "#selector", "required", "willSet", "didSet", "lazy", "subscript", "defer", "inout", "while", "continue", "fallthrough", "repeat", "indirect" + ] as Set).union(accessControlKeywords) + + static let accessControlKeywords: Set = [ + "public", "internal", "fileprivate", "private" ] struct PreprocessingRule: SyntaxRule { @@ -147,6 +150,7 @@ private extension SwiftGrammar { struct CallRule: SyntaxRule { var tokenType: TokenType { return .call } private let keywordsToAvoid: Set + private let callLikeKeywords: Set private let controlFlowTokens = ["if", "&&", "||", "for"] init() { @@ -156,6 +160,10 @@ private extension SwiftGrammar { keywordsToAvoid.remove("throw") keywordsToAvoid.remove("if") self.keywordsToAvoid = keywordsToAvoid + + var callLikeKeywords = accessControlKeywords + callLikeKeywords.insert("subscript") + self.callLikeKeywords = callLikeKeywords } func matches(_ segment: Segment) -> Bool { @@ -163,10 +171,13 @@ private extension SwiftGrammar { return false } - // Subscripting is a bit of an edge case, since it's the only keyword - // that looks like a function call, so we need to handle it explicitly - guard segment.tokens.current != "subscript" else { - return false + // There's a few keywords that might look like function calls + if callLikeKeywords.contains(segment.tokens.current) { + if let nextToken = segment.tokens.next { + guard !nextToken.starts(with: "(") else { + return false + } + } } if let previousToken = segment.tokens.previous { @@ -215,6 +226,20 @@ private extension SwiftGrammar { } if let previousToken = segment.tokens.previous { + // Highlight the '(set)' part of setter access modifiers + switch segment.tokens.current { + case "(": + return accessControlKeywords.contains(previousToken) + case "set": + if previousToken == "(" { + return true + } + case ")": + return previousToken == "set" + default: + break + } + // Don't highlight most keywords when used as a parameter label if !segment.tokens.current.isAny(of: "_", "self", "let", "var", "true", "false") { guard !previousToken.isAny(of: "(", ",") else { diff --git a/Tests/SplashTests/Tests/DeclarationTests.swift b/Tests/SplashTests/Tests/DeclarationTests.swift index 12dda13..d5ada27 100644 --- a/Tests/SplashTests/Tests/DeclarationTests.swift +++ b/Tests/SplashTests/Tests/DeclarationTests.swift @@ -487,6 +487,32 @@ final class DeclarationTests: SyntaxHighlighterTestCase { ]) } + func testPropertyWithSetterAccessLevel() { + let components = highlighter.highlight(""" + struct Hello { + private(set) var property: Int + } + """) + + XCTAssertEqual(components, [ + .token("struct", .keyword), + .whitespace(" "), + .plainText("Hello"), + .whitespace(" "), + .plainText("{"), + .whitespace("\n "), + .token("private(set)", .keyword), + .whitespace(" "), + .token("var", .keyword), + .whitespace(" "), + .plainText("property:"), + .whitespace(" "), + .token("Int", .type), + .whitespace("\n"), + .plainText("}") + ]) + } + func testSubscriptDeclaration() { let components = highlighter.highlight(""" extension Collection { @@ -674,6 +700,7 @@ extension DeclarationTests { ("testGenericPropertyDeclaration", testGenericPropertyDeclaration), ("testPropertyDeclarationWithWillSet", testPropertyDeclarationWithWillSet), ("testPropertyDeclarationWithDidSet", testPropertyDeclarationWithDidSet), + ("testPropertyWithSetterAccessLevel", testPropertyWithSetterAccessLevel), ("testSubscriptDeclaration", testSubscriptDeclaration), ("testDeferDeclaration", testDeferDeclaration), ("testFunctionDeclarationWithInOutParameter", testFunctionDeclarationWithInOutParameter),