Warning, /frameworks/syntax-highlighting/autotests/folding/test.swift.fold 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 <beginfold id='1'>{</beginfold id='1'>
0035     static let maxArgumentNameLength = 16
0036 
0037     init(_ args: [String: String], in directory: String) throws <beginfold id='1'>{</beginfold id='1'>
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     <endfold id='1'>}</endfold id='1'>
0044 
0045     mutating func addArguments(_ args: [String: String], in directory: String) throws <beginfold id='1'>{</beginfold id='1'>
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 <beginfold id='1'>{</beginfold id='1'>
0050             newOptions.formatOptions?.fileInfo = fileInfo
0051         <endfold id='1'>}</endfold id='1'>
0052         self = newOptions
0053     <endfold id='1'>}</endfold id='1'>
0054 <endfold id='1'>}</endfold id='1'>
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] <beginfold id='1'>{</beginfold id='1'>
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() <beginfold id='1'>{</beginfold id='1'>
0065         switch char <beginfold id='1'>{</beginfold id='1'>
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 <beginfold id='1'>{</beginfold id='1'>
0077                 arguments.append(string)
0078             <endfold id='1'>}</endfold id='1'>
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         <endfold id='1'>}</endfold id='1'>
0090     <endfold id='1'>}</endfold id='1'>
0091     if !string.isEmpty <beginfold id='1'>{</beginfold id='1'>
0092         arguments.append(string)
0093     <endfold id='1'>}</endfold id='1'>
0094     return arguments
0095 <endfold id='1'>}</endfold id='1'>
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] <beginfold id='1'>{</beginfold id='1'>
0099     var anonymousArgs = 0
0100     var namedArgs: [String: String] = [:]
0101     var name = ""
0102     for arg in args <beginfold id='1'>{</beginfold id='1'>
0103         if arg.hasPrefix("--") <beginfold id='1'>{</beginfold id='1'>
0104             // Long argument names
0105             let key = String(arg.unicodeScalars.dropFirst(2))
0106             guard names.contains(key) else <beginfold id='1'>{</beginfold id='1'>
0107                 guard let match = bestMatches(for: key, in: names).first else <beginfold id='1'>{</beginfold id='1'>
0108                     throw FormatError.options("Unknown option --\(key)")
0109                 <endfold id='1'>}</endfold id='1'>
0110                 throw FormatError.options("Unknown option --\(key). Did you mean --\(match)?")
0111             <endfold id='1'>}</endfold id='1'>
0112             name = key
0113             namedArgs[name] = namedArgs[name] ?? ""
0114             continue
0115         <endfold id='1'>}</endfold id='1'> else if arg.hasPrefix("-") <beginfold id='1'>{</beginfold id='1'>
0116             // Short argument names
0117             let flag = String(arg.unicodeScalars.dropFirst())
0118             guard let match = names.first(where: <beginfold id='1'>{</beginfold id='1'> $0.hasPrefix(flag) <endfold id='1'>}</endfold id='1'>) else <beginfold id='1'>{</beginfold id='1'>
0119                 throw FormatError.options("Unknown flag -\(flag)")
0120             <endfold id='1'>}</endfold id='1'>
0121             name = match
0122             namedArgs[name] = namedArgs[name] ?? ""
0123             continue
0124         <endfold id='1'>}</endfold id='1'>
0125         if name == "" <beginfold id='1'>{</beginfold id='1'>
0126             // Argument is anonymous
0127             name = String(anonymousArgs)
0128             anonymousArgs += 1
0129         <endfold id='1'>}</endfold id='1'>
0130         var arg = arg
0131         let hasTrailingComma = arg.hasSuffix(",") && arg != ","
0132         if hasTrailingComma <beginfold id='1'>{</beginfold id='1'>
0133             arg = String(arg.dropLast())
0134         <endfold id='1'>}</endfold id='1'>
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: <beginfold id='1'>{</beginfold id='1'>
0139                $0.argumentName == name && $0.isSetType
0140            <endfold id='1'>}</endfold id='1'>)
0141         <beginfold id='1'>{</beginfold id='1'>
0142             namedArgs[name] = existing + "," + arg
0143         <endfold id='1'>}</endfold id='1'> else <beginfold id='1'>{</beginfold id='1'>
0144             namedArgs[name] = arg
0145         <endfold id='1'>}</endfold id='1'>
0146         if !hasTrailingComma <beginfold id='1'>{</beginfold id='1'>
0147             name = ""
0148         <endfold id='1'>}</endfold id='1'>
0149     <endfold id='1'>}</endfold id='1'>
0150     return namedArgs
0151 <endfold id='1'>}</endfold id='1'>
0152 
0153 // Find best match for a given string in a list of options
0154 func bestMatches(for query: String, in options: [String]) -> [String] <beginfold id='1'>{</beginfold id='1'>
0155     let lowercaseQuery = query.lowercased()
0156     // Sort matches by Levenshtein edit distance
0157     return options
0158         .compactMap <beginfold id='1'>{</beginfold id='1'> 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 <beginfold id='1'>{</beginfold id='1'>
0163                 return nil
0164             <endfold id='1'>}</endfold id='1'>
0165             return (option, distance, commonPrefix.count)
0166         <endfold id='1'>}</endfold id='1'>
0167         .sorted <beginfold id='1'>{</beginfold id='1'>
0168             if $0.distance == $1.distance <beginfold id='1'>{</beginfold id='1'>
0169                 return $0.commonPrefix > $1.commonPrefix
0170             <endfold id='1'>}</endfold id='1'>
0171             return $0.distance < $1.distance
0172         <endfold id='1'>}</endfold id='1'>
0173         .map <beginfold id='1'>{</beginfold id='1'> $0.0 <endfold id='1'>}</endfold id='1'>
0174 <endfold id='1'>}</endfold id='1'>
0175 
0176 /// The Damerau-Levenshtein edit-distance between two strings
0177 func editDistance(_ lhs: String, _ rhs: String) -> Int <beginfold id='1'>{</beginfold id='1'>
0178     let lhs = Array(lhs)
0179     let rhs = Array(rhs)
0180     var dist = [[Int]]()
0181     for i in 0 ... lhs.count <beginfold id='1'>{</beginfold id='1'>
0182         dist.append([i])
0183     <endfold id='1'>}</endfold id='1'>
0184     for j in 1 ... rhs.count <beginfold id='1'>{</beginfold id='1'>
0185         dist[0].append(j)
0186     <endfold id='1'>}</endfold id='1'>
0187     for i in 1 ... lhs.count <beginfold id='1'>{</beginfold id='1'>
0188         for j in 1 ... rhs.count <beginfold id='1'>{</beginfold id='1'>
0189             if lhs[i - 1] == rhs[j - 1] <beginfold id='1'>{</beginfold id='1'>
0190                 dist[i].append(dist[i - 1][j - 1])
0191             <endfold id='1'>}</endfold id='1'> else <beginfold id='1'>{</beginfold id='1'>
0192                 dist[i].append(min(dist[i - 1][j] + 1,
0193                                    dist[i][j - 1] + 1,
0194                                    dist[i - 1][j - 1] + 1))
0195             <endfold id='1'>}</endfold id='1'>
0196             if i > 1, j > 1, lhs[i - 1] == rhs[j - 2], lhs[i - 2] == rhs[j - 1] <beginfold id='1'>{</beginfold id='1'>
0197                 dist[i][j] = min(dist[i][j], dist[i - 2][j - 2] + 1)
0198             <endfold id='1'>}</endfold id='1'>
0199         <endfold id='1'>}</endfold id='1'>
0200     <endfold id='1'>}</endfold id='1'>
0201     return dist[lhs.count][rhs.count]
0202 <endfold id='1'>}</endfold id='1'>
0203 
0204 // Parse a comma-delimited list of items
0205 func parseCommaDelimitedList(_ string: String) -> [String] <beginfold id='1'>{</beginfold id='1'>
0206     return string.components(separatedBy: ",").compactMap <beginfold id='1'>{</beginfold id='1'>
0207         let item = $0.trimmingCharacters(in: .whitespacesAndNewlines)
0208         return item.isEmpty ? nil : item
0209     <endfold id='1'>}</endfold id='1'>
0210 <endfold id='1'>}</endfold id='1'>
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] <beginfold id='1'>{</beginfold id='1'>
0215     return try parseCommaDelimitedList(rules).map <beginfold id='1'>{</beginfold id='1'> proposedName in
0216         if let name = allRules.first(where: <beginfold id='1'>{</beginfold id='1'>
0217             $0.lowercased() == proposedName.lowercased()
0218         <endfold id='1'>}</endfold id='1'>) <beginfold id='1'>{</beginfold id='1'>
0219             return name
0220         <endfold id='1'>}</endfold id='1'>
0221         if Descriptors.all.contains(where: <beginfold id='1'>{</beginfold id='1'>
0222             $0.argumentName == proposedName
0223         <endfold id='1'>}</endfold id='1'>) <beginfold id='1'>{</beginfold id='1'>
0224             for rule in FormatRules.all where rule.options.contains(proposedName) <beginfold id='1'>{</beginfold id='1'>
0225                 throw FormatError.options(
0226                     "'\(proposedName)' is not a formatting rule. Did you mean '\(rule.name)'?"
0227                 )
0228             <endfold id='1'>}</endfold id='1'>
0229             throw FormatError.options("'\(proposedName)' is not a formatting rule")
0230         <endfold id='1'>}</endfold id='1'>
0231         guard let match = bestMatches(for: proposedName, in: Array(allRules)).first else <beginfold id='1'>{</beginfold id='1'>
0232             throw FormatError.options("Unknown rule '\(proposedName)'")
0233         <endfold id='1'>}</endfold id='1'>
0234         throw FormatError.options("Unknown rule '\(proposedName)'. Did you mean '\(match)'?")
0235     <endfold id='1'>}</endfold id='1'>
0236 <endfold id='1'>}</endfold id='1'>
0237 
0238 // Parse single file path, disallowing globs or commas
0239 func parsePath(_ path: String, for argument: String, in directory: String) throws -> URL <beginfold id='1'>{</beginfold id='1'>
0240     let expandedPath = expandPath(path, in: directory)
0241     if !FileManager.default.fileExists(atPath: expandedPath.path) <beginfold id='1'>{</beginfold id='1'>
0242         if path.contains(",") <beginfold id='1'>{</beginfold id='1'>
0243             throw FormatError.options("\(argument) argument does not support multiple paths")
0244         <endfold id='1'>}</endfold id='1'>
0245         if pathContainsGlobSyntax(path) <beginfold id='1'>{</beginfold id='1'>
0246             throw FormatError.options("\(argument) path cannot contain wildcards")
0247         <endfold id='1'>}</endfold id='1'>
0248     <endfold id='1'>}</endfold id='1'>
0249     return expandedPath
0250 <endfold id='1'>}</endfold id='1'>
0251 
0252 // Parse one or more comma-delimited file paths, expanding globs as required
0253 func parsePaths(_ paths: String, in directory: String) throws -> [URL] <beginfold id='1'>{</beginfold id='1'>
0254     return try matchGlobs(expandGlobs(paths, in: directory), in: directory)
0255 <endfold id='1'>}</endfold id='1'>
0256 
0257 // Merge two dictionaries of arguments
0258 func mergeArguments(_ args: [String: String], into config: [String: String]) throws -> [String: String] <beginfold id='1'>{</beginfold id='1'>
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(<beginfold id='1'>{</beginfold id='1'> Set(parseCommaDelimitedList($0)) <endfold id='1'>}</endfold id='1'>)
0264     <beginfold id='1'>{</beginfold id='1'>
0265         excluded.formUnion(exclude)
0266         output["exclude"] = Array(excluded).sorted().joined(separator: ",")
0267     <endfold id='1'>}</endfold id='1'>
0268     // Merge unexcluded urls
0269     if let unexclude = output["unexclude"].map(parseCommaDelimitedList),
0270        var unexcluded = input["unexclude"].map(<beginfold id='1'>{</beginfold id='1'> Set(parseCommaDelimitedList($0)) <endfold id='1'>}</endfold id='1'>)
0271     <beginfold id='1'>{</beginfold id='1'>
0272         unexcluded.formUnion(unexclude)
0273         output["unexclude"] = Array(unexcluded).sorted().joined(separator: ",")
0274     <endfold id='1'>}</endfold id='1'>
0275     // Merge rules
0276     if let rules = try output["rules"].map(parseRules) <beginfold id='1'>{</beginfold id='1'>
0277         if rules.isEmpty <beginfold id='1'>{</beginfold id='1'>
0278             output["rules"] = nil
0279         <endfold id='1'>}</endfold id='1'> else <beginfold id='1'>{</beginfold id='1'>
0280             input["rules"] = nil
0281             input["enable"] = nil
0282             input["disable"] = nil
0283             input["lintonly"] = nil
0284         <endfold id='1'>}</endfold id='1'>
0285     <endfold id='1'>}</endfold id='1'> else <beginfold id='1'>{</beginfold id='1'>
0286         if let _disable = try output["disable"].map(parseRules) <beginfold id='1'>{</beginfold id='1'>
0287             if let rules = try input["rules"].map(parseRules) <beginfold id='1'>{</beginfold id='1'>
0288                 input["rules"] = Set(rules).subtracting(_disable).sorted().joined(separator: ",")
0289             <endfold id='1'>}</endfold id='1'>
0290             if let enable = try input["enable"].map(parseRules) <beginfold id='1'>{</beginfold id='1'>
0291                 input["enable"] = Set(enable).subtracting(_disable).sorted().joined(separator: ",")
0292             <endfold id='1'>}</endfold id='1'>
0293             if let lintonly = try input["lintonly"].map(parseRules) <beginfold id='1'>{</beginfold id='1'>
0294                 input["lintonly"] = Set(lintonly).subtracting(_disable).sorted().joined(separator: ",")
0295             <endfold id='1'>}</endfold id='1'>
0296             if let disable = try input["disable"].map(parseRules) <beginfold id='1'>{</beginfold id='1'>
0297                 input["disable"] = Set(disable).union(_disable).sorted().joined(separator: ",")
0298                 output["disable"] = nil
0299             <endfold id='1'>}</endfold id='1'>
0300         <endfold id='1'>}</endfold id='1'>
0301         if let _enable = try output["enable"].map(parseRules) <beginfold id='1'>{</beginfold id='1'>
0302             if let enable = try input["enable"].map(parseRules) <beginfold id='1'>{</beginfold id='1'>
0303                 input["enable"] = Set(enable).union(_enable).sorted().joined(separator: ",")
0304                 output["enable"] = nil
0305             <endfold id='1'>}</endfold id='1'>
0306             if let lintonly = try input["lintonly"].map(parseRules) <beginfold id='1'>{</beginfold id='1'>
0307                 input["lintonly"] = Set(lintonly).subtracting(_enable).sorted().joined(separator: ",")
0308             <endfold id='1'>}</endfold id='1'>
0309             if let disable = try input["disable"].map(parseRules) <beginfold id='1'>{</beginfold id='1'>
0310                 input["disable"] = Set(disable).subtracting(_enable).sorted().joined(separator: ",")
0311             <endfold id='1'>}</endfold id='1'>
0312         <endfold id='1'>}</endfold id='1'>
0313         if let _lintonly = try output["lintonly"].map(parseRules) <beginfold id='1'>{</beginfold id='1'>
0314             if let lintonly = try input["lintonly"].map(parseRules) <beginfold id='1'>{</beginfold id='1'>
0315                 input["lintonly"] = Set(lintonly).union(_lintonly).sorted().joined(separator: ",")
0316                 output["lintonly"] = nil
0317             <endfold id='1'>}</endfold id='1'>
0318         <endfold id='1'>}</endfold id='1'>
0319     <endfold id='1'>}</endfold id='1'>
0320     // Merge other arguments
0321     for (key, inValue) in input <beginfold id='1'>{</beginfold id='1'>
0322         guard let outValue = output[key] else <beginfold id='1'>{</beginfold id='1'>
0323             output[key] = inValue
0324             continue
0325         <endfold id='1'>}</endfold id='1'>
0326         if Descriptors.all.contains(where: <beginfold id='1'>{</beginfold id='1'> $0.argumentName == key && $0.isSetType <endfold id='1'>}</endfold id='1'>) <beginfold id='1'>{</beginfold id='1'>
0327             let inOptions = parseCommaDelimitedList(inValue)
0328             let outOptions = parseCommaDelimitedList(outValue)
0329             output[key] = Set(inOptions).union(outOptions).sorted().joined(separator: ",")
0330         <endfold id='1'>}</endfold id='1'>
0331     <endfold id='1'>}</endfold id='1'>
0332     return output
0333 <endfold id='1'>}</endfold id='1'>
0334 
0335 // Parse a configuration file into a dictionary of arguments
0336 public func parseConfigFile(_ data: Data) throws -> [String: String] <beginfold id='1'>{</beginfold id='1'>
0337     guard let input = String(data: data, encoding: .utf8) else <beginfold id='1'>{</beginfold id='1'>
0338         throw FormatError.reading("Unable to read data for configuration file")
0339     <endfold id='1'>}</endfold id='1'>
0340     let lines = try cumulate(successiveLines: input.components(separatedBy: .newlines))
0341     let arguments = try lines.flatMap <beginfold id='1'>{</beginfold id='1'> 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 <beginfold id='1'>{</beginfold id='1'>
0345             $0.replacingOccurrences(of: "\n", with: "\\n")
0346         <endfold id='1'>}</endfold id='1'>
0347         guard let key = parts.first else <beginfold id='1'>{</beginfold id='1'>
0348             return []
0349         <endfold id='1'>}</endfold id='1'>
0350         if !key.hasPrefix("-") <beginfold id='1'>{</beginfold id='1'>
0351             throw FormatError.options("Unknown option '\(key)' in configuration file")
0352         <endfold id='1'>}</endfold id='1'>
0353         return [key, parts.dropFirst().joined(separator: " ")]
0354     <endfold id='1'>}</endfold id='1'>
0355     do <beginfold id='1'>{</beginfold id='1'>
0356         return try preprocessArguments(arguments, optionsArguments)
0357     <endfold id='1'>}</endfold id='1'> catch let FormatError.options(message) <beginfold id='1'>{</beginfold id='1'>
0358         throw FormatError.options("\(message) in configuration file")
0359     <endfold id='1'>}</endfold id='1'>
0360 <endfold id='1'>}</endfold id='1'>
0361 
0362 private func cumulate(successiveLines: [String]) throws -> [String] <beginfold id='1'>{</beginfold id='1'>
0363     var cumulatedLines = [String]()
0364     var iterator = successiveLines.makeIterator()
0365     while let currentLine = iterator.next() <beginfold id='1'>{</beginfold id='1'>
0366         var cumulatedLine = effectiveContent(of: currentLine)
0367         while cumulatedLine.hasSuffix("\\") <beginfold id='1'>{</beginfold id='1'>
0368             guard let nextLine = iterator.next() else <beginfold id='1'>{</beginfold id='1'>
0369                 throw FormatError.reading("Configuration file ends with an illegal line continuation character '\'")
0370             <endfold id='1'>}</endfold id='1'>
0371             if !nextLine.trimmingCharacters(in: .whitespaces).starts(with: "#") <beginfold id='1'>{</beginfold id='1'>
0372                 cumulatedLine = cumulatedLine.dropLast() + effectiveContent(of: nextLine)
0373             <endfold id='1'>}</endfold id='1'>
0374         <endfold id='1'>}</endfold id='1'>
0375         cumulatedLines.append(String(cumulatedLine))
0376     <endfold id='1'>}</endfold id='1'>
0377     return cumulatedLines
0378 <endfold id='1'>}</endfold id='1'>
0379 
0380 private func effectiveContent(of line: String) -> String <beginfold id='1'>{</beginfold id='1'>
0381     return line
0382         .prefix <beginfold id='1'>{</beginfold id='1'> $0 != "#" <endfold id='1'>}</endfold id='1'>
0383         .trimmingCharacters(in: .whitespaces)
0384 <endfold id='1'>}</endfold id='1'>
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 <beginfold id='1'>{</beginfold id='1'>
0392     var arguments = [[String: String]]()
0393     if let fileOptions = options.fileOptions <beginfold id='1'>{</beginfold id='1'>
0394         arguments.append(argumentsFor(
0395             Options(fileOptions: fileOptions),
0396             excludingDefaults: excludingDefaults
0397         ))
0398     <endfold id='1'>}</endfold id='1'>
0399     if let formatOptions = options.formatOptions <beginfold id='1'>{</beginfold id='1'>
0400         arguments.append(argumentsFor(
0401             Options(formatOptions: formatOptions),
0402             excludingDefaults: excludingDefaults
0403         ))
0404     <endfold id='1'>}</endfold id='1'> else if swiftVersion != .undefined <beginfold id='1'>{</beginfold id='1'>
0405         let descriptor = Descriptors.swiftVersion
0406         arguments.append([descriptor.argumentName: swiftVersion.rawValue])
0407     <endfold id='1'>}</endfold id='1'>
0408     if let rules = options.rules <beginfold id='1'>{</beginfold id='1'>
0409         arguments.append(argumentsFor(
0410             Options(rules: rules),
0411             excludingDefaults: excludingDefaults
0412         ))
0413     <endfold id='1'>}</endfold id='1'>
0414     return arguments
0415         .map <beginfold id='1'>{</beginfold id='1'> serialize(arguments: $0, separator: separator) <endfold id='1'>}</endfold id='1'>
0416         .filter <beginfold id='1'>{</beginfold id='1'> !$0.isEmpty <endfold id='1'>}</endfold id='1'>
0417         .joined(separator: separator)
0418 <endfold id='1'>}</endfold id='1'>
0419 
0420 // Serialize arguments
0421 func serialize(arguments: [String: String],
0422                separator: String = "\n") -> String
0423 <beginfold id='1'>{</beginfold id='1'>
0424     return arguments.map <beginfold id='1'>{</beginfold id='1'>
0425         var value = $1
0426         if value.contains(" ") <beginfold id='1'>{</beginfold id='1'>
0427             value = "\"\(value.replacingOccurrences(of: "\"", with: "\\\""))\""
0428         <endfold id='1'>}</endfold id='1'>
0429         return "--\($0) \(value)"
0430     <endfold id='1'>}</endfold id='1'>.sorted().joined(separator: separator)
0431 <endfold id='1'>}</endfold id='1'>
0432 
0433 // Get command line arguments from options
0434 func argumentsFor(_ options: Options, excludingDefaults: Bool = false) -> [String: String] <beginfold id='1'>{</beginfold id='1'>
0435     var args = [String: String]()
0436     if let fileOptions = options.fileOptions <beginfold id='1'>{</beginfold id='1'>
0437         var arguments = Set(fileArguments)
0438         do <beginfold id='1'>{</beginfold id='1'>
0439             if !excludingDefaults || fileOptions.followSymlinks != FileOptions.default.followSymlinks <beginfold id='1'>{</beginfold id='1'>
0440                 args["symlinks"] = fileOptions.followSymlinks ? "follow" : "ignore"
0441             <endfold id='1'>}</endfold id='1'>
0442             arguments.remove("symlinks")
0443         <endfold id='1'>}</endfold id='1'>
0444         do <beginfold id='1'>{</beginfold id='1'>
0445             if !fileOptions.excludedGlobs.isEmpty <beginfold id='1'>{</beginfold id='1'>
0446                 // TODO: find a better alternative to stringifying url
0447                 args["exclude"] = fileOptions.excludedGlobs.map <beginfold id='1'>{</beginfold id='1'> $0.description <endfold id='1'>}</endfold id='1'>.sorted().joined(separator: ",")
0448             <endfold id='1'>}</endfold id='1'>
0449             arguments.remove("exclude")
0450         <endfold id='1'>}</endfold id='1'>
0451         do <beginfold id='1'>{</beginfold id='1'>
0452             if !fileOptions.unexcludedGlobs.isEmpty <beginfold id='1'>{</beginfold id='1'>
0453                 // TODO: find a better alternative to stringifying url
0454                 args["unexclude"] = fileOptions.unexcludedGlobs.map <beginfold id='1'>{</beginfold id='1'> $0.description <endfold id='1'>}</endfold id='1'>.sorted().joined(separator: ",")
0455             <endfold id='1'>}</endfold id='1'>
0456             arguments.remove("unexclude")
0457         <endfold id='1'>}</endfold id='1'>
0458         do <beginfold id='1'>{</beginfold id='1'>
0459             if !excludingDefaults || fileOptions.minVersion != FileOptions.default.minVersion <beginfold id='1'>{</beginfold id='1'>
0460                 args["minversion"] = fileOptions.minVersion.description
0461             <endfold id='1'>}</endfold id='1'>
0462             arguments.remove("minversion")
0463         <endfold id='1'>}</endfold id='1'>
0464         assert(arguments.isEmpty)
0465     <endfold id='1'>}</endfold id='1'>
0466     if let formatOptions = options.formatOptions <beginfold id='1'>{</beginfold id='1'>
0467         for descriptor in Descriptors.all where !descriptor.isRenamed <beginfold id='1'>{</beginfold id='1'>
0468             let value = descriptor.fromOptions(formatOptions)
0469             guard value != descriptor.fromOptions(.default) ||
0470                 (!excludingDefaults && !descriptor.isDeprecated)
0471             else <beginfold id='1'>{</beginfold id='1'>
0472                 continue
0473             <endfold id='1'>}</endfold id='1'>
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             <beginfold id='1'>{</beginfold id='1'>
0479                 continue
0480             <endfold id='1'>}</endfold id='1'>
0481             args[descriptor.argumentName] = value
0482         <endfold id='1'>}</endfold id='1'>
0483         // Special case for wrapParameters
0484         let argumentName = Descriptors.wrapParameters.argumentName
0485         if args[argumentName] == WrapMode.default.rawValue <beginfold id='1'>{</beginfold id='1'>
0486             args[argumentName] = args[Descriptors.wrapArguments.argumentName]
0487         <endfold id='1'>}</endfold id='1'>
0488     <endfold id='1'>}</endfold id='1'>
0489     if options.lint <beginfold id='1'>{</beginfold id='1'>
0490         args["lint"] = ""
0491     <endfold id='1'>}</endfold id='1'>
0492     if let rules = options.rules <beginfold id='1'>{</beginfold id='1'>
0493         let defaultRules = allRules.subtracting(FormatRules.disabledByDefault)
0494 
0495         let enabled = rules.subtracting(defaultRules)
0496         if !enabled.isEmpty <beginfold id='1'>{</beginfold id='1'>
0497             args["enable"] = enabled.sorted().joined(separator: ",")
0498         <endfold id='1'>}</endfold id='1'>
0499 
0500         let disabled = defaultRules.subtracting(rules)
0501         if !disabled.isEmpty <beginfold id='1'>{</beginfold id='1'>
0502             args["disable"] = disabled.sorted().joined(separator: ",")
0503         <endfold id='1'>}</endfold id='1'>
0504     <endfold id='1'>}</endfold id='1'>
0505     return args
0506 <endfold id='1'>}</endfold id='1'>
0507 
0508 private func processOption(_ key: String,
0509                            in args: [String: String],
0510                            from: inout Set<String>,
0511                            handler: (String) throws -> Void) throws
0512 <beginfold id='1'>{</beginfold id='1'>
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 <beginfold id='1'>{</beginfold id='1'>
0518         return
0519     <endfold id='1'>}</endfold id='1'>
0520     do <beginfold id='1'>{</beginfold id='1'>
0521         try handler(value)
0522     <endfold id='1'>}</endfold id='1'> catch <beginfold id='1'>{</beginfold id='1'>
0523         guard !value.isEmpty else <beginfold id='1'>{</beginfold id='1'>
0524             throw FormatError.options("--\(key) option expects a value")
0525         <endfold id='1'>}</endfold id='1'>
0526         if case var FormatError.options(string) = error, !string.isEmpty <beginfold id='1'>{</beginfold id='1'>
0527             if !string.contains(key) <beginfold id='1'>{</beginfold id='1'>
0528                 string += " in --\(key)"
0529             <endfold id='1'>}</endfold id='1'>
0530             throw FormatError.options(string)
0531         <endfold id='1'>}</endfold id='1'>
0532         throw FormatError.options("Unsupported --\(key) value '\(value)'")
0533     <endfold id='1'>}</endfold id='1'>
0534 <endfold id='1'>}</endfold id='1'>
0535 
0536 // Parse rule names from arguments
0537 public func rulesFor(_ args: [String: String], lint: Bool) throws -> Set<String> <beginfold id='1'>{</beginfold id='1'>
0538     var rules = allRules
0539     rules = try args["rules"].map <beginfold id='1'>{</beginfold id='1'>
0540         try Set(parseRules($0))
0541     <endfold id='1'>}</endfold id='1'> ?? rules.subtracting(FormatRules.disabledByDefault)
0542     try args["enable"].map <beginfold id='1'>{</beginfold id='1'>
0543         try rules.formUnion(parseRules($0))
0544     <endfold id='1'>}</endfold id='1'>
0545     try args["disable"].map <beginfold id='1'>{</beginfold id='1'>
0546         try rules.subtract(parseRules($0))
0547     <endfold id='1'>}</endfold id='1'>
0548     try args["lintonly"].map <beginfold id='1'>{</beginfold id='1'> rulesString in
0549         if lint <beginfold id='1'>{</beginfold id='1'>
0550             try rules.formUnion(parseRules(rulesString))
0551         <endfold id='1'>}</endfold id='1'> else <beginfold id='1'>{</beginfold id='1'>
0552             try rules.subtract(parseRules(rulesString))
0553         <endfold id='1'>}</endfold id='1'>
0554     <endfold id='1'>}</endfold id='1'>
0555     return rules
0556 <endfold id='1'>}</endfold id='1'>
0557 
0558 // Parse FileOptions from arguments
0559 func fileOptionsFor(_ args: [String: String], in directory: String) throws -> FileOptions? <beginfold id='1'>{</beginfold id='1'>
0560     var options = FileOptions()
0561     var arguments = Set(fileArguments)
0562 
0563     var containsFileOption = false
0564     try processOption("symlinks", in: args, from: &arguments) <beginfold id='1'>{</beginfold id='1'>
0565         containsFileOption = true
0566         switch $0.lowercased() <beginfold id='1'>{</beginfold id='1'>
0567         case "follow":
0568             options.followSymlinks = true
0569         case "ignore":
0570             options.followSymlinks = false
0571         default:
0572             throw FormatError.options("")
0573         <endfold id='1'>}</endfold id='1'>
0574     <endfold id='1'>}</endfold id='1'>
0575     try processOption("exclude", in: args, from: &arguments) <beginfold id='1'>{</beginfold id='1'>
0576         containsFileOption = true
0577         options.excludedGlobs += expandGlobs($0, in: directory)
0578     <endfold id='1'>}</endfold id='1'>
0579     try processOption("unexclude", in: args, from: &arguments) <beginfold id='1'>{</beginfold id='1'>
0580         containsFileOption = true
0581         options.unexcludedGlobs += expandGlobs($0, in: directory)
0582     <endfold id='1'>}</endfold id='1'>
0583     try processOption("minversion", in: args, from: &arguments) <beginfold id='1'>{</beginfold id='1'>
0584         containsFileOption = true
0585         guard let minVersion = Version(rawValue: $0) else <beginfold id='1'>{</beginfold id='1'>
0586             throw FormatError.options("Unsupported --minversion value '\($0)'")
0587         <endfold id='1'>}</endfold id='1'>
0588         guard minVersion <= Version(stringLiteral: swiftFormatVersion) else <beginfold id='1'>{</beginfold id='1'>
0589             throw FormatError.options("Project specifies SwiftFormat --minversion of \(minVersion)")
0590         <endfold id='1'>}</endfold id='1'>
0591         options.minVersion = minVersion
0592     <endfold id='1'>}</endfold id='1'>
0593     assert(arguments.isEmpty, "\(arguments.joined(separator: ","))")
0594     return containsFileOption ? options : nil
0595 <endfold id='1'>}</endfold id='1'>
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? <beginfold id='1'>{</beginfold id='1'>
0600     var options = FormatOptions.default
0601     var arguments = Set(formattingArguments)
0602 
0603     var containsFormatOption = false
0604     for option in Descriptors.all <beginfold id='1'>{</beginfold id='1'>
0605         try processOption(option.argumentName, in: args, from: &arguments) <beginfold id='1'>{</beginfold id='1'>
0606             containsFormatOption = true
0607             try option.toOptions($0, &options)
0608         <endfold id='1'>}</endfold id='1'>
0609     <endfold id='1'>}</endfold id='1'>
0610     assert(arguments.isEmpty, "\(arguments.joined(separator: ","))")
0611     return containsFormatOption ? options : nil
0612 <endfold id='1'>}</endfold id='1'>
0613 
0614 // Get deprecation warnings from a set of arguments
0615 func warningsForArguments(_ args: [String: String]) -> [String] <beginfold id='1'>{</beginfold id='1'>
0616     var warnings = [String]()
0617     for option in Descriptors.all <beginfold id='1'>{</beginfold id='1'>
0618         if args[option.argumentName] != nil, let message = option.deprecationMessage <beginfold id='1'>{</beginfold id='1'>
0619             warnings.append("--\(option.argumentName) option is deprecated. \(message)")
0620         <endfold id='1'>}</endfold id='1'>
0621     <endfold id='1'>}</endfold id='1'>
0622     for name in Set(rulesArguments.flatMap <beginfold id='1'>{</beginfold id='1'> (try? args[$0].map(parseRules) ?? []) ?? [] <endfold id='1'>}</endfold id='1'>) <beginfold id='1'>{</beginfold id='1'>
0623         if let message = FormatRules.byName[name]?.deprecationMessage <beginfold id='1'>{</beginfold id='1'>
0624             warnings.append("\(name) rule is deprecated. \(message)")
0625         <endfold id='1'>}</endfold id='1'>
0626     <endfold id='1'>}</endfold id='1'>
0627     if let rules = try? rulesFor(args, lint: true) <beginfold id='1'>{</beginfold id='1'>
0628         for arg in args.keys where formattingArguments.contains(arg) <beginfold id='1'>{</beginfold id='1'>
0629             if !rules.contains(where: <beginfold id='1'>{</beginfold id='1'>
0630                 guard let rule = FormatRules.byName[$0] else <beginfold id='1'>{</beginfold id='1'>
0631                     return false
0632                 <endfold id='1'>}</endfold id='1'>
0633                 return rule.options.contains(arg) || rule.sharedOptions.contains(arg)
0634             <endfold id='1'>}</endfold id='1'>) <beginfold id='1'>{</beginfold id='1'>
0635                 let expected = FormatRules.all.first(where: <beginfold id='1'>{</beginfold id='1'>
0636                     $0.options.contains(arg)
0637                 <endfold id='1'>}</endfold id='1'>)?.name ?? "associated"
0638                 warnings.append("--\(arg) option has no effect when \(expected) rule is disabled")
0639             <endfold id='1'>}</endfold id='1'>
0640         <endfold id='1'>}</endfold id='1'>
0641     <endfold id='1'>}</endfold id='1'>
0642     return warnings
0643 <endfold id='1'>}</endfold id='1'>
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 <beginfold id='1'>{</beginfold id='1'> $0.argumentName <endfold id='1'>}</endfold id='1'>
0660 let internalArguments = Descriptors.internal.map <beginfold id='1'>{</beginfold id='1'> $0.argumentName <endfold id='1'>}</endfold id='1'>
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 <beginfold id='1'>{</beginfold id='1'>
0686     $0.isDeprecated ? $0.argumentName : nil
0687 <endfold id='1'>}</endfold id='1'>