Files
SwiftFormat/Tests/ArgumentsTests.swift
2026-01-25 09:28:33 -08:00

981 lines
36 KiB
Swift

//
// ArgumentsTests.swift
// SwiftFormat
//
// Created by Nick Lockwood on 07/08/2018.
// Copyright © 2018 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 ArgumentsTests: XCTestCase {
// MARK: arg parser
func testParseSimpleArguments() {
let input = "hello world"
let output = ["", "hello", "world"]
XCTAssertEqual(parseArguments(input), output)
}
func testParseEscapedSpace() {
let input = "hello\\ world"
let output = ["", "hello world"]
XCTAssertEqual(parseArguments(input), output)
}
func testParseEscapedN() {
let input = "hello\\nworld"
let output = ["", "hellonworld"]
XCTAssertEqual(parseArguments(input), output)
}
func testParseQuoteArguments() {
let input = "\"hello world\""
let output = ["", "hello world"]
XCTAssertEqual(parseArguments(input), output)
}
func testParseEscapedQuote() {
let input = "hello \\\"world\\\""
let output = ["", "hello", "\"world\""]
XCTAssertEqual(parseArguments(input), output)
}
func testParseEscapedQuoteInString() {
let input = "\"hello \\\"world\\\"\""
let output = ["", "hello \"world\""]
XCTAssertEqual(parseArguments(input), output)
}
func testParseQuotedEscapedN() {
let input = "\"hello\\nworld\""
let output = ["", "hello\\nworld"]
XCTAssertEqual(parseArguments(input), output)
}
func testCommentedLine() {
let input = "#hello"
let output = [""]
XCTAssertEqual(parseArguments(input, ignoreComments: false), output)
}
func testCommentInLine() {
let input = "hello#world"
let output = ["", "hello"]
XCTAssertEqual(parseArguments(input, ignoreComments: false), output)
}
func testCommentAfterSpace() {
let input = "hello #world"
let output = ["", "hello"]
XCTAssertEqual(parseArguments(input, ignoreComments: false), output)
}
func testCommentBeforeSpace() {
let input = "hello# world"
let output = ["", "hello"]
XCTAssertEqual(parseArguments(input, ignoreComments: false), output)
}
func testCommentContainingSpace() {
let input = "hello #wide world"
let output = ["", "hello"]
XCTAssertEqual(parseArguments(input, ignoreComments: false), output)
}
func testEscapedComment() {
let input = "hello \\#world"
let output = ["", "hello", "#world"]
XCTAssertEqual(parseArguments(input, ignoreComments: false), output)
}
func testQuotedComment() {
let input = "hello \"#world\""
let output = ["", "hello", "#world"]
XCTAssertEqual(parseArguments(input, ignoreComments: false), output)
}
func testQuotedURLMacro() {
let input = "--url-macro \"#URL,URLFoundation\""
let output = ["", "--url-macro", "#URL,URLFoundation"]
XCTAssertEqual(parseArguments(input, ignoreComments: false), output)
}
func testLegacyOptionsWithLegacyOptionNames() throws {
let testCases: [(legacy: String, current: String)] = [
("lineaftermarks", "line-after-marks"),
("indentcase", "indent-case"),
("trailingcommas", "trailing-commas"),
("wrapArguments", "wrap-arguments"),
("hexliteralcase", "hex-literal-case"),
("nospaceoperators", "no-space-operators"),
("modifierorder", "modifier-order"),
("extensionACL", "extension-acl"),
("propertyTypes", "property-types"),
("swiftVersion", "swift-version"),
]
for (legacy, current) in testCases {
do {
let legacyArgs = try preprocessArguments(["", "--\(legacy)", "true"], commandLineArguments)
let currentArgs = try preprocessArguments(["", "--\(current)", "true"], commandLineArguments)
// Both should map to the same internal option name
XCTAssertEqual(legacyArgs[current] ?? legacyArgs[legacy], currentArgs[current])
} catch {
XCTFail("Legacy option --\(legacy) should work but failed with: \(error)")
}
}
}
// MARK: arg preprocessor
func testPreprocessArguments() {
let input = ["", "foo", "bar", "-o", "baz", "-i", "4", "-l", "cr", "-s", "inline"]
let output = ["0": "", "1": "foo", "2": "bar", "output": "baz", "indent": "4", "linebreaks": "cr", "semicolons": "inline"]
XCTAssertEqual(try preprocessArguments(input, [
"output",
"indent",
"linebreaks",
"semicolons",
]), output)
}
func testPreprocessArgumentsNormalizesKeyCase() {
let input = ["", "--Help", "-VERSION", "-foo", "BaR"]
let output = ["0": "", "help": "", "version": "", "foo": "BaR"]
XCTAssertEqual(try preprocessArguments(input, [
"help",
"version",
"foo",
]), output)
}
func testEmptyArgsAreRecognized() {
let input = ["", "--help", "--version"]
let output = ["0": "", "help": "", "version": ""]
XCTAssertEqual(try preprocessArguments(input, [
"help",
"version",
]), output)
}
func testInvalidArgumentThrows() {
XCTAssertThrowsError(try preprocessArguments(["", "--vers"], [
"verbose",
"version",
])) { error in
XCTAssertEqual("\(error)", "Unknown option --vers. Did you mean --version?")
}
}
// merging
func testDuplicateDisableArgumentsAreMerged() {
let input = ["", "--disable", "foo", "--disable", "bar"]
let output = ["0": "", "disable": "foo,bar"]
XCTAssertEqual(try preprocessArguments(input, [
"disable",
]), output)
}
func testDuplicateExcludeArgumentsAreMerged() {
let input = ["", "--exclude", "foo", "--exclude", "bar"]
let output = ["0": "", "exclude": "foo,bar"]
XCTAssertEqual(try preprocessArguments(input, [
"exclude",
]), output)
}
func testDuplicateUnexcludeArgumentsAreMerged() {
let input = ["", "--unexclude", "foo", "--unexclude", "bar"]
let output = ["0": "", "unexclude": "foo,bar"]
XCTAssertEqual(try preprocessArguments(input, [
"unexclude",
]), output)
}
func testDuplicateSelfrequiredArgumentsAreMerged() {
let input = ["", "--self-required", "foo", "--self-required", "bar"]
let output = ["0": "", "self-required": "foo,bar"]
XCTAssertEqual(try preprocessArguments(input, [
"self-required",
]), output)
}
func testDuplicateNoSpaceOperatorsArgumentsAreMerged() {
let input = ["", "--nospaceoperators", "+", "--nospaceoperators", "*"]
let output = ["0": "", "no-space-operators": "+,*"]
XCTAssertEqual(try preprocessArguments(input, [
"no-space-operators",
]), output)
}
func testDuplicateNoWrapOperatorsArgumentsAreMerged() {
let input = ["", "--nowrapoperators", "+", "--nowrapoperators", "."]
let output = ["0": "", "no-wrap-operators": "+,."]
XCTAssertEqual(try preprocessArguments(input, [
"no-wrap-operators",
]), output)
}
func testDuplicateRangesArgumentsAreNotMerged() {
let input = ["", "--ranges", "spaced", "--ranges", "no-space"]
let output = ["0": "", "ranges": "no-space"]
XCTAssertEqual(try preprocessArguments(input, [
"ranges",
]), output)
}
// comma-delimited values
func testSpacesIgnoredInCommaDelimitedArguments() {
let input = ["", "--rules", "foo,", "bar"]
let output = ["0": "", "rules": "foo,bar"]
XCTAssertEqual(try preprocessArguments(input, [
"rules",
]), output)
}
func testNextArgumentNotIgnoredAfterCommaInArguments() {
let input = ["", "--enable", "foo,", "--disable", "bar"]
let output = ["0": "", "enable": "foo", "disable": "bar"]
XCTAssertEqual(try preprocessArguments(input, [
"enable",
"disable",
]), output)
}
// flags
func testVMatchesVerbose() {
let input = ["", "-v"]
let output = ["0": "", "verbose": ""]
XCTAssertEqual(try preprocessArguments(input, commandLineArguments), output)
}
func testHMatchesHelp() {
let input = ["", "-h"]
let output = ["0": "", "help": ""]
XCTAssertEqual(try preprocessArguments(input, commandLineArguments), output)
}
func testOMatchesOutput() {
let input = ["", "-o"]
let output = ["0": "", "output": ""]
XCTAssertEqual(try preprocessArguments(input, commandLineArguments), output)
}
func testNoMatchFlagThrows() {
let input = ["", "-v"]
XCTAssertThrowsError(try preprocessArguments(input, [
"help", "file",
]))
}
// MARK: format options to arguments
func testCommandLineArgumentsHaveValidNames() {
for key in argumentsFor(.default).keys {
XCTAssertTrue(optionsArguments.contains(key), "\(key) is not a valid argument name")
}
}
func testFormattingArgumentsAreAllImplemented() throws {
CLI.print = { _, _ in }
for key in formattingArguments {
guard let value = argumentsFor(.default)[key] else {
XCTAssert(deprecatedArguments.contains(key))
continue
}
XCTAssert(!deprecatedArguments.contains(key), key)
_ = try formatOptionsFor([key: value])
}
}
func testEmptyFormatOptions() throws {
XCTAssertNil(try formatOptionsFor([:]))
XCTAssertNil(try formatOptionsFor(["--disable": "void"]))
}
func testFileHeaderOptionToArguments() {
let options = FormatOptions(fileHeader: "// Hello World\n// Goodbye World")
let args = argumentsFor(Options(formatOptions: options), excludingDefaults: true)
XCTAssertEqual(args["header"], "// Hello World\\n// Goodbye World")
}
// TODO: should this go in OptionDescriptorTests instead?
func testRenamedArgument() {
XCTAssert(Descriptors.specifierOrder.isRenamed)
}
// MARK: config file parsing
func testParseArgumentsContainingBlankLines() {
let config = """
--allman true
--rules braces,fileHeader
"""
let data = Data(config.utf8)
do {
let args = try parseConfigFile(data)[0]
XCTAssertEqual(args.count, 2)
} catch {
XCTFail("\(error)")
}
}
func testParseConfigFileWithHeaders() {
let config = """
--allman true
--rules braces,fileHeader
[Header]
--filter Foo
--allman false
[Header2]
--filter Bar
--indent 4
"""
let data = Data(config.utf8)
do {
let segments = try parseConfigFile(data)
XCTAssertEqual(segments.count, 3)
XCTAssertEqual(segments[0].count, 2)
XCTAssertEqual(segments[1].count, 2)
XCTAssertEqual(segments[2].count, 2)
} catch {
XCTFail("\(error)")
}
}
func testParseArgumentsContainingAnonymousValues() throws {
let config = """
hello
--allman true
"""
let data = Data(config.utf8)
XCTAssertThrowsError(try parseConfigFile(data)[0]) { error in
guard case let FormatError.options(message) = error else {
XCTFail("\(error)")
return
}
XCTAssert(message.contains("hello"))
}
}
func testParseArgumentsContainingSpaces() throws {
let config = "--rules braces, fileHeader, consecutiveSpaces"
let data = Data(config.utf8)
let args = try parseConfigFile(data)[0]
XCTAssertEqual(args.count, 1)
XCTAssertEqual(args["rules"], "braces, fileHeader, consecutiveSpaces")
}
func testParseURLMacroArgumentInConfigFileFailsWithoutQuotes() throws {
let config = "--url-macro #URL,URLFoundation"
let data = Data(config.utf8)
let args = try parseConfigFile(data)[0]
// This should fail because #URL,URLFoundation is treated as a comment
XCTAssertEqual(args.count, 1)
XCTAssertEqual(args["url-macro"], "")
}
func testParseURLMacroArgumentInConfigFileWithQuotes() throws {
let config = "--url-macro \"#URL,URLFoundation\""
let data = Data(config.utf8)
let args = try parseConfigFile(data)[0]
XCTAssertEqual(args.count, 1)
XCTAssertEqual(args["url-macro"], "#URL,URLFoundation")
}
func testParseArgumentsOnMultipleLines() throws {
let config = """
--rules braces, \\
fileHeader, \\
andOperator, typeSugar
--allman true
--hexgrouping \\
4, \\
8
"""
let data = Data(config.utf8)
let args = try parseConfigFile(data)[0]
XCTAssertEqual(args["rules"], "braces, fileHeader, andOperator, typeSugar")
XCTAssertEqual(args["allman"], "true")
XCTAssertEqual(args["hex-grouping"], "4, 8")
}
func testCommentsInConsecutiveLines() throws {
let config = """
--rules braces, \\
# some comment
fileHeader, \\
# another comment invalidating this line separator \\
# yet another comment
andOperator
--hexgrouping \\
4, \\ # comment after line separator
8 # comment invalidating this line separator \\
"""
let data = Data(config.utf8)
let args = try parseConfigFile(data)[0]
XCTAssertEqual(args["rules"], "braces, fileHeader, andOperator")
XCTAssertEqual(args["hex-grouping"], "4, 8")
}
func testLineContinuationCharacterOnLastLine() throws {
let config = """
--rules braces,\\
fileHeader\\
"""
let data = Data(config.utf8)
XCTAssertThrowsError(try parseConfigFile(data)[0]) {
XCTAssert($0.localizedDescription.contains("line continuation character"))
}
}
func testParseArgumentsContainingEscapedCharacters() throws {
let config = "--header hello\\ world\\ngoodbye\\ world"
let data = Data(config.utf8)
let args = try parseConfigFile(data)[0]
XCTAssertEqual(args.count, 1)
XCTAssertEqual(args["header"], "hello world\\ngoodbye world")
}
func testParseArgumentsContainingQuotedCharacters() throws {
let config = """
--header "hello world\\ngoodbye world"
"""
let data = Data(config.utf8)
let args = try parseConfigFile(data)[0]
XCTAssertEqual(args.count, 1)
XCTAssertEqual(args["header"], "hello world\\ngoodbye world")
}
func testParseIgnoreFileHeader() throws {
let config = "--header ignore"
let data = Data(config.utf8)
let args = try parseConfigFile(data)[0]
let options = try Options(args, in: "/")
XCTAssertEqual(options.formatOptions?.fileHeader, .ignore)
}
func testParseUppercaseIgnoreFileHeader() throws {
let config = "--header IGNORE"
let data = Data(config.utf8)
let args = try parseConfigFile(data)[0]
let options = try Options(args, in: "/")
XCTAssertEqual(options.formatOptions?.fileHeader, .ignore)
}
func testParseArgumentsContainingSwiftVersion() throws {
let config = "--swiftversion 5.1"
let data = Data(config.utf8)
let args = try parseConfigFile(data)[0]
XCTAssertEqual(args.count, 1)
XCTAssertEqual(args["swift-version"], "5.1")
}
func testParseArgumentsContainingLanguageVersion() throws {
let config = "--languagemode 6"
let data = Data(config.utf8)
let args = try parseConfigFile(data)[0]
XCTAssertEqual(args.count, 1)
XCTAssertEqual(args["language-mode"], "6")
}
func testParseArgumentsContainingDisableAll() throws {
let config = "--disable all"
let data = Data(config.utf8)
let args = try parseConfigFile(data)[0]
let options = try Options(args, in: "/")
XCTAssertEqual(options.rules, [])
}
func testPopulatesDefaultLanguageMode() {
let swift5Options = FormatOptions(swiftVersion: "5.0")
XCTAssertEqual(swift5Options.languageMode, "5")
let swift6Options = FormatOptions(swiftVersion: "6.0")
XCTAssertEqual(swift6Options.languageMode, "5")
let swift6LangModeOptions = FormatOptions(swiftVersion: "6.0", languageMode: "6")
XCTAssertEqual(swift6LangModeOptions.languageMode, "6")
}
// MARK: config file serialization
// file header comment encoding
func testSerializeFileHeaderContainingSpace() {
let options = Options(formatOptions: FormatOptions(fileHeader: "// hello world"))
let config = serialize(options: options, excludingDefaults: true)
XCTAssertEqual(config, "--header \"// hello world\"")
}
func testSerializeFileHeaderContainingEscapedSpace() {
let options = Options(formatOptions: FormatOptions(fileHeader: "// hello\\ world"))
let config = serialize(options: options, excludingDefaults: true)
XCTAssertEqual(config, "--header \"// hello\\ world\"")
}
func testSerializeFileHeaderContainingLinebreak() {
let options = Options(formatOptions: FormatOptions(fileHeader: "//hello\nworld"))
let config = serialize(options: options, excludingDefaults: true)
XCTAssertEqual(config, "--header //hello\\nworld")
}
func testSerializeFileHeaderContainingLinebreakAndSpaces() {
let options = Options(formatOptions: FormatOptions(fileHeader: "// hello\n// world"))
let config = serialize(options: options, excludingDefaults: true)
XCTAssertEqual(config, "--header \"// hello\\n// world\"")
}
func testSerializeOptionsWithPoundCharacter() {
let options = Options(formatOptions: FormatOptions(
urlMacro: .macro("#URL", module: "URLFoundation"),
preferFileMacro: false
))
let config = serialize(options: options, excludingDefaults: true)
XCTAssertEqual(config, """
--file-macro "#fileID"
--url-macro "#URL,URLFoundation"
""")
}
// trailing separator
func testSerializeOptionsDisabledDefaultRulesEnabledIsEmpty() {
let rules = defaultRules
let config: String = serialize(options: Options(formatOptions: nil, rules: rules))
XCTAssertEqual(config, "")
}
func testSerializeOptionsDisabledAllRulesEnabledNoTerminatingSeparator() {
let rules = allRules
let config: String = serialize(options: Options(formatOptions: nil, rules: rules))
XCTAssertFalse(config.contains("--disable"))
XCTAssertNotEqual(config.last, "\n")
}
func testSerializeOptionsDisabledSomeRulesDisabledNoTerminatingSeparator() {
let rules = Set(defaultRules.prefix(3))
let config: String = serialize(options: Options(formatOptions: nil, rules: rules))
XCTAssertTrue(config.contains("--disable"))
XCTAssertFalse(config.contains("--enable"))
XCTAssertNotEqual(config.last, "\n")
}
func testSerializeOptionsEnabledDefaultRulesEnabledNoTerminatingSeparator() {
let rules = defaultRules
let config: String = serialize(options: Options(formatOptions: .default, rules: rules))
XCTAssertNotEqual(config, "")
XCTAssertFalse(config.contains("--disable"))
XCTAssertFalse(config.contains("--enable"))
XCTAssertNotEqual(config.last, "\n")
}
func testSerializeOptionsEnabledAllRulesEnabledNoTerminatingSeparator() {
let rules = allRules
let config: String = serialize(options: Options(formatOptions: .default, rules: rules))
XCTAssertFalse(config.contains("--disable"))
XCTAssertNotEqual(config.last, "\n")
}
func testSerializeOptionsEnabledSomeRulesDisabledNoTerminatingSeparator() {
let rules = Set(defaultRules.prefix(3))
let config: String = serialize(options: Options(formatOptions: .default, rules: rules))
XCTAssertTrue(config.contains("--disable"))
XCTAssertFalse(config.contains("--enable"))
XCTAssertNotEqual(config.last, "\n")
}
// swift version
func testSerializeSwiftVersion() {
let version = Version(rawValue: "5.2") ?? "0"
let options = Options(formatOptions: FormatOptions(swiftVersion: version))
let config = serialize(options: options, excludingDefaults: true)
XCTAssertEqual(config, "--swift-version 5.2")
}
// MARK: config file merging
func testMergeFormatOptionArguments() throws {
let args = ["allman": "false", "commas": "always"]
let config = ["allman": "true", "binary-grouping": "4,8"]
let result = try mergeArguments(args, into: config)
for (key, value) in result {
// args take precedence over config
XCTAssertEqual(value, args[key] ?? config[key])
}
for key in Set(args.keys).union(config.keys) {
// all keys should be present in result
XCTAssertNotNil(result[key])
}
}
func testMergeExcludedURLs() throws {
let args = ["exclude": "foo,bar"]
let config = ["exclude": "bar,baz"]
let result = try mergeArguments(args, into: config)
XCTAssertEqual(result["exclude"], "bar,baz,foo")
}
func testMergeUnexcludedURLs() throws {
let args = ["unexclude": "foo,bar"]
let config = ["unexclude": "bar,baz"]
let result = try mergeArguments(args, into: config)
XCTAssertEqual(result["unexclude"], "bar,baz,foo")
}
func testMergeRules() throws {
let args = ["rules": "braces,fileHeader"]
let config = ["rules": "consecutiveSpaces,braces"]
let result = try mergeArguments(args, into: config)
let rules = try parseRules(XCTUnwrap(result["rules"]), ignoreUnknown: false)
XCTAssertEqual(rules, ["braces", "fileHeader"])
}
func testMergeEmptyRules() throws {
let args = ["rules": ""]
let config = ["rules": "consecutiveSpaces,braces"]
let result = try mergeArguments(args, into: config)
let rules = try parseRules(XCTUnwrap(result["rules"]), ignoreUnknown: false)
XCTAssertEqual(Set(rules), Set(["braces", "consecutiveSpaces"]))
}
func testMergeEnableRules() throws {
let args = ["enable": "braces,fileHeader"]
let config = ["enable": "consecutiveSpaces,braces"]
let result = try mergeArguments(args, into: config)
let enabled = try parseRules(XCTUnwrap(result["enable"]), ignoreUnknown: false)
XCTAssertEqual(enabled, ["braces", "consecutiveSpaces", "fileHeader"])
}
func testMergeDisableRules() throws {
let args = ["disable": "braces,fileHeader"]
let config = ["disable": "consecutiveSpaces,braces"]
let result = try mergeArguments(args, into: config)
let disabled = try parseRules(XCTUnwrap(result["disable"]), ignoreUnknown: false)
XCTAssertEqual(disabled, ["braces", "consecutiveSpaces", "fileHeader"])
}
func testRulesArgumentOverridesAllConfigRules() throws {
let args = ["rules": "braces,fileHeader"]
let config = ["rules": "consecutiveSpaces", "disable": "braces", "enable": "redundantSelf"]
let result = try mergeArguments(args, into: config)
let disabled = try parseRules(XCTUnwrap(result["rules"]), ignoreUnknown: false)
XCTAssertEqual(disabled, ["braces", "fileHeader"])
XCTAssertNil(result["enabled"])
XCTAssertNil(result["disabled"])
}
func testEnabledArgumentOverridesConfigRules() throws {
let args = ["enable": "braces"]
let config = ["rules": "fileHeader", "disable": "consecutiveSpaces,braces"]
let result = try mergeArguments(args, into: config)
let rules = try parseRules(XCTUnwrap(result["rules"]), ignoreUnknown: false)
XCTAssertEqual(rules, ["fileHeader"])
let enabled = try parseRules(XCTUnwrap(result["enable"]), ignoreUnknown: false)
XCTAssertEqual(enabled, ["braces"])
let disabled = try parseRules(XCTUnwrap(result["disable"]), ignoreUnknown: false)
XCTAssertEqual(disabled, ["consecutiveSpaces"])
}
func testDisableArgumentOverridesConfigRules() throws {
let args = ["disable": "braces"]
let config = ["rules": "braces,fileHeader", "enable": "consecutiveSpaces,braces"]
let result = try mergeArguments(args, into: config)
let rules = try parseRules(XCTUnwrap(result["rules"]), ignoreUnknown: false)
XCTAssertEqual(rules, ["fileHeader"])
let enabled = try parseRules(XCTUnwrap(result["enable"]), ignoreUnknown: false)
XCTAssertEqual(enabled, ["consecutiveSpaces"])
let disabled = try parseRules(XCTUnwrap(result["disable"]), ignoreUnknown: false)
XCTAssertEqual(disabled, ["braces"])
}
func testMergeSelfRequiredOptions() throws {
let args = ["self-required": "log,assert"]
let config = ["self-required": "expect"]
let result = try mergeArguments(args, into: config)
let selfRequired = try parseCommaDelimitedList(XCTUnwrap(result["self-required"]))
XCTAssertEqual(selfRequired, ["log", "assert"])
}
func testMergeAcronyms() throws {
let args = ["acronyms": "url"]
let config = ["acronyms": "id,uuid"]
let result = try mergeArguments(args, into: config)
let acronyms = try parseCommaDelimitedList(XCTUnwrap(result["acronyms"]))
XCTAssertEqual(acronyms, ["url"])
}
// MARK: add arguments
func testAddFormatArguments() throws {
var options = Options(
formatOptions: FormatOptions(indent: " ", semicolons: .inlineOnly)
)
try options.addArguments(["indent": "2", "linebreaks": "crlf"], in: "")
guard let formatOptions = options.formatOptions else {
XCTFail()
return
}
XCTAssertEqual(formatOptions.indent, " ")
XCTAssertEqual(formatOptions.linebreak, "\r\n")
XCTAssertEqual(formatOptions.semicolons, .inlineOnly)
}
func testAddArgumentsDoesntBreakSwiftVersion() throws {
var options = Options(formatOptions: FormatOptions(swiftVersion: "4.2"))
try options.addArguments(["indent": "2"], in: "")
guard let formatOptions = options.formatOptions else {
XCTFail()
return
}
XCTAssertEqual(formatOptions.swiftVersion, "4.2")
}
func testAddArgumentsDoesntBreakFragment() throws {
var options = Options(formatOptions: FormatOptions(fragment: true))
try options.addArguments(["indent": "2"], in: "")
guard let formatOptions = options.formatOptions else {
XCTFail()
return
}
XCTAssertTrue(formatOptions.fragment)
}
func testAddArgumentsDoesntBreakFileInfo() throws {
let fileInfo = FileInfo(filePath: "~/Foo.swift", creationDate: Date())
var options = Options(formatOptions: FormatOptions(fileInfo: fileInfo))
try options.addArguments(["indent": "2"], in: "")
guard let formatOptions = options.formatOptions else {
XCTFail()
return
}
XCTAssertEqual(formatOptions.fileInfo, fileInfo)
}
// MARK: options parsing
func testParseEmptyOptions() throws {
let options = try Options([:], in: "")
XCTAssertNil(options.formatOptions)
XCTAssertNil(options.fileOptions)
XCTAssertEqual(options.rules, defaultRules)
}
func testParseExcludedURLsFileOption() throws {
let options = try Options(["exclude": "foo bar, baz"], in: "/dir")
let paths = options.fileOptions?.excludedGlobs.map(\.description) ?? []
XCTAssertEqual(paths, ["/dir/foo bar", "/dir/baz"])
}
func testParseUnexcludedURLsFileOption() throws {
let options = try Options(["unexclude": "foo bar, baz"], in: "/dir")
let paths = options.fileOptions?.unexcludedGlobs.map(\.description) ?? []
XCTAssertEqual(paths, ["/dir/foo bar", "/dir/baz"])
}
func testParseDeprecatedOption() throws {
let options = try Options(["ranges": "nospace"], in: "")
XCTAssertEqual(options.formatOptions?.spaceAroundRangeOperators, .remove)
}
func testParseNoSpaceOperatorsOption() throws {
let options = try Options(["no-space-operators": "...,..<"], in: "")
XCTAssertEqual(options.formatOptions?.noSpaceOperators, ["...", "..<"])
}
func testParseNoWrapOperatorsOption() throws {
let options = try Options(["no-wrap-operators": ".,:,*"], in: "")
XCTAssertEqual(options.formatOptions?.noWrapOperators, [".", ":", "*"])
}
func testParseModifierOrderOption() throws {
let options = try Options(["modifier-order": "private(set),public,unowned"], in: "")
XCTAssertEqual(options.formatOptions?.modifierOrder, ["private(set)", "public", "unowned"])
}
func testParseParameterizedModifierOrderOption() throws {
let options = try Options(["modifier-order": "unowned(unsafe),unowned(safe)"], in: "")
XCTAssertEqual(options.formatOptions?.modifierOrder, ["unowned(unsafe)", "unowned(safe)"])
}
func testParseInvalidModifierOrderOption() throws {
XCTAssertThrowsError(try Options(["modifier-order": "unknowned"], in: "")) { error in
XCTAssertEqual("\(error)", "Unsupported --modifier-order value 'unknowned'. Did you mean 'unowned'?")
}
}
func testParseSpecifierOrderOption() throws {
let options = try Options(["specifier-order": "private(set),public"], in: "")
XCTAssertEqual(options.formatOptions?.modifierOrder, ["private(set)", "public"])
}
func testParseSwiftVersionOption() throws {
let options = try Options(["swift-version": "4.2"], in: "")
XCTAssertEqual(options.formatOptions?.swiftVersion, "4.2")
}
// MARK: parse rules
func testParseRulesCaseInsensitive() throws {
let rules = try parseRules("strongoutlets", ignoreUnknown: false)
XCTAssertEqual(rules, ["strongOutlets"])
}
func testParseAllRule() throws {
let rules = try parseRules("all", ignoreUnknown: false)
XCTAssertEqual(rules, FormatRules.all.compactMap {
$0.isDeprecated ? nil : $0.name
})
}
func testParseInvalidRuleThrows() {
XCTAssertThrowsError(try parseRules("strongOutlet", ignoreUnknown: false)) { error in
XCTAssertEqual("\(error)", "Unknown rule 'strongOutlet'. Did you mean 'strongOutlets'?")
}
}
func testParseOptionAsRuleThrows() {
XCTAssertThrowsError(try parseRules("import-grouping", ignoreUnknown: false)) { error in
XCTAssert("\(error)".contains("'sortImports'"))
}
}
func testSuppressInvalidRuleError() throws {
let rules = try parseRules("strongOutlet,isEmpty", ignoreUnknown: true)
XCTAssertEqual(rules, ["isEmpty"])
}
func testSuppressOptionAsRuleError() throws {
let rules = try parseRules("import-grouping,isEmpty", ignoreUnknown: true)
XCTAssertEqual(rules, ["isEmpty"])
}
// MARK: lintonly
func testLintonlyRulesContain() throws {
let options = try Options(["lint": "", "lint-only": "wrapEnumCases"], in: "")
XCTAssert(options.rules?.contains("wrapEnumCases") == true)
let arguments = argumentsFor(options)
XCTAssertEqual(arguments, ["lint": "", "enable": "wrapEnumCases"])
}
func testLintonlyRulesDontContain() throws {
let options = try Options(["lint-only": "unusedArguments"], in: "")
XCTAssert(options.rules?.contains("unusedArguments") == false)
let arguments = argumentsFor(options)
XCTAssertEqual(arguments, ["disable": "unusedArguments"])
}
func testLintonlyMergeOptionsAdd() throws {
var options = try Options(["lint": "", "disable": "unusedArguments"], in: "")
try options.addArguments(["lint-only": "unusedArguments"], in: "")
XCTAssert(options.rules?.contains("unusedArguments") == true)
}
func testLintonlyMergeOptionsRemove() throws {
var options = try Options(["enable": "wrapEnumCases"], in: "")
try options.addArguments(["lint-only": "wrapEnumCases"], in: "")
XCTAssert(options.rules?.contains("wrapEnumCases") == false)
}
// MARK: Edit distance
func testEditDistance() {
XCTAssertEqual("foo".editDistance(from: "fob"), 1)
XCTAssertEqual("foo".editDistance(from: "boo"), 1)
XCTAssertEqual("foo".editDistance(from: "bar"), 3)
XCTAssertEqual("aba".editDistance(from: "bbb"), 2)
XCTAssertEqual("foob".editDistance(from: "foo"), 1)
XCTAssertEqual("foo".editDistance(from: "foob"), 1)
XCTAssertEqual("foo".editDistance(from: "Foo"), 1)
XCTAssertEqual("FOO".editDistance(from: "foo"), 3)
}
func testEditDistanceWithEmptyStrings() {
XCTAssertEqual("foo".editDistance(from: ""), 3)
XCTAssertEqual("".editDistance(from: "foo"), 3)
XCTAssertEqual("".editDistance(from: ""), 0)
}
// MARK: Warnings
func testDeprecatedRuleWarning() {
let warnings = warningsForArguments(["rules": "specifiers"])
XCTAssertEqual(warnings.count, 1)
XCTAssert((warnings.first ?? "").contains("rule is deprecated"))
}
func testDeprecatedOptionWarning() {
let warnings = warningsForArguments(["insert-lines": "enabled"])
XCTAssertEqual(warnings.count, 1)
XCTAssert((warnings.first ?? "").contains("option is deprecated"))
}
func testUnusedOptionWarning() {
let warnings = warningsForArguments([
"disable": "sortImports",
"import-grouping": "testable-bottom",
])
XCTAssertEqual(warnings.count, 1)
XCTAssert((warnings.first ?? "").contains("option has no effect"))
}
func testLintOnlyRuleDoesntTriggerUnusedOptionWarning() {
let warnings = warningsForArguments([
"lintonly": "sortImports",
"import-grouping": "testable-bottom",
])
XCTAssertEqual(warnings, [])
}
func testLintOnlyRuleDoesntTriggerUnusedOptionWarning2() throws {
let options = try Options([
"lint-only": "sortImports",
"import-grouping": "testable-bottom",
], in: "")
let arguments = argumentsFor(options, excludingDefaults: true)
// This is a bug / known issue - since --lintonly rules aren't tracked through
// conversion to Options, resulting in a false positive for 'option has no effect'
let warnings = warningsForArguments(arguments)
XCTAssertEqual(warnings.count, 1)
XCTAssert((warnings.first ?? "").contains("option has no effect"))
// This is the solution to the aforementioned bug
XCTAssertEqual(warningsForArguments(arguments, ignoreUnusedOptions: true), [])
}
}