Skip to content

Commit a324e6d

Browse files
authored
Handle overridden declarations requiring testable (#41)
In the case you have a module that defines a `public class`, if you try to subclass it in your tests you must use @testable on the import.
1 parent df147fc commit a324e6d

File tree

1 file changed

+14
-7
lines changed

1 file changed

+14
-7
lines changed

Sources/unnecessary-testable/main.swift

Lines changed: 14 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -25,20 +25,26 @@ private func getTestableImports(path: String) -> Set<String> {
2525
})
2626
}
2727

28-
private func getReferenceUSRs(unitReader: UnitReader, unitToRecord: [String: RecordReader]) -> Set<String> {
28+
private func getReferenceUSRs(unitReader: UnitReader, unitToRecord: [String: RecordReader])
29+
-> (Set<String>, Set<String>)
30+
{
2931
// Empty source files have units but no records
3032
guard let recordReader = unitToRecord[unitReader.mainFile] else {
31-
return []
33+
return ([], [])
3234
}
3335

3436
var usrs = Set<String>()
37+
var overrideUSRs = Set<String>()
3538
recordReader.forEach { (occurrence: SymbolOccurrence) in
3639
if occurrence.roles.contains(.reference) {
3740
usrs.insert(occurrence.symbol.usr)
41+
if occurrence.roles.contains(.overrideOf) || occurrence.roles.contains(.baseOf) {
42+
overrideUSRs.insert(occurrence.symbol.usr)
43+
}
3844
}
3945
}
4046

41-
return usrs
47+
return (usrs, overrideUSRs)
4248
}
4349

4450
// TODO: Improve this. Issues:
@@ -56,7 +62,7 @@ private func getReferenceUSRs(unitReader: UnitReader, unitToRecord: [String: Rec
5662
// the protocol itself is
5763
// - This doesn't differentiate between `public` and `public final`, so if you subclass the class you need
5864
// the testable import in the `public` case
59-
private func isPublic(file: String, occurrence: SymbolOccurrence) -> Bool {
65+
private func isPublic(file: String, occurrence: SymbolOccurrence, isOverride: Bool) -> Bool {
6066
// Assume implicit declarations (generated memberwise initializers) require testable
6167
if occurrence.roles.contains(.implicit) && !occurrence.roles.contains(.accessorOf) {
6268
return false
@@ -73,7 +79,7 @@ private func isPublic(file: String, occurrence: SymbolOccurrence) -> Bool {
7379
return true
7480
}
7581

76-
let isPublic = text.contains("public ") || text.contains("open ")
82+
let isPublic = (text.contains("public ") && !isOverride) || text.contains("open ")
7783
// Handle public members that explicitly set 'internal(set)' for allowing setting from tests
7884
return isPublic && !text.contains(" internal(")
7985
}
@@ -180,7 +186,7 @@ func main(indexStorePath: String) {
180186
continue
181187
}
182188

183-
let referencedUSRs = getReferenceUSRs(unitReader: unitReader, unitToRecord: unitToRecord)
189+
let (referencedUSRs, overrideUSRs) = getReferenceUSRs(unitReader: unitReader, unitToRecord: unitToRecord)
184190
var seenModules = Set<String>()
185191
var requiredTestableImports = Set<String>()
186192
for dependentUnit in units {
@@ -205,7 +211,8 @@ func main(indexStorePath: String) {
205211
referencedUSRs.contains(occurrence.symbol.usr) &&
206212
!isChildOfProtocol(occurrence: occurrence) &&
207213
!isGetterOrSetterFunction(occurrence: occurrence) &&
208-
!isPublic(file: dependentUnit.mainFile, occurrence: occurrence)
214+
!isPublic(file: dependentUnit.mainFile, occurrence: occurrence,
215+
isOverride: overrideUSRs.contains(occurrence.symbol.usr))
209216
{
210217
requiredTestableImports.insert(moduleName)
211218
}

0 commit comments

Comments
 (0)