Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add ability to customize severity via config file #880

Open
wants to merge 3 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -2,4 +2,4 @@
.swiftpm/
swift-format.xcodeproj/
Package.resolved

.index-build
2 changes: 1 addition & 1 deletion Documentation/RuleDocumentation.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@

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

Expand Down
27 changes: 24 additions & 3 deletions Sources/SwiftFormat/API/Configuration.swift
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,13 @@ internal let highestSupportedConfigurationVersion = 1
/// Holds the complete set of configured values and defaults.
public struct Configuration: Codable, Equatable {

public enum RuleSeverity: String, Codable, CaseIterable, Equatable, Sendable {
case warning = "warning"
case error = "error"
case ruleDefault = "true"
case disabled = "false"
}

private enum CodingKeys: CodingKey {
case version
case maximumBlankLines
Expand All @@ -42,6 +49,7 @@ public struct Configuration: Codable, Equatable {
case fileScopedDeclarationPrivacy
case indentSwitchCaseLabels
case rules
case ruleSeverity
case spacesAroundRangeFormationOperators
case noAssignmentInExpressions
case multiElementCollectionTrailingCommas
Expand All @@ -53,7 +61,7 @@ public struct Configuration: Codable, Equatable {
/// names.
///
/// This value is generated by `generate-swift-format` based on the `isOptIn` value of each rule.
public static let defaultRuleEnablements: [String: Bool] = RuleRegistry.rules
public static let defaultRuleEnablements: [String: Configuration.RuleSeverity] = RuleRegistry.rules

/// The version of this configuration.
private var version: Int = highestSupportedConfigurationVersion
Expand All @@ -62,7 +70,7 @@ public struct Configuration: Codable, Equatable {

/// The dictionary containing the rule names that we wish to run on. A rule is not used if it is
/// marked as `false`, or if it is missing from the dictionary.
public var rules: [String: Bool]
public var rules: [String: Configuration.RuleSeverity]

/// The maximum number of consecutive blank lines that may appear in a file.
public var maximumBlankLines: Int
Expand Down Expand Up @@ -388,7 +396,7 @@ public struct Configuration: Codable, Equatable {
// default-initialized. To get an empty rules dictionary, one can explicitly
// set the `rules` key to `{}`.
self.rules =
try container.decodeIfPresent([String: Bool].self, forKey: .rules)
try container.decodeIfPresent([String: Configuration.RuleSeverity].self, forKey: .rules)
?? defaults.rules
}

Expand Down Expand Up @@ -486,3 +494,16 @@ public struct NoAssignmentInExpressionsConfiguration: Codable, Equatable {

public init() {}
}

extension Configuration.RuleSeverity {
func findingSeverity(ruleDefault: Finding.Severity) -> Finding.Severity {
switch self {
case .warning: return .warning
case .error: return .error
case .ruleDefault:
return ruleDefault
case .disabled:
return .disabled
}
}
}
1 change: 1 addition & 0 deletions Sources/SwiftFormat/API/Finding.swift
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ public struct Finding {
case error
case refactoring
case convention
case disabled
}

/// The file path and location in that file where a finding was encountered.
Expand Down
11 changes: 5 additions & 6 deletions Sources/SwiftFormat/API/FindingCategorizing.swift
Original file line number Diff line number Diff line change
Expand Up @@ -17,13 +17,12 @@
/// to be displayed as part of the diagnostic message when the finding is presented to the user.
/// For example, the category `Indentation` in the message `[Indentation] Indent by 2 spaces`.
public protocol FindingCategorizing: CustomStringConvertible {
/// The default severity of findings emitted in this category.
/// The severity of findings emitted in this category.
///
/// By default, all findings are warnings. Individual categories may choose to override this to
/// By default, all findings are warnings. Individual categories or configuration may choose to override this to
/// make the findings in those categories more severe.
var defaultSeverity: Finding.Severity { get }
}
var severity: Finding.Severity { get }

extension FindingCategorizing {
public var defaultSeverity: Finding.Severity { .warning }
/// The name of the category.
var name: String { get }
}
7 changes: 6 additions & 1 deletion Sources/SwiftFormat/Core/Context.swift
Original file line number Diff line number Diff line change
Expand Up @@ -108,7 +108,12 @@ public final class Context {
let ruleName = ruleNameCache[ObjectIdentifier(rule)] ?? R.ruleName
switch ruleMask.ruleState(ruleName, at: loc) {
case .default:
return configuration.rules[ruleName] ?? false
guard let configSeverity = configuration.rules[ruleName] else { return false }
if case .disabled = configSeverity {
return false
} else {
return true
}
case .disabled:
return false
}
Expand Down
5 changes: 3 additions & 2 deletions Sources/SwiftFormat/Core/FindingEmitter.swift
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,8 @@ final class FindingEmitter {
_ message: Finding.Message,
category: FindingCategorizing,
location: Finding.Location? = nil,
notes: [Finding.Note] = []
notes: [Finding.Note] = [],
context: Context
) {
guard let consumer = self.consumer else { return }

Expand All @@ -54,7 +55,7 @@ final class FindingEmitter {
Finding(
category: category,
message: message,
severity: category.defaultSeverity,
severity: category.severity,
location: location,
notes: notes
)
Expand Down
13 changes: 12 additions & 1 deletion Sources/SwiftFormat/Core/Rule.swift
Original file line number Diff line number Diff line change
Expand Up @@ -86,12 +86,23 @@ extension Rule {
syntaxLocation = nil
}

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

let category = RuleBasedFindingCategory(ruleType: type(of: self), severity: severity)
context.findingEmitter.emit(
message,
category: category,
location: syntaxLocation.flatMap(Finding.Location.init),
notes: notes
notes: notes,
context: context
)
}
}

extension Configuration {
func findingSeverity(for rule: any Rule.Type, defaultSeverity: Finding.Severity) -> Finding.Severity {
guard let severity = self.rules[rule.ruleName] else { return defaultSeverity }
return severity.findingSeverity(ruleDefault: defaultSeverity)
}
}
8 changes: 6 additions & 2 deletions Sources/SwiftFormat/Core/RuleBasedFindingCategory.swift
Original file line number Diff line number Diff line change
Expand Up @@ -22,10 +22,14 @@ struct RuleBasedFindingCategory: FindingCategorizing {

var description: String { ruleType.ruleName }

var severity: Finding.Severity?
var severity: Finding.Severity

var name: String {
return description
}

/// Creates a finding category that wraps the given rule type.
init(ruleType: Rule.Type, severity: Finding.Severity? = nil) {
init(ruleType: Rule.Type, severity: Finding.Severity) {
self.ruleType = ruleType
self.severity = severity
}
Expand Down
97 changes: 53 additions & 44 deletions Sources/SwiftFormat/Core/RuleRegistry+Generated.swift
Original file line number Diff line number Diff line change
Expand Up @@ -13,49 +13,58 @@
// This file is automatically generated with generate-swift-format. Do not edit!

@_spi(Internal) public enum RuleRegistry {
public static let rules: [String: Bool] = [
"AllPublicDeclarationsHaveDocumentation": false,
"AlwaysUseLiteralForEmptyCollectionInit": false,
"AlwaysUseLowerCamelCase": true,
"AmbiguousTrailingClosureOverload": true,
"AvoidRetroactiveConformances": true,
"BeginDocumentationCommentWithOneLineSummary": false,
"DoNotUseSemicolons": true,
"DontRepeatTypeInStaticProperties": true,
"FileScopedDeclarationPrivacy": true,
"FullyIndirectEnum": true,
"GroupNumericLiterals": true,
"IdentifiersMustBeASCII": true,
"NeverForceUnwrap": false,
"NeverUseForceTry": false,
"NeverUseImplicitlyUnwrappedOptionals": false,
"NoAccessLevelOnExtensionDeclaration": true,
"NoAssignmentInExpressions": true,
"NoBlockComments": true,
"NoCasesWithOnlyFallthrough": true,
"NoEmptyLinesOpeningClosingBraces": false,
"NoEmptyTrailingClosureParentheses": true,
"NoLabelsInCasePatterns": true,
"NoLeadingUnderscores": false,
"NoParensAroundConditions": true,
"NoPlaygroundLiterals": true,
"NoVoidReturnOnFunctionSignature": true,
"OmitExplicitReturns": false,
"OneCasePerLine": true,
"OneVariableDeclarationPerLine": true,
"OnlyOneTrailingClosureArgument": true,
"OrderedImports": true,
"ReplaceForEachWithForLoop": true,
"ReturnVoidInsteadOfEmptyTuple": true,
"TypeNamesShouldBeCapitalized": true,
"UseEarlyExits": false,
"UseExplicitNilCheckInConditions": true,
"UseLetInEveryBoundCaseVariable": true,
"UseShorthandTypeNames": true,
"UseSingleLinePropertyGetter": true,
"UseSynthesizedInitializer": true,
"UseTripleSlashForDocumentationComments": true,
"UseWhereClausesInForLoops": false,
"ValidateDocumentationComments": false,
public static let rules: [String: Configuration.RuleSeverity] = [
"AllPublicDeclarationsHaveDocumentation": .disabled,
"AlwaysUseLiteralForEmptyCollectionInit": .disabled,
"AlwaysUseLowerCamelCase": .ruleDefault,
"AmbiguousTrailingClosureOverload": .ruleDefault,
"AvoidRetroactiveConformances": .ruleDefault,
"BeginDocumentationCommentWithOneLineSummary": .disabled,
"DoNotUseSemicolons": .ruleDefault,
"DontRepeatTypeInStaticProperties": .ruleDefault,
"FileScopedDeclarationPrivacy": .ruleDefault,
"FullyIndirectEnum": .ruleDefault,
"GroupNumericLiterals": .ruleDefault,
"IdentifiersMustBeASCII": .ruleDefault,
"NeverForceUnwrap": .disabled,
"NeverUseForceTry": .disabled,
"NeverUseImplicitlyUnwrappedOptionals": .disabled,
"NoAccessLevelOnExtensionDeclaration": .ruleDefault,
"NoAssignmentInExpressions": .ruleDefault,
"NoBlockComments": .ruleDefault,
"NoCasesWithOnlyFallthrough": .ruleDefault,
"NoEmptyLinesOpeningClosingBraces": .disabled,
"NoEmptyTrailingClosureParentheses": .ruleDefault,
"NoLabelsInCasePatterns": .ruleDefault,
"NoLeadingUnderscores": .disabled,
"NoParensAroundConditions": .ruleDefault,
"NoPlaygroundLiterals": .ruleDefault,
"NoVoidReturnOnFunctionSignature": .ruleDefault,
"OmitExplicitReturns": .disabled,
"OneCasePerLine": .ruleDefault,
"OneVariableDeclarationPerLine": .ruleDefault,
"OnlyOneTrailingClosureArgument": .ruleDefault,
"OrderedImports": .ruleDefault,
"ReplaceForEachWithForLoop": .ruleDefault,
"ReturnVoidInsteadOfEmptyTuple": .ruleDefault,
"TypeNamesShouldBeCapitalized": .ruleDefault,
"UseEarlyExits": .disabled,
"UseExplicitNilCheckInConditions": .ruleDefault,
"UseLetInEveryBoundCaseVariable": .ruleDefault,
"UseShorthandTypeNames": .ruleDefault,
"UseSingleLinePropertyGetter": .ruleDefault,
"UseSynthesizedInitializer": .ruleDefault,
"UseTripleSlashForDocumentationComments": .ruleDefault,
"UseWhereClausesInForLoops": .disabled,
"ValidateDocumentationComments": .disabled,
"AddLines": .ruleDefault,
"EndOfLineComment": .ruleDefault,
"Indentation": .ruleDefault,
"LineLength": .ruleDefault,
"RemoveLine": .ruleDefault,
"Spacing": .ruleDefault,
"SpacingCharacter": .ruleDefault,
"TrailingComma": .ruleDefault,
"TrailingWhitespace": .ruleDefault,
]
}
27 changes: 23 additions & 4 deletions Sources/SwiftFormat/PrettyPrint/PrettyPrint.swift
Original file line number Diff line number Diff line change
Expand Up @@ -475,7 +475,7 @@ public class PrettyPrinter {

if wasEndOfLine {
if !(canFit(comment.length) || isBreakingSuppressed) {
diagnose(.moveEndOfLineComment, category: .endOfLineComment)
diagnose(.moveEndOfLineComment, category: .endOfLineComment().withSeverity(configuration))
}
}
outputBuffer.write(comment.print(indent: currentIndentation))
Expand Down Expand Up @@ -515,9 +515,9 @@ public class PrettyPrinter {
startLineNumber != openCloseBreakCompensatingLineNumber && !isSingleElement
&& configuration.multiElementCollectionTrailingCommas
if shouldHaveTrailingComma && !hasTrailingComma {
diagnose(.addTrailingComma, category: .trailingComma)
diagnose(.addTrailingComma, category: .trailingComma().withSeverity(configuration))
} else if !shouldHaveTrailingComma && hasTrailingComma {
diagnose(.removeTrailingComma, category: .trailingComma)
diagnose(.removeTrailingComma, category: .trailingComma().withSeverity(configuration))
}

let shouldWriteComma = whitespaceOnly ? hasTrailingComma : shouldHaveTrailingComma
Expand Down Expand Up @@ -814,12 +814,14 @@ public class PrettyPrinter {

/// Emits a finding with the given message and category at the current location in `outputBuffer`.
private func diagnose(_ message: Finding.Message, category: PrettyPrintFindingCategory) {
if case .disabled = category.severity { return }
// Add 1 since columns uses 1-based indices.
let column = outputBuffer.column + 1
context.findingEmitter.emit(
message,
category: category,
location: Finding.Location(file: context.fileURL.path, line: outputBuffer.lineNumber, column: column)
location: Finding.Location(file: context.fileURL.path, line: outputBuffer.lineNumber, column: column),
context: context
)
}
}
Expand All @@ -834,3 +836,20 @@ extension Finding.Message {
fileprivate static let removeTrailingComma: Finding.Message =
"remove trailing comma from the last element in single line collection literal"
}

extension PrettyPrintFindingCategory {
func withSeverity(_ configuration: Configuration) -> Self {
let category: PrettyPrintFindingCategory = self
let severity =
configuration
.rules[category.name]?
.findingSeverity(ruleDefault: category.severity) ?? category.severity

switch self {
case .endOfLineComment:
return .endOfLineComment(severity)
case .trailingComma:
return .trailingComma(severity)
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -12,16 +12,29 @@

/// Categories for findings emitted by the pretty printer.
enum PrettyPrintFindingCategory: FindingCategorizing {

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

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

var description: String {
switch self {
case .endOfLineComment: return "EndOfLineComment"
case .trailingComma: return "TrailingComma"
}
}

var name: String {
self.description
}

var severity: Finding.Severity {
switch self {
case .endOfLineComment(let severity): return severity
case .trailingComma(let severity): return severity
}
}

}
Loading