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'>