Warning, /frameworks/syntax-highlighting/autotests/input/test.swift is written in an unsupported language. File is not indexed.

0001 //
0002 //  Arguments.swift
0003 //  SwiftFormat
0004 //
0005 //  Created by Nick Lockwood on 07/08/2018.
0006 //  Copyright © 2018 Nick Lockwood.
0007 //
0008 //  Distributed under the permissive MIT license
0009 //  Get the latest version from here:
0010 //
0011 //  https://github.com/nicklockwood/SwiftFormat
0012 //
0013 //  Permission is hereby granted, free of charge, to any person obtaining a copy
0014 //  of this software and associated documentation files (the "Software"), to deal
0015 //  in the Software without restriction, including without limitation the rights
0016 //  to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
0017 //  copies of the Software, and to permit persons to whom the Software is
0018 //  furnished to do so, subject to the following conditions:
0019 //
0020 //  The above copyright notice and this permission notice shall be included in all
0021 //  copies or substantial portions of the Software.
0022 //
0023 //  THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
0024 //  IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
0025 //  FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
0026 //  AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
0027 //  LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
0028 //  OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
0029 //  SOFTWARE.
0030 //
0031 
0032 import Foundation
0033 
0034 extension Options {
0035     static let maxArgumentNameLength = 16
0036 
0037     init(_ args: [String: String], in directory: String) throws {
0038         fileOptions = try fileOptionsFor(args, in: directory)
0039         formatOptions = try formatOptionsFor(args)
0040         let lint = args.keys.contains("lint")
0041         self.lint = lint
0042         rules = try rulesFor(args, lint: lint)
0043     }
0044 
0045     mutating func addArguments(_ args: [String: String], in directory: String) throws {
0046         let oldArguments = argumentsFor(self)
0047         let newArguments = try mergeArguments(args, into: oldArguments)
0048         var newOptions = try Options(newArguments, in: directory)
0049         if let fileInfo = formatOptions?.fileInfo {
0050             newOptions.formatOptions?.fileInfo = fileInfo
0051         }
0052         self = newOptions
0053     }
0054 }
0055 
0056 // Parse a space-delimited string into an array of command-line arguments
0057 // Replicates the behavior implemented by the console when parsing input
0058 func parseArguments(_ argumentString: String, ignoreComments: Bool = true) -> [String] {
0059     var arguments = [""] // Arguments always begin with script path
0060     var characters = String.UnicodeScalarView.SubSequence(argumentString.unicodeScalars)
0061     var string = ""
0062     var escaped = false
0063     var quoted = false
0064     loop: while let char = characters.popFirst() {
0065         switch char {
0066         case "#" where !ignoreComments && !escaped && !quoted:
0067             break loop // comment
0068         case "\\" where !escaped:
0069             escaped = true
0070         case "\"" where !escaped && !quoted:
0071             quoted = true
0072         case "\"" where !escaped && quoted:
0073             quoted = false
0074             fallthrough
0075         case " " where !escaped && !quoted:
0076             if !string.isEmpty {
0077                 arguments.append(string)
0078             }
0079             string.removeAll()
0080         case "\"" where escaped:
0081             escaped = false
0082             string.append("\"")
0083         case _ where escaped && quoted:
0084             string.append("\\")
0085             fallthrough
0086         default:
0087             escaped = false
0088             string.append(Character(char))
0089         }
0090     }
0091     if !string.isEmpty {
0092         arguments.append(string)
0093     }
0094     return arguments
0095 }
0096 
0097 // Parse a flat array of command-line arguments into a dictionary of flags and values
0098 func preprocessArguments(_ args: [String], _ names: [String]) throws -> [String: String] {
0099     var anonymousArgs = 0
0100     var namedArgs: [String: String] = [:]
0101     var name = ""
0102     for arg in args {
0103         if arg.hasPrefix("--") {
0104             // Long argument names
0105             let key = String(arg.unicodeScalars.dropFirst(2))
0106             guard names.contains(key) else {
0107                 guard let match = bestMatches(for: key, in: names).first else {
0108                     throw FormatError.options("Unknown option --\(key)")
0109                 }
0110                 throw FormatError.options("Unknown option --\(key). Did you mean --\(match)?")
0111             }
0112             name = key
0113             namedArgs[name] = namedArgs[name] ?? ""
0114             continue
0115         } else if arg.hasPrefix("-") {
0116             // Short argument names
0117             let flag = String(arg.unicodeScalars.dropFirst())
0118             guard let match = names.first(where: { $0.hasPrefix(flag) }) else {
0119                 throw FormatError.options("Unknown flag -\(flag)")
0120             }
0121             name = match
0122             namedArgs[name] = namedArgs[name] ?? ""
0123             continue
0124         }
0125         if name == "" {
0126             // Argument is anonymous
0127             name = String(anonymousArgs)
0128             anonymousArgs += 1
0129         }
0130         var arg = arg
0131         let hasTrailingComma = arg.hasSuffix(",") && arg != ","
0132         if hasTrailingComma {
0133             arg = String(arg.dropLast())
0134         }
0135         if let existing = namedArgs[name], !existing.isEmpty,
0136            // TODO: find a more general way to represent merge-able options
0137            ["exclude", "unexclude", "disable", "enable", "lintonly", "rules"].contains(name) ||
0138            Descriptors.all.contains(where: {
0139                $0.argumentName == name && $0.isSetType
0140            })
0141         {
0142             namedArgs[name] = existing + "," + arg
0143         } else {
0144             namedArgs[name] = arg
0145         }
0146         if !hasTrailingComma {
0147             name = ""
0148         }
0149     }
0150     return namedArgs
0151 }
0152 
0153 // Find best match for a given string in a list of options
0154 func bestMatches(for query: String, in options: [String]) -> [String] {
0155     let lowercaseQuery = query.lowercased()
0156     // Sort matches by Levenshtein edit distance
0157     return options
0158         .compactMap { option -> (String, distance: Int, commonPrefix: Int)? in
0159             let lowercaseOption = option.lowercased()
0160             let distance = editDistance(lowercaseOption, lowercaseQuery)
0161             let commonPrefix = lowercaseOption.commonPrefix(with: lowercaseQuery)
0162             if commonPrefix.isEmpty, distance > lowercaseQuery.count / 2 {
0163                 return nil
0164             }
0165             return (option, distance, commonPrefix.count)
0166         }
0167         .sorted {
0168             if $0.distance == $1.distance {
0169                 return $0.commonPrefix > $1.commonPrefix
0170             }
0171             return $0.distance < $1.distance
0172         }
0173         .map { $0.0 }
0174 }
0175 
0176 /// The Damerau-Levenshtein edit-distance between two strings
0177 func editDistance(_ lhs: String, _ rhs: String) -> Int {
0178     let lhs = Array(lhs)
0179     let rhs = Array(rhs)
0180     var dist = [[Int]]()
0181     for i in 0 ... lhs.count {
0182         dist.append([i])
0183     }
0184     for j in 1 ... rhs.count {
0185         dist[0].append(j)
0186     }
0187     for i in 1 ... lhs.count {
0188         for j in 1 ... rhs.count {
0189             if lhs[i - 1] == rhs[j - 1] {
0190                 dist[i].append(dist[i - 1][j - 1])
0191             } else {
0192                 dist[i].append(min(dist[i - 1][j] + 1,
0193                                    dist[i][j - 1] + 1,
0194                                    dist[i - 1][j - 1] + 1))
0195             }
0196             if i > 1, j > 1, lhs[i - 1] == rhs[j - 2], lhs[i - 2] == rhs[j - 1] {
0197                 dist[i][j] = min(dist[i][j], dist[i - 2][j - 2] + 1)
0198             }
0199         }
0200     }
0201     return dist[lhs.count][rhs.count]
0202 }
0203 
0204 // Parse a comma-delimited list of items
0205 func parseCommaDelimitedList(_ string: String) -> [String] {
0206     return string.components(separatedBy: ",").compactMap {
0207         let item = $0.trimmingCharacters(in: .whitespacesAndNewlines)
0208         return item.isEmpty ? nil : item
0209     }
0210 }
0211 
0212 // Parse a comma-delimited string into an array of rules
0213 let allRules = Set(FormatRules.byName.keys)
0214 func parseRules(_ rules: String) throws -> [String] {
0215     return try parseCommaDelimitedList(rules).map { proposedName in
0216         if let name = allRules.first(where: {
0217             $0.lowercased() == proposedName.lowercased()
0218         }) {
0219             return name
0220         }
0221         if Descriptors.all.contains(where: {
0222             $0.argumentName == proposedName
0223         }) {
0224             for rule in FormatRules.all where rule.options.contains(proposedName) {
0225                 throw FormatError.options(
0226                     "'\(proposedName)' is not a formatting rule. Did you mean '\(rule.name)'?"
0227                 )
0228             }
0229             throw FormatError.options("'\(proposedName)' is not a formatting rule")
0230         }
0231         guard let match = bestMatches(for: proposedName, in: Array(allRules)).first else {
0232             throw FormatError.options("Unknown rule '\(proposedName)'")
0233         }
0234         throw FormatError.options("Unknown rule '\(proposedName)'. Did you mean '\(match)'?")
0235     }
0236 }
0237 
0238 // Parse single file path, disallowing globs or commas
0239 func parsePath(_ path: String, for argument: String, in directory: String) throws -> URL {
0240     let expandedPath = expandPath(path, in: directory)
0241     if !FileManager.default.fileExists(atPath: expandedPath.path) {
0242         if path.contains(",") {
0243             throw FormatError.options("\(argument) argument does not support multiple paths")
0244         }
0245         if pathContainsGlobSyntax(path) {
0246             throw FormatError.options("\(argument) path cannot contain wildcards")
0247         }
0248     }
0249     return expandedPath
0250 }
0251 
0252 // Parse one or more comma-delimited file paths, expanding globs as required
0253 func parsePaths(_ paths: String, in directory: String) throws -> [URL] {
0254     return try matchGlobs(expandGlobs(paths, in: directory), in: directory)
0255 }
0256 
0257 // Merge two dictionaries of arguments
0258 func mergeArguments(_ args: [String: String], into config: [String: String]) throws -> [String: String] {
0259     var input = config
0260     var output = args
0261     // Merge excluded urls
0262     if let exclude = output["exclude"].map(parseCommaDelimitedList),
0263        var excluded = input["exclude"].map({ Set(parseCommaDelimitedList($0)) })
0264     {
0265         excluded.formUnion(exclude)
0266         output["exclude"] = Array(excluded).sorted().joined(separator: ",")
0267     }
0268     // Merge unexcluded urls
0269     if let unexclude = output["unexclude"].map(parseCommaDelimitedList),
0270        var unexcluded = input["unexclude"].map({ Set(parseCommaDelimitedList($0)) })
0271     {
0272         unexcluded.formUnion(unexclude)
0273         output["unexclude"] = Array(unexcluded).sorted().joined(separator: ",")
0274     }
0275     // Merge rules
0276     if let rules = try output["rules"].map(parseRules) {
0277         if rules.isEmpty {
0278             output["rules"] = nil
0279         } else {
0280             input["rules"] = nil
0281             input["enable"] = nil
0282             input["disable"] = nil
0283             input["lintonly"] = nil
0284         }
0285     } else {
0286         if let _disable = try output["disable"].map(parseRules) {
0287             if let rules = try input["rules"].map(parseRules) {
0288                 input["rules"] = Set(rules).subtracting(_disable).sorted().joined(separator: ",")
0289             }
0290             if let enable = try input["enable"].map(parseRules) {
0291                 input["enable"] = Set(enable).subtracting(_disable).sorted().joined(separator: ",")
0292             }
0293             if let lintonly = try input["lintonly"].map(parseRules) {
0294                 input["lintonly"] = Set(lintonly).subtracting(_disable).sorted().joined(separator: ",")
0295             }
0296             if let disable = try input["disable"].map(parseRules) {
0297                 input["disable"] = Set(disable).union(_disable).sorted().joined(separator: ",")
0298                 output["disable"] = nil
0299             }
0300         }
0301         if let _enable = try output["enable"].map(parseRules) {
0302             if let enable = try input["enable"].map(parseRules) {
0303                 input["enable"] = Set(enable).union(_enable).sorted().joined(separator: ",")
0304                 output["enable"] = nil
0305             }
0306             if let lintonly = try input["lintonly"].map(parseRules) {
0307                 input["lintonly"] = Set(lintonly).subtracting(_enable).sorted().joined(separator: ",")
0308             }
0309             if let disable = try input["disable"].map(parseRules) {
0310                 input["disable"] = Set(disable).subtracting(_enable).sorted().joined(separator: ",")
0311             }
0312         }
0313         if let _lintonly = try output["lintonly"].map(parseRules) {
0314             if let lintonly = try input["lintonly"].map(parseRules) {
0315                 input["lintonly"] = Set(lintonly).union(_lintonly).sorted().joined(separator: ",")
0316                 output["lintonly"] = nil
0317             }
0318         }
0319     }
0320     // Merge other arguments
0321     for (key, inValue) in input {
0322         guard let outValue = output[key] else {
0323             output[key] = inValue
0324             continue
0325         }
0326         if Descriptors.all.contains(where: { $0.argumentName == key && $0.isSetType }) {
0327             let inOptions = parseCommaDelimitedList(inValue)
0328             let outOptions = parseCommaDelimitedList(outValue)
0329             output[key] = Set(inOptions).union(outOptions).sorted().joined(separator: ",")
0330         }
0331     }
0332     return output
0333 }
0334 
0335 // Parse a configuration file into a dictionary of arguments
0336 public func parseConfigFile(_ data: Data) throws -> [String: String] {
0337     guard let input = String(data: data, encoding: .utf8) else {
0338         throw FormatError.reading("Unable to read data for configuration file")
0339     }
0340     let lines = try cumulate(successiveLines: input.components(separatedBy: .newlines))
0341     let arguments = try lines.flatMap { line -> [String] in
0342         // TODO: parseArguments isn't a perfect fit here - should we use a different approach?
0343         let line = line.replacingOccurrences(of: "\\n", with: "\n")
0344         let parts = parseArguments(line, ignoreComments: false).dropFirst().map {
0345             $0.replacingOccurrences(of: "\n", with: "\\n")
0346         }
0347         guard let key = parts.first else {
0348             return []
0349         }
0350         if !key.hasPrefix("-") {
0351             throw FormatError.options("Unknown option '\(key)' in configuration file")
0352         }
0353         return [key, parts.dropFirst().joined(separator: " ")]
0354     }
0355     do {
0356         return try preprocessArguments(arguments, optionsArguments)
0357     } catch let FormatError.options(message) {
0358         throw FormatError.options("\(message) in configuration file")
0359     }
0360 }
0361 
0362 private func cumulate(successiveLines: [String]) throws -> [String] {
0363     var cumulatedLines = [String]()
0364     var iterator = successiveLines.makeIterator()
0365     while let currentLine = iterator.next() {
0366         var cumulatedLine = effectiveContent(of: currentLine)
0367         while cumulatedLine.hasSuffix("\\") {
0368             guard let nextLine = iterator.next() else {
0369                 throw FormatError.reading("Configuration file ends with an illegal line continuation character '\'")
0370             }
0371             if !nextLine.trimmingCharacters(in: .whitespaces).starts(with: "#") {
0372                 cumulatedLine = cumulatedLine.dropLast() + effectiveContent(of: nextLine)
0373             }
0374         }
0375         cumulatedLines.append(String(cumulatedLine))
0376     }
0377     return cumulatedLines
0378 }
0379 
0380 private func effectiveContent(of line: String) -> String {
0381     return line
0382         .prefix { $0 != "#" }
0383         .trimmingCharacters(in: .whitespaces)
0384 }
0385 
0386 // Serialize a set of options into either an arguments string or a file
0387 func serialize(options: Options,
0388                swiftVersion: Version = .undefined,
0389                excludingDefaults: Bool = false,
0390                separator: String = "\n") -> String
0391 {
0392     var arguments = [[String: String]]()
0393     if let fileOptions = options.fileOptions {
0394         arguments.append(argumentsFor(
0395             Options(fileOptions: fileOptions),
0396             excludingDefaults: excludingDefaults
0397         ))
0398     }
0399     if let formatOptions = options.formatOptions {
0400         arguments.append(argumentsFor(
0401             Options(formatOptions: formatOptions),
0402             excludingDefaults: excludingDefaults
0403         ))
0404     } else if swiftVersion != .undefined {
0405         let descriptor = Descriptors.swiftVersion
0406         arguments.append([descriptor.argumentName: swiftVersion.rawValue])
0407     }
0408     if let rules = options.rules {
0409         arguments.append(argumentsFor(
0410             Options(rules: rules),
0411             excludingDefaults: excludingDefaults
0412         ))
0413     }
0414     return arguments
0415         .map { serialize(arguments: $0, separator: separator) }
0416         .filter { !$0.isEmpty }
0417         .joined(separator: separator)
0418 }
0419 
0420 // Serialize arguments
0421 func serialize(arguments: [String: String],
0422                separator: String = "\n") -> String
0423 {
0424     return arguments.map {
0425         var value = $1
0426         if value.contains(" ") {
0427             value = "\"\(value.replacingOccurrences(of: "\"", with: "\\\""))\""
0428         }
0429         return "--\($0) \(value)"
0430     }.sorted().joined(separator: separator)
0431 }
0432 
0433 // Get command line arguments from options
0434 func argumentsFor(_ options: Options, excludingDefaults: Bool = false) -> [String: String] {
0435     var args = [String: String]()
0436     if let fileOptions = options.fileOptions {
0437         var arguments = Set(fileArguments)
0438         do {
0439             if !excludingDefaults || fileOptions.followSymlinks != FileOptions.default.followSymlinks {
0440                 args["symlinks"] = fileOptions.followSymlinks ? "follow" : "ignore"
0441             }
0442             arguments.remove("symlinks")
0443         }
0444         do {
0445             if !fileOptions.excludedGlobs.isEmpty {
0446                 // TODO: find a better alternative to stringifying url
0447                 args["exclude"] = fileOptions.excludedGlobs.map { $0.description }.sorted().joined(separator: ",")
0448             }
0449             arguments.remove("exclude")
0450         }
0451         do {
0452             if !fileOptions.unexcludedGlobs.isEmpty {
0453                 // TODO: find a better alternative to stringifying url
0454                 args["unexclude"] = fileOptions.unexcludedGlobs.map { $0.description }.sorted().joined(separator: ",")
0455             }
0456             arguments.remove("unexclude")
0457         }
0458         do {
0459             if !excludingDefaults || fileOptions.minVersion != FileOptions.default.minVersion {
0460                 args["minversion"] = fileOptions.minVersion.description
0461             }
0462             arguments.remove("minversion")
0463         }
0464         assert(arguments.isEmpty)
0465     }
0466     if let formatOptions = options.formatOptions {
0467         for descriptor in Descriptors.all where !descriptor.isRenamed {
0468             let value = descriptor.fromOptions(formatOptions)
0469             guard value != descriptor.fromOptions(.default) ||
0470                 (!excludingDefaults && !descriptor.isDeprecated)
0471             else {
0472                 continue
0473             }
0474             // Special case for swiftVersion
0475             // TODO: find a better solution for this
0476             if descriptor.argumentName == Descriptors.swiftVersion.argumentName,
0477                value == Version.undefined.rawValue
0478             {
0479                 continue
0480             }
0481             args[descriptor.argumentName] = value
0482         }
0483         // Special case for wrapParameters
0484         let argumentName = Descriptors.wrapParameters.argumentName
0485         if args[argumentName] == WrapMode.default.rawValue {
0486             args[argumentName] = args[Descriptors.wrapArguments.argumentName]
0487         }
0488     }
0489     if options.lint {
0490         args["lint"] = ""
0491     }
0492     if let rules = options.rules {
0493         let defaultRules = allRules.subtracting(FormatRules.disabledByDefault)
0494 
0495         let enabled = rules.subtracting(defaultRules)
0496         if !enabled.isEmpty {
0497             args["enable"] = enabled.sorted().joined(separator: ",")
0498         }
0499 
0500         let disabled = defaultRules.subtracting(rules)
0501         if !disabled.isEmpty {
0502             args["disable"] = disabled.sorted().joined(separator: ",")
0503         }
0504     }
0505     return args
0506 }
0507 
0508 private func processOption(_ key: String,
0509                            in args: [String: String],
0510                            from: inout Set<String>,
0511                            handler: (String) throws -> Void) throws
0512 {
0513     precondition(optionsArguments.contains(key), "\(key) not in optionsArguments")
0514     var arguments = from
0515     arguments.remove(key)
0516     from = arguments
0517     guard let value = args[key] else {
0518         return
0519     }
0520     do {
0521         try handler(value)
0522     } catch {
0523         guard !value.isEmpty else {
0524             throw FormatError.options("--\(key) option expects a value")
0525         }
0526         if case var FormatError.options(string) = error, !string.isEmpty {
0527             if !string.contains(key) {
0528                 string += " in --\(key)"
0529             }
0530             throw FormatError.options(string)
0531         }
0532         throw FormatError.options("Unsupported --\(key) value '\(value)'")
0533     }
0534 }
0535 
0536 // Parse rule names from arguments
0537 public func rulesFor(_ args: [String: String], lint: Bool) throws -> Set<String> {
0538     var rules = allRules
0539     rules = try args["rules"].map {
0540         try Set(parseRules($0))
0541     } ?? rules.subtracting(FormatRules.disabledByDefault)
0542     try args["enable"].map {
0543         try rules.formUnion(parseRules($0))
0544     }
0545     try args["disable"].map {
0546         try rules.subtract(parseRules($0))
0547     }
0548     try args["lintonly"].map { rulesString in
0549         if lint {
0550             try rules.formUnion(parseRules(rulesString))
0551         } else {
0552             try rules.subtract(parseRules(rulesString))
0553         }
0554     }
0555     return rules
0556 }
0557 
0558 // Parse FileOptions from arguments
0559 func fileOptionsFor(_ args: [String: String], in directory: String) throws -> FileOptions? {
0560     var options = FileOptions()
0561     var arguments = Set(fileArguments)
0562 
0563     var containsFileOption = false
0564     try processOption("symlinks", in: args, from: &arguments) {
0565         containsFileOption = true
0566         switch $0.lowercased() {
0567         case "follow":
0568             options.followSymlinks = true
0569         case "ignore":
0570             options.followSymlinks = false
0571         default:
0572             throw FormatError.options("")
0573         }
0574     }
0575     try processOption("exclude", in: args, from: &arguments) {
0576         containsFileOption = true
0577         options.excludedGlobs += expandGlobs($0, in: directory)
0578     }
0579     try processOption("unexclude", in: args, from: &arguments) {
0580         containsFileOption = true
0581         options.unexcludedGlobs += expandGlobs($0, in: directory)
0582     }
0583     try processOption("minversion", in: args, from: &arguments) {
0584         containsFileOption = true
0585         guard let minVersion = Version(rawValue: $0) else {
0586             throw FormatError.options("Unsupported --minversion value '\($0)'")
0587         }
0588         guard minVersion <= Version(stringLiteral: swiftFormatVersion) else {
0589             throw FormatError.options("Project specifies SwiftFormat --minversion of \(minVersion)")
0590         }
0591         options.minVersion = minVersion
0592     }
0593     assert(arguments.isEmpty, "\(arguments.joined(separator: ","))")
0594     return containsFileOption ? options : nil
0595 }
0596 
0597 // Parse FormatOptions from arguments
0598 // Returns nil if the arguments dictionary does not contain any formatting arguments
0599 public func formatOptionsFor(_ args: [String: String]) throws -> FormatOptions? {
0600     var options = FormatOptions.default
0601     var arguments = Set(formattingArguments)
0602 
0603     var containsFormatOption = false
0604     for option in Descriptors.all {
0605         try processOption(option.argumentName, in: args, from: &arguments) {
0606             containsFormatOption = true
0607             try option.toOptions($0, &options)
0608         }
0609     }
0610     assert(arguments.isEmpty, "\(arguments.joined(separator: ","))")
0611     return containsFormatOption ? options : nil
0612 }
0613 
0614 // Get deprecation warnings from a set of arguments
0615 func warningsForArguments(_ args: [String: String]) -> [String] {
0616     var warnings = [String]()
0617     for option in Descriptors.all {
0618         if args[option.argumentName] != nil, let message = option.deprecationMessage {
0619             warnings.append("--\(option.argumentName) option is deprecated. \(message)")
0620         }
0621     }
0622     for name in Set(rulesArguments.flatMap { (try? args[$0].map(parseRules) ?? []) ?? [] }) {
0623         if let message = FormatRules.byName[name]?.deprecationMessage {
0624             warnings.append("\(name) rule is deprecated. \(message)")
0625         }
0626     }
0627     if let rules = try? rulesFor(args, lint: true) {
0628         for arg in args.keys where formattingArguments.contains(arg) {
0629             if !rules.contains(where: {
0630                 guard let rule = FormatRules.byName[$0] else {
0631                     return false
0632                 }
0633                 return rule.options.contains(arg) || rule.sharedOptions.contains(arg)
0634             }) {
0635                 let expected = FormatRules.all.first(where: {
0636                     $0.options.contains(arg)
0637                 })?.name ?? "associated"
0638                 warnings.append("--\(arg) option has no effect when \(expected) rule is disabled")
0639             }
0640         }
0641     }
0642     return warnings
0643 }
0644 
0645 let fileArguments = [
0646     "symlinks",
0647     "exclude",
0648     "unexclude",
0649     "minversion",
0650 ]
0651 
0652 let rulesArguments = [
0653     "disable",
0654     "enable",
0655     "lintonly",
0656     "rules",
0657 ]
0658 
0659 let formattingArguments = Descriptors.formatting.map { $0.argumentName }
0660 let internalArguments = Descriptors.internal.map { $0.argumentName }
0661 let optionsArguments = fileArguments + rulesArguments + formattingArguments + internalArguments
0662 
0663 let commandLineArguments = [
0664     // Input options
0665     "filelist",
0666     "stdinpath",
0667     "config",
0668     "inferoptions",
0669     "linerange",
0670     "output",
0671     "cache",
0672     "dryrun",
0673     "lint",
0674     "lenient",
0675     "verbose",
0676     "quiet",
0677     "report",
0678     // Misc
0679     "help",
0680     "version",
0681     "options",
0682     "ruleinfo",
0683 ] + optionsArguments
0684 
0685 let deprecatedArguments = Descriptors.all.compactMap {
0686     $0.isDeprecated ? $0.argumentName : nil
0687 }