diff --git a/Sources/Arguments.swift b/Sources/Arguments.swift index bc8bf644..e2ac1b75 100644 --- a/Sources/Arguments.swift +++ b/Sources/Arguments.swift @@ -509,12 +509,12 @@ func warningsForArguments(_ args: [String: String]) -> [String] { var warnings = [String]() for option in FormatOptions.Descriptor.all { if args[option.argumentName] != nil, let message = option.deprecationMessage { - warnings.append(message) + warnings.append("--\(option.argumentName) option is deprecated. \(message)") } } for name in Set(rulesArguments.flatMap { (try? args[$0].map(parseRules) ?? []) ?? [] }) { if let message = FormatRules.byName[name]?.deprecationMessage { - warnings.append(message) + warnings.append("\(name) rule is deprecated. \(message)") } } return warnings diff --git a/Sources/CommandLine.swift b/Sources/CommandLine.swift index 7d0aa94e..0fb50acf 100644 --- a/Sources/CommandLine.swift +++ b/Sources/CommandLine.swift @@ -136,6 +136,12 @@ func printRuleInfo(for name: String, as type: CLI.OutputType) throws { print(name, as: type) print("", as: type) print(stripMarkdown(rule.help), as: type) + if let message = rule.deprecationMessage { + print("", as: type) + print("Note: \(rule.name) rule is deprecated. \(message)") + print("") + return + } if !rule.options.isEmpty { print("\nOptions:\n", as: type) print(rule.options.compactMap { diff --git a/Sources/OptionsDescriptor.swift b/Sources/OptionsDescriptor.swift index 9ac3c8d2..170abef6 100644 --- a/Sources/OptionsDescriptor.swift +++ b/Sources/OptionsDescriptor.swift @@ -47,20 +47,17 @@ extension FormatOptions { let propertyName: String // internal property; ok to change this let displayName: String let help: String + let deprecationMessage: String? let toOptions: (String, inout FormatOptions) throws -> Void let fromOptions: (FormatOptions) -> String private(set) var type: ArgumentType - var deprecationMessage: String? { - return FormatOptions.Descriptor.deprecatedMessage[argumentName] - } - var isDeprecated: Bool { return deprecationMessage != nil } var isRenamed: Bool { - return deprecationMessage != nil && FormatOptions.Descriptor.all.contains(where: { + return isDeprecated && FormatOptions.Descriptor.all.contains(where: { $0.propertyName == propertyName && $0.argumentName != argumentName }) } @@ -85,6 +82,7 @@ extension FormatOptions { propertyName: String, displayName: String, help: String, + deprecationMessage: String? = nil, keyPath: WritableKeyPath, trueValues: [String], falseValues: [String]) @@ -94,6 +92,7 @@ extension FormatOptions { self.propertyName = propertyName self.displayName = displayName self.help = help + self.deprecationMessage = deprecationMessage type = .binary(true: trueValues, false: falseValues) toOptions = { value, options in switch value.lowercased() { @@ -114,6 +113,7 @@ extension FormatOptions { propertyName: String, displayName: String, help: String, + deprecationMessage: String? = nil, keyPath: WritableKeyPath, fromArgument: @escaping (String) -> T?, toArgument: @escaping (T) -> String) @@ -122,6 +122,7 @@ extension FormatOptions { self.propertyName = propertyName self.displayName = displayName self.help = help + self.deprecationMessage = deprecationMessage type = .text toOptions = { key, options in guard let value = fromArgument(key) else { @@ -138,6 +139,7 @@ extension FormatOptions { propertyName: String, displayName: String, help: String, + deprecationMessage: String? = nil, keyPath: WritableKeyPath, options: DictionaryLiteral) { @@ -147,6 +149,7 @@ extension FormatOptions { propertyName: propertyName, displayName: displayName, help: help, + deprecationMessage: deprecationMessage, keyPath: keyPath, fromArgument: { map[$0.lowercased()] }, toArgument: { value in @@ -166,6 +169,7 @@ extension FormatOptions { propertyName: String, displayName: String, help: String, + deprecationMessage: String? = nil, keyPath: WritableKeyPath) { self.init( @@ -173,6 +177,7 @@ extension FormatOptions { propertyName: propertyName, displayName: displayName, help: help, + deprecationMessage: deprecationMessage, keyPath: keyPath, fromArgument: { Int($0).map { max(0, $0) } }, toArgument: { String($0) } @@ -183,13 +188,15 @@ extension FormatOptions { init(argumentName: String, propertyName: String, displayName: String, - help: String = "", + help: String, + deprecationMessage: String? = nil, keyPath: WritableKeyPath) where T.RawValue == String { self.argumentName = argumentName self.propertyName = propertyName self.displayName = displayName self.help = help + self.deprecationMessage = deprecationMessage type = .text toOptions = { value, options in guard let value = T(rawValue: value) ?? T(rawValue: value.lowercased()) else { @@ -205,7 +212,8 @@ extension FormatOptions { init(argumentName: String, propertyName: String, displayName: String, - help: String = "", + help: String, + deprecationMessage: String? = nil, keyPath: WritableKeyPath, options: [String]) where T.RawValue == String { @@ -214,6 +222,7 @@ extension FormatOptions { propertyName: propertyName, displayName: displayName, help: help, + deprecationMessage: deprecationMessage, keyPath: keyPath ) type = .enum(options) @@ -223,6 +232,7 @@ extension FormatOptions { propertyName: String, displayName: String, help: String, + deprecationMessage: String? = nil, keyPath: WritableKeyPath, validate: @escaping (String) throws -> Void = { _ in }) { @@ -230,6 +240,7 @@ extension FormatOptions { self.propertyName = propertyName self.displayName = displayName self.help = help + self.deprecationMessage = deprecationMessage type = .array toOptions = { value, options in let values = parseCommaDelimitedList(value) @@ -250,6 +261,7 @@ extension FormatOptions { propertyName: String, displayName: String, help: String, + deprecationMessage: String? = nil, keyPath: WritableKeyPath>, validate: @escaping (String) throws -> Void = { _ in }) { @@ -257,6 +269,7 @@ extension FormatOptions { self.propertyName = propertyName self.displayName = displayName self.help = help + self.deprecationMessage = deprecationMessage type = .set toOptions = { value, options in let values = parseCommaDelimitedList(value) @@ -821,22 +834,12 @@ extension FormatOptions.Descriptor { // MARK: - DEPRECATED - static let deprecatedMessage = [ - empty.argumentName: "--empty option is deprecated. Use --voidtype instead.", - indentComments.argumentName: "--comments option is deprecated. Relative indent within multiline comments is now preserved by default.", - insertBlankLines.argumentName: "--insertlines option is deprecated. Use '--enable blankLinesBetweenScopes' or '--enable blankLinesAroundMark' or '--disable blankLinesBetweenScopes' or '--disable blankLinesAroundMark' instead.", - removeBlankLines.argumentName: "--removelines option is deprecated. Use '--enable blankLinesAtStartOfScope' or '--enable blankLinesAtEndOfScope' or '--disable blankLinesAtStartOfScope' or '--disable blankLinesAtEndOfScope' instead.", - hexLiterals.argumentName: "--hexliterals option is deprecated. Use --hexliteralcase instead.", - wrapElements.argumentName: "--wrapelements option is deprecated. Use --wrapcollections instead.", - experimentalRules.argumentName: "--experimentalRules option is deprecated. Use --enable to opt-in to rules individually.", - specifierOrder.argumentName: "--specifierorder option is deprecated. Use --modifierorder instead.", - ] - static let empty = FormatOptions.Descriptor( argumentName: "empty", propertyName: "empty", displayName: "Empty", help: "deprecated", + deprecationMessage: "Use --voidtype instead.", keyPath: \.useVoid, trueValues: ["void"], falseValues: ["tuple", "tuples"] @@ -846,6 +849,7 @@ extension FormatOptions.Descriptor { propertyName: "indentComments", displayName: "Comments", help: "Indenting of comment bodies: \"indent\" (default) or \"ignore\"", + deprecationMessage: "Relative indent within multiline comments is now preserved by default.", keyPath: \.indentComments, trueValues: ["indent", "indented"], falseValues: ["ignore"] @@ -855,6 +859,7 @@ extension FormatOptions.Descriptor { propertyName: "insertBlankLines", displayName: "Insert Lines", help: "deprecated", + deprecationMessage: "Use '--enable blankLinesBetweenScopes' or '--enable blankLinesAroundMark' or '--disable blankLinesBetweenScopes' or '--disable blankLinesAroundMark' instead.", keyPath: \.insertBlankLines, trueValues: ["enabled", "true"], falseValues: ["disabled", "false"] @@ -864,6 +869,7 @@ extension FormatOptions.Descriptor { propertyName: "removeBlankLines", displayName: "Remove Lines", help: "deprecated", + deprecationMessage: "Use '--enable blankLinesAtStartOfScope' or '--enable blankLinesAtEndOfScope' or '--disable blankLinesAtStartOfScope' or '--disable blankLinesAtEndOfScope' instead.", keyPath: \.removeBlankLines, trueValues: ["enabled", "true"], falseValues: ["disabled", "false"] @@ -873,6 +879,7 @@ extension FormatOptions.Descriptor { propertyName: "uppercaseHex", displayName: "hexliterals", help: "deprecated", + deprecationMessage: "Use --hexliteralcase instead.", keyPath: \.uppercaseHex, trueValues: ["uppercase", "upper"], falseValues: ["lowercase", "lower"] @@ -882,6 +889,7 @@ extension FormatOptions.Descriptor { propertyName: "wrapCollections", displayName: "Wrap Elements", help: "deprecated", + deprecationMessage: "Use --wrapcollections instead.", keyPath: \.wrapCollections, options: ["before-first", "after-first", "preserve", "disabled"] ) @@ -890,6 +898,7 @@ extension FormatOptions.Descriptor { propertyName: "experimentalRules", displayName: "Experimental Rules", help: "Experimental rules: \"enabled\" or \"disabled\" (default)", + deprecationMessage: "Use --enable to opt-in to rules individually.", keyPath: \.experimentalRules, trueValues: ["enabled", "true"], falseValues: ["disabled", "false"] @@ -899,6 +908,7 @@ extension FormatOptions.Descriptor { propertyName: "modifierOrder", displayName: "Specifier Order", help: "deprecated", + deprecationMessage: "Use --modifierorder instead.", keyPath: \FormatOptions.modifierOrder, validate: { guard _FormatRules.allModifiers.contains($0) else { diff --git a/Sources/Rules.swift b/Sources/Rules.swift index de12cffb..3c1f8160 100644 --- a/Sources/Rules.swift +++ b/Sources/Rules.swift @@ -37,20 +37,20 @@ public final class FormatRule: Equatable, Comparable { fileprivate(set) var index = 0 let help: String let runOnceOnly: Bool + let disabledByDefault: Bool let orderAfter: [String] let options: [String] let sharedOptions: [String] - - var deprecationMessage: String? { - return FormatRule.deprecatedMessage[name] - } + let deprecationMessage: String? var isDeprecated: Bool { return deprecationMessage != nil } fileprivate init(help: String, + deprecationMessage: String? = nil, runOnceOnly: Bool = false, + disabledByDefault: Bool = false, orderAfter: [String] = [], options: [String] = [], sharedOptions: [String] = [], @@ -59,9 +59,11 @@ public final class FormatRule: Equatable, Comparable { self.fn = fn self.help = help self.runOnceOnly = runOnceOnly + self.disabledByDefault = disabledByDefault || deprecationMessage != nil self.orderAfter = orderAfter self.options = options self.sharedOptions = sharedOptions + self.deprecationMessage = deprecationMessage } public func apply(with formatter: Formatter) { @@ -77,10 +79,6 @@ public final class FormatRule: Equatable, Comparable { public static func < (lhs: FormatRule, rhs: FormatRule) -> Bool { return lhs.index < rhs.index } - - static let deprecatedMessage = [ - "specifiers": "specifiers rule is deprecated. Use modifierOrder instead.", - ] } public let FormatRules = _FormatRules() @@ -124,14 +122,8 @@ private func allRules(except rules: [String]) -> [FormatRule] { } private let _allRules = allRules(except: []) +private let _disabledByDefault = _allRules.filter { $0.disabledByDefault }.map { $0.name } private let _defaultRules = allRules(except: _disabledByDefault) -private let _deprecatedRules = FormatRule.deprecatedMessage.keys -private let _disabledByDefault = _deprecatedRules + [ - "isEmpty", - "multilineEnumCases", - "multilineSwitchCases", - "organizeDeclarations", -] public extension _FormatRules { /// A Dictionary of rules by name @@ -1888,6 +1880,7 @@ public struct _FormatRules { /// Deprecated public let specifiers = FormatRule( help: "Use consistent ordering for member modifiers.", + deprecationMessage: "Use modifierOrder instead.", options: ["modifierorder"] ) { formatter in _ = formatter.options.modifierOrder @@ -3503,7 +3496,7 @@ public struct _FormatRules { /// Formats enum cases declaration into one case per line public let multilineEnumCases = FormatRule( help: "Writes one enum case per line.", - options: [], + disabledByDefault: true, sharedOptions: ["linebreaks"] ) { formatter in formatter.forEach(.keyword("case")) { i, _ in @@ -3531,7 +3524,7 @@ public struct _FormatRules { /// Writes one switch case per line public let multilineSwitchCases = FormatRule( help: "Writes one switch case per line.", - options: [], + disabledByDefault: true, sharedOptions: ["linebreaks", "tabwidth", "indent", "smarttabs"] ) { formatter in formatter.forEach(.endOfScope("case")) { i, _ in @@ -4066,7 +4059,8 @@ public struct _FormatRules { /// Replace count == 0 with isEmpty public let isEmpty = FormatRule( - help: "Prefer `isEmpty` over comparing `count` against zero." + help: "Prefer `isEmpty` over comparing `count` against zero.", + disabledByDefault: true ) { formatter in formatter.forEach(.identifier("count")) { i, _ in guard let dotIndex = formatter.index(of: .nonSpaceOrLinebreak, before: i, if: { @@ -4900,7 +4894,9 @@ public struct _FormatRules { public let organizeDeclarations = FormatRule( help: "Organizes declarations within class, struct, and enum bodies.", runOnceOnly: true, - options: ["categorymark", "beforemarks", "lifecycle", "structthreshold", "classthreshold", "enumthreshold"] + disabledByDefault: true, + options: ["categorymark", "beforemarks", "lifecycle", "structthreshold", + "classthreshold", "enumthreshold"] ) { formatter in /// Categories of declarations within an individual type enum Category: String, CaseIterable { diff --git a/Tests/MetadataTests.swift b/Tests/MetadataTests.swift index 5fe6fdfe..2770f6e4 100644 --- a/Tests/MetadataTests.swift +++ b/Tests/MetadataTests.swift @@ -34,7 +34,7 @@ class MetadataTests: XCTestCase { for rule in FormatRules.all { result += "\n\n## \(rule.name)\n\n\(rule.help)" if let message = rule.deprecationMessage { - result += "\n\n*Note: \(message)*" + result += "\n\n*Note: \(rule.name) rule is deprecated. \(message)*" continue } if !rule.options.isEmpty {