Skip to content

Commit eceb971

Browse files
LaurenWhiteallevato
authored andcommitted
Implement group numeric literals (swiftlang#79)
1 parent ce99bae commit eceb971

File tree

2 files changed

+108
-0
lines changed

2 files changed

+108
-0
lines changed

tools/swift-format/Sources/Rules/GroupNumericLiterals.swift

+60
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,67 @@ import SwiftSyntax
1010
///
1111
/// Format: All numeric literals that should be grouped will have `_`s inserted where appropriate.
1212
///
13+
/// TODO: Minimum numeric literal length bounds and numeric groupings selected arbitrarily, could
14+
/// be reevaluated.
15+
///
16+
/// TODO: Handle floating point literals
17+
///
1318
/// - SeeAlso: https://google.github.io/swift#numeric-literals
1419
public final class GroupNumericLiterals: SyntaxFormatRule {
20+
public override func visit(_ node: IntegerLiteralExprSyntax) -> ExprSyntax {
21+
22+
var digits = node.digits.text
23+
guard !digits.contains("_") else { return node }
24+
25+
let isNegative = digits.first == "-"
26+
digits = isNegative ? String(digits.dropFirst()) : digits
27+
28+
var newDigits = ""
29+
30+
switch digits.prefix(2) {
31+
case "0x":
32+
// Hexadecimal
33+
let digitsNoPrefix = String(digits.dropFirst(2))
34+
guard let intDigits = Int(digitsNoPrefix, radix: 16) else { return node }
35+
guard intDigits >= 0x1000_0000 else { return node }
36+
diagnose(.groupNumericLiteral(byStride: 4), on: node)
37+
newDigits = "0x" + groupDigitsByStride(digits: digitsNoPrefix, stride: 4)
38+
case "0b":
39+
// Binary
40+
let digitsNoPrefix = String(digits.dropFirst(2))
41+
guard let intDigits = Int(digitsNoPrefix, radix: 2) else { return node }
42+
guard intDigits >= 0b1_000000000 else { return node }
43+
diagnose(.groupNumericLiteral(byStride: 8), on: node)
44+
newDigits = "0b" + groupDigitsByStride(digits: digitsNoPrefix, stride: 8)
45+
case "0o":
46+
// Octal
47+
return node
48+
default:
49+
// Decimal
50+
guard let intDigits = Int(digits) else { return node }
51+
guard intDigits >= 1_000_000 else { return node }
52+
diagnose(.groupNumericLiteral(byStride: 3), on: node)
53+
newDigits = groupDigitsByStride(digits: digits, stride: 3)
54+
}
55+
56+
newDigits = isNegative ? "-" + newDigits : newDigits
57+
return node.withDigits(SyntaxFactory.makeIdentifier(newDigits))
58+
}
59+
60+
func groupDigitsByStride(digits: String, stride: Int) -> String {
61+
var newGrouping = Array(digits)
62+
var i = 1
63+
while i * stride < digits.count {
64+
newGrouping.insert("_", at: digits.count - i * stride)
65+
i += 1
66+
}
67+
return String(newGrouping)
68+
}
69+
}
1570

71+
extension Diagnostic.Message {
72+
static func groupNumericLiteral(byStride: Int) -> Diagnostic.Message {
73+
let ending = byStride == 3 ? "rd" : "th"
74+
return .init(.warning, "group numeric literal using '_' every \(byStride)\(ending) number")
75+
}
1676
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
import Foundation
2+
import XCTest
3+
import SwiftSyntax
4+
5+
@testable import Rules
6+
7+
public class GroupNumericLiteralsTests: DiagnosingTestCase {
8+
public func testNumericGrouping() {
9+
XCTAssertFormatting(
10+
GroupNumericLiterals.self,
11+
input: """
12+
let a = 9876543210
13+
let b = 1234
14+
let c = 0x34950309233
15+
let d = -0x34242
16+
let e = 0b10010010101
17+
let f = 0b101
18+
let g = 11_15_1999
19+
let h = 0o21743
20+
let i = -53096828347
21+
""",
22+
expected: """
23+
let a = 9_876_543_210
24+
let b = 1234
25+
let c = 0x349_5030_9233
26+
let d = -0x34242
27+
let e = 0b100_10010101
28+
let f = 0b101
29+
let g = 11_15_1999
30+
let h = 0o21743
31+
let i = -53_096_828_347
32+
""")
33+
XCTAssertDiagnosed(.groupNumericLiteral(byStride: 3))
34+
XCTAssertDiagnosed(.groupNumericLiteral(byStride: 3))
35+
XCTAssertNotDiagnosed(.groupNumericLiteral(byStride: 3))
36+
XCTAssertDiagnosed(.groupNumericLiteral(byStride: 4))
37+
XCTAssertNotDiagnosed(.groupNumericLiteral(byStride: 4))
38+
XCTAssertDiagnosed(.groupNumericLiteral(byStride: 8))
39+
XCTAssertNotDiagnosed(.groupNumericLiteral(byStride: 8))
40+
}
41+
42+
#if !os(macOS)
43+
static let allTests = [
44+
GroupNumericLiterals.testNumericGrouping,
45+
]
46+
#endif
47+
48+
}

0 commit comments

Comments
 (0)