Skip to content

Commit 0e55eeb

Browse files
committed
Fix of in fixed-size arrays being mistaken for an identifier
1 parent 9f6093a commit 0e55eeb

File tree

7 files changed

+107
-27
lines changed

7 files changed

+107
-27
lines changed

Sources/ParsingHelpers.swift

Lines changed: 14 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -389,14 +389,14 @@ extension Formatter {
389389

390390
/// Returns true if the token at specified index is a modifier
391391
func isModifier(at index: Int) -> Bool {
392-
guard let token = token(at: index), token._isModifierKeyword else {
392+
guard let token = token(at: index), token.isModifierKeyword else {
393393
return false
394394
}
395395

396396
if token == .keyword("class"),
397397
let nextToken = next(.nonSpaceOrCommentOrLinebreak, after: index)
398398
{
399-
return nextToken.isDeclarationTypeKeyword || nextToken._isModifierKeyword
399+
return nextToken.isDeclarationTypeKeyword || nextToken.isModifierKeyword
400400
}
401401

402402
// Async is only a valid modifier on local let/var declarations.
@@ -2536,7 +2536,7 @@ extension Formatter {
25362536
if nextToken.isDeclarationTypeKeyword {
25372537
return nextToken.string
25382538
}
2539-
guard nextToken._isModifierKeyword else {
2539+
guard nextToken.isModifierKeyword else {
25402540
break
25412541
}
25422542
nextIndex = i
@@ -3652,12 +3652,22 @@ extension Token {
36523652
/// This doesn't necessarily mean that the keyword is a modifier: some modifiers
36533653
/// like `class` and `async` are contextual.
36543654
/// In rule implementations, prefer using the `Formatter.isModifier(at:)` helper.
3655-
var _isModifierKeyword: Bool {
3655+
var isModifierKeyword: Bool {
36563656
switch self {
36573657
case let .keyword(keyword), let .identifier(keyword):
36583658
return _FormatRules.allModifiers.contains(keyword)
36593659
default:
36603660
return false
36613661
}
36623662
}
3663+
3664+
/// These identifiers are treated as keywords when used in a type position
3665+
var isKeywordInTypeContext: Bool {
3666+
switch self {
3667+
case let .keyword(keyword), let .identifier(keyword):
3668+
return ["borrowing", "consuming", "isolated", "sending", "some", "any", "of"].contains(keyword)
3669+
default:
3670+
return false
3671+
}
3672+
}
36633673
}

Sources/Rules/SpaceAroundBrackets.swift

Lines changed: 2 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -25,19 +25,14 @@ public extension FormatRule {
2525
return
2626
}
2727
switch prevToken {
28-
case .keyword,
29-
.identifier("borrowing") where formatter.isTypePosition(at: index),
30-
.identifier("consuming") where formatter.isTypePosition(at: index),
31-
.identifier("sending") where formatter.isTypePosition(at: index):
28+
case .identifier where prevToken.isKeywordInTypeContext && formatter.isTypePosition(at: index), .keyword:
3229
formatter.insert(.space(" "), at: i)
3330
case .space:
3431
let index = i - 2
3532
if let token = formatter.token(at: index) {
3633
switch token {
3734
case .identifier("as"), .identifier("is"), // not treated as keywords inside macro
38-
.identifier("borrowing") where formatter.isTypePosition(at: index),
39-
.identifier("consuming") where formatter.isTypePosition(at: index),
40-
.identifier("sending") where formatter.isTypePosition(at: index),
35+
.identifier where token.isKeywordInTypeContext && formatter.isTypePosition(at: index),
4136
.identifier("try"), .keyword("try"):
4237
break
4338
case .identifier, .number, .endOfScope("]"), .endOfScope("}"), .endOfScope(")"):

Sources/Rules/SpaceAroundOperators.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,7 @@ public extension FormatRule {
3737
default:
3838
formatter.insert(.space(" "), at: i + 1)
3939
}
40-
case .operator("?", .postfix), .operator("!", .postfix):
40+
case let token where token.isUnwrapOperator:
4141
if let prevToken = formatter.token(at: i - 1),
4242
formatter.token(at: i + 1)?.isSpaceOrLinebreak == false,
4343
[.keyword("as"), .keyword("try")].contains(prevToken)

Sources/Rules/SpaceAroundParens.swift

Lines changed: 2 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -30,25 +30,15 @@ public extension FormatRule {
3030
fallthrough
3131
case .endOfScope("]") where formatter.isInClosureArguments(at: index),
3232
.endOfScope(")") where formatter.isAttribute(at: index),
33-
.identifier("some") where formatter.isTypePosition(at: index),
34-
.identifier("any") where formatter.isTypePosition(at: index),
35-
.identifier("borrowing") where formatter.isTypePosition(at: index),
36-
.identifier("consuming") where formatter.isTypePosition(at: index),
37-
.identifier("isolated") where formatter.isTypePosition(at: index),
38-
.identifier("sending") where formatter.isTypePosition(at: index):
33+
.identifier where prevToken.isKeywordInTypeContext && formatter.isTypePosition(at: index):
3934
formatter.insert(.space(" "), at: i)
4035
case .space:
4136
let index = i - 2
4237
guard let token = formatter.token(at: index) else {
4338
return
4439
}
4540
switch token {
46-
case .identifier("some") where formatter.isTypePosition(at: index),
47-
.identifier("any") where formatter.isTypePosition(at: index),
48-
.identifier("borrowing") where formatter.isTypePosition(at: index),
49-
.identifier("consuming") where formatter.isTypePosition(at: index),
50-
.identifier("isolated") where formatter.isTypePosition(at: index),
51-
.identifier("sending") where formatter.isTypePosition(at: index):
41+
case .identifier where token.isKeywordInTypeContext && formatter.isTypePosition(at: index):
5242
break
5343
case let .keyword(string) where !formatter.spaceAfter(string, index: index):
5444
fallthrough

Sources/Tokenizer.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,7 @@ import Foundation
3838
/// behave like identifiers. So too have context-specific keywords such as the following:
3939
/// any, associativity, async, convenience, didSet, dynamic, final, get, indirect, infix, lazy,
4040
/// left, mutating, none, nonmutating, open, optional, override, postfix, precedence,
41-
/// prefix, Protocol, required, right, set, some, any, Type, unowned, weak, willSet
41+
/// prefix, Protocol, required, right, set, some, any, of, Type, unowned, weak, willSet
4242
let swiftKeywords = Set([
4343
"let", "return", "func", "var", "if", "public", "as", "else", "in", "import",
4444
"class", "try", "guard", "case", "for", "init", "extension", "private", "static",

Tests/Rules/SpaceAroundBracketsTests.swift

Lines changed: 26 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,30 @@ final class SpaceAroundBracketsTests: XCTestCase {
3434
testFormatting(for: input, rule: .spaceAroundBrackets)
3535
}
3636

37+
func testSpaceNotRemovedAfterOfArray() {
38+
let input = """
39+
let foo: [4 of [String]]
40+
"""
41+
testFormatting(for: input, rule: .spaceAroundBrackets)
42+
}
43+
44+
func testSpaceAddedAfterOfArray() {
45+
let input = """
46+
let foo: [4 of[String]]
47+
"""
48+
let output = """
49+
let foo: [4 of [String]]
50+
"""
51+
testFormatting(for: input, output, rule: .spaceAroundBrackets)
52+
}
53+
54+
func testOfIdentifierBracketSpacing() {
55+
let input = """
56+
if foo.of[String.self] {}
57+
"""
58+
testFormatting(for: input, rule: .spaceAroundBrackets)
59+
}
60+
3761
func testAsArrayCastingSpacing() {
3862
let input = """
3963
foo as[String]
@@ -61,9 +85,9 @@ final class SpaceAroundBracketsTests: XCTestCase {
6185
testFormatting(for: input, output, rule: .spaceAroundBrackets)
6286
}
6387

64-
func testKeywordAsIdentifierBracketSpacing() {
88+
func testIsIdentifierBracketSpacing() {
6589
let input = """
66-
if foo.is[String] {}
90+
if foo.is[String.self] {}
6791
"""
6892
testFormatting(for: input, rule: .spaceAroundBrackets)
6993
}

Tests/Rules/SpaceAroundParensTests.swift

Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -536,4 +536,65 @@ final class SpaceAroundParensTests: XCTestCase {
536536
"""
537537
testFormatting(for: input, output, rule: .spaceAroundParens)
538538
}
539+
540+
func testOfTupleSpacing() {
541+
let input = """
542+
let foo: [4 of(String, Int)]
543+
"""
544+
let output = """
545+
let foo: [4 of (String, Int)]
546+
"""
547+
testFormatting(for: input, output, rule: .spaceAroundParens)
548+
}
549+
550+
func testOfIdentifierParenSpacing() {
551+
let input = """
552+
if foo.of(String.self) {}
553+
"""
554+
testFormatting(for: input, rule: .spaceAroundParens)
555+
}
556+
557+
func testAsTupleCastingSpacing() {
558+
let input = """
559+
foo as(String, Int)
560+
"""
561+
let output = """
562+
foo as (String, Int)
563+
"""
564+
testFormatting(for: input, output, rule: .spaceAroundParens)
565+
}
566+
567+
func testAsOptionalTupleCastingSpacing() {
568+
let input = """
569+
foo as? (String, Int)
570+
"""
571+
testFormatting(for: input, rule: .spaceAroundParens)
572+
}
573+
574+
func testIsTupleTestingSpacing() {
575+
let input = """
576+
if foo is(String, Int) {}
577+
"""
578+
let output = """
579+
if foo is (String, Int) {}
580+
"""
581+
testFormatting(for: input, output, rule: .spaceAroundParens)
582+
}
583+
584+
func testIsIdentifierParenSpacing() {
585+
let input = """
586+
if foo.is(String.self, Int.self) {}
587+
"""
588+
testFormatting(for: input, rule: .spaceAroundParens)
589+
}
590+
591+
func testSpaceBeforeTupleIndexCall() {
592+
let input = """
593+
foo.1 (2)
594+
"""
595+
let output = """
596+
foo.1(2)
597+
"""
598+
testFormatting(for: input, output, rule: .spaceAroundParens)
599+
}
539600
}

0 commit comments

Comments
 (0)