mirror of
https://github.com/nicklockwood/SwiftFormat.git
synced 2026-05-17 10:30:35 +00:00
1638 lines
46 KiB
Swift
1638 lines
46 KiB
Swift
//
|
|
// CommandLineTests.swift
|
|
// SwiftFormat
|
|
//
|
|
// Created by Nick Lockwood on 10/01/2017.
|
|
// Copyright 2017 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
|
|
|
|
private func createTmpFile(_ path: String, contents: String) throws -> URL {
|
|
let url = URL(fileURLWithPath: NSTemporaryDirectory()).appendingPathComponent(path)
|
|
let directory = url.deletingLastPathComponent()
|
|
if !FileManager.default.fileExists(atPath: directory.path) {
|
|
try FileManager.default.createDirectory(
|
|
at: directory,
|
|
withIntermediateDirectories: true,
|
|
attributes: nil
|
|
)
|
|
}
|
|
try contents.write(to: url, atomically: true, encoding: .utf8)
|
|
return url
|
|
}
|
|
|
|
private func withTmpFile(_ path: String? = nil, contents: String, fn: (URL) -> Void) throws {
|
|
let path = path ?? (UUID().uuidString + ".swift")
|
|
let prefix = UUID().uuidString
|
|
let url = try createTmpFile("\(prefix)/\(path)", contents: contents)
|
|
fn(url)
|
|
try FileManager.default.removeItem(at: url)
|
|
}
|
|
|
|
private func withTmpFiles(_ files: [String: String], fn: (URL) throws -> Void) throws {
|
|
var urls = [URL]()
|
|
defer {
|
|
for url in urls {
|
|
try? FileManager.default.removeItem(at: url)
|
|
}
|
|
}
|
|
let prefix = UUID().uuidString
|
|
for (path, contents) in files {
|
|
try urls.append(createTmpFile("\(prefix)/\(path)", contents: contents))
|
|
}
|
|
for url in urls where ["swift", "md"].contains(url.pathExtension) {
|
|
try fn(url)
|
|
}
|
|
}
|
|
|
|
final class CommandLineTests: XCTestCase {
|
|
// MARK: stdin
|
|
|
|
func testStdin() {
|
|
CLI.print = { message, type in
|
|
switch type {
|
|
case .raw, .content:
|
|
XCTAssertEqual(message, "func foo() {\n bar()\n}\n")
|
|
case .error, .warning:
|
|
XCTFail()
|
|
case .info, .success:
|
|
break
|
|
}
|
|
}
|
|
var readCount = 0
|
|
CLI.readLine = {
|
|
readCount += 1
|
|
switch readCount {
|
|
case 1:
|
|
return "func foo()\n"
|
|
case 2:
|
|
return "{\n"
|
|
case 3:
|
|
return "bar()\n"
|
|
case 4:
|
|
return "}"
|
|
default:
|
|
return nil
|
|
}
|
|
}
|
|
_ = processArguments([""], in: "")
|
|
readCount = 0
|
|
_ = processArguments(["", "stdin"], in: "")
|
|
}
|
|
|
|
func testStdinOutputTokens() {
|
|
CLI.print = { message, type in
|
|
switch type {
|
|
case .raw, .content:
|
|
XCTAssertEqual(message, """
|
|
{"tokens":[\
|
|
{"string":"func","type":"keyword"},\
|
|
{"string":" ","type":"space"},\
|
|
{"string":"foo","type":"identifier"},\
|
|
{"string":"(","type":"startOfScope"},\
|
|
{"string":")","type":"endOfScope"},\
|
|
{"string":" ","type":"space"},\
|
|
{"string":"{","type":"startOfScope"},\
|
|
{"originalLine":2,"string":"\\n","type":"linebreak"},\
|
|
{"string":" ","type":"space"},\
|
|
{"string":"bar","type":"identifier"},\
|
|
{"string":"(","type":"startOfScope"},\
|
|
{"string":")","type":"endOfScope"},\
|
|
{"string":" ","type":"space"},\
|
|
{"operatorType":"infix","string":"+","type":"operator"},\
|
|
{"string":" ","type":"space"},\
|
|
{"string":"baaz","type":"identifier"},\
|
|
{"string":"(","type":"startOfScope"},\
|
|
{"string":")","type":"endOfScope"},\
|
|
{"string":" ","type":"space"},\
|
|
{"operatorType":"infix","string":"+","type":"operator"},\
|
|
{"string":" ","type":"space"},\
|
|
{"numberType":"integer","string":"25","type":"number"},\
|
|
{"originalLine":3,"string":"\\n","type":"linebreak"},\
|
|
{"string":"}","type":"endOfScope"},\
|
|
{"originalLine":4,"string":"\\n","type":"linebreak"}\
|
|
],"version":"\(swiftFormatVersion)"}
|
|
""")
|
|
case .error, .warning:
|
|
XCTFail()
|
|
case .info, .success:
|
|
break
|
|
}
|
|
}
|
|
var readCount = 0
|
|
CLI.readLine = {
|
|
readCount += 1
|
|
switch readCount {
|
|
case 1:
|
|
return "func foo()\n"
|
|
case 2:
|
|
return "{\n"
|
|
case 3:
|
|
return "bar() + baaz() + 25\n"
|
|
case 4:
|
|
return "}"
|
|
default:
|
|
return nil
|
|
}
|
|
}
|
|
|
|
_ = processArguments(["", "stdin", "--outputtokens"], in: "")
|
|
}
|
|
|
|
func testExcludeStdinPath() throws {
|
|
CLI.print = { message, type in
|
|
switch type {
|
|
case .raw, .content:
|
|
XCTAssertEqual(message, "func foo() {\n}\n")
|
|
case .error, .warning:
|
|
XCTFail()
|
|
case .info, .success:
|
|
break
|
|
}
|
|
}
|
|
var readCount = 0
|
|
CLI.readLine = {
|
|
readCount += 1
|
|
switch readCount {
|
|
case 1:
|
|
return "func foo() {\n"
|
|
case 2:
|
|
return "}\n"
|
|
default:
|
|
return nil
|
|
}
|
|
}
|
|
try withTmpFile(contents: "") { url in
|
|
_ = processArguments([
|
|
"",
|
|
"stdin",
|
|
"--stdinpath", url.path,
|
|
"--exclude", url.path,
|
|
], in: "")
|
|
}
|
|
}
|
|
|
|
func testExcludeStdinPath2() throws {
|
|
CLI.print = { message, type in
|
|
switch type {
|
|
case .raw, .content:
|
|
XCTAssertEqual(message, "func foo() {\n}\n")
|
|
case .error, .warning:
|
|
XCTFail()
|
|
case .info, .success:
|
|
break
|
|
}
|
|
}
|
|
var readCount = 0
|
|
CLI.readLine = {
|
|
readCount += 1
|
|
switch readCount {
|
|
case 1:
|
|
return "func foo() {\n"
|
|
case 2:
|
|
return "}\n"
|
|
default:
|
|
return nil
|
|
}
|
|
}
|
|
try withTmpFiles([
|
|
".swiftformat": "--exclude *",
|
|
"foo.swift": "",
|
|
]) { url in
|
|
_ = processArguments([
|
|
"",
|
|
"stdin",
|
|
"--stdinpath", url.path,
|
|
], in: "")
|
|
}
|
|
}
|
|
|
|
func testExcludeStdinPath3() throws {
|
|
CLI.print = { message, type in
|
|
switch type {
|
|
case .raw, .content:
|
|
XCTAssertEqual(message, "func foo() {\n}\n")
|
|
case .error, .warning:
|
|
XCTFail()
|
|
case .info, .success:
|
|
break
|
|
}
|
|
}
|
|
var readCount = 0
|
|
CLI.readLine = {
|
|
readCount += 1
|
|
switch readCount {
|
|
case 1:
|
|
return "func foo() {\n"
|
|
case 2:
|
|
return "}\n"
|
|
default:
|
|
return nil
|
|
}
|
|
}
|
|
try withTmpFiles([
|
|
".swiftformat": "--exclude foo",
|
|
"foo/bar/baz.swift": "",
|
|
]) { url in
|
|
_ = processArguments([
|
|
"",
|
|
"stdin",
|
|
"--stdinpath", url.path,
|
|
], in: "")
|
|
}
|
|
}
|
|
|
|
func testUnexcludeStdinPath() throws {
|
|
CLI.print = { message, type in
|
|
switch type {
|
|
case .raw, .content:
|
|
XCTAssertEqual(message, "func foo() {}\n")
|
|
case .error, .warning:
|
|
XCTFail()
|
|
case .info, .success:
|
|
break
|
|
}
|
|
}
|
|
var readCount = 0
|
|
CLI.readLine = {
|
|
readCount += 1
|
|
switch readCount {
|
|
case 1:
|
|
return "func foo() {\n"
|
|
case 2:
|
|
return "}\n"
|
|
default:
|
|
return nil
|
|
}
|
|
}
|
|
try withTmpFiles([
|
|
".swiftformat": """
|
|
--exclude foo
|
|
--unexclude **/baz.*
|
|
""",
|
|
"foo/bar/baz.swift": "",
|
|
]) { url in
|
|
_ = processArguments([
|
|
"",
|
|
"stdin",
|
|
"--stdinpath", url.path,
|
|
], in: "")
|
|
}
|
|
}
|
|
|
|
// MARK: help
|
|
|
|
func testOptionsHelpText() {
|
|
for option in Descriptors.all {
|
|
XCTAssertFalse(
|
|
option.help.contains("\n"),
|
|
"Help for option --\(option.argumentName) contains linebreak"
|
|
)
|
|
}
|
|
}
|
|
|
|
func testHelpOptionsImplemented() {
|
|
CLI.print = { message, _ in
|
|
if message.hasPrefix("--") {
|
|
let name = String(message["--".endIndex ..< message.endIndex]).components(separatedBy: " ")[0]
|
|
XCTAssertTrue(commandLineArguments.contains(name), name)
|
|
}
|
|
}
|
|
printHelp(as: .content)
|
|
}
|
|
|
|
func testHelpOptionsDocumented() {
|
|
var arguments = Set(commandLineArguments).subtracting(deprecatedArguments)
|
|
CLI.print = { allHelpMessage, _ in
|
|
allHelpMessage
|
|
.components(separatedBy: "\n")
|
|
.forEach { message in
|
|
if message.hasPrefix("--") {
|
|
let name = String(message["--".endIndex ..< message.endIndex]).components(separatedBy: " ")[0]
|
|
XCTAssert(arguments.contains(name), "Unknown option --\(name) in help")
|
|
arguments.remove(name)
|
|
}
|
|
}
|
|
}
|
|
printHelp(as: .content)
|
|
printOptions(as: .content)
|
|
XCTAssert(arguments.isEmpty, "\(arguments.joined(separator: ",")) not listed in help")
|
|
}
|
|
|
|
func testRedundantDefaultsInHelpOptionsDescriptions() {
|
|
var descriptions = [String]()
|
|
CLI.print = { message, _ in
|
|
descriptions += message
|
|
.components(separatedBy: "\n")
|
|
.filter { !$0.trimmingCharacters(in: .whitespaces).isEmpty }
|
|
}
|
|
printOptions(as: .content)
|
|
for description in descriptions {
|
|
XCTAssertFalse(description.contains("\"default\"") && description.contains("(default)"),
|
|
"Found both 'default' and '(default)' in option description: \(description)")
|
|
}
|
|
}
|
|
|
|
func testHelpOptionFormatting() {
|
|
let shortOption = OptionDescriptor(
|
|
argumentName: "option",
|
|
displayName: "option",
|
|
help: "Short option description",
|
|
keyPath: \.fragment
|
|
)
|
|
|
|
let mediumOption = OptionDescriptor(
|
|
argumentName: "option-medium",
|
|
displayName: "option-medium",
|
|
help: "Option with a medium name and description length",
|
|
keyPath: \.fragment
|
|
)
|
|
|
|
let optionWithArgs = OptionDescriptor(
|
|
argumentName: "option-with-args",
|
|
displayName: "option-with-args",
|
|
help: "Option description with arguments:",
|
|
keyPath: \.fragment
|
|
)
|
|
|
|
let longOption = OptionDescriptor(
|
|
argumentName: "option-with-longer-name",
|
|
displayName: "option-with-longer-name",
|
|
help: """
|
|
This is a longer option with a name over the original 16 character limit, \
|
|
and a help text over the original 80 character limit.
|
|
""",
|
|
keyPath: \.fragment
|
|
)
|
|
|
|
CLI.print = { output, _ in
|
|
guard !output.isEmpty else { return }
|
|
XCTAssertEqual(output, """
|
|
--option Short option description
|
|
--option-medium Option with a medium name and description length
|
|
--option-with-args Option description with arguments: "true" or "false" (default)
|
|
--option-with-longer-name
|
|
This is a longer option with a name over the original 16 character limit, and a help text over the original 80 character limit.
|
|
""")
|
|
}
|
|
|
|
printOptions([shortOption, mediumOption, optionWithArgs, longOption], as: .content)
|
|
}
|
|
|
|
// MARK: cache
|
|
|
|
func testHashIsFasterThanFormatting() throws {
|
|
let sourceFile = URL(fileURLWithPath: #file)
|
|
let source = try String(contentsOf: sourceFile, encoding: .utf8)
|
|
let hash = computeHash(source + ";")
|
|
|
|
let hashTime = timeEvent { _ = computeHash(source) == hash }
|
|
let formatTime = try timeEvent { _ = try format(source) }
|
|
XCTAssertLessThan(hashTime, formatTime)
|
|
}
|
|
|
|
func testCacheHit() {
|
|
let input = "let foo = bar"
|
|
XCTAssertEqual(computeHash(input), computeHash(input))
|
|
}
|
|
|
|
func testCacheMiss() {
|
|
let input = "let foo = bar"
|
|
let output = "let foo = bar\n"
|
|
XCTAssertNotEqual(computeHash(input), computeHash(output))
|
|
}
|
|
|
|
func testCachePotentialFalsePositive() {
|
|
let input = "let foo = bar;"
|
|
let output = "let foo = bar\n"
|
|
XCTAssertNotEqual(computeHash(input), computeHash(output))
|
|
}
|
|
|
|
func testCachePotentialFalsePositive2() {
|
|
let input = """
|
|
import Foo
|
|
import Bar
|
|
|
|
"""
|
|
let output = """
|
|
import Bar
|
|
import Foo
|
|
|
|
"""
|
|
XCTAssertNotEqual(computeHash(input), computeHash(output))
|
|
}
|
|
|
|
// MARK: rules
|
|
|
|
func testRulesNotMarkedAsDisabled() {
|
|
CLI.print = { message, _ in
|
|
XCTAssert(!message.contains("(disabled)") ||
|
|
FormatRules.disabledByDefault.contains(where: { message.contains($0.name) }))
|
|
}
|
|
XCTAssertEqual(CLI.run(in: projectDirectory.path, with: "--rules"), .ok)
|
|
}
|
|
|
|
func testEnableOverridesDisableAll() {
|
|
CLI.print = { message, _ in
|
|
XCTAssertFalse(message.contains("wrap (disabled)"))
|
|
}
|
|
XCTAssertEqual(CLI.run(in: projectDirectory.path,
|
|
with: "--disable all --enable wrap --rules"), .ok)
|
|
}
|
|
|
|
// MARK: quiet mode
|
|
|
|
func testQuietModeNoOutput() {
|
|
CLI.print = { message, _ in
|
|
XCTFail(message)
|
|
}
|
|
XCTAssertEqual(CLI.run(in: projectDirectory.path, with: "--quiet --dry-run"), .ok)
|
|
}
|
|
|
|
func testQuietModeAllowsContent() {
|
|
CLI.print = { message, type in
|
|
XCTAssertEqual(type, .content, message)
|
|
}
|
|
XCTAssertEqual(CLI.run(in: projectDirectory.path, with: "--quiet --help"), .ok)
|
|
}
|
|
|
|
func testQuietModeAllowsErrors() {
|
|
CLI.print = { message, type in
|
|
XCTAssertEqual(type, .error, message)
|
|
}
|
|
XCTAssertEqual(CLI.run(in: projectDirectory.path, with: "foobar.swift --quiet"), .error)
|
|
}
|
|
|
|
// MARK: split input paths
|
|
|
|
func testSplitInputPaths() {
|
|
CLI.print = { message, type in
|
|
XCTAssertEqual(type, .error, message)
|
|
}
|
|
XCTAssertEqual(CLI.run(in: projectDirectory.path, with: "Sources --dry-run Tests --rules indent"), .error)
|
|
}
|
|
|
|
// MARK: file list
|
|
|
|
func testParseFileList() {
|
|
let source = """
|
|
#foo
|
|
Package.swift #bar
|
|
|
|
#baz
|
|
Sources/FormatRule.swift
|
|
CommandLineTool/*.swift
|
|
"""
|
|
XCTAssertEqual(try parseFileList(source, in: projectDirectory.path), [
|
|
URL(fileURLWithPath: "\(projectDirectory.path)/Package.swift"),
|
|
URL(fileURLWithPath: "\(projectDirectory.path)/Sources/FormatRule.swift"),
|
|
URL(fileURLWithPath: "\(projectDirectory.path)/CommandLineTool/main.swift"),
|
|
])
|
|
}
|
|
|
|
// MARK: script input files
|
|
|
|
func testParseScriptInput() throws {
|
|
let result = try parseScriptInput(from: [
|
|
"SCRIPT_INPUT_FILE_COUNT": "2",
|
|
"SCRIPT_INPUT_FILE_0": "\(projectDirectory.path)/File1.swift",
|
|
"SCRIPT_INPUT_FILE_1": "\(projectDirectory.path)/File2.swift",
|
|
])
|
|
XCTAssertEqual(
|
|
result,
|
|
[
|
|
URL(fileURLWithPath: "\(projectDirectory.path)/File1.swift"),
|
|
URL(fileURLWithPath: "\(projectDirectory.path)/File2.swift"),
|
|
]
|
|
)
|
|
}
|
|
|
|
// MARK: config
|
|
|
|
func testBadConfigFails() {
|
|
var error = ""
|
|
CLI.print = { message, type in
|
|
if type == .error {
|
|
error += message + "\n"
|
|
}
|
|
}
|
|
XCTAssertEqual(CLI.run(in: projectDirectory.path, with: "Tests/BadConfig/Test.swift --unexclude Tests/BadConfig --config Tests/BadConfig/.swiftformat --lint"), .error)
|
|
XCTAssert(error.contains("'ifdef' is not a formatting rule"), error)
|
|
}
|
|
|
|
func testBadConfigFails2() {
|
|
var error = ""
|
|
CLI.print = { message, type in
|
|
if type == .error {
|
|
error += message + "\n"
|
|
}
|
|
}
|
|
XCTAssertEqual(CLI.run(in: projectDirectory.path, with: "Tests/BadConfig/Test.swift --unexclude Tests/BadConfig --lint"), .error)
|
|
XCTAssert(error.contains("'ifdef' is not a formatting rule"), error)
|
|
}
|
|
|
|
func testWarnIfOptionsSpecifiedForDisabledRule() {
|
|
CLI.print = { message, type in
|
|
if type == .warning {
|
|
XCTAssertEqual(
|
|
message,
|
|
"warning: --header option has no effect when fileHeader rule is disabled"
|
|
)
|
|
}
|
|
}
|
|
XCTAssertEqual(CLI.run(in: projectDirectory.path, with: "stdin --lint --rules indent --header foo"), .ok)
|
|
}
|
|
|
|
func testMultipleConfigFiles() throws {
|
|
try withTmpFiles([
|
|
"config1.swiftformat": """
|
|
--indent 2
|
|
--rules trailingCommas
|
|
--rules redundantSelf
|
|
""",
|
|
"config2.swiftformat": """
|
|
--indent 4
|
|
--disable trailingCommas
|
|
""",
|
|
"test.swift": """
|
|
func foo() {
|
|
let array = [1, 2, 3,]
|
|
self.bar()
|
|
}
|
|
""",
|
|
]) { url in
|
|
guard url.pathExtension == "swift" else { return }
|
|
let testDir = url.deletingLastPathComponent().path
|
|
|
|
CLI.print = { _, _ in }
|
|
|
|
XCTAssertEqual(
|
|
CLI.run(in: testDir, with: "test.swift --config config1.swiftformat --config config2.swiftformat"),
|
|
.ok
|
|
)
|
|
|
|
let output = try String(contentsOf: url, encoding: .utf8)
|
|
|
|
XCTAssertEqual(output, """
|
|
func foo() {
|
|
let array = [1, 2, 3,]
|
|
bar()
|
|
}
|
|
""")
|
|
}
|
|
}
|
|
|
|
func testMultipleConfigFilesWithCommaDelimitedPaths() throws {
|
|
try withTmpFiles([
|
|
"config1.swiftformat": """
|
|
--indent 2
|
|
--rules trailingCommas
|
|
--rules redundantSelf
|
|
""",
|
|
"config2.swiftformat": """
|
|
--indent 4
|
|
--disable trailingCommas
|
|
""",
|
|
"test.swift": """
|
|
func foo() {
|
|
let array = [1, 2, 3,]
|
|
self.bar()
|
|
}
|
|
""",
|
|
]) { url in
|
|
guard url.pathExtension == "swift" else { return }
|
|
let testDir = url.deletingLastPathComponent().path
|
|
|
|
CLI.print = { _, _ in }
|
|
|
|
XCTAssertEqual(
|
|
CLI.run(in: testDir, with: "test.swift --config config1.swiftformat,config2.swiftformat"),
|
|
.ok
|
|
)
|
|
|
|
let output = try String(contentsOf: url, encoding: .utf8)
|
|
|
|
XCTAssertEqual(output, """
|
|
func foo() {
|
|
let array = [1, 2, 3,]
|
|
bar()
|
|
}
|
|
""")
|
|
}
|
|
}
|
|
|
|
func testLintCommandOutputsOrganizeDeclarationOrderingViolations() {
|
|
var output: [String] = []
|
|
CLI.print = { message, _ in
|
|
output.append(message)
|
|
}
|
|
|
|
let input = """
|
|
struct Test {
|
|
func test() {
|
|
print("Test")
|
|
}
|
|
var foo: Foo
|
|
func bar() {
|
|
print("Bar")
|
|
}
|
|
}
|
|
"""
|
|
|
|
let lines = input.components(separatedBy: "\n").map { $0 + "\n" }
|
|
|
|
var readCount = 0
|
|
CLI.readLine = {
|
|
guard readCount < lines.count else { return nil }
|
|
defer { readCount += 1 }
|
|
return lines[readCount]
|
|
}
|
|
|
|
_ = processArguments(["", "stdin", "--lint", "--rules", "organizeDeclarations"], in: "")
|
|
|
|
XCTAssertEqual(output, [
|
|
"Running SwiftFormat...",
|
|
"(lint mode - no files will be changed.)",
|
|
":2:1: error: (organizeDeclarations) Organize declarations within class, struct, enum, actor, and extension bodies.",
|
|
":5:1: error: (organizeDeclarations) Organize declarations within class, struct, enum, actor, and extension bodies.",
|
|
"Source input did not pass lint check.",
|
|
])
|
|
}
|
|
|
|
func testTrailingCommasCollectionsOnlyDoesNotTriggerDeprecationWarning_issue_2141() throws {
|
|
var warnings = [String]()
|
|
CLI.print = { message, type in
|
|
if type == .warning {
|
|
warnings.append(message)
|
|
}
|
|
}
|
|
|
|
try withTmpFiles([
|
|
"foo/bar/baz.swift": "",
|
|
]) { url in
|
|
_ = processArguments(["", url.path, "--trailing-commas", "collections-only", "--swift-version", "6.0"], in: "")
|
|
}
|
|
|
|
// Should not contain the deprecation warning about --commas
|
|
XCTAssertEqual(warnings, [])
|
|
}
|
|
|
|
func testConfigFilesWithFilter() throws {
|
|
var errors = [String]()
|
|
|
|
CLI.print = { message, type in
|
|
print(message)
|
|
if type == .error {
|
|
errors.append(message)
|
|
}
|
|
}
|
|
|
|
let configURL = try createTmpFile("Test/config.swiftformat", contents: """
|
|
--rules indent
|
|
--rules braces
|
|
--indent 1
|
|
""")
|
|
|
|
let testsConfigURL = try createTmpFile("Test/tests.swiftformat", contents: """
|
|
--filter **/Tests/**
|
|
--indent 2
|
|
--enable linebreakAtEndOfFile
|
|
""")
|
|
|
|
let toolsConfigURL = try createTmpFile("Test/tools.swiftformat", contents: """
|
|
--filter **/Tools/**
|
|
--indent 3
|
|
--disable braces
|
|
""")
|
|
|
|
let nonTestFile = try createTmpFile("Test/Foo/Sources/Foo.swift", contents: """
|
|
func foo()
|
|
{
|
|
print("bar")
|
|
}
|
|
""")
|
|
|
|
let testFile = try createTmpFile("Test/Foo/Tests/FooTests.swift", contents: """
|
|
func foo()
|
|
{
|
|
print("bar")
|
|
}
|
|
""")
|
|
|
|
let toolsFile = try createTmpFile("Test/Tools/MyTool/FooTool.swift", contents: """
|
|
func foo()
|
|
{
|
|
print("bar")
|
|
}
|
|
""")
|
|
|
|
_ = processArguments([
|
|
"",
|
|
configURL.deletingLastPathComponent().path,
|
|
"--config", configURL.path,
|
|
"--config", testsConfigURL.path,
|
|
"--config", toolsConfigURL.path,
|
|
], in: "")
|
|
|
|
XCTAssertEqual(try String(contentsOf: nonTestFile, encoding: .utf8), """
|
|
func foo() {
|
|
print("bar")
|
|
}
|
|
""")
|
|
|
|
XCTAssertEqual(try String(contentsOf: testFile, encoding: .utf8), """
|
|
func foo() {
|
|
print("bar")
|
|
}
|
|
|
|
""")
|
|
|
|
XCTAssertEqual(try String(contentsOf: toolsFile, encoding: .utf8), """
|
|
func foo()
|
|
{
|
|
print("bar")
|
|
}
|
|
""")
|
|
|
|
let tempFiles = [configURL, testsConfigURL, toolsConfigURL, nonTestFile, testFile, toolsFile]
|
|
for tempFile in tempFiles {
|
|
try FileManager.default.removeItem(at: tempFile)
|
|
}
|
|
|
|
XCTAssertEqual(errors, [])
|
|
}
|
|
|
|
func testSingleConfigFileWithFilters() throws {
|
|
var errors = [String]()
|
|
|
|
CLI.print = { message, type in
|
|
print(message)
|
|
if type == .error {
|
|
errors.append(message)
|
|
}
|
|
}
|
|
|
|
let configURL = try createTmpFile("Test/config.swiftformat", contents: """
|
|
--indent 1
|
|
|
|
[Tests]
|
|
--filter **/Tests/**
|
|
--indent 2
|
|
|
|
[Tools Directory]
|
|
--filter **/Tools/**
|
|
--indent 3
|
|
""")
|
|
|
|
let nonTestFile = try createTmpFile("Test/Foo/Sources/Foo.swift", contents: """
|
|
func foo() {
|
|
print("bar")
|
|
}
|
|
""")
|
|
|
|
let testFile = try createTmpFile("Test/Foo/Tests/FooTests.swift", contents: """
|
|
func foo() {
|
|
print("bar")
|
|
}
|
|
""")
|
|
|
|
let toolsFile = try createTmpFile("Test/Tools/MyTool/FooTool.swift", contents: """
|
|
func foo() {
|
|
print("bar")
|
|
}
|
|
""")
|
|
|
|
_ = processArguments([
|
|
"",
|
|
configURL.deletingLastPathComponent().path,
|
|
"--config", configURL.path,
|
|
], in: "")
|
|
|
|
XCTAssertEqual(try String(contentsOf: nonTestFile, encoding: .utf8), """
|
|
func foo() {
|
|
print("bar")
|
|
}
|
|
|
|
""")
|
|
|
|
XCTAssertEqual(try String(contentsOf: testFile, encoding: .utf8), """
|
|
func foo() {
|
|
print("bar")
|
|
}
|
|
|
|
""")
|
|
|
|
XCTAssertEqual(try String(contentsOf: toolsFile, encoding: .utf8), """
|
|
func foo() {
|
|
print("bar")
|
|
}
|
|
|
|
""")
|
|
|
|
let tempFiles = [configURL, nonTestFile, testFile, toolsFile]
|
|
for tempFile in tempFiles {
|
|
try FileManager.default.removeItem(at: tempFile)
|
|
}
|
|
|
|
XCTAssertEqual(errors, [])
|
|
}
|
|
|
|
func testBaseConfigFileWithFilters() throws {
|
|
var errors = [String]()
|
|
|
|
CLI.print = { message, type in
|
|
print(message)
|
|
if type == .error {
|
|
errors.append(message)
|
|
}
|
|
}
|
|
|
|
let configURL = try createTmpFile("Test/config.swiftformat", contents: """
|
|
--indent 1
|
|
|
|
[Tests]
|
|
--filter **/Tests/**
|
|
--indent 2
|
|
|
|
[Tools Directory]
|
|
--filter **/Tools/**
|
|
--indent 3
|
|
""")
|
|
|
|
let nonTestFile = try createTmpFile("Test/Foo/Sources/Foo.swift", contents: """
|
|
func foo() {
|
|
print("bar")
|
|
}
|
|
""")
|
|
|
|
let testFile = try createTmpFile("Test/Foo/Tests/FooTests.swift", contents: """
|
|
func foo() {
|
|
print("bar")
|
|
}
|
|
""")
|
|
|
|
let toolsFile = try createTmpFile("Test/Tools/MyTool/FooTool.swift", contents: """
|
|
func foo() {
|
|
print("bar")
|
|
}
|
|
""")
|
|
|
|
_ = processArguments([
|
|
"",
|
|
configURL.deletingLastPathComponent().path,
|
|
"--base-config", configURL.path,
|
|
], in: "")
|
|
|
|
XCTAssertEqual(try String(contentsOf: nonTestFile, encoding: .utf8), """
|
|
func foo() {
|
|
print("bar")
|
|
}
|
|
|
|
""")
|
|
|
|
XCTAssertEqual(try String(contentsOf: testFile, encoding: .utf8), """
|
|
func foo() {
|
|
print("bar")
|
|
}
|
|
|
|
""")
|
|
|
|
XCTAssertEqual(try String(contentsOf: toolsFile, encoding: .utf8), """
|
|
func foo() {
|
|
print("bar")
|
|
}
|
|
|
|
""")
|
|
|
|
let tempFiles = [configURL, nonTestFile, testFile, toolsFile]
|
|
for tempFile in tempFiles {
|
|
try FileManager.default.removeItem(at: tempFile)
|
|
}
|
|
|
|
XCTAssertEqual(errors, [])
|
|
}
|
|
|
|
func testStdinPathFileSpecificPath() throws {
|
|
var output = [String]()
|
|
CLI.print = { message, type in
|
|
switch type {
|
|
case .raw, .content:
|
|
output.append(message)
|
|
case .error, .warning:
|
|
XCTFail(message)
|
|
case .info, .success:
|
|
break
|
|
}
|
|
}
|
|
var readCount = 0
|
|
CLI.readLine = {
|
|
readCount += 1
|
|
switch readCount {
|
|
case 1:
|
|
return "func foo()\n"
|
|
case 2:
|
|
return "{\n"
|
|
case 3:
|
|
return "bar()\n"
|
|
case 4:
|
|
return "}"
|
|
default:
|
|
return nil
|
|
}
|
|
}
|
|
|
|
let configURL = try createTmpFile("Test/config.swiftformat", contents: """
|
|
--indent 1
|
|
|
|
[Tests]
|
|
--filter **/Tests/**
|
|
--indent 2
|
|
""")
|
|
|
|
let testFile = try createTmpFile("Test/MyModule/Tests/Foo.swift", contents: """
|
|
func foo()
|
|
{
|
|
print("bar")
|
|
}
|
|
""")
|
|
|
|
_ = processArguments([
|
|
"",
|
|
"stdin",
|
|
"--stdin-path", testFile.path,
|
|
"--config", configURL.path,
|
|
], in: "")
|
|
|
|
XCTAssertEqual(output, ["""
|
|
func foo() {
|
|
bar()
|
|
}
|
|
|
|
"""])
|
|
|
|
let tempFiles = [configURL, testFile]
|
|
for tempFile in tempFiles {
|
|
try FileManager.default.removeItem(at: tempFile)
|
|
}
|
|
}
|
|
|
|
func testStdinPathWithNonExistingFile() {
|
|
var output = [String]()
|
|
CLI.print = { message, type in
|
|
switch type {
|
|
case .raw, .content:
|
|
output.append(message)
|
|
case .error, .warning:
|
|
XCTFail(message)
|
|
case .info, .success:
|
|
break
|
|
}
|
|
}
|
|
var readCount = 0
|
|
CLI.readLine = {
|
|
readCount += 1
|
|
switch readCount {
|
|
case 1:
|
|
return "func foo()\n"
|
|
case 2:
|
|
return "{\n"
|
|
case 3:
|
|
return "bar()\n"
|
|
case 4:
|
|
return "}"
|
|
default:
|
|
return nil
|
|
}
|
|
}
|
|
|
|
// Use a path that doesn't exist
|
|
let nonExistingPath = "/tmp/deleted_file_\(UUID().uuidString).swift"
|
|
|
|
_ = processArguments([
|
|
"",
|
|
"stdin",
|
|
"--stdin-path", nonExistingPath,
|
|
], in: "")
|
|
|
|
// Should still format the input, despite file not existing
|
|
XCTAssertEqual(output, ["""
|
|
func foo() {
|
|
bar()
|
|
}
|
|
|
|
"""])
|
|
}
|
|
|
|
func testStdinPathWithNonExistingFileExcluded() {
|
|
var output = [String]()
|
|
CLI.print = { message, type in
|
|
switch type {
|
|
case .raw, .content:
|
|
output.append(message)
|
|
case .error, .warning:
|
|
XCTFail(message)
|
|
case .info, .success:
|
|
break
|
|
}
|
|
}
|
|
var readCount = 0
|
|
CLI.readLine = {
|
|
readCount += 1
|
|
switch readCount {
|
|
case 1:
|
|
return "func foo()\n"
|
|
case 2:
|
|
return "{\n"
|
|
case 3:
|
|
return "bar()\n"
|
|
case 4:
|
|
return "}"
|
|
default:
|
|
return nil
|
|
}
|
|
}
|
|
|
|
// Use a path that doesn't exist but matches exclusion pattern
|
|
let nonExistingPath = "/tmp/excluded/deleted_file.swift"
|
|
|
|
_ = processArguments([
|
|
"",
|
|
"stdin",
|
|
"--stdin-path", nonExistingPath,
|
|
"--exclude", "/tmp/excluded",
|
|
], in: "")
|
|
|
|
// Should NOT format because the path is excluded
|
|
XCTAssertEqual(output, ["func foo()\n{\nbar()\n}"])
|
|
}
|
|
|
|
func testSwiftVersionFileWithNoConfigFile() throws {
|
|
var errors = [String]()
|
|
|
|
CLI.print = { message, type in
|
|
print(message)
|
|
if type == .error {
|
|
errors.append(message)
|
|
}
|
|
}
|
|
|
|
try withTmpFiles([
|
|
".swift-version": """
|
|
6.1
|
|
""",
|
|
"Test.swift": """
|
|
func foo(
|
|
foo: Foo,
|
|
bar: Bar
|
|
) {}
|
|
""",
|
|
]) { url in
|
|
guard url.pathExtension == "swift" else { return }
|
|
_ = processArguments(["", url.path, "--rules", "trailingCommas"], in: "")
|
|
|
|
XCTAssertEqual(try String(contentsOf: url, encoding: .utf8), """
|
|
func foo(
|
|
foo: Foo,
|
|
bar: Bar,
|
|
) {}
|
|
""")
|
|
}
|
|
|
|
XCTAssertEqual(errors, [])
|
|
}
|
|
|
|
func testSwiftVersionFileWithConfigFile() throws {
|
|
var errors = [String]()
|
|
|
|
CLI.print = { message, type in
|
|
print(message)
|
|
if type == .error {
|
|
errors.append(message)
|
|
}
|
|
}
|
|
|
|
try withTmpFiles([
|
|
".swift-version": """
|
|
6.1
|
|
""",
|
|
".swiftformat": """
|
|
--rules trailingCommas
|
|
--rules indent
|
|
--indent 2
|
|
""",
|
|
"Test.swift": """
|
|
func foo(
|
|
foo: Foo,
|
|
bar: Bar
|
|
) {}
|
|
""",
|
|
]) { url in
|
|
guard url.pathExtension == "swift" else { return }
|
|
_ = processArguments(["", url.path], in: "")
|
|
|
|
XCTAssertEqual(try String(contentsOf: url, encoding: .utf8), """
|
|
func foo(
|
|
foo: Foo,
|
|
bar: Bar,
|
|
) {}
|
|
""")
|
|
}
|
|
|
|
XCTAssertEqual(errors, [])
|
|
}
|
|
|
|
// MARK: Markdown
|
|
|
|
func testFormatMarkdownFile() throws {
|
|
CLI.print = { message, type in
|
|
if type == .error {
|
|
XCTFail(message)
|
|
}
|
|
}
|
|
|
|
try withTmpFiles([
|
|
"README.md": """
|
|
# Sample README
|
|
|
|
This is a nice project with lots of cool APIs to know about, including:
|
|
|
|
```swift
|
|
func foo(
|
|
bar: Bar,
|
|
baaz: Baaz
|
|
) -> Foo { ... }
|
|
```
|
|
|
|
```swift --indent 2
|
|
func foo(
|
|
bar: Bar,
|
|
baaz: Baaz
|
|
) -> Foo { ... }
|
|
```
|
|
|
|
```swift --disable indent
|
|
print( "foo" )
|
|
print( "bar" )
|
|
print( "baaz" )
|
|
```
|
|
|
|
```swift --disable spaceInsideParens
|
|
print( "foo" )
|
|
print( "bar" )
|
|
print( "baaz" )
|
|
```
|
|
|
|
```swift --enable organizeDeclarations
|
|
class Foo {
|
|
init() {}
|
|
func bar() {}
|
|
}
|
|
```
|
|
|
|
```swift
|
|
--markdownfiles format-lenient ignores blocks that can't be parsed:
|
|
print("Foo
|
|
```
|
|
|
|
Thanks for reading!
|
|
""",
|
|
]) { url in
|
|
_ = processArguments([
|
|
"",
|
|
url.path,
|
|
"--markdownfiles", "format-lenient",
|
|
"--rules", "indent",
|
|
"--rules", "braces",
|
|
"--rules", "spaceInsideParens",
|
|
"--rules", "linebreakAtEndOfFile",
|
|
], in: "")
|
|
|
|
let updatedReadme = try String(contentsOf: url, encoding: .utf8)
|
|
|
|
// The Swift code blocks should be indented correctly:
|
|
XCTAssertEqual(updatedReadme, """
|
|
# Sample README
|
|
|
|
This is a nice project with lots of cool APIs to know about, including:
|
|
|
|
```swift
|
|
func foo(
|
|
bar: Bar,
|
|
baaz: Baaz
|
|
) -> Foo { ... }
|
|
```
|
|
|
|
```swift --indent 2
|
|
func foo(
|
|
bar: Bar,
|
|
baaz: Baaz
|
|
) -> Foo { ... }
|
|
```
|
|
|
|
```swift --disable indent
|
|
print("foo")
|
|
print("bar")
|
|
print("baaz")
|
|
```
|
|
|
|
```swift --disable spaceInsideParens
|
|
print( "foo" )
|
|
print( "bar" )
|
|
print( "baaz" )
|
|
```
|
|
|
|
```swift --enable organizeDeclarations
|
|
class Foo {
|
|
|
|
// MARK: Lifecycle
|
|
|
|
init() {}
|
|
|
|
// MARK: Internal
|
|
|
|
func bar() {}
|
|
}
|
|
```
|
|
|
|
```swift
|
|
--markdownfiles format-lenient ignores blocks that can't be parsed:
|
|
print("Foo
|
|
```
|
|
|
|
Thanks for reading!
|
|
""")
|
|
}
|
|
}
|
|
|
|
func testStrictMarkdownFormatting() throws {
|
|
var errors = [String]()
|
|
|
|
CLI.print = { message, type in
|
|
if type == .error {
|
|
errors.append(message)
|
|
}
|
|
}
|
|
|
|
try withTmpFiles([
|
|
"README.md": """
|
|
# Sample README
|
|
|
|
This is a nice project with lots of cool APIs to know about, including:
|
|
|
|
```swift
|
|
--markdownfiles format-strict fails if there are parsing errors:
|
|
print("Foo
|
|
```
|
|
|
|
```swift no-format
|
|
This block is ignored
|
|
print("Foo
|
|
```
|
|
|
|
Thanks for reading!
|
|
""",
|
|
]) { url in
|
|
_ = processArguments([
|
|
"",
|
|
url.path,
|
|
"--markdownfiles", "format-strict",
|
|
"--rules", "indent",
|
|
], in: "")
|
|
}
|
|
|
|
XCTAssertEqual(errors.count, 1)
|
|
XCTAssert(errors[0].contains("Unexpected end of file at 7:11"))
|
|
}
|
|
|
|
func testDoesntFormatSwiftBlockInDiffBlock() throws {
|
|
var errors = [String]()
|
|
|
|
CLI.print = { message, type in
|
|
if type == .error {
|
|
errors.append(message)
|
|
}
|
|
}
|
|
|
|
try withTmpFiles([
|
|
"README.md": """
|
|
````diff
|
|
```swift
|
|
- func foo( ) { }
|
|
+ func foo() { }
|
|
```
|
|
````
|
|
|
|
```swift
|
|
func foo( ) { }
|
|
```
|
|
|
|
```swift
|
|
```
|
|
""",
|
|
]) { url in
|
|
_ = processArguments([
|
|
"",
|
|
url.path,
|
|
"--markdownfiles", "format-strict",
|
|
"--rules", "consecutiveSpaces",
|
|
"--rules", "spaceInsideBrackets",
|
|
"--rules", "spaceInsideParens",
|
|
], in: "")
|
|
|
|
let updatedReadme = try String(contentsOf: url, encoding: .utf8)
|
|
XCTAssertEqual(updatedReadme, """
|
|
````diff
|
|
```swift
|
|
- func foo( ) { }
|
|
+ func foo() { }
|
|
```
|
|
````
|
|
|
|
```swift
|
|
func foo() { }
|
|
```
|
|
|
|
```swift
|
|
```
|
|
""")
|
|
}
|
|
|
|
XCTAssertEqual(errors, [])
|
|
}
|
|
|
|
func testUnbalancedCodeBlockTokens() throws {
|
|
var errors = [String]()
|
|
|
|
CLI.print = { message, type in
|
|
if type == .error {
|
|
errors.append(message)
|
|
}
|
|
}
|
|
|
|
try withTmpFiles([
|
|
"README.md": """
|
|
# Sample README
|
|
|
|
This markdown file has unbalanced code block tokens:
|
|
|
|
```swift
|
|
print("Hello, world!")
|
|
// Missing closing ```
|
|
|
|
This should cause an error in strict mode.
|
|
""",
|
|
]) { url in
|
|
_ = processArguments([
|
|
"",
|
|
url.path,
|
|
"--markdownfiles", "format-strict",
|
|
"--rules", "indent",
|
|
], in: "")
|
|
}
|
|
|
|
XCTAssertEqual(errors.count, 1)
|
|
XCTAssert(errors[0].contains("Unbalanced code block delimiters in markdown"))
|
|
}
|
|
|
|
// MARK: Reporters
|
|
|
|
func testWrite() throws {
|
|
let reporter = GithubActionsLogReporter(environment: ["GITHUB_WORKSPACE": "/bar"])
|
|
let rule = FormatRule.consecutiveSpaces
|
|
reporter.report([
|
|
.init(line: 1, rule: rule, filePath: "/bar/foo.swift", isMove: false),
|
|
.init(line: 2, rule: rule, filePath: "/bar/foo.swift", isMove: false),
|
|
])
|
|
let expectedOutput = """
|
|
::warning file=foo.swift,line=1::\(rule.help) (\(rule.name))
|
|
::warning file=foo.swift,line=2::\(rule.help) (\(rule.name))
|
|
|
|
"""
|
|
let output = try XCTUnwrap(reporter.write())
|
|
let outputString = String(decoding: output, as: UTF8.self)
|
|
XCTAssertEqual(outputString, expectedOutput)
|
|
}
|
|
|
|
func testJSONReporterEndToEnd() throws {
|
|
try withTmpFiles([
|
|
"foo.swift": "func foo() {\n}\n",
|
|
]) { url in
|
|
CLI.print = { message, type in
|
|
switch type {
|
|
case .raw:
|
|
XCTAssert(message.contains("\"rule_id\" : \"emptyBraces\""))
|
|
case .error, .warning:
|
|
break
|
|
case .info, .success:
|
|
break
|
|
case .content:
|
|
XCTFail()
|
|
}
|
|
}
|
|
_ = processArguments([
|
|
"",
|
|
"--lint",
|
|
"--reporter",
|
|
"json",
|
|
url.path,
|
|
], in: "")
|
|
}
|
|
}
|
|
|
|
func testJSONReporterInferredFromURL() throws {
|
|
let outputURL = try createTmpFile("report.json", contents: "")
|
|
try withTmpFiles([
|
|
"foo.swift": "func foo() {\n}\n",
|
|
]) { url in
|
|
CLI.print = { _, _ in }
|
|
_ = processArguments([
|
|
"",
|
|
"--lint",
|
|
"--report",
|
|
outputURL.path,
|
|
url.path,
|
|
], in: "")
|
|
}
|
|
let output = try String(contentsOf: outputURL)
|
|
XCTAssert(output.contains("\"rule_id\" : \"emptyBraces\""))
|
|
}
|
|
|
|
func testGithubActionsLogReporterEndToEnd() throws {
|
|
try withTmpFiles([
|
|
"foo.swift": "func foo() {\n}\n",
|
|
]) { url in
|
|
CLI.print = { message, type in
|
|
switch type {
|
|
case .raw:
|
|
XCTAssert(message.hasPrefix("::warning file=foo.swift,line=1::"))
|
|
case .error, .warning:
|
|
break
|
|
case .info, .success:
|
|
break
|
|
case .content:
|
|
XCTFail()
|
|
}
|
|
}
|
|
_ = processArguments([
|
|
"",
|
|
"--lint",
|
|
"--reporter",
|
|
"github-actions-log",
|
|
url.path,
|
|
],
|
|
environment: ["GITHUB_WORKSPACE": url.deletingLastPathComponent().path],
|
|
in: "")
|
|
}
|
|
}
|
|
|
|
func testGithubActionsLogReporterMisspelled() throws {
|
|
try withTmpFiles([
|
|
"foo.swift": "func foo() {\n}\n",
|
|
]) { url in
|
|
CLI.print = { message, type in
|
|
switch type {
|
|
case .raw, .warning, .info:
|
|
break
|
|
case .error:
|
|
XCTAssert(message.contains("Did you mean 'github-actions-log'?"))
|
|
case .content, .success:
|
|
XCTFail()
|
|
}
|
|
}
|
|
_ = processArguments([
|
|
"",
|
|
"--lint",
|
|
"--reporter",
|
|
"github-action-log",
|
|
url.path,
|
|
], in: "")
|
|
}
|
|
}
|
|
|
|
func testXMLReporterEndToEnd() throws {
|
|
try withTmpFiles([
|
|
"foo.swift": "func foo() {\n}\n",
|
|
]) { url in
|
|
CLI.print = { message, type in
|
|
switch type {
|
|
case .raw:
|
|
XCTAssert(message.contains("<error line=\"1\" column=\"0\" severity=\"warning\""))
|
|
case .error, .warning:
|
|
break
|
|
case .info, .success:
|
|
break
|
|
case .content:
|
|
XCTFail()
|
|
}
|
|
}
|
|
_ = processArguments([
|
|
"",
|
|
"--lint",
|
|
"--reporter",
|
|
"xml",
|
|
url.path,
|
|
], in: "")
|
|
}
|
|
}
|
|
|
|
func testXMLReporterInferredFromURL() throws {
|
|
let outputURL = try createTmpFile("report.xml", contents: "")
|
|
try withTmpFiles([
|
|
"foo.swift": "func foo() {\n}\n",
|
|
]) { url in
|
|
CLI.print = { _, _ in }
|
|
_ = processArguments([
|
|
"",
|
|
"--lint",
|
|
"--report",
|
|
outputURL.path,
|
|
url.path,
|
|
], in: "")
|
|
}
|
|
let output = try String(contentsOf: outputURL)
|
|
XCTAssert(output.contains("<error line=\"1\" column=\"0\" severity=\"warning\""))
|
|
}
|
|
|
|
func testSARIFReporterEndToEnd() throws {
|
|
try withTmpFiles([
|
|
"foo.swift": "func foo() {\n}\n",
|
|
]) { url in
|
|
CLI.print = { message, type in
|
|
switch type {
|
|
case .raw:
|
|
XCTAssert(message.contains("\"ruleId\" : \"emptyBraces\""))
|
|
case .error, .warning:
|
|
break
|
|
case .info, .success:
|
|
break
|
|
case .content:
|
|
XCTFail()
|
|
}
|
|
}
|
|
_ = processArguments([
|
|
"",
|
|
"--lint",
|
|
"--reporter",
|
|
"sarif",
|
|
url.path,
|
|
], in: "")
|
|
}
|
|
}
|
|
|
|
func testSARIFReporterInferredFromURL() throws {
|
|
let outputURL = try createTmpFile("report.sarif", contents: "")
|
|
try withTmpFiles([
|
|
"foo.swift": "func foo() {\n}\n",
|
|
]) { url in
|
|
CLI.print = { _, _ in }
|
|
_ = processArguments([
|
|
"",
|
|
"--lint",
|
|
"--report",
|
|
outputURL.path,
|
|
url.path,
|
|
], in: "")
|
|
}
|
|
let output = try String(contentsOf: outputURL)
|
|
XCTAssert(output.contains("\"ruleId\" : \"emptyBraces\""))
|
|
}
|
|
|
|
// MARK: Rule info
|
|
|
|
func testRuleInfo() {
|
|
CLI.print = { _, _ in }
|
|
for rule in FormatRules.all {
|
|
do {
|
|
try printRuleInfo(for: rule.name, as: .content)
|
|
} catch {
|
|
XCTFail("RuleInfo for \(rule.name) threw error: \(error)")
|
|
}
|
|
}
|
|
}
|
|
}
|