Add support for Swift 5.5's concurrency features (#126)
This patch adds syntax highlighting support for the new concurrency keywords introduced in Swift 5.5 - `actor`, `async`, and `await`. It also includes supporting changes to make sure that usages of these new features/keywords are highlighted correctly, and to protect against regressions within existing Splash-highlighted code.
This commit is contained in:
parent
7f87f191b6
commit
7f4df436eb
|
@ -81,7 +81,8 @@ private extension SwiftGrammar {
|
|||
"lazy", "subscript", "defer", "inout", "while",
|
||||
"continue", "fallthrough", "repeat", "indirect",
|
||||
"deinit", "is", "#file", "#line", "#function",
|
||||
"dynamic", "some", "#available", "convenience", "unowned"
|
||||
"dynamic", "some", "#available", "convenience", "unowned",
|
||||
"async", "await", "actor"
|
||||
] as Set<String>).union(accessControlKeywords)
|
||||
|
||||
static let accessControlKeywords: Set<String> = [
|
||||
|
@ -91,7 +92,8 @@ private extension SwiftGrammar {
|
|||
static let declarationKeywords: Set<String> = [
|
||||
"class", "struct", "enum", "func",
|
||||
"protocol", "typealias", "import",
|
||||
"associatedtype", "subscript", "init"
|
||||
"associatedtype", "subscript", "init",
|
||||
"actor"
|
||||
]
|
||||
|
||||
struct PreprocessingRule: SyntaxRule {
|
||||
|
@ -252,6 +254,7 @@ private extension SwiftGrammar {
|
|||
keywordsToAvoid.remove("throw")
|
||||
keywordsToAvoid.remove("if")
|
||||
keywordsToAvoid.remove("in")
|
||||
keywordsToAvoid.remove("await")
|
||||
self.keywordsToAvoid = keywordsToAvoid
|
||||
|
||||
var callLikeKeywords = accessControlKeywords
|
||||
|
@ -351,14 +354,32 @@ private extension SwiftGrammar {
|
|||
}
|
||||
}
|
||||
|
||||
if let previousToken = segment.tokens.previous {
|
||||
// Don't highlight variables with the same name as a keyword
|
||||
// when used in optional binding, such as if let, guard let:
|
||||
if !segment.tokens.onSameLine.isEmpty, segment.tokens.current != "self" {
|
||||
guard !previousToken.isAny(of: "let", "var") else {
|
||||
if segment.trailingWhitespace == nil {
|
||||
if !segment.tokens.current.isAny(of: "self", "super") {
|
||||
guard segment.tokens.next != "." else {
|
||||
return false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if let previousToken = segment.tokens.previous {
|
||||
if !segment.tokens.onSameLine.isEmpty {
|
||||
// Don't highlight variables with the same name as a keyword
|
||||
// when used in optional binding, such as if let, guard let:
|
||||
if segment.tokens.current != "self" {
|
||||
guard !previousToken.isAny(of: "let", "var") else {
|
||||
return false
|
||||
}
|
||||
|
||||
if segment.tokens.current == "actor" {
|
||||
if accessControlKeywords.contains(previousToken) {
|
||||
return true
|
||||
}
|
||||
|
||||
return previousToken.first == "@"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if !declarationKeywords.contains(segment.tokens.current) {
|
||||
// Highlight the '(set)' part of setter access modifiers
|
||||
|
@ -376,7 +397,7 @@ private extension SwiftGrammar {
|
|||
}
|
||||
|
||||
// Don't highlight most keywords when used as a parameter label
|
||||
if !segment.tokens.current.isAny(of: "self", "let", "var", "true", "false", "inout", "nil", "try") {
|
||||
if !segment.tokens.current.isAny(of: "self", "let", "var", "true", "false", "inout", "nil", "try", "actor") {
|
||||
guard !previousToken.isAny(of: "(", ",", ">(") else {
|
||||
return false
|
||||
}
|
||||
|
@ -451,11 +472,7 @@ private extension SwiftGrammar {
|
|||
return !foundOpeningBracket
|
||||
}
|
||||
|
||||
guard !keywords.contains(token) else {
|
||||
return true
|
||||
}
|
||||
|
||||
if token.isAny(of: "=", "==", "(") {
|
||||
if token.isAny(of: "=", "==", "(", "_", "@escaping") {
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1326,4 +1326,183 @@ final class DeclarationTests: SyntaxHighlighterTestCase {
|
|||
.plainText("}")
|
||||
])
|
||||
}
|
||||
|
||||
func testNonThrowingAsyncFunctionDeclaration() {
|
||||
let components = highlighter.highlight("func test() async {}")
|
||||
|
||||
XCTAssertEqual(components, [
|
||||
.token("func", .keyword),
|
||||
.whitespace(" "),
|
||||
.plainText("test()"),
|
||||
.whitespace(" "),
|
||||
.token("async", .keyword),
|
||||
.whitespace(" "),
|
||||
.plainText("{}")
|
||||
])
|
||||
}
|
||||
|
||||
func testNonThrowingAsyncFunctionDeclarationWithReturnValue() {
|
||||
let components = highlighter.highlight("func test() async -> Int { 0 }")
|
||||
|
||||
XCTAssertEqual(components, [
|
||||
.token("func", .keyword),
|
||||
.whitespace(" "),
|
||||
.plainText("test()"),
|
||||
.whitespace(" "),
|
||||
.token("async", .keyword),
|
||||
.whitespace(" "),
|
||||
.plainText("->"),
|
||||
.whitespace(" "),
|
||||
.token("Int", .type),
|
||||
.whitespace(" "),
|
||||
.plainText("{"),
|
||||
.whitespace(" "),
|
||||
.token("0", .number),
|
||||
.whitespace(" "),
|
||||
.plainText("}")
|
||||
])
|
||||
}
|
||||
|
||||
func testThrowingAsyncFunctionDeclaration() {
|
||||
let components = highlighter.highlight("func test() async throws {}")
|
||||
|
||||
XCTAssertEqual(components, [
|
||||
.token("func", .keyword),
|
||||
.whitespace(" "),
|
||||
.plainText("test()"),
|
||||
.whitespace(" "),
|
||||
.token("async", .keyword),
|
||||
.whitespace(" "),
|
||||
.token("throws", .keyword),
|
||||
.whitespace(" "),
|
||||
.plainText("{}")
|
||||
])
|
||||
}
|
||||
|
||||
func testDeclaringGenericFunctionNamedAwait() {
|
||||
let components = highlighter.highlight("""
|
||||
func await<T>(_ function: () -> T) {}
|
||||
""")
|
||||
|
||||
XCTAssertEqual(components, [
|
||||
.token("func", .keyword),
|
||||
.whitespace(" "),
|
||||
.plainText("await<T>("),
|
||||
.token("_", .keyword),
|
||||
.whitespace(" "),
|
||||
.plainText("function:"),
|
||||
.whitespace(" "),
|
||||
.plainText("()"),
|
||||
.whitespace(" "),
|
||||
.plainText("->"),
|
||||
.whitespace(" "),
|
||||
.token("T", .type),
|
||||
.plainText(")"),
|
||||
.whitespace(" "),
|
||||
.plainText("{}")
|
||||
])
|
||||
}
|
||||
|
||||
func testActorDeclaration() {
|
||||
let components = highlighter.highlight("""
|
||||
actor MyActor {
|
||||
var value = 0
|
||||
func action() {}
|
||||
}
|
||||
""")
|
||||
|
||||
XCTAssertEqual(components, [
|
||||
.token("actor", .keyword),
|
||||
.whitespace(" "),
|
||||
.plainText("MyActor"),
|
||||
.whitespace(" "),
|
||||
.plainText("{"),
|
||||
.whitespace("\n "),
|
||||
.token("var", .keyword),
|
||||
.whitespace(" "),
|
||||
.plainText("value"),
|
||||
.whitespace(" "),
|
||||
.plainText("="),
|
||||
.whitespace(" "),
|
||||
.token("0", .number),
|
||||
.whitespace("\n "),
|
||||
.token("func", .keyword),
|
||||
.whitespace(" "),
|
||||
.plainText("action()"),
|
||||
.whitespace(" "),
|
||||
.plainText("{}"),
|
||||
.whitespace("\n"),
|
||||
.plainText("}")
|
||||
])
|
||||
}
|
||||
|
||||
func testPublicActorDeclaration() {
|
||||
let components = highlighter.highlight("public actor MyActor {}")
|
||||
|
||||
XCTAssertEqual(components, [
|
||||
.token("public", .keyword),
|
||||
.whitespace(" "),
|
||||
.token("actor", .keyword),
|
||||
.whitespace(" "),
|
||||
.plainText("MyActor"),
|
||||
.whitespace(" "),
|
||||
.plainText("{}")
|
||||
])
|
||||
}
|
||||
|
||||
func testDeclaringAndMutatingLocalVariableNamedActor() {
|
||||
let components = highlighter.highlight("""
|
||||
let actor = Actor()
|
||||
actor.position = scene.center
|
||||
""")
|
||||
|
||||
XCTAssertEqual(components, [
|
||||
.token("let", .keyword),
|
||||
.whitespace(" "),
|
||||
.plainText("actor"),
|
||||
.whitespace(" "),
|
||||
.plainText("="),
|
||||
.whitespace(" "),
|
||||
.token("Actor", .type),
|
||||
.plainText("()"),
|
||||
.whitespace("\n"),
|
||||
.plainText("actor."),
|
||||
.token("position", .property),
|
||||
.whitespace(" "),
|
||||
.plainText("="),
|
||||
.whitespace(" "),
|
||||
.plainText("scene."),
|
||||
.token("center", .property)
|
||||
])
|
||||
}
|
||||
|
||||
func testPassingAndReferencingLocalVariableNamedActor() {
|
||||
let components = highlighter.highlight("""
|
||||
prepare(actor: actor)
|
||||
scene.add(actor)
|
||||
latestActor = actor
|
||||
return actor
|
||||
""")
|
||||
|
||||
XCTAssertEqual(components, [
|
||||
.token("prepare", .call),
|
||||
.plainText("(actor:"),
|
||||
.whitespace(" "),
|
||||
.plainText("actor)"),
|
||||
.whitespace("\n"),
|
||||
.plainText("scene."),
|
||||
.token("add", .call),
|
||||
.plainText("(actor)"),
|
||||
.whitespace("\n"),
|
||||
.plainText("latestActor"),
|
||||
.whitespace(" "),
|
||||
.plainText("="),
|
||||
.whitespace(" "),
|
||||
.plainText("actor"),
|
||||
.whitespace("\n"),
|
||||
.token("return", .keyword),
|
||||
.whitespace(" "),
|
||||
.plainText("actor")
|
||||
])
|
||||
}
|
||||
}
|
||||
|
|
|
@ -467,4 +467,92 @@ final class StatementTests: SyntaxHighlighterTestCase {
|
|||
.plainText("queryItems")
|
||||
])
|
||||
}
|
||||
|
||||
func testAwaitingFunctionCall() {
|
||||
let components = highlighter.highlight("let result = await call()")
|
||||
|
||||
XCTAssertEqual(components, [
|
||||
.token("let", .keyword),
|
||||
.whitespace(" "),
|
||||
.plainText("result"),
|
||||
.whitespace(" "),
|
||||
.plainText("="),
|
||||
.whitespace(" "),
|
||||
.token("await", .keyword),
|
||||
.whitespace(" "),
|
||||
.token("call", .call),
|
||||
.plainText("()")
|
||||
])
|
||||
}
|
||||
|
||||
func testAwaitingVariable() {
|
||||
let components = highlighter.highlight("let result = await value")
|
||||
|
||||
XCTAssertEqual(components, [
|
||||
.token("let", .keyword),
|
||||
.whitespace(" "),
|
||||
.plainText("result"),
|
||||
.whitespace(" "),
|
||||
.plainText("="),
|
||||
.whitespace(" "),
|
||||
.token("await", .keyword),
|
||||
.whitespace(" "),
|
||||
.plainText("value")
|
||||
])
|
||||
}
|
||||
|
||||
func testAwaitingAsyncSequenceElement() {
|
||||
let components = highlighter.highlight("for await value in sequence {}")
|
||||
|
||||
XCTAssertEqual(components, [
|
||||
.token("for", .keyword),
|
||||
.whitespace(" "),
|
||||
.token("await", .keyword),
|
||||
.whitespace(" "),
|
||||
.plainText("value"),
|
||||
.whitespace(" "),
|
||||
.token("in", .keyword),
|
||||
.whitespace(" "),
|
||||
.plainText("sequence"),
|
||||
.whitespace(" "),
|
||||
.plainText("{}")
|
||||
])
|
||||
}
|
||||
|
||||
func testAwaitingThrowingAsyncSequenceElement() {
|
||||
let components = highlighter.highlight("for try await value in sequence {}")
|
||||
|
||||
XCTAssertEqual(components, [
|
||||
.token("for", .keyword),
|
||||
.whitespace(" "),
|
||||
.token("try", .keyword),
|
||||
.whitespace(" "),
|
||||
.token("await", .keyword),
|
||||
.whitespace(" "),
|
||||
.plainText("value"),
|
||||
.whitespace(" "),
|
||||
.token("in", .keyword),
|
||||
.whitespace(" "),
|
||||
.plainText("sequence"),
|
||||
.whitespace(" "),
|
||||
.plainText("{}")
|
||||
])
|
||||
}
|
||||
|
||||
func testAsyncLetExpression() {
|
||||
let components = highlighter.highlight("async let result = call()")
|
||||
|
||||
XCTAssertEqual(components, [
|
||||
.token("async", .keyword),
|
||||
.whitespace(" "),
|
||||
.token("let", .keyword),
|
||||
.whitespace(" "),
|
||||
.plainText("result"),
|
||||
.whitespace(" "),
|
||||
.plainText("="),
|
||||
.whitespace(" "),
|
||||
.token("call", .call),
|
||||
.plainText("()")
|
||||
])
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue