Add disallow_default_parameter opt-in rule (#6506)

Co-authored-by: Danny Mösch <danny.moesch@icloud.com>
This commit is contained in:
William Laverty
2026-03-05 13:22:21 -08:00
committed by GitHub
parent afae448953
commit 87bfcc6832
15 changed files with 179 additions and 48 deletions
@@ -39,6 +39,7 @@ public let builtInRules: [any Rule.Type] = [
DirectReturnRule.self,
DiscardedNotificationCenterObserverRule.self,
DiscouragedAssertRule.self,
DiscouragedDefaultParameterRule.self,
DiscouragedDirectInitRule.self,
DiscouragedNoneNameRule.self,
DiscouragedObjectLiteralRule.self,
@@ -0,0 +1,91 @@
import SwiftSyntax
@SwiftSyntaxRule(optIn: true)
struct DiscouragedDefaultParameterRule: Rule {
var configuration = DiscouragedDefaultParameterConfiguration()
static let description = RuleDescription(
identifier: "discouraged_default_parameter",
name: "Discouraged Default Parameter",
description: "Default parameter values should not be used in functions with certain access levels.",
rationale: """
By discouraging default parameter values in functions, that are exposed to other source files in the module
or package and their consumers, we can promote call sites and reduce the likelihood of bugs caused by
unexpected (or changed) default values being used.
""",
kind: .lint,
nonTriggeringExamples: [
Example("public func foo(bar: Int = 0) {}"),
Example("open func foo(bar: Int = 0) {}"),
Example("func foo(bar: Int) {}"),
Example("private func foo(bar: Int = 0) {}"),
Example("fileprivate func foo(bar: Int = 0) {}"),
Example("public init(value: Int = 42) {}"),
Example(
"func foo(bar: Int = 0) {}",
configuration: ["disallowed_access_levels": ["private"]]
),
],
triggeringExamples: [
Example("func foo(bar: Int ↓= 0) {}"),
Example("internal func foo(bar: Int ↓= 0) {}"),
Example("package func foo(bar: Int ↓= 0) {}"),
Example("func foo(bar: Int ↓= 0, baz: String ↓= \"\") {}"),
Example("init(value: Int ↓= 42) {}"),
Example(
"private func foo(bar: Int ↓= 0) {}",
configuration: ["disallowed_access_levels": ["private"]]
),
Example(
"fileprivate func foo(bar: Int ↓= 0) {}",
configuration: ["disallowed_access_levels": ["fileprivate"]]
),
]
)
}
private extension DiscouragedDefaultParameterRule {
final class Visitor: ViolationsSyntaxVisitor<ConfigurationType> {
override func visitPost(_ node: FunctionDeclSyntax) {
collectViolations(modifiers: node.modifiers, parameterClause: node.signature.parameterClause)
}
override func visitPost(_ node: InitializerDeclSyntax) {
collectViolations(modifiers: node.modifiers, parameterClause: node.signature.parameterClause)
}
override func visitPost(_ node: SubscriptDeclSyntax) {
collectViolations(modifiers: node.modifiers, parameterClause: node.parameterClause)
}
private func collectViolations(
modifiers: DeclModifierListSyntax,
parameterClause: FunctionParameterClauseSyntax
) {
guard let accessLevel = effectiveAccessLevel(modifiers),
configuration.disallowedAccessLevels.contains(accessLevel) else {
return
}
let levelName = accessLevel.rawValue
for param in parameterClause.parameters {
if let defaultValue = param.defaultValue {
violations.append(
ReasonedRuleViolation(
position: defaultValue.positionAfterSkippingLeadingTrivia,
reason: "Default parameter values should not be used in '\(levelName)' functions"
)
)
}
}
}
private func effectiveAccessLevel(_ modifiers: DeclModifierListSyntax)
-> DiscouragedDefaultParameterConfiguration.AccessLevel? {
if modifiers.contains(keyword: .private) { return .private }
if modifiers.contains(keyword: .fileprivate) { return .fileprivate }
if modifiers.contains(keyword: .package) { return .package }
if modifiers.contains(keyword: .public) || modifiers.contains(keyword: .open) { return nil }
return .internal
}
}
}
@@ -0,0 +1,21 @@
import SwiftLintCore
@AutoConfigParser
struct DiscouragedDefaultParameterConfiguration: SeverityBasedRuleConfiguration {
@ConfigurationElement(key: "severity")
private(set) var severityConfiguration = SeverityConfiguration<Parent>(.warning)
@ConfigurationElement(key: "disallowed_access_levels")
private(set) var disallowedAccessLevels: Set<AccessLevel> = [.internal, .package]
@AcceptableByConfigurationElement
enum AccessLevel: String, Comparable {
case `private`
case `fileprivate`
case `internal`
case `package`
static func < (lhs: Self, rhs: Self) -> Bool {
lhs.rawValue < rhs.rawValue
}
}
}