Skip to content

Commit 21693ff

Browse files
committed
Allow to customize Rule severity
In order to customize the severity of rules, I added the possibility to do so via the configuration files. If no severity is specified, we use the one pre-determined by the Rule itself. Example: ``` { "rules": { "AlwaysUseLowerCamelCase": "warning", "AmbiguousTrailingClosureOverload": "error", "UseLetInEveryBoundCaseVariable": "true", // use rule default "UseWhereClausesInForLoops": "false", // disabled } } ``` In addition, one can now control how pretty-print violations should be treated in the same way Example: ``` { "rules": { "TrailingComma": "warning", "LineLength": "error", "Indentation": "true", // use rule default "TrailingWhitespace": "false", // disabled } } ``` Issue: #879
1 parent 080535c commit 21693ff

24 files changed

+431
-122
lines changed

Documentation/RuleDocumentation.md

+1-1
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44

55
Use the rules below in the `rules` block of your `.swift-format`
66
configuration file, as described in
7-
[Configuration](Configuration.md). All of these rules can be
7+
[Configuration](Documentation/Configuration.md). All of these rules can be
88
applied in the linter, but only some of them can format your source code
99
automatically.
1010

Sources/SwiftFormat/API/Configuration+Default.swift

-1
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,6 @@ extension Configuration {
2222
/// the JSON will be populated from this default configuration.
2323
public init() {
2424
self.rules = Self.defaultRuleEnablements
25-
self.ruleSeverity = [:]
2625
self.maximumBlankLines = 1
2726
self.lineLength = 100
2827
self.tabWidth = 8

Sources/SwiftFormat/API/Configuration.swift

+10-11
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,8 @@ public struct Configuration: Codable, Equatable {
2727
public enum RuleSeverity: String, Codable, CaseIterable, Equatable, Sendable {
2828
case warning = "warning"
2929
case error = "error"
30+
case ruleDefault = "true"
31+
case disabled = "false"
3032
}
3133

3234
private enum CodingKeys: CodingKey {
@@ -59,7 +61,7 @@ public struct Configuration: Codable, Equatable {
5961
/// names.
6062
///
6163
/// This value is generated by `generate-swift-format` based on the `isOptIn` value of each rule.
62-
public static let defaultRuleEnablements: [String: Bool] = RuleRegistry.rules
64+
public static let defaultRuleEnablements: [String: Configuration.RuleSeverity] = RuleRegistry.rules
6365

6466
/// The version of this configuration.
6567
private var version: Int = highestSupportedConfigurationVersion
@@ -68,11 +70,7 @@ public struct Configuration: Codable, Equatable {
6870

6971
/// The dictionary containing the rule names that we wish to run on. A rule is not used if it is
7072
/// marked as `false`, or if it is missing from the dictionary.
71-
public var rules: [String: Bool]
72-
73-
/// The dictionary containing the severities for the rule names that we wish to run on. If a rule
74-
/// is not listed here, the default severity is used.
75-
public var ruleSeverity: [String: RuleSeverity]
73+
public var rules: [String: Configuration.RuleSeverity]
7674

7775
/// The maximum number of consecutive blank lines that may appear in a file.
7876
public var maximumBlankLines: Int
@@ -398,11 +396,8 @@ public struct Configuration: Codable, Equatable {
398396
// default-initialized. To get an empty rules dictionary, one can explicitly
399397
// set the `rules` key to `{}`.
400398
self.rules =
401-
try container.decodeIfPresent([String: Bool].self, forKey: .rules)
399+
try container.decodeIfPresent([String: Configuration.RuleSeverity].self, forKey: .rules)
402400
?? defaults.rules
403-
404-
self.ruleSeverity =
405-
try container.decodeIfPresent([String: RuleSeverity].self, forKey: .ruleSeverity) ?? [:]
406401
}
407402

408403
public func encode(to encoder: Encoder) throws {
@@ -515,10 +510,14 @@ fileprivate extension URL {
515510
}
516511

517512
extension Configuration.RuleSeverity {
518-
var findingSeverity: Finding.Severity {
513+
func findingSeverity(ruleDefault: Finding.Severity) -> Finding.Severity {
519514
switch self {
520515
case .warning: return .warning
521516
case .error: return .error
517+
case .ruleDefault:
518+
return ruleDefault
519+
case .disabled:
520+
return .disabled
522521
}
523522
}
524523
}

Sources/SwiftFormat/API/Finding.swift

+1
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ public struct Finding {
1818
case error
1919
case refactoring
2020
case convention
21+
case disabled
2122
}
2223

2324
/// The file path and location in that file where a finding was encountered.

Sources/SwiftFormat/API/FindingCategorizing.swift

+1-11
Original file line numberDiff line numberDiff line change
@@ -21,19 +21,9 @@ public protocol FindingCategorizing: CustomStringConvertible {
2121
///
2222
/// By default, all findings are warnings. Individual categories or configuration may choose to override this to
2323
/// make the findings in those categories more severe.
24-
func severity(configuration: Configuration) -> Finding.Severity
24+
var severity: Finding.Severity { get }
2525

2626
/// The name of the category.
2727
var name: String {get}
2828
}
2929

30-
extension FindingCategorizing {
31-
func severity(configuration: Configuration) -> Finding.Severity {
32-
return severityFromConfig(configuration: configuration)
33-
}
34-
35-
func severityFromConfig(configuration: Configuration) -> Finding.Severity {
36-
guard let customSeverity = configuration.ruleSeverity[self.name] else { return .warning }
37-
return customSeverity.findingSeverity
38-
}
39-
}

Sources/SwiftFormat/Core/Context.swift

+6-1
Original file line numberDiff line numberDiff line change
@@ -108,7 +108,12 @@ public final class Context {
108108
let ruleName = ruleNameCache[ObjectIdentifier(rule)] ?? R.ruleName
109109
switch ruleMask.ruleState(ruleName, at: loc) {
110110
case .default:
111-
return configuration.rules[ruleName] ?? false
111+
guard let configSeverity = configuration.rules[ruleName] else { return false }
112+
if case .disabled = configSeverity {
113+
return false
114+
} else {
115+
return true
116+
}
112117
case .disabled:
113118
return false
114119
}

Sources/SwiftFormat/Core/FindingEmitter.swift

+1-1
Original file line numberDiff line numberDiff line change
@@ -55,7 +55,7 @@ final class FindingEmitter {
5555
Finding(
5656
category: category,
5757
message: message,
58-
severity: category.severity(configuration: context.configuration),
58+
severity: category.severity,
5959
location: location,
6060
notes: notes
6161
)

Sources/SwiftFormat/Core/Rule.swift

+4-4
Original file line numberDiff line numberDiff line change
@@ -86,7 +86,7 @@ extension Rule {
8686
syntaxLocation = nil
8787
}
8888

89-
let severity: Finding.Severity? = severity ?? context.configuration.findingSeverity(for: type(of: self))
89+
let severity: Finding.Severity = severity ?? context.configuration.findingSeverity(for: type(of: self), defaultSeverity: .warning)
9090

9191
let category = RuleBasedFindingCategory(ruleType: type(of: self), severity: severity)
9292
context.findingEmitter.emit(
@@ -100,8 +100,8 @@ extension Rule {
100100
}
101101

102102
extension Configuration {
103-
func findingSeverity(for rule: any Rule.Type) -> Finding.Severity? {
104-
guard let severity = self.ruleSeverity[rule.ruleName] else { return nil }
105-
return severity.findingSeverity
103+
func findingSeverity(for rule: any Rule.Type, defaultSeverity: Finding.Severity) -> Finding.Severity {
104+
guard let severity = self.rules[rule.ruleName] else { return defaultSeverity }
105+
return severity.findingSeverity(ruleDefault: defaultSeverity)
106106
}
107107
}

Sources/SwiftFormat/Core/RuleBasedFindingCategory.swift

+2-9
Original file line numberDiff line numberDiff line change
@@ -22,22 +22,15 @@ struct RuleBasedFindingCategory: FindingCategorizing {
2222

2323
var description: String { ruleType.ruleName }
2424

25-
var severity: Finding.Severity?
25+
var severity: Finding.Severity
2626

2727
var name: String {
2828
return description
2929
}
3030

3131
/// Creates a finding category that wraps the given rule type.
32-
init(ruleType: Rule.Type, severity: Finding.Severity? = nil) {
32+
init(ruleType: Rule.Type, severity: Finding.Severity) {
3333
self.ruleType = ruleType
3434
self.severity = severity
3535
}
36-
37-
func severity(configuration: Configuration) -> Finding.Severity {
38-
if let severity = severity {
39-
return severity
40-
}
41-
return severityFromConfig(configuration: configuration)
42-
}
4336
}

Sources/SwiftFormat/Core/RuleRegistry+Generated.swift

+53-44
Original file line numberDiff line numberDiff line change
@@ -13,49 +13,58 @@
1313
// This file is automatically generated with generate-swift-format. Do not edit!
1414

1515
@_spi(Internal) public enum RuleRegistry {
16-
public static let rules: [String: Bool] = [
17-
"AllPublicDeclarationsHaveDocumentation": false,
18-
"AlwaysUseLiteralForEmptyCollectionInit": false,
19-
"AlwaysUseLowerCamelCase": true,
20-
"AmbiguousTrailingClosureOverload": true,
21-
"AvoidRetroactiveConformances": true,
22-
"BeginDocumentationCommentWithOneLineSummary": false,
23-
"DoNotUseSemicolons": true,
24-
"DontRepeatTypeInStaticProperties": true,
25-
"FileScopedDeclarationPrivacy": true,
26-
"FullyIndirectEnum": true,
27-
"GroupNumericLiterals": true,
28-
"IdentifiersMustBeASCII": true,
29-
"NeverForceUnwrap": false,
30-
"NeverUseForceTry": false,
31-
"NeverUseImplicitlyUnwrappedOptionals": false,
32-
"NoAccessLevelOnExtensionDeclaration": true,
33-
"NoAssignmentInExpressions": true,
34-
"NoBlockComments": true,
35-
"NoCasesWithOnlyFallthrough": true,
36-
"NoEmptyLinesOpeningClosingBraces": false,
37-
"NoEmptyTrailingClosureParentheses": true,
38-
"NoLabelsInCasePatterns": true,
39-
"NoLeadingUnderscores": false,
40-
"NoParensAroundConditions": true,
41-
"NoPlaygroundLiterals": true,
42-
"NoVoidReturnOnFunctionSignature": true,
43-
"OmitExplicitReturns": false,
44-
"OneCasePerLine": true,
45-
"OneVariableDeclarationPerLine": true,
46-
"OnlyOneTrailingClosureArgument": true,
47-
"OrderedImports": true,
48-
"ReplaceForEachWithForLoop": true,
49-
"ReturnVoidInsteadOfEmptyTuple": true,
50-
"TypeNamesShouldBeCapitalized": true,
51-
"UseEarlyExits": false,
52-
"UseExplicitNilCheckInConditions": true,
53-
"UseLetInEveryBoundCaseVariable": true,
54-
"UseShorthandTypeNames": true,
55-
"UseSingleLinePropertyGetter": true,
56-
"UseSynthesizedInitializer": true,
57-
"UseTripleSlashForDocumentationComments": true,
58-
"UseWhereClausesInForLoops": false,
59-
"ValidateDocumentationComments": false,
16+
public static let rules: [String: Configuration.RuleSeverity] = [
17+
"AllPublicDeclarationsHaveDocumentation": .disabled,
18+
"AlwaysUseLiteralForEmptyCollectionInit": .disabled,
19+
"AlwaysUseLowerCamelCase": .ruleDefault,
20+
"AmbiguousTrailingClosureOverload": .ruleDefault,
21+
"AvoidRetroactiveConformances": .ruleDefault,
22+
"BeginDocumentationCommentWithOneLineSummary": .disabled,
23+
"DoNotUseSemicolons": .ruleDefault,
24+
"DontRepeatTypeInStaticProperties": .ruleDefault,
25+
"FileScopedDeclarationPrivacy": .ruleDefault,
26+
"FullyIndirectEnum": .ruleDefault,
27+
"GroupNumericLiterals": .ruleDefault,
28+
"IdentifiersMustBeASCII": .ruleDefault,
29+
"NeverForceUnwrap": .disabled,
30+
"NeverUseForceTry": .disabled,
31+
"NeverUseImplicitlyUnwrappedOptionals": .disabled,
32+
"NoAccessLevelOnExtensionDeclaration": .ruleDefault,
33+
"NoAssignmentInExpressions": .ruleDefault,
34+
"NoBlockComments": .ruleDefault,
35+
"NoCasesWithOnlyFallthrough": .ruleDefault,
36+
"NoEmptyLinesOpeningClosingBraces": .disabled,
37+
"NoEmptyTrailingClosureParentheses": .ruleDefault,
38+
"NoLabelsInCasePatterns": .ruleDefault,
39+
"NoLeadingUnderscores": .disabled,
40+
"NoParensAroundConditions": .ruleDefault,
41+
"NoPlaygroundLiterals": .ruleDefault,
42+
"NoVoidReturnOnFunctionSignature": .ruleDefault,
43+
"OmitExplicitReturns": .disabled,
44+
"OneCasePerLine": .ruleDefault,
45+
"OneVariableDeclarationPerLine": .ruleDefault,
46+
"OnlyOneTrailingClosureArgument": .ruleDefault,
47+
"OrderedImports": .ruleDefault,
48+
"ReplaceForEachWithForLoop": .ruleDefault,
49+
"ReturnVoidInsteadOfEmptyTuple": .ruleDefault,
50+
"TypeNamesShouldBeCapitalized": .ruleDefault,
51+
"UseEarlyExits": .disabled,
52+
"UseExplicitNilCheckInConditions": .ruleDefault,
53+
"UseLetInEveryBoundCaseVariable": .ruleDefault,
54+
"UseShorthandTypeNames": .ruleDefault,
55+
"UseSingleLinePropertyGetter": .ruleDefault,
56+
"UseSynthesizedInitializer": .ruleDefault,
57+
"UseTripleSlashForDocumentationComments": .ruleDefault,
58+
"UseWhereClausesInForLoops": .disabled,
59+
"ValidateDocumentationComments": .disabled,
60+
"AddLines": .ruleDefault,
61+
"EndOfLineComment": .ruleDefault,
62+
"Indentation": .ruleDefault,
63+
"LineLength": .ruleDefault,
64+
"RemoveLine": .ruleDefault,
65+
"Spacing": .ruleDefault,
66+
"SpacingCharacter": .ruleDefault,
67+
"TrailingComma": .ruleDefault,
68+
"TrailingWhitespace": .ruleDefault,
6069
]
6170
}

Sources/SwiftFormat/PrettyPrint/PrettyPrint.swift

+20-3
Original file line numberDiff line numberDiff line change
@@ -475,7 +475,7 @@ public class PrettyPrinter {
475475

476476
if wasEndOfLine {
477477
if !(canFit(comment.length) || isBreakingSuppressed) {
478-
diagnose(.moveEndOfLineComment, category: .endOfLineComment)
478+
diagnose(.moveEndOfLineComment, category: .endOfLineComment().withSeverity(configuration))
479479
}
480480
}
481481
outputBuffer.write(comment.print(indent: currentIndentation))
@@ -515,9 +515,9 @@ public class PrettyPrinter {
515515
startLineNumber != openCloseBreakCompensatingLineNumber && !isSingleElement
516516
&& configuration.multiElementCollectionTrailingCommas
517517
if shouldHaveTrailingComma && !hasTrailingComma {
518-
diagnose(.addTrailingComma, category: .trailingComma)
518+
diagnose(.addTrailingComma, category: .trailingComma().withSeverity(configuration))
519519
} else if !shouldHaveTrailingComma && hasTrailingComma {
520-
diagnose(.removeTrailingComma, category: .trailingComma)
520+
diagnose(.removeTrailingComma, category: .trailingComma().withSeverity(configuration))
521521
}
522522

523523
let shouldWriteComma = whitespaceOnly ? hasTrailingComma : shouldHaveTrailingComma
@@ -814,6 +814,7 @@ public class PrettyPrinter {
814814

815815
/// Emits a finding with the given message and category at the current location in `outputBuffer`.
816816
private func diagnose(_ message: Finding.Message, category: PrettyPrintFindingCategory) {
817+
if case .disabled = category.severity { return }
817818
// Add 1 since columns uses 1-based indices.
818819
let column = outputBuffer.column + 1
819820
context.findingEmitter.emit(
@@ -835,3 +836,19 @@ extension Finding.Message {
835836
fileprivate static let removeTrailingComma: Finding.Message =
836837
"remove trailing comma from the last element in single line collection literal"
837838
}
839+
840+
extension PrettyPrintFindingCategory {
841+
func withSeverity(_ configuration: Configuration) -> Self {
842+
let category: PrettyPrintFindingCategory = self
843+
let severity = configuration
844+
.rules[category.name]?
845+
.findingSeverity(ruleDefault: category.severity) ?? category.severity
846+
847+
switch self {
848+
case .endOfLineComment:
849+
return .endOfLineComment(severity)
850+
case .trailingComma:
851+
return .trailingComma(severity)
852+
}
853+
}
854+
}

Sources/SwiftFormat/PrettyPrint/PrettyPrintFindingCategory.swift

+9-2
Original file line numberDiff line numberDiff line change
@@ -14,10 +14,10 @@
1414
enum PrettyPrintFindingCategory: FindingCategorizing {
1515

1616
/// Finding related to an end-of-line comment.
17-
case endOfLineComment
17+
case endOfLineComment(Finding.Severity = .warning)
1818

1919
/// Findings related to the presence of absence of a trailing comma in collection literals.
20-
case trailingComma
20+
case trailingComma(Finding.Severity = .warning)
2121

2222
var description: String {
2323
switch self {
@@ -30,4 +30,11 @@ enum PrettyPrintFindingCategory: FindingCategorizing {
3030
self.description
3131
}
3232

33+
var severity: Finding.Severity {
34+
switch self {
35+
case .endOfLineComment(let severity): return severity
36+
case .trailingComma(let severity): return severity
37+
}
38+
}
39+
3340
}

0 commit comments

Comments
 (0)