Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add opt-in rule balanced_xctest_lifecycle #3495

Merged
merged 7 commits into from
Mar 1, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 7 additions & 2 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -48,9 +48,10 @@
`assert(false)`.
[Otavio Cordeiro](https://github.com/otaviocc)

* Add opt-in rule `private_subject` rule which warns against
public Combine subjects.
* Adds `balanced_xctest_lifecycle` opt-in rule to enforce balanced `setUp`
and `tearDown` methods in a test class.
[Otavio Cordeiro](https://github.com/otaviocc)
[#3452](https://github.com/realm/SwiftLint/issues/3452)

* Tweak the auto-correction result console output for clarity.
[mokagio](https://github.com/mokagio)
Expand Down Expand Up @@ -91,6 +92,10 @@
variable when running analyze.
[jpsim](https://github.com/jpsim)

* Add opt-in rule `private_subject` rule which warns against
public Combine subjects.
[Otavio Cordeiro](https://github.com/otaviocc)

#### Bug Fixes

* Fix `custom_rules` merging when the parent configuration is based on
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 @@ -155,6 +155,7 @@ public let primaryRuleList = RuleList(rules: [
RedundantVoidReturnRule.self,
RequiredDeinitRule.self,
RequiredEnumCaseRule.self,
BalancedXCTestLifecycleRule.self,
ReturnArrowWhitespaceRule.self,
ShorthandOperatorRule.self,
SingleTestClassRule.self,
Expand Down
159 changes: 159 additions & 0 deletions Source/SwiftLintFramework/Rules/Lint/BalancedXCTestLifecycleRule.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,159 @@
import SourceKittenFramework

public struct BalancedXCTestLifecycleRule: Rule, OptInRule, ConfigurationProviderRule, AutomaticTestableRule {
// MARK: - Properties

public var configuration = SeverityConfiguration(.warning)

public static let description = RuleDescription(
identifier: "balanced_xctest_lifecycle",
name: "Balanced XCTest life-cycle",
description: "Test classes must implement balanced setUp and tearDown methods.",
kind: .lint,
nonTriggeringExamples: [
Example(#"""
final class FooTests: XCTestCase {
override func setUp() {}
override func tearDown() {}
}
"""#),
Example(#"""
final class FooTests: XCTestCase {
override func setUpWithError() throws {}
override func tearDown() {}
}
"""#),
Example(#"""
final class FooTests: XCTestCase {
override func setUp() {}
override func tearDownWithError() throws {}
}
"""#),
Example(#"""
final class FooTests: XCTestCase {
override func setUpWithError() throws {}
override func tearDownWithError() throws {}
}
final class BarTests: XCTestCase {
override func setUpWithError() throws {}
override func tearDownWithError() throws {}
}
"""#),
Example(#"""
struct FooTests {
override func setUp() {}
}
class BarTests {
override func setUpWithError() throws {}
}
"""#),
Example(#"""
final class FooTests: XCTestCase {
override func setUpAlLExamples() {}
}
"""#),
Example(#"""
final class FooTests: XCTestCase {
class func setUp() {}
class func tearDown() {}
}
"""#)
],
triggeringExamples: [
Example(#"""
final class ↓FooTests: XCTestCase {
override func setUp() {}
}
"""#),
Example(#"""
final class ↓FooTests: XCTestCase {
override func setUpWithError() throws {}
}
"""#),
Example(#"""
final class FooTests: XCTestCase {
override func setUp() {}
override func tearDownWithError() throws {}
}
final class ↓BarTests: XCTestCase {
override func setUpWithError() throws {}
}
"""#),
Example(#"""
final class ↓FooTests: XCTestCase {
class func tearDown() {}
}
"""#),
Example(#"""
final class ↓FooTests: XCTestCase {
override func tearDown() {}
}
"""#),
Example(#"""
final class ↓FooTests: XCTestCase {
override func tearDownWithError() throws {}
}
"""#),
Example(#"""
final class FooTests: XCTestCase {
override func setUp() {}
override func tearDownWithError() throws {}
}
final class ↓BarTests: XCTestCase {
override func tearDownWithError() throws {}
}
"""#)
]
)

// MARK: - Life cycle

public init() {}

// MARK: - Public

public func validate(file: SwiftLintFile) -> [StyleViolation] {
testClasses(in: file).compactMap { violations(in: file, for: $0) }
}

// MARK: - Private

private func testClasses(in file: SwiftLintFile) -> [SourceKittenDictionary] {
file.structureDictionary.substructure.filter { dictionary in
guard dictionary.declarationKind == .class else { return false }
return dictionary.inheritedTypes.contains("XCTestCase")
}
}

private func violations(in file: SwiftLintFile,
for dictionary: SourceKittenDictionary) -> StyleViolation? {
let methods = dictionary.substructure
.compactMap { XCTMethod($0.name) }

guard
methods.contains(.setUp) != methods.contains(.tearDown),
let offset = dictionary.nameOffset
else {
return nil
}

return StyleViolation(ruleDescription: Self.description,
severity: configuration.severity,
location: Location(file: file, byteOffset: offset))
}
}

// MARK: - Private

private enum XCTMethod {
case setUp
case tearDown

init?(_ name: String?) {
switch name {
case "setUp()", "setUpWithError()": self = .setUp
case "tearDown()", "tearDownWithError()": self = .tearDown
default: return nil
}
}
}
16 changes: 14 additions & 2 deletions Tests/LinuxMain.swift
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
// Generated using Sourcery 1.0.2 — https://github.com/krzysztofzablocki/Sourcery
// Generated using Sourcery 1.2.0 — https://github.com/krzysztofzablocki/Sourcery
// DO NOT EDIT

@testable import SwiftLintFrameworkTests
import XCTest

Expand Down Expand Up @@ -34,6 +33,12 @@ extension AttributesRuleTests {
]
}

extension BalancedXCTestLifecycleRuleTests {
static var allTests: [(String, (BalancedXCTestLifecycleRuleTests) -> () throws -> Void)] = [
("testWithDefaultConfiguration", testWithDefaultConfiguration)
]
}

extension BlockBasedKVORuleTests {
static var allTests: [(String, (BlockBasedKVORuleTests) -> () throws -> Void)] = [
("testWithDefaultConfiguration", testWithDefaultConfiguration)
Expand Down Expand Up @@ -185,12 +190,15 @@ extension ConfigurationTests {
("testInitWithRelativePathAndRootPath", testInitWithRelativePathAndRootPath),
("testEnableAllRulesConfiguration", testEnableAllRulesConfiguration),
("testOnlyRules", testOnlyRules),
("testOnlyRulesWithCustomRules", testOnlyRulesWithCustomRules),
("testWarningThreshold_value", testWarningThreshold_value),
("testWarningThreshold_nil", testWarningThreshold_nil),
("testOtherRuleConfigurationsAlongsideOnlyRules", testOtherRuleConfigurationsAlongsideOnlyRules),
("testDisabledRules", testDisabledRules),
("testDisabledRulesWithUnknownRule", testDisabledRulesWithUnknownRule),
("testDuplicatedRules", testDuplicatedRules),
("testIncludedExcludedRelativeLocationLevel1", testIncludedExcludedRelativeLocationLevel1),
("testIncludedExcludedRelativeLocationLevel0", testIncludedExcludedRelativeLocationLevel0),
("testExcludedPaths", testExcludedPaths),
("testForceExcludesFile", testForceExcludesFile),
("testForceExcludesFileNotPresentInExcluded", testForceExcludesFileNotPresentInExcluded),
Expand Down Expand Up @@ -220,6 +228,9 @@ extension ConfigurationTests {
("testOnlyRulesMerging", testOnlyRulesMerging),
("testCustomRulesMerging", testCustomRulesMerging),
("testMergingAllowsDisablingParentsCustomRules", testMergingAllowsDisablingParentsCustomRules),
("testCustomRulesMergingWithOnlyRulesCase1", testCustomRulesMergingWithOnlyRulesCase1),
("testCustomRulesMergingWithOnlyRulesCase2", testCustomRulesMergingWithOnlyRulesCase2),
("testCustomRulesReconfiguration", testCustomRulesReconfiguration),
("testLevel0", testLevel0),
("testLevel1", testLevel1),
("testLevel2", testLevel2),
Expand Down Expand Up @@ -1785,6 +1796,7 @@ XCTMain([
testCase(AnyObjectProtocolRuleTests.allTests),
testCase(ArrayInitRuleTests.allTests),
testCase(AttributesRuleTests.allTests),
testCase(BalancedXCTestLifecycleRuleTests.allTests),
testCase(BlockBasedKVORuleTests.allTests),
testCase(ClassDelegateProtocolRuleTests.allTests),
testCase(ClosingBraceRuleTests.allTests),
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
// Generated using Sourcery 1.0.2 — https://github.com/krzysztofzablocki/Sourcery
// Generated using Sourcery 1.2.0 — https://github.com/krzysztofzablocki/Sourcery
// DO NOT EDIT

import SwiftLintFramework
import XCTest

Expand All @@ -18,6 +17,12 @@ class ArrayInitRuleTests: XCTestCase {
}
}

class BalancedXCTestLifecycleRuleTests: XCTestCase {
func testWithDefaultConfiguration() {
verifyRule(BalancedXCTestLifecycleRule.description)
}
}

class BlockBasedKVORuleTests: XCTestCase {
func testWithDefaultConfiguration() {
verifyRule(BlockBasedKVORule.description)
Expand Down