-
-
Save regexident/8394fa2e58bdc4ee634d5228a3f2ad0f to your computer and use it in GitHub Desktop.
Simple Swift Formatter using SwiftSyntax
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import Foundation | |
import SwiftSyntax | |
func main() throws { | |
guard CommandLine.arguments.count > 1 else { | |
print("usage: swift-format [file]") | |
exit(-1) | |
} | |
let url = URL(fileURLWithPath: CommandLine.arguments[1]) | |
let file = try Syntax.parse(url) | |
let bracesFixed = FixBraces().visit(file) | |
let equalsFixed = FixEqualsSpacing().visit(bracesFixed) | |
let semiclonsRemoved = RemoveSemicolons().visit(equalsFixed) | |
try "\(semiclonsRemoved)".write(to: url, atomically: true, | |
encoding: .utf8) | |
} | |
/// Determines if a token has a newline in its leading trivia. | |
func hasLeadingNewline(_ token: TokenSyntax) -> Bool { | |
for piece in token.leadingTrivia { | |
if case .newlines(_) = piece { return true } | |
} | |
return false | |
} | |
/// Ensures all opening braces and brackets appear on the same line | |
/// as the previous token. | |
class FixBraces: SyntaxRewriter { | |
override func visit(_ token: TokenSyntax) -> Syntax { | |
switch token.tokenKind { | |
case .leftBrace where hasLeadingNewline(token): | |
return token.withLeadingTrivia(.spaces(1)) | |
default: | |
return token | |
} | |
} | |
} | |
/// Ensure all equal signs have 1 space before and after them. | |
class FixEqualsSpacing: SyntaxRewriter { | |
override func visit(_ token: TokenSyntax) -> Syntax { | |
// The child after this one, if there is one. | |
let sibling = token.parent?.child(at: token.indexInParent + 1) | |
// If the next token is an equals, then replace this trailing trivia | |
// with a single space. | |
if let nextToken = sibling as? TokenSyntax, | |
case .equal = nextToken.tokenKind { | |
return token.withTrailingTrivia(.spaces(1)) | |
} | |
// Otherwise, if this token is an equals, replace its trailing trivia | |
// with one space. | |
if case .equal = token.tokenKind { | |
return token.withTrailingTrivia(.spaces(1)) | |
} | |
return token | |
} | |
} | |
/// Removes all unnecessary semicolons (semicolons that end a line in | |
/// a scope). | |
class RemoveSemicolons: SyntaxRewriter { | |
func shouldRemoveSemicolon(_ token: TokenSyntax) -> Bool { | |
guard case .semicolon = token.tokenKind else { | |
return false | |
} | |
let nextToken = token.parent?.child(at: token.indexInParent + 1) | |
if let sibling = nextToken as? TokenSyntax, | |
sibling.tokenKind != .rightBrace { | |
return hasLeadingNewline(sibling) | |
} | |
return true | |
} | |
override func visit(_ token: TokenSyntax) -> TokenSyntax { | |
if shouldRemoveSemicolon(token) { | |
return SyntaxFactory.makeIdentifier("") | |
} | |
return token | |
} | |
} | |
try main() |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment