mirror of
https://github.com/nicklockwood/SwiftFormat.git
synced 2026-05-17 10:30:35 +00:00
981 lines
36 KiB
Swift
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), [])
|
|
}
|
|
}
|