Updated to version 0.4

This commit is contained in:
Nick Lockwood
2016-08-24 18:24:49 +01:00
parent 6dc09b9d47
commit 2d7ec83b7c
10 changed files with 625 additions and 337 deletions
+32 -16
View File
@@ -2,7 +2,7 @@
// SwiftFormat
// main.swift
//
// Version 0.3
// Version 0.4
//
// Created by Nick Lockwood on 12/08/2016.
// Copyright 2016 Charcoal Design
@@ -33,7 +33,7 @@
import Foundation
let version = "0.3"
let version = "0.4"
func processInput(inputURL: NSURL, andWriteToOutput outputURL: NSURL, withOptions options: FormattingOptions) -> Int {
let manager = NSFileManager.defaultManager()
@@ -144,22 +144,23 @@ func processArguments(args: [String]) {
guard let args = preprocessArguments(args, [
"output",
"indent",
"semicolons",
"help",
"version",
]) else {
return
}
// Version
if args["version"] != nil {
print("swiftformat, version \(version)")
return
}
// Get input / output paths
let inputPath = args["1"]
let outputPath = args["output"] ?? inputPath
var inputPath = args["1"]
var outputPath = args["output"] ?? inputPath
// Show help if requested specifically or if no arguments are passed
if args["help"] != nil || inputPath == nil {
print("swiftformat, version \(version)")
@@ -167,22 +168,27 @@ func processArguments(args: [String]) {
print("")
print("usage: swiftformat <file> [-o path] [-i spaces]")
print("")
print(" <file> input file or directory path")
print(" -o, --output output path (defaults to input path)")
print(" -i, --indent number of spaces to indent, or \"tab\" to use tabs")
print(" -h, --help this help page")
print(" -v, --version version information")
print(" <file> input file or directory path")
print(" -o, --output output path (defaults to input path)")
print(" -i, --indent number of spaces to indent, or \"tab\" to use tabs")
print(" -s, --semicolons allow semicolons. values are \"never\" or \"inline\" (default)")
print(" -h, --help this help page")
print(" -v, --version version information")
print("")
return
}
print("running swiftformat...")
// Expand input / output paths
inputPath = NSString(string: inputPath!).stringByExpandingTildeInPath
outputPath = NSString(string: outputPath!).stringByExpandingTildeInPath
// Convert paths to file URLs relative to current directory
let directoryURL = NSURL(fileURLWithPath: NSFileManager.defaultManager().currentDirectoryPath)
let inputURL = NSURL(fileURLWithPath: inputPath!, relativeToURL: directoryURL)
let outputURL = NSURL(fileURLWithPath: outputPath!, relativeToURL: directoryURL)
// Get options
var options = FormattingOptions()
if let indent = args["indent"] {
@@ -195,7 +201,17 @@ func processArguments(args: [String]) {
return
}
}
if let semicolons = args["semicolons"] {
if semicolons == "inline" {
options.allowInlineSemicolons = true
} else if semicolons == "never" {
options.allowInlineSemicolons = false
} else {
print("error: unsupported semicolons value: \(semicolons).")
return
}
}
// Format the code
let filesWritten = processInput(inputURL, andWriteToOutput: outputURL, withOptions: options)
print("swiftformat completed. \(filesWritten) file(s) updated.")
Binary file not shown.
+1 -1
View File
@@ -1,6 +1,6 @@
SwiftFormat
Version 0.3, August 23rd, 2016
Version 0.4, August 24th, 2016
Copyright (c) 2016 Nick Lockwood
+22 -3
View File
@@ -176,6 +176,16 @@ Here are all the rules that SwiftFormat currently applies:
// MARK - UIScrollViewDelegate --> // MARK: - UIScrollViewDelegate
*semicolons* - removes semicolons at the end of lines and (optionally) replaces inline semicolons with a linebreak:
let foo = 5; --> let foo = 5
let foo = 5; let bar = 6 --> let foo = 5
let bar = 6
return; --> return;
goto(fail) goto(fail)
FAQ
-----
@@ -296,16 +306,25 @@ Or begin each line with a `*` (or any other non-whitespace character)
What's next?
--------------
I expect people will discover (and hopefully report) a lot of bugs in this first release, so the next step will be to fix all of those.
There are a bunch of additional rules I'd like to add, such as removing trailing semicolons, or correctly formatting headerdoc comments.
There are a bunch of additional rules I'd like to add, such as correctly formatting headerdoc comments.
At some point I should probably add an intermediate parsing stage that identifies high-level constructs such as classes and functions and assembles them into a syntax tree. I did't bother doing this originally because I thought it would be easier to implement formatting at the token level, but in fact this just meant that the logic for distinguishing between syntax constructs had to be split between the tokenizer and the formatting rules, making both of them more complex than they ought to be.
With a syntax tree in place, it should become possible to add much more sophisticated rules, such as converting uppercase enums to lowercase for Swift 3, etc.
Release notes
----------------
Version 0.4
- Added new `semicolons` rule, which removes semicolons wherever it's safe to do so
- Added `--semicolons` command-line argument for enabling inline semicolon stripping
- The `todos` rule now corrects `MARK :` to `MARK:` instead of `MARK: :`
- Paths containing ~ are now handled correctly by the command line tool
- Fixed some bugs in generics and custom operator parsing, and added more tests
- Removed trailing whitespace on blank lines caused by the `indent` rule
Version 0.3
- Fixed several cases where generics were misidentified as operators
+196 -33
View File
@@ -2,7 +2,7 @@
// SwiftFormat
// Formatter.swift
//
// Version 0.3
// Version 0.4
//
// Created by Nick Lockwood on 12/08/2016.
// Copyright 2016 Charcoal Design
@@ -36,7 +36,15 @@ import Foundation
/// Configuration options for formatting. These aren't actually used by the
/// Formatter class itself, but it makes them available to the format rules.
public struct FormattingOptions {
var indent: String = " "
public var indent: String
public var allowInlineSemicolons: Bool
public init(indent: String = " ",
allowInlineSemicolons: Bool = true) {
self.indent = indent
self.allowInlineSemicolons = allowInlineSemicolons
}
}
/// This is a utility class used for manipulating a tokenized source file.
@@ -48,20 +56,20 @@ public struct FormattingOptions {
public class Formatter {
private(set) var tokens: [Token]
let options: FormattingOptions
private var indexStack: [Int] = []
init(_ tokens: [Token], options: FormattingOptions) {
self.tokens = tokens
self.options = options
}
/// Returns the token at the specified index, or nil if index is invalid
public func tokenAtIndex(index: Int) -> Token? {
guard index >= 0 && index < tokens.count else { return nil }
return tokens[index]
}
/// Replaces the token at the specified index with one or more new tokens
public func replaceTokenAtIndex(index: Int, with tokens: Token...) {
if tokens.count == 0 {
@@ -73,7 +81,7 @@ public class Formatter {
}
}
}
/// Replaces the tokens in the specified range with new tokens
public func replaceTokensInRange(range: Range<Int>, with tokens: Token...) {
let max = min(range.count, tokens.count)
@@ -90,7 +98,7 @@ public class Formatter {
}
}
}
/// Removes the token at the specified indez
public func removeTokenAtIndex(index: Int) {
tokens.removeAtIndex(index)
@@ -100,17 +108,17 @@ public class Formatter {
}
}
}
/// Removes the tokens in the specified range
public func removeTokensInRange(range: Range<Int>) {
replaceTokensInRange(range)
}
/// Removes the last token
public func removeLastToken() {
tokens.removeLast()
}
/// Inserts a tokens at the specified index
public func insertToken(token: Token, atIndex index: Int) {
tokens.insert(token, atIndex: index)
@@ -120,7 +128,7 @@ public class Formatter {
}
}
}
/// Loops through each token in the array. It is safe to mutate the token
/// array inside the body block, but note that the index and token arguments
/// may not reflect the current token any more after a mutation
@@ -134,19 +142,19 @@ public class Formatter {
}
indexStack.popLast()
}
/// As above, but only loops through tokens with the specified type
public func forEachToken(ofType type: TokenType, _ body: (Int, Token) -> Void) {
forEachToken(matching: { $0.type == type }, body)
}
/// As above, but only loops through tokens with the specified type and string
public func forEachToken(string: String, ofType type: TokenType, _ body: (Int, Token) -> Void) {
forEachToken(matching: {
return $0.type == type && $0.string == string
}, body)
}
/// As above, but only loops through tokens with the specified string.
/// Tokens of type `StringBody` and `CommentBody` are ignored, as these
/// can't be usefully identified by their string value
@@ -155,7 +163,7 @@ public class Formatter {
return $0.string == string && $0.type != .StringBody && $0.type != .CommentBody
}, body)
}
private func forEachToken(matching condition: (Token) -> Bool, _ body: (Int, Token) -> Void) {
forEachToken { index, token in
if condition(token) {
@@ -200,7 +208,7 @@ public func spaceAroundParens(formatter: Formatter) {
return false
}
}
formatter.forEachToken("(") { i, token in
guard let previousToken = formatter.tokenAtIndex(i - 1) else {
return
@@ -268,7 +276,7 @@ public func spaceAroundBrackets(formatter: Formatter) {
return false
}
}
formatter.forEachToken("[") { i, token in
guard let previousToken = formatter.tokenAtIndex(i - 1) else {
return
@@ -396,7 +404,7 @@ public func spaceAroundOperators(formatter: Formatter) {
return false
}
}
func isRvalue(token: Token) -> Bool {
switch token.type {
case .Identifier, .Number, .StartOfScope:
@@ -405,7 +413,7 @@ public func spaceAroundOperators(formatter: Formatter) {
return false
}
}
func spaceAfter(identifier: String) -> Bool {
switch identifier {
case "case",
@@ -423,7 +431,7 @@ public func spaceAroundOperators(formatter: Formatter) {
return false
}
}
var scopeStack: [Token] = []
formatter.forEachToken { i, token in
switch token.type {
@@ -595,7 +603,7 @@ public func indent(formatter: Formatter) {
}
return index
}
func nextNonWhitespaceToken(fromIndex index: Int) -> Token? {
var index = index
while let token = formatter.tokenAtIndex(index) {
@@ -606,7 +614,7 @@ public func indent(formatter: Formatter) {
}
return nil
}
func setIndent(indent: String, atIndex index: Int) {
if formatter.tokenAtIndex(index)?.type == .Whitespace {
if indent != "" {
@@ -618,7 +626,7 @@ public func indent(formatter: Formatter) {
formatter.insertToken(Token(.Whitespace, indent), atIndex: index)
}
}
var scopeIndexStack: [Int] = []
var scopeStartLineIndexes: [Int] = []
var lastNonWhitespaceOrLinebreakIndex = -1
@@ -626,14 +634,14 @@ public func indent(formatter: Formatter) {
var indentStack = [""]
var lineIndex = 0
var linewrapped = false
func currentScope() -> Token? {
if let scopeIndex = scopeIndexStack.last {
return formatter.tokens[scopeIndex]
}
return nil
}
func tokenIsEndOfStatement(i: Int) -> Bool {
if let token = formatter.tokenAtIndex(i) {
switch token.type {
@@ -703,7 +711,7 @@ public func indent(formatter: Formatter) {
}
return true
}
func tokenIsStartOfStatement(i: Int) -> Bool {
if let token = formatter.tokenAtIndex(i) {
switch token.type {
@@ -744,7 +752,7 @@ public func indent(formatter: Formatter) {
}
return true
}
formatter.forEachToken { i, token in
if token.type == .StartOfScope {
// Handle start of scope
@@ -831,8 +839,12 @@ public func indent(formatter: Formatter) {
indentStack.append(indentStack.last ?? "")
}
lineIndex += 1
let indent = (indentStack.last ?? "") + (linewrapped ? formatter.options.indent : "")
setIndent(indent, atIndex: i + 1)
setIndent("", atIndex: i + 1)
// Only indent if line isn't blank
if let nextToken = formatter.tokenAtIndex(i + 1) where nextToken.type != .Linebreak {
let indent = (indentStack.last ?? "") + (linewrapped ? formatter.options.indent : "")
setIndent(indent, atIndex: i + 1)
}
}
}
// Track token for line wraps
@@ -953,7 +965,161 @@ public func todos(formatter: Formatter) {
}
}
/// Remove semicolons, except where doing so would change the meaning of the code
public func semicolons(formatter: Formatter) {
func firstNonWhitespaceOrComment(fromIndex index: Int) -> Token? {
var i = index
var scopeStack: [Token] = []
while let token = formatter.tokenAtIndex(i) {
if let scope = scopeStack.last {
if token.closesScopeForToken(scope) {
scopeStack.popLast()
if token.type == .Linebreak {
return token
}
}
} else {
switch token.type {
case .Whitespace:
break
case .StartOfScope:
if token.string == "/*" || token.string == "//" {
scopeStack.append(token)
} else {
return token
}
default:
return token
}
}
i += 1
}
return nil
}
func firstNonWhitespaceOrCommentOrLinebreak(fromIndex index: Int) -> Token? {
var i = index
var scopeStack: [Token] = []
while let token = formatter.tokenAtIndex(i) {
if let scope = scopeStack.last {
if token.closesScopeForToken(scope) {
scopeStack.popLast()
}
} else {
switch token.type {
case .Whitespace, .Linebreak:
break
case .StartOfScope:
if token.string == "/*" || token.string == "//" {
scopeStack.append(token)
} else {
return token
}
default:
return token
}
}
i += 1
}
return nil
}
func lastNonWhitespaceOrCommentOrLinebreak(fromIndex index: Int) -> Token? {
var i = index
var scopeStack: [Token] = []
while let token = formatter.tokenAtIndex(i) {
if let scope = scopeStack.last {
if token.type == .StartOfScope && scope.closesScopeForToken(token) {
scopeStack.popLast()
} else {
return token
}
} else {
switch token.type {
case .Whitespace, .Linebreak:
break
case .EndOfScope:
if token.string == "*/" {
scopeStack.append(token)
} else {
return token
}
default:
return token
}
}
i -= 1
}
return nil
}
func currentScopeAtIndex(index: Int) -> Token? {
var i = index
var scopeStack: [Token] = []
while let token = formatter.tokenAtIndex(i) {
if token.type == .StartOfScope {
if let scope = scopeStack.last where scope.closesScopeForToken(token) {
scopeStack.popLast()
} else {
return token
}
} else if token.type == .EndOfScope {
scopeStack.append(token)
}
i -= 1
}
return nil
}
func indentAtIndex(index: Int) -> Token? {
var i = index
while let token = formatter.tokenAtIndex(i) {
if token.type == .Linebreak {
break
}
i -= 1
}
if let token = formatter.tokenAtIndex(i + 1) {
if token.type == .Whitespace {
return token
}
}
return nil
}
formatter.forEachToken(";") { i, token in
if let nextToken = firstNonWhitespaceOrCommentOrLinebreak(fromIndex: i + 1) {
let lastToken = lastNonWhitespaceOrCommentOrLinebreak(fromIndex: i - 1)
if lastToken == nil || nextToken.string == "}" {
// Safe to remove
formatter.removeTokenAtIndex(i)
} else if lastToken?.string == "return" || currentScopeAtIndex(i)?.string == "(" {
// Not safe to remove or replace
} else if firstNonWhitespaceOrComment(fromIndex: i + 1)?.type == .Linebreak {
// Safe to remove
formatter.removeTokenAtIndex(i)
} else if !formatter.options.allowInlineSemicolons {
// Replace with a linebreak
if formatter.tokenAtIndex(i + 1)?.type == .Whitespace {
formatter.removeTokenAtIndex(i + 1)
}
if let indent = indentAtIndex(i) {
formatter.insertToken(indent, atIndex: i + 1)
}
formatter.replaceTokenAtIndex(i, with: Token(.Linebreak, "\n"))
}
} else {
// Safe to remove
formatter.removeTokenAtIndex(i)
}
}
}
public let defaultRules: [FormatRule] = [
semicolons,
knrBraces,
elseOnSameLine,
indent,
spaceAroundParens,
spaceInsideParens,
spaceAroundBrackets,
@@ -967,9 +1133,6 @@ public let defaultRules: [FormatRule] = [
noTrailingWhitespace,
noConsecutiveBlankLines,
linebreakAtEndOfFile,
indent,
knrBraces,
elseOnSameLine,
trailingCommas,
todos,
]
+1 -1
View File
@@ -2,7 +2,7 @@
// SwiftFormat
// SwiftFormat.h
//
// Version 0.3
// Version 0.4
//
// Created by Nick Lockwood on 12/08/2016.
// Copyright 2016 Charcoal Design
+4 -4
View File
@@ -2,7 +2,7 @@
// SwiftFormat
// SwiftFormat.swift
//
// Version 0.3
// Version 0.4
//
// Created by Nick Lockwood on 12/08/2016.
// Copyright 2016 Charcoal Design
@@ -37,15 +37,15 @@ import Foundation
public func format(source: String,
rules: [FormatRule] = defaultRules,
options: FormattingOptions = FormattingOptions()) -> String {
// Parse
var tokens = tokenize(source)
// Format
let formatter = Formatter(tokens, options: options)
rules.forEach { $0(formatter) }
tokens = formatter.tokens
// Output
return tokens.reduce("", combine: { $0 + $1.string })
}
+39 -39
View File
@@ -2,7 +2,7 @@
// SwiftFormat
// Tokenizer.swift
//
// Version 0.3
// Version 0.4
//
// Created by Nick Lockwood on 11/08/2016.
// Copyright 2016 Charcoal Design
@@ -50,12 +50,12 @@ public enum TokenType {
public struct Token: Equatable {
public let type: TokenType
public let string: String
public init(_ type: TokenType, _ string: String) {
self.type = type
self.string = string
}
public var isWhitespaceOrComment: Bool {
switch type {
case .Whitespace, .CommentBody:
@@ -68,11 +68,11 @@ public struct Token: Equatable {
return false
}
}
public var isWhitespaceOrCommentOrLinebreak: Bool {
return type == .Linebreak || isWhitespaceOrComment
}
public func closesScopeForToken(token: Token) -> Bool {
guard type != .StringBody && type != .CommentBody else {
return false
@@ -105,17 +105,17 @@ public func ==(lhs: Token, rhs: Token) -> Bool {
}
private extension Character {
var unicodeValue: UInt32 {
return String(self).unicodeScalars.first?.value ?? 0
}
var isAlpha: Bool { return isalpha(Int32(unicodeValue)) > 0 }
var isDigit: Bool { return isdigit(Int32(unicodeValue)) > 0 }
}
private extension String.CharacterView {
mutating func scanCharacter(matching: (Character) -> Bool) -> String? {
if let c = first where matching(c) {
self = suffixFrom(startIndex.advancedBy(1))
@@ -123,7 +123,7 @@ private extension String.CharacterView {
}
return nil
}
mutating func scanString(matching string: String) -> String? {
if startsWith(string.characters) {
self = suffixFrom(startIndex.advancedBy(string.characters.count))
@@ -131,7 +131,7 @@ private extension String.CharacterView {
}
return nil
}
mutating func scanCharacters(matching: (Character) -> Bool) -> String? {
var index = endIndex
for (i, c) in enumerate() {
@@ -147,49 +147,49 @@ private extension String.CharacterView {
}
return nil
}
mutating func scanInteger() -> String? {
return scanCharacters({ $0.isDigit })
}
}
private extension String.CharacterView {
mutating func parseToken(type: TokenType, _ character: Character) -> Token? {
if let _ = scanCharacter({ $0 == character }) {
return Token(type, String(character))
}
return nil
}
mutating func parseToken(type: TokenType, _ string: String) -> Token? {
if let string = scanString(matching: string) {
return Token(type, string)
}
return nil
}
mutating func parseToken(type: TokenType, oneOf characters: String.CharacterView) -> Token? {
if let string = scanCharacter({ characters.contains($0) }) {
return Token(type, String(string))
}
return nil
}
mutating func parseToken(type: TokenType, _ characters: String.CharacterView) -> Token? {
if let string = scanCharacters({ characters.contains($0) }) {
return Token(type, string)
}
return nil
}
mutating func parseToken(type: TokenType, upTo characters: String.CharacterView) -> Token? {
if let string = scanCharacters({ !characters.contains($0) }) {
return Token(type, string)
}
return nil
}
mutating func parseToken(type: TokenType, upTo character: Character) -> Token? {
if let string = scanCharacters({ $0 != character }) {
return Token(type, string)
@@ -199,11 +199,11 @@ private extension String.CharacterView {
}
private extension String.CharacterView {
mutating func parseWhitespace() -> Token? {
return parseToken(.Whitespace, " \t".characters) // TODO: vertical tab
}
mutating func parseOperator() -> Token? {
func isHead(c: Character) -> Bool {
if "./=­-+!*%<>&|^~?".characters.contains(c) {
@@ -230,7 +230,7 @@ private extension String.CharacterView {
return false
}
}
func isTail(c: Character) -> Bool {
if isHead(c) {
return true
@@ -247,7 +247,7 @@ private extension String.CharacterView {
return false
}
}
if var tail = scanCharacter(isHead) {
var head = ""
while let c = scanCharacter(isTail) {
@@ -274,19 +274,19 @@ private extension String.CharacterView {
}
return nil
}
mutating func parsePunctuation() -> Token? {
return parseToken(.Operator, ":;,".characters)
}
mutating func parseStartOfScope() -> Token? {
return parseToken(.StartOfScope, oneOf: "([{\"".characters)
}
mutating func parseEndOfScope() -> Token? {
return parseToken(.EndOfScope, oneOf: "}])".characters)
}
mutating func parseIdentifier() -> Token? {
func isHead(c: Character) -> Bool {
if c.isAlpha || c == "_" || c == "$" {
@@ -344,7 +344,7 @@ private extension String.CharacterView {
return false
}
}
func isTail(c: Character) -> Bool {
if isHead(c) || c.isDigit {
return true
@@ -359,7 +359,7 @@ private extension String.CharacterView {
return false
}
}
func scanIdentifier() -> String? {
if let head = scanCharacter({ isHead($0) || $0 == "@" || $0 == "#" }) {
if let tail = scanCharacters({ isTail($0) }) {
@@ -369,7 +369,7 @@ private extension String.CharacterView {
}
return nil
}
let start = self
if scanCharacter({ $0 == "`" }) != nil {
if let identifier = scanIdentifier() {
@@ -389,7 +389,7 @@ private extension String.CharacterView {
}
return nil
}
mutating func parseNumber() -> Token? {
var number = ""
if let integer = scanInteger() {
@@ -415,7 +415,7 @@ private extension String.CharacterView {
}
return nil
}
mutating func parseLineBreak() -> Token? {
if scanCharacter({ $0 == "\r" }) != nil {
if scanCharacter({ $0 == "\n" }) != nil {
@@ -425,7 +425,7 @@ private extension String.CharacterView {
}
return parseToken(.Linebreak, "\n")
}
mutating func parseToken() -> Token? {
// Have to split into groups for Swift to be able to process this
if let token = parseWhitespace() ??
@@ -453,7 +453,7 @@ func tokenize(source: String) -> [Token] {
var characters = source.characters
var lastNonWhitespaceIndex: Int?
var closedGenericScopeIndexes: [Int] = []
func processStringBody() {
var string = ""
var escaped = false
@@ -487,7 +487,7 @@ func tokenize(source: String) -> [Token] {
string += c
}
}
func processCommentBody() {
var comment = ""
while let c = characters.scanCharacter({ _ in true }) {
@@ -528,7 +528,7 @@ func tokenize(source: String) -> [Token] {
comment += c
}
}
func processToken() {
let token = tokens.last!
if token.type != .Whitespace {
@@ -548,7 +548,7 @@ func tokenize(source: String) -> [Token] {
case .StartOfScope:
wasOperator = (token.string == "\"")
case .Operator:
wasOperator = !["->", ">", ",", ":", ";", "?", "!", "."].contains(token.string)
wasOperator = !["=", "->", ">", ",", ":", ";", "?", "!", "."].contains(token.string)
default:
wasOperator = false
}
@@ -596,7 +596,7 @@ func tokenize(source: String) -> [Token] {
processToken()
return
}
} else if scopeIndexStack.last != nil && tokens[scopeIndexStack.last!].string == "\"" {
processStringBody()
}
@@ -654,18 +654,18 @@ func tokenize(source: String) -> [Token] {
}
}
}
while let token = characters.parseToken() {
tokens.append(token)
processToken()
}
if let scopeIndex = scopeIndexStack.last where tokens[scopeIndex].string == "<" {
// If we encountered an end-of-file while a generic scope was
// still open, the opening < must have been an operator
tokens[scopeIndex] = Token(.Operator, "<")
scopeIndexStack.popLast()
}
return tokens
}
File diff suppressed because it is too large Load Diff
+102 -82
View File
@@ -2,7 +2,7 @@
// SwiftFormat
// TokenizerTests.swift
//
// Version 0.3
// Version 0.4
//
// Created by Nick Lockwood on 12/08/2016.
// Copyright 2016 Charcoal Design
@@ -39,9 +39,9 @@ func XCTAssertEqualArrays<T: Equatable>(first: [T], _ second: [T]) {
}
class TokenizerTests: XCTestCase {
// MARK: Strings
func testEmptyString() {
let input = "\"\""
let output = [
@@ -50,7 +50,7 @@ class TokenizerTests: XCTestCase {
]
XCTAssertEqualArrays(tokenize(input), output)
}
func testSimpleString() {
let input = "\"foo\""
let output = [
@@ -60,7 +60,7 @@ class TokenizerTests: XCTestCase {
]
XCTAssertEqualArrays(tokenize(input), output)
}
func testStringWithEscape() {
let input = "\"hello\\tworld\""
let output = [
@@ -70,7 +70,7 @@ class TokenizerTests: XCTestCase {
]
XCTAssertEqualArrays(tokenize(input), output)
}
func testStringWithEscapedQuotes() {
let input = "\"\\\"nice\\\" to meet you\""
let output = [
@@ -80,7 +80,7 @@ class TokenizerTests: XCTestCase {
]
XCTAssertEqualArrays(tokenize(input), output)
}
func testStringWithEscapedLogic() {
let input = "\"hello \\(name)\""
let output = [
@@ -93,7 +93,7 @@ class TokenizerTests: XCTestCase {
]
XCTAssertEqualArrays(tokenize(input), output)
}
func testStringWithEscapedBackslash() {
let input = "\"\\\\\""
let output = [
@@ -103,9 +103,9 @@ class TokenizerTests: XCTestCase {
]
XCTAssertEqualArrays(tokenize(input), output)
}
// MARK: Single-line comments
func testSingleLineComment() {
let input = "//foo"
let output = [
@@ -114,7 +114,7 @@ class TokenizerTests: XCTestCase {
]
XCTAssertEqualArrays(tokenize(input), output)
}
func testSingleLineCommentWithSpace() {
let input = "// foo"
let output = [
@@ -124,7 +124,7 @@ class TokenizerTests: XCTestCase {
]
XCTAssertEqualArrays(tokenize(input), output)
}
func testSingleLineCommentWithLinebreak() {
let input = "//foo\nbar"
let output = [
@@ -135,9 +135,9 @@ class TokenizerTests: XCTestCase {
]
XCTAssertEqualArrays(tokenize(input), output)
}
// MARK: Multiline comments
func testSingleLineMultilineComment() {
let input = "/*foo*/"
let output = [
@@ -147,7 +147,7 @@ class TokenizerTests: XCTestCase {
]
XCTAssertEqualArrays(tokenize(input), output)
}
func testSingleLineMultilineCommentWithSpace() {
let input = "/* foo*/"
let output = [
@@ -158,7 +158,7 @@ class TokenizerTests: XCTestCase {
]
XCTAssertEqualArrays(tokenize(input), output)
}
func testMultilineComment() {
let input = "/*foo\nbar*/"
let output = [
@@ -170,7 +170,7 @@ class TokenizerTests: XCTestCase {
]
XCTAssertEqualArrays(tokenize(input), output)
}
func testMultilineCommentWithWhitespace() {
let input = "/*foo\n bar*/"
let output = [
@@ -183,7 +183,7 @@ class TokenizerTests: XCTestCase {
]
XCTAssertEqualArrays(tokenize(input), output)
}
func testNestedComments() {
let input = "/*foo/*bar*/baz*/"
let output = [
@@ -197,27 +197,27 @@ class TokenizerTests: XCTestCase {
]
XCTAssertEqualArrays(tokenize(input), output)
}
// MARK: Numbers
func testZero() {
let input = "0"
let output = [Token(.Number, "0")]
XCTAssertEqualArrays(tokenize(input), output)
}
func testSmallInteger() {
let input = "5"
let output = [Token(.Number, "5")]
XCTAssertEqualArrays(tokenize(input), output)
}
func testLargeInteger() {
let input = "12345678901234567890"
let output = [Token(.Number, "12345678901234567890")]
XCTAssertEqualArrays(tokenize(input), output)
}
func testNegativeInteger() {
let input = "-7"
let output = [
@@ -226,19 +226,19 @@ class TokenizerTests: XCTestCase {
]
XCTAssertEqualArrays(tokenize(input), output)
}
func testSmallFloat() {
let input = "0.2"
let output = [Token(.Number, "0.2")]
XCTAssertEqualArrays(tokenize(input), output)
}
func testLargeFloat() {
let input = "1234.567890"
let output = [Token(.Number, "1234.567890")]
XCTAssertEqualArrays(tokenize(input), output)
}
func testNegativeFloat() {
let input = "-0.34"
let output = [
@@ -247,114 +247,114 @@ class TokenizerTests: XCTestCase {
]
XCTAssertEqualArrays(tokenize(input), output)
}
func testExponential() {
let input = "1234e5"
let output = [Token(.Number, "1234e5")]
XCTAssertEqualArrays(tokenize(input), output)
}
func testPositiveExponential() {
let input = "0.123e+4"
let output = [Token(.Number, "0.123e+4")]
XCTAssertEqualArrays(tokenize(input), output)
}
func testNegativeExponential() {
let input = "0.123e-4"
let output = [Token(.Number, "0.123e-4")]
XCTAssertEqualArrays(tokenize(input), output)
}
func testCapitalExponential() {
let input = "0.123E-4"
let output = [Token(.Number, "0.123E-4")]
XCTAssertEqualArrays(tokenize(input), output)
}
// MARK: Identifiers
func testFoo() {
let input = "foo"
let output = [Token(.Identifier, "foo")]
XCTAssertEqualArrays(tokenize(input), output)
}
func testDollar0() {
let input = "$0"
let output = [Token(.Identifier, "$0")]
XCTAssertEqualArrays(tokenize(input), output)
}
func testDollar() {
// Note: support for this is deprecated in Swift 3
let input = "$"
let output = [Token(.Identifier, "$")]
XCTAssertEqualArrays(tokenize(input), output)
}
func testFooDollar() {
let input = "foo$"
let output = [Token(.Identifier, "foo$")]
XCTAssertEqualArrays(tokenize(input), output)
}
func test_() {
let input = "_"
let output = [Token(.Identifier, "_")]
XCTAssertEqualArrays(tokenize(input), output)
}
func test_foo() {
let input = "_foo"
let output = [Token(.Identifier, "_foo")]
XCTAssertEqualArrays(tokenize(input), output)
}
func testFoo_bar() {
let input = "foo_bar"
let output = [Token(.Identifier, "foo_bar")]
XCTAssertEqualArrays(tokenize(input), output)
}
func testAtFoo() {
let input = "@foo"
let output = [Token(.Identifier, "@foo")]
XCTAssertEqualArrays(tokenize(input), output)
}
func testHashFoo() {
let input = "#foo"
let output = [Token(.Identifier, "#foo")]
XCTAssertEqualArrays(tokenize(input), output)
}
func testUnicode() {
let input = "µsec"
let output = [Token(.Identifier, "µsec")]
XCTAssertEqualArrays(tokenize(input), output)
}
func testEmoji() {
let input = "💩"
let output = [Token(.Identifier, "💩")]
XCTAssertEqualArrays(tokenize(input), output)
}
func testBacktickEscapedClass() {
let input = "`class`"
let output = [Token(.Identifier, "`class`")]
XCTAssertEqualArrays(tokenize(input), output)
}
// MARK: Operators
func testBasicOperator() {
let input = "+="
let output = [Token(.Operator, "+=")]
XCTAssertEqualArrays(tokenize(input), output)
}
func testDivide() {
let input = "a / b"
let output = [
@@ -366,13 +366,13 @@ class TokenizerTests: XCTestCase {
]
XCTAssertEqualArrays(tokenize(input), output)
}
func testCustomOperator() {
let input = "~="
let output = [Token(.Operator, "~=")]
XCTAssertEqualArrays(tokenize(input), output)
}
func testSequentialOperators() {
let input = "a *= -b"
let output = [
@@ -385,19 +385,19 @@ class TokenizerTests: XCTestCase {
]
XCTAssertEqualArrays(tokenize(input), output)
}
func testDotPrefixedOperator() {
let input = "..."
let output = [Token(.Operator, "...")]
XCTAssertEqualArrays(tokenize(input), output)
}
func testUnicodeOperator() {
let input = "≥"
let output = [Token(.Operator, "≥")]
XCTAssertEqualArrays(tokenize(input), output)
}
func testOperatorFollowedByComment() {
let input = "a +/* b */"
let output = [
@@ -411,7 +411,7 @@ class TokenizerTests: XCTestCase {
]
XCTAssertEqualArrays(tokenize(input), output)
}
func testOperatorPrecededByComment() {
let input = "/* a */-b"
let output = [
@@ -424,9 +424,9 @@ class TokenizerTests: XCTestCase {
]
XCTAssertEqualArrays(tokenize(input), output)
}
// MARK: chevrons (might be operators or generics)
func testLessThanGreaterThan() {
let input = "a<b == a>c"
let output = [
@@ -442,7 +442,7 @@ class TokenizerTests: XCTestCase {
]
XCTAssertEqualArrays(tokenize(input), output)
}
func testBitshift() {
let input = "a>>b"
let output = [
@@ -452,7 +452,7 @@ class TokenizerTests: XCTestCase {
]
XCTAssertEqualArrays(tokenize(input), output)
}
func testTripleShift() {
let input = "a>>>b"
let output = [
@@ -462,7 +462,7 @@ class TokenizerTests: XCTestCase {
]
XCTAssertEqualArrays(tokenize(input), output)
}
func testTripleShiftEquals() {
let input = "a>>=b"
let output = [
@@ -472,7 +472,7 @@ class TokenizerTests: XCTestCase {
]
XCTAssertEqualArrays(tokenize(input), output)
}
func testBitshiftThatLooksLikeAGeneric() {
let input = "a<b, b<c, d>>e"
let output = [
@@ -492,7 +492,7 @@ class TokenizerTests: XCTestCase {
]
XCTAssertEqualArrays(tokenize(input), output)
}
func testBasicGeneric() {
let input = "Foo<Bar, Baz>"
let output = [
@@ -506,7 +506,7 @@ class TokenizerTests: XCTestCase {
]
XCTAssertEqualArrays(tokenize(input), output)
}
func testNestedGenerics() {
let input = "Foo<Bar<Baz>>"
let output = [
@@ -520,7 +520,7 @@ class TokenizerTests: XCTestCase {
]
XCTAssertEqualArrays(tokenize(input), output)
}
func testFunctionThatLooksLikeGenericType() {
let input = "y<CGRectGetMaxY(r)"
let output = [
@@ -533,7 +533,7 @@ class TokenizerTests: XCTestCase {
]
XCTAssertEqualArrays(tokenize(input), output)
}
func testGenericClassDeclaration() {
let input = "class Foo<T,U> {}"
let output = [
@@ -551,7 +551,7 @@ class TokenizerTests: XCTestCase {
]
XCTAssertEqualArrays(tokenize(input), output)
}
func testGenericSubclassDeclaration() {
let input = "class Foo<T,U>: Bar"
let output = [
@@ -569,7 +569,7 @@ class TokenizerTests: XCTestCase {
]
XCTAssertEqualArrays(tokenize(input), output)
}
func testGenericFunctionDeclaration() {
let input = "func foo<T>(bar:T)"
let output = [
@@ -587,7 +587,7 @@ class TokenizerTests: XCTestCase {
]
XCTAssertEqualArrays(tokenize(input), output)
}
func testGenericClassInit() {
let input = "foo = Foo<Int,String>()"
let output = [
@@ -606,7 +606,7 @@ class TokenizerTests: XCTestCase {
]
XCTAssertEqualArrays(tokenize(input), output)
}
func testGenericFollowedByDot() {
let input = "Foo<Bar>.baz()"
let output = [
@@ -621,7 +621,7 @@ class TokenizerTests: XCTestCase {
]
XCTAssertEqualArrays(tokenize(input), output)
}
func testConstantThatLooksLikeGenericType() {
let input = "(y<Pi)"
let output = [
@@ -633,7 +633,7 @@ class TokenizerTests: XCTestCase {
]
XCTAssertEqualArrays(tokenize(input), output)
}
func testTupleOfBoolsThatLooksLikeGeneric() {
let input = "(Foo<T,U>V)"
let output = [
@@ -649,7 +649,7 @@ class TokenizerTests: XCTestCase {
]
XCTAssertEqualArrays(tokenize(input), output)
}
func testGenericClassInitThatLooksLikeTuple() {
let input = "(Foo<String,Int>(Bar))"
let output = [
@@ -667,7 +667,7 @@ class TokenizerTests: XCTestCase {
]
XCTAssertEqualArrays(tokenize(input), output)
}
func testCustomChevronOperatorThatLooksLikeGeneric() {
let input = "Foo<Bar,Baz>>>5"
let output = [
@@ -681,7 +681,7 @@ class TokenizerTests: XCTestCase {
]
XCTAssertEqualArrays(tokenize(input), output)
}
func testGenericAsFunctionType() {
let input = "Foo<Bar,Baz>->Void"
let output = [
@@ -696,7 +696,7 @@ class TokenizerTests: XCTestCase {
]
XCTAssertEqualArrays(tokenize(input), output)
}
func testGenericContainingArrayType() {
let input = "Foo<[Bar],Baz>"
let output = [
@@ -711,7 +711,7 @@ class TokenizerTests: XCTestCase {
]
XCTAssertEqualArrays(tokenize(input), output)
}
func testGenericContainingTupleType() {
let input = "Foo<(Bar,Baz)>"
let output = [
@@ -726,7 +726,7 @@ class TokenizerTests: XCTestCase {
]
XCTAssertEqualArrays(tokenize(input), output)
}
func testGenericContainingArrayAndTupleType() {
let input = "Foo<[Bar],(Baz)>"
let output = [
@@ -743,7 +743,7 @@ class TokenizerTests: XCTestCase {
]
XCTAssertEqualArrays(tokenize(input), output)
}
func testGenericFollowedByIn() {
let input = "Foo<Bar,Baz> in"
let output = [
@@ -758,7 +758,7 @@ class TokenizerTests: XCTestCase {
]
XCTAssertEqualArrays(tokenize(input), output)
}
func testOptionalGenericType() {
let input = "Foo<T?,U>"
let output = [
@@ -772,7 +772,7 @@ class TokenizerTests: XCTestCase {
]
XCTAssertEqualArrays(tokenize(input), output)
}
func testTrailingOptionalGenericType() {
let input = "Foo<T?>"
let output = [
@@ -784,7 +784,7 @@ class TokenizerTests: XCTestCase {
]
XCTAssertEqualArrays(tokenize(input), output)
}
func testNestedOptionalGenericType() {
let input = "Foo<Bar<T?>>"
let output = [
@@ -799,7 +799,7 @@ class TokenizerTests: XCTestCase {
]
XCTAssertEqualArrays(tokenize(input), output)
}
func testCustomOperatorStartingWithOpenChevron() {
let input = "foo<--bar"
let output = [
@@ -809,7 +809,7 @@ class TokenizerTests: XCTestCase {
]
XCTAssertEqualArrays(tokenize(input), output)
}
func testCustomOperatorEndingWithCloseChevron() {
let input = "foo-->bar"
let output = [
@@ -819,7 +819,7 @@ class TokenizerTests: XCTestCase {
]
XCTAssertEqualArrays(tokenize(input), output)
}
func testGreaterThanLessThanOperator() {
let input = "foo><bar"
let output = [
@@ -829,7 +829,7 @@ class TokenizerTests: XCTestCase {
]
XCTAssertEqualArrays(tokenize(input), output)
}
func testLessThanGreaterThanOperator() {
let input = "foo<>bar"
let output = [
@@ -839,4 +839,24 @@ class TokenizerTests: XCTestCase {
]
XCTAssertEqualArrays(tokenize(input), output)
}
func testGenericFollowedByAssign() {
let input = "let foo: Bar<Baz> = 5"
let output = [
Token(.Identifier, "let"),
Token(.Whitespace, " "),
Token(.Identifier, "foo"),
Token(.Operator, ":"),
Token(.Whitespace, " "),
Token(.Identifier, "Bar"),
Token(.StartOfScope, "<"),
Token(.Identifier, "Baz"),
Token(.EndOfScope, ">"),
Token(.Whitespace, " "),
Token(.Operator, "="),
Token(.Whitespace, " "),
Token(.Number, "5"),
]
XCTAssertEqualArrays(tokenize(input), output)
}
}