Skip to content

Commit

Permalink
Add comma_inheritance_rule rule
Browse files Browse the repository at this point in the history
Fixes #3950
  • Loading branch information
marcelofabri committed Apr 17, 2022
1 parent a786e31 commit 74602be
Show file tree
Hide file tree
Showing 5 changed files with 130 additions and 3 deletions.
5 changes: 5 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,11 @@
[Marcelo Fabri](https://github.com/marcelofabri)
[#3343](https://github.com/realm/SwiftLint/issues/3343)

* Add `comma_inheritance` rule to validate that inheritance clauses use commas
instead of `&`.
[Marcelo Fabri](https://github.com/marcelofabri)
[#3950](https://github.com/realm/SwiftLint/issues/3950)

#### Bug Fixes

* Fix false positives in `unused_closure_parameter` when using parameters with
Expand Down
1 change: 1 addition & 0 deletions Source/SwiftLintFramework/Models/PrimaryRuleList.swift
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ public let primaryRuleList = RuleList(rules: [
ClosureSpacingRule.self,
CollectionAlignmentRule.self,
ColonRule.self,
CommaInheritanceRule.self,
CommaRule.self,
CommentSpacingRule.self,
CompilerProtocolInitRule.self,
Expand Down
115 changes: 115 additions & 0 deletions Source/SwiftLintFramework/Rules/Style/CommaInheritanceRule.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,115 @@
import Foundation
import SourceKittenFramework
import SwiftSyntax

public struct CommaInheritanceRule: SubstitutionCorrectableRule, ConfigurationProviderRule, AutomaticTestableRule {
public var configuration = SeverityConfiguration(.warning)

public init() {}

public static let description = RuleDescription(
identifier: "comma_inheritance",
name: "Comma Inheritance Rule",
description: "Use commas to separate types in inheritance lists",
kind: .style,
nonTriggeringExamples: [
Example("struct A: Codable, Equatable {}"),
Example("enum B: Codable, Equatable {}"),
Example("class C: Codable, Equatable {}"),
Example("protocol D: Codable, Equatable {}"),
Example("typealias E = Equatable & Codable"),
Example("func foo<T: Equatable & Codable>(_ param: T) {}"),
Example("""
protocol G {
associatedtype Model: Codable, Equatable
}
""")
],
triggeringExamples: [
Example("struct A: Codable↓ & Equatable {}"),
Example("struct A: Codable↓ & Equatable {}"),
Example("struct A: Codable↓&Equatable {}"),
Example("struct A: Codable↓& Equatable {}"),
Example("enum B: Codable↓ & Equatable {}"),
Example("class C: Codable↓ & Equatable {}"),
Example("protocol D: Codable↓ & Equatable {}"),
Example("""
protocol G {
associatedtype Model: Codable↓ & Equatable
}
""")
],
corrections: [
Example("struct A: Codable↓ & Equatable {}"): Example("struct A: Codable, Equatable {}"),
Example("struct A: Codable↓ & Equatable {}"): Example("struct A: Codable, Equatable {}"),
Example("struct A: Codable↓&Equatable {}"): Example("struct A: Codable, Equatable {}"),
Example("struct A: Codable↓& Equatable {}"): Example("struct A: Codable, Equatable {}"),
Example("enum B: Codable↓ & Equatable {}"): Example("enum B: Codable, Equatable {}"),
Example("class C: Codable↓ & Equatable {}"): Example("class C: Codable, Equatable {}"),
Example("protocol D: Codable↓ & Equatable {}"): Example("protocol D: Codable, Equatable {}"),
Example("""
protocol G {
associatedtype Model: Codable↓ & Equatable
}
"""): Example("""
protocol G {
associatedtype Model: Codable, Equatable
}
""")
]
)

// MARK: - Rule

public func validate(file: SwiftLintFile) -> [StyleViolation] {
return violationRanges(in: file).map {
StyleViolation(ruleDescription: Self.description,
severity: configuration.severity,
location: Location(file: file, characterOffset: $0.location))
}
}

// MARK: - SubstitutionCorrectableRule

public func substitution(for violationRange: NSRange, in file: SwiftLintFile) -> (NSRange, String)? {
return (violationRange, ", ")
}

public func violationRanges(in file: SwiftLintFile) -> [NSRange] {
guard let tree = file.syntaxTree else {
return []
}

let visitor = CommaInheritanceRuleVisitor()
visitor.walk(tree)
return visitor.violationRanges.compactMap {
file.stringView.byteRangeToNSRange($0)
}
}
}

private final class CommaInheritanceRuleVisitor: SyntaxVisitor {
private(set) var violationRanges: [ByteRange] = []

override func visitPost(_ node: InheritedTypeSyntax) {
for type in node.children {
guard let composition = type.as(CompositionTypeSyntax.self) else {
continue
}

for ampersand in composition.elements.compactMap(\.ampersand) {
let position: AbsolutePosition
if let previousToken = ampersand.previousToken {
position = previousToken.endPositionBeforeTrailingTrivia
} else {
position = ampersand.position
}

violationRanges.append(ByteRange(
location: ByteCount(position),
length: ByteCount(ampersand.endPosition.utf8Offset - position.utf8Offset)
))
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,12 @@ class ClosureSpacingRuleTests: XCTestCase {
}
}

class CommaInheritanceRuleTests: XCTestCase {
func testWithDefaultConfiguration() {
verifyRule(CommaInheritanceRule.description)
}
}

class CommaRuleTests: XCTestCase {
func testWithDefaultConfiguration() {
verifyRule(CommaRule.description)
Expand Down
6 changes: 3 additions & 3 deletions Tests/SwiftLintFrameworkTests/CollectingRuleTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ class CollectingRuleTests: XCTestCase {
}

func testCollectsAnalyzerFiles() {
struct Spec: MockCollectingRule & AnalyzerRule {
struct Spec: MockCollectingRule, AnalyzerRule {
func collectInfo(for file: SwiftLintFile, compilerArguments: [String]) -> [String] {
return compilerArguments
}
Expand All @@ -53,7 +53,7 @@ class CollectingRuleTests: XCTestCase {
}

func testCorrects() {
struct Spec: MockCollectingRule & CollectingCorrectableRule {
struct Spec: MockCollectingRule, CollectingCorrectableRule {
func collectInfo(for file: SwiftLintFile) -> String {
return file.contents
}
Expand All @@ -77,7 +77,7 @@ class CollectingRuleTests: XCTestCase {
}
}

struct AnalyzerSpec: MockCollectingRule & AnalyzerRule & CollectingCorrectableRule {
struct AnalyzerSpec: MockCollectingRule, AnalyzerRule, CollectingCorrectableRule {
func collectInfo(for file: SwiftLintFile, compilerArguments: [String]) -> String {
return file.contents
}
Expand Down

0 comments on commit 74602be

Please sign in to comment.