Skip to content

Commit dca8bec

Browse files
committed
Merge branch 'adding-swift-testing-checks' into allow-underscore-for-testing-funcs
2 parents 77779ac + 5a63aee commit dca8bec

8 files changed

+77
-0
lines changed

Documentation/RuleDocumentation.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -189,6 +189,7 @@ Force-unwraps are strongly discouraged and must be documented.
189189

190190
This rule does not apply to test code, defined as code which:
191191
* Contains the line `import XCTest`
192+
* The function is marked with `@Test` attribute
192193

193194
Lint: If a force unwrap is used, a lint warning is raised.
194195

@@ -200,6 +201,7 @@ Force-try (`try!`) is forbidden.
200201

201202
This rule does not apply to test code, defined as code which:
202203
* Contains the line `import XCTest`
204+
* The function is marked with `@Test` attribute
203205

204206
Lint: Using `try!` results in a lint error.
205207

@@ -215,6 +217,7 @@ Certain properties (e.g. `@IBOutlet`) tied to the UI lifecycle are ignored.
215217

216218
This rule does not apply to test code, defined as code which:
217219
* Contains the line `import XCTest`
220+
* The function is marked with `@Test` attribute
218221

219222
TODO: Create exceptions for other UI elements (ex: viewDidLoad)
220223

Sources/SwiftFormat/Core/SyntaxProtocol+Convenience.swift

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -149,6 +149,21 @@ extension SyntaxProtocol {
149149
}
150150
return leadingTrivia.hasAnyComments
151151
}
152+
153+
/// Indicates whether the node has any function ancestor marked with `@Test` attribute.
154+
var hasTestAncestor: Bool {
155+
var parent = self.parent
156+
while let existingParent = parent {
157+
if let functionDecl = existingParent.as(FunctionDeclSyntax.self),
158+
functionDecl.attributes.contains(where: {
159+
$0.as(AttributeSyntax.self)?.attributeName.as(IdentifierTypeSyntax.self)?.name.text == "Test"
160+
}) {
161+
return true
162+
}
163+
parent = existingParent.parent
164+
}
165+
return false
166+
}
152167
}
153168

154169
extension SyntaxCollection {

Sources/SwiftFormat/Rules/NeverForceUnwrap.swift

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,8 @@ public final class NeverForceUnwrap: SyntaxLintRule {
3434

3535
public override func visit(_ node: ForceUnwrapExprSyntax) -> SyntaxVisitorContinueKind {
3636
guard context.importsXCTest == .doesNotImportXCTest else { return .skipChildren }
37+
// Allow force unwrapping if it is in a function marked with @Test attribute.
38+
if node.hasTestAncestor { return .skipChildren }
3739
diagnose(.doNotForceUnwrap(name: node.expression.trimmedDescription), on: node)
3840
return .skipChildren
3941
}
@@ -44,6 +46,8 @@ public final class NeverForceUnwrap: SyntaxLintRule {
4446
guard context.importsXCTest == .doesNotImportXCTest else { return .skipChildren }
4547
guard let questionOrExclamation = node.questionOrExclamationMark else { return .skipChildren }
4648
guard questionOrExclamation.tokenKind == .exclamationMark else { return .skipChildren }
49+
// Allow force cast if it is in a function marked with @Test attribute.
50+
if node.hasTestAncestor { return .skipChildren }
4751
diagnose(.doNotForceCast(name: node.type.trimmedDescription), on: node)
4852
return .skipChildren
4953
}

Sources/SwiftFormat/Rules/NeverUseForceTry.swift

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,8 @@ public final class NeverUseForceTry: SyntaxLintRule {
3636
public override func visit(_ node: TryExprSyntax) -> SyntaxVisitorContinueKind {
3737
guard context.importsXCTest == .doesNotImportXCTest else { return .skipChildren }
3838
guard let mark = node.questionOrExclamationMark else { return .visitChildren }
39+
// Allow force try if it is in a function marked with @Test attribute.
40+
if node.hasTestAncestor { return .skipChildren }
3941
if mark.tokenKind == .exclamationMark {
4042
diagnose(.doNotForceTry, on: node.tryKeyword)
4143
}

Sources/SwiftFormat/Rules/NeverUseImplicitlyUnwrappedOptionals.swift

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,8 @@ public final class NeverUseImplicitlyUnwrappedOptionals: SyntaxLintRule {
3939

4040
public override func visit(_ node: VariableDeclSyntax) -> SyntaxVisitorContinueKind {
4141
guard context.importsXCTest == .doesNotImportXCTest else { return .skipChildren }
42+
// Allow implicitly unwrapping if it is in a function marked with @Test attribute.
43+
if node.hasTestAncestor { return .skipChildren }
4244
// Ignores IBOutlet variables
4345
for attribute in node.attributes {
4446
if (attribute.as(AttributeSyntax.self))?.attributeName.as(IdentifierTypeSyntax.self)?.name.text == "IBOutlet" {

Tests/SwiftFormatTests/Rules/NeverForceUnwrapTests.swift

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,4 +40,23 @@ final class NeverForceUnwrapTests: LintOrFormatRuleTestCase {
4040
findings: []
4141
)
4242
}
43+
44+
func testIgnoreTestAttributeFunction() {
45+
assertLint(
46+
NeverForceUnwrap.self,
47+
"""
48+
@Test
49+
func testSomeFunc() {
50+
var b = a as! Int
51+
}
52+
@Test
53+
func testAnotherFunc() {
54+
func nestedFunc() {
55+
let c = someValue()!
56+
}
57+
}
58+
""",
59+
findings: []
60+
)
61+
}
4362
}

Tests/SwiftFormatTests/Rules/NeverUseForceTryTests.swift

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,4 +38,20 @@ final class NeverUseForceTryTests: LintOrFormatRuleTestCase {
3838
findings: []
3939
)
4040
}
41+
42+
func testAllowForceTryInTestAttributeFunction() {
43+
assertLint(
44+
NeverUseForceTry.self,
45+
"""
46+
@Test
47+
func testSomeFunc() {
48+
let document = try! Document(path: "important.data")
49+
func nestedFunc() {
50+
let x = try! someThrowingFunction()
51+
}
52+
}
53+
""",
54+
findings: []
55+
)
56+
}
4157
}

Tests/SwiftFormatTests/Rules/NeverUseImplicitlyUnwrappedOptionalsTests.swift

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,4 +35,20 @@ final class NeverUseImplicitlyUnwrappedOptionalsTests: LintOrFormatRuleTestCase
3535
findings: []
3636
)
3737
}
38+
39+
func testIgnoreTestAttrinuteFunction() {
40+
assertLint(
41+
NeverUseImplicitlyUnwrappedOptionals.self,
42+
"""
43+
@Test
44+
func testSomeFunc() {
45+
var s: String!
46+
func nestedFunc() {
47+
var f: Foo!
48+
}
49+
}
50+
""",
51+
findings: []
52+
)
53+
}
3854
}

0 commit comments

Comments
 (0)