// // SwiftFormatTests.swift // SwiftFormat // // Created by Nick Lockwood on 28/08/2016. // Copyright 2016 Nick Lockwood // // Distributed under the permissive MIT license // Get the latest version from here: // // https://github.com/nicklockwood/SwiftFormat // // Permission is hereby granted, free of charge, to any person obtaining a copy // of this software and associated documentation files (the "Software"), to deal // in the Software without restriction, including without limitation the rights // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell // copies of the Software, and to permit persons to whom the Software is // furnished to do so, subject to the following conditions: // // The above copyright notice and this permission notice shall be included in all // copies or substantial portions of the Software. // // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE // SOFTWARE. // import XCTest @testable import SwiftFormat final class SwiftFormatTests: XCTestCase { // MARK: enumerateFiles func testInputFileMatchesOutputFileForNilOutput() { var files = [URL]() let inputURL = URL(fileURLWithPath: #file) let errors = enumerateFiles(withInputURLs: [inputURL]) { inputURL, outputURL, _ in XCTAssertEqual(inputURL, outputURL) XCTAssertEqual(inputURL, URL(fileURLWithPath: #file)) return { files.append(inputURL) } } XCTAssertEqual(errors.count, 0) XCTAssertEqual(files.count, 1) } func testInputFileMatchesOutputFileForSameOutput() { var files = [URL]() let inputURL = URL(fileURLWithPath: #file) let errors = enumerateFiles(withInputURLs: [inputURL], outputURL: inputURL) { inputURL, outputURL, _ in XCTAssertEqual(inputURL, outputURL) XCTAssertEqual(inputURL, URL(fileURLWithPath: #file)) return { files.append(inputURL) } } XCTAssertEqual(errors.count, 0) XCTAssertEqual(files.count, 1) } func testInputFilesMatchOutputFilesForNilOutput() { var files = [URL]() let inputURL = URL(fileURLWithPath: #file).deletingLastPathComponent().deletingLastPathComponent() let errors = enumerateFiles(withInputURLs: [inputURL]) { inputURL, outputURL, _ in XCTAssertEqual(inputURL, outputURL) return { files.append(inputURL) } } XCTAssertEqual(errors.count, 0) XCTAssertGreaterThanOrEqual(files.count, 180) } func testInputFilesMatchOutputFilesForSameOutput() { var files = [URL]() let inputURL = URL(fileURLWithPath: #file).deletingLastPathComponent().deletingLastPathComponent() let errors = enumerateFiles(withInputURLs: [inputURL], outputURL: inputURL) { inputURL, outputURL, _ in XCTAssertEqual(inputURL, outputURL) return { files.append(inputURL) } } XCTAssertEqual(errors.count, 0) XCTAssertGreaterThanOrEqual(files.count, 180) } func testInputFileNotEnumeratedWhenExcluded() { var files = [URL]() let currentFile = URL(fileURLWithPath: #file) let options = Options(fileOptions: FileOptions(excludedGlobs: [ Glob.path(currentFile.deletingLastPathComponent().path), ])) let inputURL = currentFile.deletingLastPathComponent().deletingLastPathComponent() let errors = enumerateFiles(withInputURLs: [inputURL], outputURL: inputURL, options: options) { inputURL, outputURL, _ in XCTAssertEqual(inputURL, outputURL) return { files.append(inputURL) } } var allFiles = [URL]() let allFilesInputURL = URL(fileURLWithPath: #file).deletingLastPathComponent().deletingLastPathComponent() _ = enumerateFiles(withInputURLs: [allFilesInputURL], outputURL: allFilesInputURL) { inputURL, outputURL, _ in XCTAssertEqual(inputURL, outputURL) return { allFiles.append(inputURL) } } XCTAssertEqual(errors.count, 0) XCTAssertLessThan(files.count, allFiles.count) } // MARK: format function func testFormatReturnsInputWithNoRules() { let input = "foo () " XCTAssertEqual(try format(input, rules: []).output, input) } func testFormatUsesDefaultRulesIfNoneSpecified() { let input = "foo () " let output = "foo()\n" XCTAssertEqual(try format(input).output, output) } // MARK: lint function func testLintReturnsNoChangesWithNoRules() { let input = "foo () " XCTAssertEqual(try lint(input, rules: []), []) } func testLintWithDefaultRules() { let input = "foo () " XCTAssertEqual(try lint(input), [ .init(line: 1, rule: .linebreakAtEndOfFile, filePath: nil, isMove: false), .init(line: 1, rule: .spaceAroundParens, filePath: nil, isMove: false), .init(line: 1, rule: .trailingSpace, filePath: nil, isMove: false), ]) } func testLintConsecutiveBlankLinesAtEndOfFile() { let input = "foo\n\n" XCTAssertEqual(try lint(input), [ .init(line: 2, rule: .consecutiveBlankLines, filePath: nil, isMove: false), ]) } // MARK: fragments func testFormattingFailsForFragment() { let input = "foo () {" XCTAssertThrowsError(try format(input, rules: [])) { XCTAssertEqual("\($0)", "Unexpected end of file at 1:9") } } func testFormattingSucceedsForFragmentWithOption() { let input = "foo () {" let options = FormatOptions(fragment: true) XCTAssertEqual(try format(input, rules: [], options: options).output, input) } // MARK: conflict markers func testFormattingFailsForConflict() { let input = "foo () {\n<<<<<< old\n bar()\n======\n baz()\n>>>>>> new\n}" XCTAssertThrowsError(try format(input, rules: [])) { XCTAssertEqual("\($0)", "Found conflict marker <<<<<< at 2:1") } } func testFormattingSucceedsForConflictWithOption() { let input = "foo () {\n<<<<<< old\n bar()\n======\n baz()\n>>>>>> new\n}" let options = FormatOptions(ignoreConflictMarkers: true) XCTAssertEqual(try format(input, rules: [], options: options).output, input) } // MARK: empty file func testNoTimeoutForEmptyFile() { let input = "" XCTAssertEqual(try format(input).output, input) } // MARK: offsetForToken func testOffsetForToken() { let tokens = tokenize("// a comment\n let foo = 5\n") let offset = offsetForToken(at: 7, in: tokens, tabWidth: 1) XCTAssertEqual(offset, SourceOffset(line: 2, column: 9)) } func testOffsetForTokenWithTabs() { let tokens = tokenize("// a comment\n\tlet foo = 5\n") let offset = offsetForToken(at: 7, in: tokens, tabWidth: 2) XCTAssertEqual(offset, SourceOffset(line: 2, column: 7)) } // MARK: tokenIndex for offset func testTokenIndexForOffset() { let tokens = tokenize("// a comment\n let foo = 5\n") let offset = SourceOffset(line: 2, column: 9) XCTAssertEqual(tokenIndex(for: offset, in: tokens, tabWidth: 1), 7) } func testTokenIndexForOffsetWithTabs() { let tokens = tokenize("// a comment\n\tlet foo = 5\n") let offset = SourceOffset(line: 2, column: 7) XCTAssertEqual(tokenIndex(for: offset, in: tokens, tabWidth: 2), 7) } func testTokenIndexForLastLine() { let tokens = tokenize(""" let foo = 5 let bar = 6 """) let offset = SourceOffset(line: 2, column: 0) XCTAssertEqual(tokenIndex(for: offset, in: tokens, tabWidth: 1), 8) } func testTokenIndexPastEndOfFile() { let tokens = tokenize(""" let foo = 5 let bar = 6 """) let offset = SourceOffset(line: 3, column: 0) XCTAssertEqual(tokenIndex(for: offset, in: tokens, tabWidth: 1), 15) } func testTokenIndexForBlankLastLine() { let tokens = tokenize(""" let foo = 5 let bar = 6 """) let offset = SourceOffset(line: 3, column: 0) XCTAssertEqual(tokenIndex(for: offset, in: tokens, tabWidth: 1), 16) } // MARK: tokenRange func testTokenRange() { let tokens = tokenize("// a comment\n let foo = 5\n") XCTAssertEqual(tokenRange(forLineRange: 1 ... 1, in: tokens), 0 ..< 3) } // MARK: newOffset func testNewOffsetsForUnchangedPosition() { let tokens = tokenize("foo\nbar\nbaz") let offset1 = SourceOffset(line: 1, column: 1) let offset2 = SourceOffset(line: 2, column: 1) let offset3 = SourceOffset(line: 3, column: 1) XCTAssertEqual(newOffset(for: offset1, in: tokens, tabWidth: 1), offset1) XCTAssertEqual(newOffset(for: offset2, in: tokens, tabWidth: 1), offset2) XCTAssertEqual(newOffset(for: offset3, in: tokens, tabWidth: 1), offset3) } func testNewOffsetsForRemovedLine() throws { let input = tokenize("foo\nbar\n\n\nbaz\nquux") let offset1 = SourceOffset(line: 1, column: 1) let offset2 = SourceOffset(line: 2, column: 1) let offset3 = SourceOffset(line: 5, column: 1) let offset4 = SourceOffset(line: 6, column: 1) let output = try format(input, rules: [.consecutiveBlankLines]).tokens let expected3 = SourceOffset(line: 4, column: 1) let expected4 = SourceOffset(line: 5, column: 1) XCTAssertEqual(newOffset(for: offset1, in: output, tabWidth: 1), offset1) XCTAssertEqual(newOffset(for: offset2, in: output, tabWidth: 1), offset2) XCTAssertEqual(newOffset(for: offset3, in: output, tabWidth: 1), expected3) XCTAssertEqual(newOffset(for: offset4, in: output, tabWidth: 1), expected4) } func testNewOffsetsForEmptyOutput() { let offset = SourceOffset(line: 1, column: 1) XCTAssertEqual(newOffset(for: offset, in: [], tabWidth: 1), offset) } // MARK: expand path func testExpandPathWithRelativePath() { XCTAssertEqual( expandPath("relpath/to/file.swift", in: "/dir").path, "/dir/relpath/to/file.swift" ) } func testExpandPathWithFullPath() { XCTAssertEqual( expandPath("/full/path/to/file.swift", in: "/dir").path, "/full/path/to/file.swift" ) } func testExpandPathWithUserPath() { XCTAssertEqual( expandPath("~/file.swift", in: "/dir").path, NSString(string: "~/file.swift").expandingTildeInPath ) } // MARK: shared option inference func testLinebreakInferredForBlankLinesBetweenScopes() { let input = "class Foo {\r func bar() {\r }\r func baz() {\r }\r}" let output = "class Foo {\r func bar() {\r }\r\r func baz() {\r }\r}" XCTAssertEqual(try format(input, rules: [.blankLinesBetweenScopes]).output, output) } }