mirror of
https://github.com/realm/SwiftLint.git
synced 2026-05-07 20:12:49 +00:00
Add disallow_default_parameter opt-in rule (#6506)
Co-authored-by: Danny Mösch <danny.moesch@icloud.com>
This commit is contained in:
@@ -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
|
||||
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
+21
@@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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"]
|
||||
|
||||
Reference in New Issue
Block a user