Add unknown-rules option

This commit is contained in:
Nick Lockwood
2025-09-25 21:20:42 +01:00
parent 34f16aaed2
commit 83e5f6aa58
3 changed files with 46 additions and 17 deletions
+19 -1
View File
@@ -324,13 +324,15 @@ func parseCommaDelimitedList(_ string: String) -> [String] {
/// Parse a comma-delimited string into an array of rules
let allRules = Set(FormatRules.all.map(\.name))
let defaultRules = Set(FormatRules.default.map(\.name))
func parseRules(_ rules: String) throws -> [String] {
func parseRules(_ rules: String, ignoreUnknown: Bool) throws -> [String] {
try parseCommaDelimitedList(rules).flatMap { proposedName -> [String] in
let lowercaseName = proposedName.lowercased()
if let name = allRules.first(where: { $0.lowercased() == lowercaseName }) {
return [name]
} else if lowercaseName == "all" {
return FormatRules.all.compactMap { $0.isDeprecated ? nil : $0.name }
} else if ignoreUnknown {
return []
}
if Descriptors.all.contains(where: { $0.argumentName == lowercaseName }) {
for rule in FormatRules.all where rule.options.contains(lowercaseName) {
@@ -348,6 +350,18 @@ func parseRules(_ rules: String) throws -> [String] {
}
}
func curryParseRules(config: [String: String]) -> (String) throws -> [String] {
{
try parseRules($0, ignoreUnknown: config["unknown-rules"].map {
switch $0 {
case "ignore": return true
case "error": return false
default: throw FormatError.options("Unknown value '\($0)' for --unknown-rules option")
}
} ?? false)
}
}
/// Parse single file path, disallowing globs or commas
func parsePath(_ path: String, for argument: String, in directory: String) throws -> URL {
let expandedPath = expandPath(path, in: directory)
@@ -369,6 +383,7 @@ func parsePaths(_ paths: String, in directory: String) throws -> [URL] {
/// Merge two dictionaries of arguments
func mergeArguments(_ args: [String: String], into config: [String: String]) throws -> [String: String] {
let parseRules = curryParseRules(config: config)
var input = config
var output = args
// Merge excluded urls
@@ -661,6 +676,7 @@ private func processOption(_ key: String,
/// Parse rule names from arguments
public func rulesFor(_ args: [String: String], lint: Bool, initial: Set<String>? = nil) throws -> Set<String> {
let parseRules = curryParseRules(config: args)
var rules = initial ?? allRules
if let specifiedRules = try args["rules"].map({ try Set(parseRules($0)) }) {
@@ -779,6 +795,7 @@ func warningsForArguments(_ args: [String: String], ignoreUnusedOptions: Bool =
warnings.append("--\(option.argumentName) option is deprecated. \(message)")
}
}
let parseRules = curryParseRules(config: args)
for name in Set(rulesArguments.flatMap { (try? args[$0].map(parseRules) ?? []) ?? [] }) {
if let message = FormatRules.byName[name]?.deprecationMessage {
warnings.append("\(name) rule is deprecated. \(message)")
@@ -837,6 +854,7 @@ let commandLineArguments = [
"strict",
"verbose",
"quiet",
"unknown-rules",
"reporter",
"report",
// Misc
+2 -1
View File
@@ -208,6 +208,7 @@ func printHelp(as type: CLI.OutputType) {
--conflict-markers \(stripMarkdown(Descriptors.ignoreConflictMarkers.help))
--swift-version \(stripMarkdown(Descriptors.swiftVersion.help))
--language-mode \(stripMarkdown(Descriptors.languageMode.help))
--unknown-rules How unknown rules are handled: "error" (default) or "ignore"
--min-version The minimum SwiftFormat version to be used for these files
--cache Path to cache file, or "clear" or "ignore" the default cache
--dry-run Run in "dry" mode (without actually changing any files)
@@ -492,7 +493,7 @@ func processArguments(_ args: [String], environment: [String: String] = [:], in
}
// Show rule info
if let names = try args["rule-info"].map(parseRules) {
if let names = try args["rule-info"].map({ try parseRules($0, ignoreUnknown: false) }) {
let names = names.isEmpty ? allRules.sorted() : names.sorted()
for name in names {
try printRuleInfo(for: name, as: .content)
+25 -15
View File
@@ -660,7 +660,7 @@ class ArgumentsTests: XCTestCase {
let args = ["rules": "braces,fileHeader"]
let config = ["rules": "consecutiveSpaces,braces"]
let result = try mergeArguments(args, into: config)
let rules = try parseRules(result["rules"]!)
let rules = try parseRules(result["rules"]!, ignoreUnknown: false)
XCTAssertEqual(rules, ["braces", "fileHeader"])
}
@@ -668,7 +668,7 @@ class ArgumentsTests: XCTestCase {
let args = ["rules": ""]
let config = ["rules": "consecutiveSpaces,braces"]
let result = try mergeArguments(args, into: config)
let rules = try parseRules(result["rules"]!)
let rules = try parseRules(result["rules"]!, ignoreUnknown: false)
XCTAssertEqual(Set(rules), Set(["braces", "consecutiveSpaces"]))
}
@@ -676,7 +676,7 @@ class ArgumentsTests: XCTestCase {
let args = ["enable": "braces,fileHeader"]
let config = ["enable": "consecutiveSpaces,braces"]
let result = try mergeArguments(args, into: config)
let enabled = try parseRules(result["enable"]!)
let enabled = try parseRules(result["enable"]!, ignoreUnknown: false)
XCTAssertEqual(enabled, ["braces", "consecutiveSpaces", "fileHeader"])
}
@@ -684,7 +684,7 @@ class ArgumentsTests: XCTestCase {
let args = ["disable": "braces,fileHeader"]
let config = ["disable": "consecutiveSpaces,braces"]
let result = try mergeArguments(args, into: config)
let disabled = try parseRules(result["disable"]!)
let disabled = try parseRules(result["disable"]!, ignoreUnknown: false)
XCTAssertEqual(disabled, ["braces", "consecutiveSpaces", "fileHeader"])
}
@@ -692,7 +692,7 @@ class ArgumentsTests: XCTestCase {
let args = ["rules": "braces,fileHeader"]
let config = ["rules": "consecutiveSpaces", "disable": "braces", "enable": "redundantSelf"]
let result = try mergeArguments(args, into: config)
let disabled = try parseRules(result["rules"]!)
let disabled = try parseRules(result["rules"]!, ignoreUnknown: false)
XCTAssertEqual(disabled, ["braces", "fileHeader"])
XCTAssertNil(result["enabled"])
XCTAssertNil(result["disabled"])
@@ -702,11 +702,11 @@ class ArgumentsTests: XCTestCase {
let args = ["enable": "braces"]
let config = ["rules": "fileHeader", "disable": "consecutiveSpaces,braces"]
let result = try mergeArguments(args, into: config)
let rules = try parseRules(result["rules"]!)
let rules = try parseRules(result["rules"]!, ignoreUnknown: false)
XCTAssertEqual(rules, ["fileHeader"])
let enabled = try parseRules(result["enable"]!)
let enabled = try parseRules(result["enable"]!, ignoreUnknown: false)
XCTAssertEqual(enabled, ["braces"])
let disabled = try parseRules(result["disable"]!)
let disabled = try parseRules(result["disable"]!, ignoreUnknown: false)
XCTAssertEqual(disabled, ["consecutiveSpaces"])
}
@@ -714,11 +714,11 @@ class ArgumentsTests: XCTestCase {
let args = ["disable": "braces"]
let config = ["rules": "braces,fileHeader", "enable": "consecutiveSpaces,braces"]
let result = try mergeArguments(args, into: config)
let rules = try parseRules(result["rules"]!)
let rules = try parseRules(result["rules"]!, ignoreUnknown: false)
XCTAssertEqual(rules, ["fileHeader"])
let enabled = try parseRules(result["enable"]!)
let enabled = try parseRules(result["enable"]!, ignoreUnknown: false)
XCTAssertEqual(enabled, ["consecutiveSpaces"])
let disabled = try parseRules(result["disable"]!)
let disabled = try parseRules(result["disable"]!, ignoreUnknown: false)
XCTAssertEqual(disabled, ["braces"])
}
@@ -850,29 +850,39 @@ class ArgumentsTests: XCTestCase {
// MARK: parse rules
func testParseRulesCaseInsensitive() throws {
let rules = try parseRules("strongoutlets")
let rules = try parseRules("strongoutlets", ignoreUnknown: false)
XCTAssertEqual(rules, ["strongOutlets"])
}
func testParseAllRule() throws {
let rules = try parseRules("all")
let rules = try parseRules("all", ignoreUnknown: false)
XCTAssertEqual(rules, FormatRules.all.compactMap {
$0.isDeprecated ? nil : $0.name
})
}
func testParseInvalidRuleThrows() {
XCTAssertThrowsError(try parseRules("strongOutlet")) { error in
XCTAssertThrowsError(try parseRules("strongOutlet", ignoreUnknown: false)) { error in
XCTAssertEqual("\(error)", "Unknown rule 'strongOutlet'. Did you mean 'strongOutlets'?")
}
}
func testParseOptionAsRuleThrows() {
XCTAssertThrowsError(try parseRules("import-grouping")) { error in
XCTAssertThrowsError(try parseRules("import-grouping", ignoreUnknown: false)) { error in
XCTAssert("\(error)".contains("'sortImports'"))
}
}
func testSuppressInvalidRuleError() throws {
let rules = try parseRules("strongOutlet,isEmpty", ignoreUnknown: true)
XCTAssertEqual(rules, ["isEmpty"])
}
func testSuppressOptionAsRuleError() throws {
let rules = try parseRules("import-grouping,isEmpty", ignoreUnknown: true)
XCTAssertEqual(rules, ["isEmpty"])
}
// MARK: lintonly
func testLintonlyRulesContain() throws {