Skip to content

Commit 4b12c49

Browse files
authored
Parse Swift operators as link components for operators with symbol diacritics (#1125)
1 parent 62fb5d7 commit 4b12c49

File tree

2 files changed

+53
-2
lines changed

2 files changed

+53
-2
lines changed

Sources/SwiftDocC/Infrastructure/Link Resolution/PathHierarchy+PathComponent.swift

Lines changed: 50 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -248,7 +248,7 @@ private struct PathComponentScanner {
248248

249249
mutating func _scanOperatorName() -> Substring? {
250250
// If the next component is a Swift operator, parse the full operator before splitting on "/" ("/" may appear in the operator name)
251-
if remaining.unicodeScalars.prefix(3).allSatisfy(\.isValidSwiftOperatorHead) {
251+
if remaining.unicodeScalars.prefix(3).isValidSwiftOperator() {
252252
return scanUntil(index: remaining.firstIndex(of: Self.swiftOperatorEnd)) + scan(length: 1)
253253
}
254254

@@ -354,6 +354,20 @@ private extension StringProtocol {
354354
}
355355
}
356356

357+
private extension Collection<Unicode.Scalar> {
358+
/// Determines if this sequence of unicode scalars represent a valid Swift operator name
359+
/// - Complexity: O(_n_), where _n_ is the length of the collection.
360+
func isValidSwiftOperator() -> Bool {
361+
// See https://docs.swift.org/swift-book/documentation/the-swift-programming-language/lexicalstructure#Operators
362+
363+
// The first character of an operator supports fewer characters than the rest of the operator name
364+
guard let first, first.isValidSwiftOperatorHead else {
365+
return false
366+
}
367+
return dropFirst().allSatisfy { $0.isValidSwiftOperatorCharacter }
368+
}
369+
}
370+
357371
private extension Unicode.Scalar {
358372
/// Checks if this unicode scalar is a valid C99 Extended Identifier.
359373
var isValidC99ExtendedIdentifier: Bool {
@@ -547,10 +561,11 @@ private extension Unicode.Scalar {
547561
}
548562
}
549563

564+
/// A Boolean value that indicates if this scalar is a valid Swift operator head.
550565
var isValidSwiftOperatorHead: Bool {
551566
// See https://docs.swift.org/swift-book/documentation/the-swift-programming-language/lexicalstructure#Operators
552567
switch value {
553-
case
568+
case
554569
// ! % & * + - . / < = > ? ^| ~
555570
0x21, 0x25, 0x26, 0x2A, 0x2B, 0x2D...0x2F, 0x3C, 0x3D...0x3F, 0x5E, 0x7C, 0x7E,
556571
// ¡ ¢ £ ¤ ¥ ¦ §
@@ -573,6 +588,12 @@ private extension Unicode.Scalar {
573588
0x2041 ... 0x2053,
574589
// ⁕ ⁖ ⁗ ⁘ ⁙ ⁚ ⁛ ⁜ ⁝ ⁞
575590
0x2055 ... 0x205E,
591+
// Arrows
592+
0x2190 ... 0x21FF,
593+
// Mathematical Operators
594+
0x2200 ... 0x22FF,
595+
// Miscellaneous Technical
596+
0x2300 ... 0x23FF,
576597
// Box Drawing
577598
0x2500 ... 0x257F,
578599
// Block Elements
@@ -610,4 +631,31 @@ private extension Unicode.Scalar {
610631
return false
611632
}
612633
}
634+
635+
/// A Boolean value that indicates if this scalar is a valid Swift operator character (after the first character).
636+
var isValidSwiftOperatorCharacter: Bool {
637+
// See https://docs.swift.org/swift-book/documentation/the-swift-programming-language/lexicalstructure#Operators
638+
if isValidSwiftOperatorHead {
639+
return true
640+
}
641+
642+
switch value {
643+
case
644+
// Combining Diacritical Marks
645+
0x0300 ... 0x036F,
646+
// Combining Diacritical Marks Supplement
647+
0x1DC0 ... 0x1DFF,
648+
// Combining Diacritical Marks for Symbols
649+
0x20D0 ... 0x20FF,
650+
// Variation Selectors
651+
0xFE00 ... 0xFE0F,
652+
// Combining Half Marks
653+
0xFE20 ... 0xFE2F,
654+
// Variation Selectors Supplement
655+
0xE0100 ... 0xE01EF:
656+
return true
657+
default:
658+
return false
659+
}
660+
}
613661
}

Tests/SwiftDocCTests/Infrastructure/PathHierarchyTests.swift

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3453,6 +3453,9 @@ class PathHierarchyTests: XCTestCase {
34533453
assertParsedPathComponents("MyNumber//=(_:_:)", [("MyNumber", nil), ("/=(_:_:)", nil)])
34543454
assertParsedPathComponents("MyNumber////=(_:_:)", [("MyNumber", nil), ("///=(_:_:)", nil)])
34553455
assertParsedPathComponents("MyNumber/+/-(_:_:)", [("MyNumber", nil), ("+/-(_:_:)", nil)])
3456+
3457+
// "☜⃩" is a symbol with a symbol diacritic mark.
3458+
assertParsedPathComponents("☜⃩/(_:_:)", [("☜⃩/(_:_:)", nil)])
34563459

34573460
// Check parsing return values and parameter types
34583461
assertParsedPathComponents("..<(_:_:)->Bool", [("..<(_:_:)", .typeSignature(parameterTypes: nil, returnTypes: ["Bool"]))])

0 commit comments

Comments
 (0)