Skip to content

Commit 1ed79dc

Browse files
authored
Add test for #805 (#1020)
1 parent 5308ce1 commit 1ed79dc

File tree

6 files changed

+118
-0
lines changed

6 files changed

+118
-0
lines changed

Tests/Fixtures/Package.swift

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,33 @@ var targets: [PackageDescription.Target] = [
1717
.target(
1818
name: "CrossModuleRetentionSupportFixtures"
1919
),
20+
.target(
21+
name: "UnusedImportFixtureA",
22+
path: "Sources/UnusedImportFixtures/A"
23+
),
24+
.target(
25+
name: "UnusedImportFixtureB",
26+
dependencies: [
27+
.target(name: "UnusedImportFixtureA")
28+
],
29+
path: "Sources/UnusedImportFixtures/B"
30+
),
31+
.target(
32+
name: "UnusedImportFixtureC",
33+
dependencies: [
34+
.target(name: "UnusedImportFixtureA")
35+
],
36+
path: "Sources/UnusedImportFixtures/C"
37+
),
38+
.target(
39+
name: "UnusedImportFixtureD",
40+
dependencies: [
41+
.target(name: "UnusedImportFixtureA"),
42+
.target(name: "UnusedImportFixtureB"),
43+
.target(name: "UnusedImportFixtureC")
44+
],
45+
path: "Sources/UnusedImportFixtures/D"
46+
),
2047
.target(
2148
name: "RetentionFixtures",
2249
dependencies: [
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
// Module A: Defines the base class and protocol
2+
3+
public protocol ConformanceProtocol {
4+
var conformanceProperty: Int { get }
5+
}
6+
7+
public class ConformanceClass {
8+
public init() {}
9+
}
10+
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
// Module B: Provides conformance for ConformanceClass to ConformanceProtocol
2+
// This module's import should NOT be flagged as unused when the conformance is used
3+
4+
import UnusedImportFixtureA
5+
6+
extension ConformanceClass: ConformanceProtocol {
7+
public var conformanceProperty: Int { 42 }
8+
}
9+
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
// Module C: Provides the function that accepts ConformanceProtocol
2+
3+
import UnusedImportFixtureA
4+
5+
/// A function that accepts a ConformanceProtocol conforming type
6+
public func acceptConformingType(_ value: ConformanceProtocol) -> Int {
7+
value.conformanceProperty
8+
}
9+
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
// Module D: Unused import false-positive for module that provides a conformance
2+
//
3+
// This file imports:
4+
// - UnusedImportFixtureA: provides ConformanceClass and ConformanceProtocol
5+
// - UnusedImportFixtureB: provides ConformanceClass: ConformanceProtocol conformance
6+
// - UnusedImportFixtureC: provides acceptConformingType(_:)
7+
//
8+
// Module B's import is required because it provides the conformance, but no declarations from it
9+
// are directly referenced. Without this import, the call would fail to compile.
10+
//
11+
// The import should NOT be flagged as unused.
12+
13+
import UnusedImportFixtureA // Module A: type definitions
14+
import UnusedImportFixtureB // Module B: conformance (no direct references!)
15+
import UnusedImportFixtureC // Module C: function accepting protocol
16+
17+
// periphery:ignore
18+
public class UnusedImportConformanceTestRetainer {
19+
public func use() {
20+
// This call requires ConformanceClass to conform to ConformanceProtocol.
21+
// - ConformanceClass comes from Module A
22+
// - acceptConformingType comes from Module C
23+
// - The conformance is provided by Module B (no symbols directly referenced from B!)
24+
_ = acceptConformingType(ConformanceClass())
25+
}
26+
}
27+
Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
import Configuration
2+
import SystemPackage
3+
@testable import TestShared
4+
5+
/// Tests for unused import detection.
6+
final class UnusedImportTest: SPMSourceGraphTestCase {
7+
override static func setUp() {
8+
super.setUp()
9+
10+
build(projectPath: FixturesProjectPath)
11+
index(configuration: Configuration())
12+
}
13+
14+
func testUnusedImportFalsePositiveForConformanceProvider() {
15+
// Module D imports A, B, C where:
16+
// - A provides ConformanceClass and ConformanceProtocol
17+
// - B provides the conformance (no direct references!)
18+
// - C provides acceptConformingType function
19+
//
20+
// Module B's import should NOT be flagged as unused since it provides the conformance.
21+
module("UnusedImportFixtureD") {
22+
self.assertReferenced(.module("UnusedImportFixtureA"))
23+
self.assertReferenced(.module("UnusedImportFixtureB"))
24+
self.assertReferenced(.module("UnusedImportFixtureC"))
25+
}
26+
27+
module("UnusedImportFixtureA") {
28+
self.assertReferenced(.class("ConformanceClass"))
29+
self.assertReferenced(.protocol("ConformanceProtocol"))
30+
}
31+
32+
module("UnusedImportFixtureC") {
33+
self.assertReferenced(.functionFree("acceptConformingType(_:)"))
34+
}
35+
}
36+
}

0 commit comments

Comments
 (0)