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
+1
View File
@@ -21,6 +21,7 @@ disabled_rules:
- conditional_returns_on_newline
- contrasted_opening_brace
- convenience_type
- discouraged_default_parameter
- discouraged_optional_collection
- explicit_acl
- explicit_enum_raw_value
+5
View File
@@ -16,6 +16,11 @@
[SimplyDanny](https://github.com/SimplyDanny)
[#6501](https://github.com/realm/SwiftLint/issues/6501)
* Add `discouraged_default_parameter` opt-in rule that flags default parameter
values in functions with configurable access levels.
[William-Laverty](https://github.com/William-Laverty)
[#6488](https://github.com/realm/SwiftLint/issues/6488)
* Add `ignored_literal_argument_functions` option to the `force_unwrapping` rule
to skip violations for configurable function calls when all arguments are
literal values (e.g. `URL(string: "https://example.com")!`). Defaults
@@ -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
}
}
}
+6 -6
View File
@@ -79,6 +79,12 @@ final class DiscouragedAssertRuleGeneratedTests: SwiftLintTestCase {
}
}
final class DiscouragedDefaultParameterRuleGeneratedTests: SwiftLintTestCase {
func testWithDefaultConfiguration() {
verifyRule(DiscouragedDefaultParameterRule.description)
}
}
final class DiscouragedDirectInitRuleGeneratedTests: SwiftLintTestCase {
func testWithDefaultConfiguration() {
verifyRule(DiscouragedDirectInitRule.description)
@@ -150,9 +156,3 @@ final class EmptyCountRuleGeneratedTests: SwiftLintTestCase {
verifyRule(EmptyCountRule.description)
}
}
final class EmptyEnumArgumentsRuleGeneratedTests: SwiftLintTestCase {
func testWithDefaultConfiguration() {
verifyRule(EmptyEnumArgumentsRule.description)
}
}
+6 -6
View File
@@ -7,6 +7,12 @@
@testable import SwiftLintCore
import TestHelpers
final class EmptyEnumArgumentsRuleGeneratedTests: SwiftLintTestCase {
func testWithDefaultConfiguration() {
verifyRule(EmptyEnumArgumentsRule.description)
}
}
final class EmptyParametersRuleGeneratedTests: SwiftLintTestCase {
func testWithDefaultConfiguration() {
verifyRule(EmptyParametersRule.description)
@@ -150,9 +156,3 @@ final class ForWhereRuleGeneratedTests: SwiftLintTestCase {
verifyRule(ForWhereRule.description)
}
}
final class ForceCastRuleGeneratedTests: SwiftLintTestCase {
func testWithDefaultConfiguration() {
verifyRule(ForceCastRule.description)
}
}
+6 -6
View File
@@ -7,6 +7,12 @@
@testable import SwiftLintCore
import TestHelpers
final class ForceCastRuleGeneratedTests: SwiftLintTestCase {
func testWithDefaultConfiguration() {
verifyRule(ForceCastRule.description)
}
}
final class ForceTryRuleGeneratedTests: SwiftLintTestCase {
func testWithDefaultConfiguration() {
verifyRule(ForceTryRule.description)
@@ -150,9 +156,3 @@ final class LegacyCGGeometryFunctionsRuleGeneratedTests: SwiftLintTestCase {
verifyRule(LegacyCGGeometryFunctionsRule.description)
}
}
final class LegacyConstantRuleGeneratedTests: SwiftLintTestCase {
func testWithDefaultConfiguration() {
verifyRule(LegacyConstantRule.description)
}
}
+6 -6
View File
@@ -7,6 +7,12 @@
@testable import SwiftLintCore
import TestHelpers
final class LegacyConstantRuleGeneratedTests: SwiftLintTestCase {
func testWithDefaultConfiguration() {
verifyRule(LegacyConstantRule.description)
}
}
final class LegacyConstructorRuleGeneratedTests: SwiftLintTestCase {
func testWithDefaultConfiguration() {
verifyRule(LegacyConstructorRule.description)
@@ -150,9 +156,3 @@ final class NSLocalizedStringRequireBundleRuleGeneratedTests: SwiftLintTestCase
verifyRule(NSLocalizedStringRequireBundleRule.description)
}
}
final class NSNumberInitAsFunctionReferenceRuleGeneratedTests: SwiftLintTestCase {
func testWithDefaultConfiguration() {
verifyRule(NSNumberInitAsFunctionReferenceRule.description)
}
}
+6 -6
View File
@@ -7,6 +7,12 @@
@testable import SwiftLintCore
import TestHelpers
final class NSNumberInitAsFunctionReferenceRuleGeneratedTests: SwiftLintTestCase {
func testWithDefaultConfiguration() {
verifyRule(NSNumberInitAsFunctionReferenceRule.description)
}
}
final class NSObjectPreferIsEqualRuleGeneratedTests: SwiftLintTestCase {
func testWithDefaultConfiguration() {
verifyRule(NSObjectPreferIsEqualRule.description)
@@ -150,9 +156,3 @@ final class PeriodSpacingRuleGeneratedTests: SwiftLintTestCase {
verifyRule(PeriodSpacingRule.description)
}
}
final class PreferAssetSymbolsRuleGeneratedTests: SwiftLintTestCase {
func testWithDefaultConfiguration() {
verifyRule(PreferAssetSymbolsRule.description)
}
}
+6 -6
View File
@@ -7,6 +7,12 @@
@testable import SwiftLintCore
import TestHelpers
final class PreferAssetSymbolsRuleGeneratedTests: SwiftLintTestCase {
func testWithDefaultConfiguration() {
verifyRule(PreferAssetSymbolsRule.description)
}
}
final class PreferConditionListRuleGeneratedTests: SwiftLintTestCase {
func testWithDefaultConfiguration() {
verifyRule(PreferConditionListRule.description)
@@ -150,9 +156,3 @@ final class RedundantDiscardableLetRuleGeneratedTests: SwiftLintTestCase {
verifyRule(RedundantDiscardableLetRule.description)
}
}
final class RedundantNilCoalescingRuleGeneratedTests: SwiftLintTestCase {
func testWithDefaultConfiguration() {
verifyRule(RedundantNilCoalescingRule.description)
}
}
+6 -6
View File
@@ -7,6 +7,12 @@
@testable import SwiftLintCore
import TestHelpers
final class RedundantNilCoalescingRuleGeneratedTests: SwiftLintTestCase {
func testWithDefaultConfiguration() {
verifyRule(RedundantNilCoalescingRule.description)
}
}
final class RedundantObjcAttributeRuleGeneratedTests: SwiftLintTestCase {
func testWithDefaultConfiguration() {
verifyRule(RedundantObjcAttributeRule.description)
@@ -150,9 +156,3 @@ final class StrictFilePrivateRuleGeneratedTests: SwiftLintTestCase {
verifyRule(StrictFilePrivateRule.description)
}
}
final class StrongIBOutletRuleGeneratedTests: SwiftLintTestCase {
func testWithDefaultConfiguration() {
verifyRule(StrongIBOutletRule.description)
}
}
+6 -6
View File
@@ -7,6 +7,12 @@
@testable import SwiftLintCore
import TestHelpers
final class StrongIBOutletRuleGeneratedTests: SwiftLintTestCase {
func testWithDefaultConfiguration() {
verifyRule(StrongIBOutletRule.description)
}
}
final class SuperfluousElseRuleGeneratedTests: SwiftLintTestCase {
func testWithDefaultConfiguration() {
verifyRule(SuperfluousElseRule.description)
@@ -150,9 +156,3 @@ final class UnneededSynthesizedInitializerRuleGeneratedTests: SwiftLintTestCase
verifyRule(UnneededSynthesizedInitializerRule.description)
}
}
final class UnneededThrowsRuleGeneratedTests: SwiftLintTestCase {
func testWithDefaultConfiguration() {
verifyRule(UnneededThrowsRule.description)
}
}
@@ -7,6 +7,12 @@
@testable import SwiftLintCore
import TestHelpers
final class UnneededThrowsRuleGeneratedTests: SwiftLintTestCase {
func testWithDefaultConfiguration() {
verifyRule(UnneededThrowsRule.description)
}
}
final class UnownedVariableCaptureRuleGeneratedTests: SwiftLintTestCase {
func testWithDefaultConfiguration() {
verifyRule(UnownedVariableCaptureRule.description)
@@ -205,6 +205,12 @@ discouraged_assert:
meta:
opt-in: true
correctable: false
discouraged_default_parameter:
severity: warning
disallowed_access_levels: [internal, package]
meta:
opt-in: true
correctable: false
discouraged_direct_init:
severity: warning
types: ["Bundle", "Bundle.init", "NSError", "NSError.init", "UIDevice", "UIDevice.init"]