Skip to content

Commit

Permalink
Add include_bare_init option to explicit_init rule (#5203)
Browse files Browse the repository at this point in the history
  • Loading branch information
mildm8nnered authored Sep 4, 2023
1 parent a459ccf commit 0c9827b
Show file tree
Hide file tree
Showing 5 changed files with 81 additions and 15 deletions.
11 changes: 8 additions & 3 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@
Bash, Zsh and fish.
[SimplyDanny](https://github.com/SimplyDanny)

* Add new `private_swiftui_state_property` opt-in rule to encourage setting
* Add new `private_swiftui_state_property` opt-in rule to encourage setting
SwiftUI `@State` and `@StateObject` properties to private.
[mt00chikin](https://github.com/mt00chikin)
[#3173](https://github.com/realm/SwiftLint/issues/3173)
Expand All @@ -53,10 +53,10 @@
[keith](https://github.com/keith)
[5139](https://github.com/realm/SwiftLint/pull/5139)

* Show a rule's active YAML configuration in output of
* Show a rule's active YAML configuration in output of
`swiftlint rules <rule>`.
[SimplyDanny](https://github.com/SimplyDanny)

* Add `invokeTest()` to `overridden_super_call` defaults.
[DylanBettermannDD](https://github.com/DylanBettermannDD)

Expand All @@ -68,6 +68,11 @@
values for configurations being printed for a single rule or all rules.
[SimplyDanny](https://github.com/SimplyDanny)

* Add `include_bare_init` option to the `explicit_init` rule. `include_bare_init`
encourages using named constructors over `.init()` and type inference.
[Martin Redington](https://github.com/mildm8nnered)
[#5203](https://github.com/realm/SwiftLint/issues/5203)

#### Bug Fixes

* Fix false positive in `control_statement` rule that triggered on conditions
Expand Down
38 changes: 27 additions & 11 deletions Source/SwiftLintBuiltInRules/Rules/Idiomatic/ExplicitInitRule.swift
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import SwiftSyntax
import SwiftSyntaxBuilder

struct ExplicitInitRule: SwiftSyntaxCorrectableRule, ConfigurationProviderRule, OptInRule {
var configuration = SeverityConfiguration<Self>(.warning)
var configuration = ExplicitInitConfiguration()

static let description = RuleDescription(
identifier: "explicit_init",
Expand Down Expand Up @@ -170,28 +170,36 @@ struct ExplicitInitRule: SwiftSyntaxCorrectableRule, ConfigurationProviderRule,
)

func makeVisitor(file: SwiftLintFile) -> ViolationsSyntaxVisitor {
Visitor(viewMode: .sourceAccurate)
Visitor(viewMode: .sourceAccurate, includeBareInit: configuration.includeBareInit)
}

func makeRewriter(file: SwiftLintFile) -> ViolationsSyntaxRewriter? {
Rewriter(
locationConverter: file.locationConverter,
disabledRegions: disabledRegions(file: file)
)
Rewriter(locationConverter: file.locationConverter, disabledRegions: disabledRegions(file: file))
}
}

private extension ExplicitInitRule {
final class Visitor: ViolationsSyntaxVisitor {
private let includeBareInit: Bool

init(viewMode: SyntaxTreeViewMode, includeBareInit: Bool) {
self.includeBareInit = includeBareInit
super.init(viewMode: .sourceAccurate)
}

override func visitPost(_ node: FunctionCallExprSyntax) {
guard
let calledExpression = node.calledExpression.as(MemberAccessExprSyntax.self),
let violationPosition = calledExpression.explicitInitPosition
else {
guard let calledExpression = node.calledExpression.as(MemberAccessExprSyntax.self) else {
return
}

violations.append(violationPosition)
if let violationPosition = calledExpression.explicitInitPosition {
violations.append(violationPosition)
}

if includeBareInit, let violationPosition = calledExpression.bareInitPosition {
let reason = "Prefer named constructors over .init and type inference"
violations.append(ReasonedRuleViolation(position: violationPosition, reason: reason))
}
}
}

Expand Down Expand Up @@ -230,6 +238,14 @@ private extension MemberAccessExprSyntax {
return nil
}
}

var bareInitPosition: AbsolutePosition? {
if base == nil, declName.baseName.text == "init" {
return period.positionAfterSkippingLeadingTrivia
} else {
return nil
}
}
}

private extension ExprSyntax {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
struct ExplicitInitConfiguration: SeverityBasedRuleConfiguration, Equatable {
typealias Parent = ExplicitInitRule

@ConfigurationElement(key: "severity")
private(set) var severityConfiguration = SeverityConfiguration<Parent>(.warning)
@ConfigurationElement(key: "include_bare_init")
private(set) var includeBareInit = false

mutating func apply(configuration: Any) throws {
guard let configuration = configuration as? [String: Any] else {
throw Issue.unknownConfiguration(ruleID: Parent.identifier)
}

if let severityString = configuration[$severityConfiguration] as? String {
try severityConfiguration.apply(configuration: severityString)
}

if let includeBareInit = configuration[$includeBareInit] as? Bool {
self.includeBareInit = includeBareInit
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -501,7 +501,7 @@ public extension SeverityConfiguration {
guard let option = description.options.onlyElement?.value, case .symbol = option else {
queuedFatalError(
"""
Severity configurations must have exaclty one option that is a violation severity.
Severity configurations must have exactly one option that is a violation severity.
"""
)
}
Expand Down
23 changes: 23 additions & 0 deletions Tests/SwiftLintFrameworkTests/ExplicitInitRuleTests.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
@testable import SwiftLintBuiltInRules

class ExplicitInitRuleTests: SwiftLintTestCase {
func testIncludeBareInit() {
let nonTriggeringExamples = [
Example("let foo = Foo()"),
Example("let foo = init()")
] + ExplicitInitRule.description.nonTriggeringExamples

let triggeringExamples = [
Example("let foo: Foo = ↓.init()"),
Example("let foo: [Foo] = [↓.init(), ↓.init()]"),
Example("foo(↓.init())")
]

let description = ExplicitInitRule.description
.with(nonTriggeringExamples: nonTriggeringExamples)
.with(triggeringExamples: triggeringExamples)
.with(corrections: [:])

verifyRule(description, ruleConfiguration: ["include_bare_init": true])
}
}

0 comments on commit 0c9827b

Please sign in to comment.