Skip to content

Commit a2b88ef

Browse files
committed
Begin Adding Tests
1 parent e011643 commit a2b88ef

File tree

5 files changed

+332
-0
lines changed

5 files changed

+332
-0
lines changed

Package.swift

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,5 +16,9 @@ let package = Package(
1616
name: "Core",
1717
dependencies: []
1818
),
19+
.testTarget(
20+
name: "UnusedCodeToolTests",
21+
dependencies: ["Core"]
22+
),
1923
]
2024
)

Tests/Mocks/MockCodeSamples.swift

Lines changed: 91 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,91 @@
1+
import Foundation
2+
3+
extension String {
4+
static var oneUnusedItem: String {
5+
"""
6+
protocol Bat {}
7+
8+
private final class Foo: Bat {
9+
struct Quz {
10+
func baz() {}
11+
}
12+
13+
var bar = "baz"
14+
15+
init() {
16+
Quz().baz()
17+
print(bar)
18+
}
19+
}
20+
"""
21+
}
22+
23+
static var noUnusedItems: String {
24+
"""
25+
// cute, right?
26+
\(oneUnusedItem)
27+
28+
Foo()
29+
"""
30+
}
31+
32+
static var oneUnusedItemWithComments: String {
33+
"""
34+
\(oneUnusedItem)
35+
36+
/*
37+
38+
Multi-line commented usages should be omitted as possible usages.
39+
40+
Foo()
41+
42+
*/
43+
44+
// Commented usages should be omitted as possible usages.
45+
// Foo()
46+
"""
47+
}
48+
49+
static var oneUnusedItemWithOverride: String {
50+
"""
51+
\(oneUnusedItem)
52+
53+
// Overriden items should be omitted from unused list.
54+
override func bat() {}
55+
"""
56+
}
57+
58+
static var oneUnusedItemWithRegex: String {
59+
"""
60+
\(oneUnusedItem)
61+
62+
// Regexes should be omitted as possible usages.
63+
#/.*Foo.*/#
64+
"""
65+
}
66+
67+
static var oneUnusedItemWithString: String {
68+
"""
69+
\(oneUnusedItem)
70+
71+
// Strings should be omitted as possible usages.
72+
print("Foo")
73+
"""
74+
}
75+
76+
static var noUnusedItemWithStringInterpolation: String {
77+
"""
78+
\(oneUnusedItem)
79+
80+
// Interpolated usages should count as usages.
81+
print("corge \\(Foo())grault")
82+
"""
83+
}
84+
85+
static var privateDeclarationUsage: String {
86+
"""
87+
// Usages of private items should not as usages from other files.
88+
print(Foo())
89+
"""
90+
}
91+
}

Tests/Mocks/MockFileReader.swift

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
@testable import Core
2+
import Foundation
3+
4+
struct MockFileReader: FileReader {
5+
// MARK: - Internal Properties
6+
7+
let files: [String: String]
8+
9+
// MARK: - Public Functions
10+
11+
func readFile(at filePath: String) -> String? {
12+
files[filePath]
13+
}
14+
}

Tests/SwiftParserTests.swift

Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,71 @@
1+
@testable import Core
2+
import Foundation
3+
import Testing
4+
5+
struct SwiftParserTests {
6+
private let logger = Logger(logLevel: .info)
7+
8+
/// Happy path.
9+
@Test func testParserFindsDeclarations() async throws {
10+
let fileReader = MockFileReader(files: [
11+
"foo.swift": .noUnusedItems,
12+
])
13+
let parser = SwiftParser(logger: logger)
14+
let declarations = parser.extractDeclarations(in: ["foo.swift"],
15+
ignoringItems: [],
16+
using: fileReader)
17+
#expect(declarations.count == 5)
18+
}
19+
20+
/// We should ignore literal files in the ignore list.
21+
@Test func testParserFindsDeclarationsIgnoringIgnoredFiles() async throws {
22+
let fileReader = MockFileReader(files: [
23+
"foo.swift": .noUnusedItems,
24+
])
25+
let parser = SwiftParser(logger: logger)
26+
let ignoredItem = try! IgnoredItem(line: "\"foo.swift\"")
27+
let declarations = parser.extractDeclarations(in: ["foo.swift"],
28+
ignoringItems: [ignoredItem].compactMap { $0 },
29+
using: fileReader)
30+
#expect(declarations.count == 0)
31+
}
32+
33+
/// We should ignore regex-matched files in the ignore list.
34+
@Test func testParserFindsDeclarationsIgnoringIgnoredFilesWithRegex() async throws {
35+
let fileReader = MockFileReader(files: [
36+
"foo.swift": .noUnusedItems,
37+
])
38+
let parser = SwiftParser(logger: logger)
39+
let ignoredItem = try? IgnoredItem(line: ".*.swift")
40+
let declarations = parser.extractDeclarations(in: ["foo.swift"],
41+
ignoringItems: [ignoredItem].compactMap { $0 },
42+
using: fileReader)
43+
#expect(declarations.count == 0)
44+
}
45+
46+
/// We should ignore literal declarations in the ignore list.
47+
@Test func testParserFindsDeclarationsIgnoringIgnoredItems() async throws {
48+
let fileReader = MockFileReader(files: [
49+
"foo.swift": .noUnusedItems,
50+
])
51+
let parser = SwiftParser(logger: logger)
52+
let ignoredItem = try? IgnoredItem(line: "\"foo.swift\": \"Bat\"")
53+
let declarations = parser.extractDeclarations(in: ["foo.swift"],
54+
ignoringItems: [ignoredItem].compactMap { $0 },
55+
using: fileReader)
56+
#expect(declarations.count == 4)
57+
}
58+
59+
/// We should ignore regex-matched declarations in the ignore list.
60+
@Test func testParserFindsDeclarationsIgnoringIgnoredItemsWithRegex() async throws {
61+
let fileReader = MockFileReader(files: [
62+
"foo.swift": .noUnusedItems,
63+
])
64+
let parser = SwiftParser(logger: logger)
65+
let ignoredItem = try? IgnoredItem(line: "\"foo.swift\": F.*")
66+
let declarations = parser.extractDeclarations(in: ["foo.swift"],
67+
ignoringItems: [ignoredItem].compactMap { $0 },
68+
using: fileReader)
69+
#expect(declarations.count == 4)
70+
}
71+
}

Tests/UsageAnalyzerTests.swift

Lines changed: 152 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,152 @@
1+
@testable import Core
2+
import Foundation
3+
import Testing
4+
5+
struct UsageAnalyzerTests {
6+
private let logger = Logger(logLevel: .info)
7+
8+
// Happy path.
9+
@Test func testUsageAnalyzerFindsNoUnusedItems() async throws {
10+
let files: [String: String] = [
11+
"foo.swift": .noUnusedItems,
12+
]
13+
let fileReader = MockFileReader(files: files)
14+
let parser = SwiftParser(logger: logger)
15+
let declarations = parser.extractDeclarations(in: Array(files.keys),
16+
ignoringItems: [],
17+
using: fileReader)
18+
let analyzer = UsageAnalyzer(logger: logger)
19+
let unused = analyzer.findUnused(declarations: declarations,
20+
in: Array(files.keys),
21+
xibs: [],
22+
using: fileReader)
23+
#expect(unused.count == 0)
24+
}
25+
26+
/// Base case: we should find the one unused item.
27+
@Test func testUsageAnalyzerFindsUnusedItems() async throws {
28+
let files: [String: String] = [
29+
"foo.swift": .oneUnusedItem,
30+
]
31+
let fileReader = MockFileReader(files: files)
32+
let parser = SwiftParser(logger: logger)
33+
let declarations = parser.extractDeclarations(in: Array(files.keys),
34+
ignoringItems: [],
35+
using: fileReader)
36+
let analyzer = UsageAnalyzer(logger: logger)
37+
let unused = analyzer.findUnused(declarations: declarations,
38+
in: Array(files.keys),
39+
xibs: [],
40+
using: fileReader)
41+
#expect(unused.count == 1)
42+
}
43+
44+
/// References within comments should not count as usages.
45+
@Test func testUsageAnalyzerFindsUnusedItemsWithComments() async throws {
46+
let files: [String: String] = [
47+
"foo.swift": .oneUnusedItemWithComments,
48+
]
49+
let fileReader = MockFileReader(files: files)
50+
let parser = SwiftParser(logger: logger)
51+
let declarations = parser.extractDeclarations(in: Array(files.keys),
52+
ignoringItems: [],
53+
using: fileReader)
54+
let analyzer = UsageAnalyzer(logger: logger)
55+
let unused = analyzer.findUnused(declarations: declarations,
56+
in: Array(files.keys),
57+
xibs: [],
58+
using: fileReader)
59+
#expect(unused.count == 1)
60+
}
61+
62+
/// Declarations that are overrides should not count as unused.
63+
@Test func testUsageAnalyzerFindsUnusedItemsWithOverrideModifier() async throws {
64+
let files: [String: String] = [
65+
"foo.swift": .oneUnusedItemWithOverride,
66+
]
67+
let fileReader = MockFileReader(files: files)
68+
let parser = SwiftParser(logger: logger)
69+
let declarations = parser.extractDeclarations(in: Array(files.keys),
70+
ignoringItems: [],
71+
using: fileReader)
72+
let analyzer = UsageAnalyzer(logger: logger)
73+
let unused = analyzer.findUnused(declarations: declarations,
74+
in: Array(files.keys),
75+
xibs: [],
76+
using: fileReader)
77+
#expect(unused.count == 1)
78+
}
79+
80+
/// Private declarations referenced from a different file should not count as usages.
81+
@Test func testUsageAnalyzerFindsUnusedItemsWithPrivateModifier() async throws {
82+
let files: [String: String] = [
83+
"foo.swift": .oneUnusedItem,
84+
"bar.swift": .privateDeclarationUsage,
85+
]
86+
let fileReader = MockFileReader(files: files)
87+
let parser = SwiftParser(logger: logger)
88+
let declarations = parser.extractDeclarations(in: Array(files.keys),
89+
ignoringItems: [],
90+
using: fileReader)
91+
let analyzer = UsageAnalyzer(logger: logger)
92+
let unused = analyzer.findUnused(declarations: declarations,
93+
in: Array(files.keys),
94+
xibs: [],
95+
using: fileReader)
96+
#expect(unused.count == 1)
97+
}
98+
99+
/// Declarations matched inside a regex should not count as usages.
100+
@Test func testUsageAnalyzerFindsUnusedItemsWithRegex() async throws {
101+
let files: [String: String] = [
102+
"foo.swift": .oneUnusedItemWithRegex,
103+
]
104+
let fileReader = MockFileReader(files: files)
105+
let parser = SwiftParser(logger: logger)
106+
let declarations = parser.extractDeclarations(in: Array(files.keys),
107+
ignoringItems: [],
108+
using: fileReader)
109+
let analyzer = UsageAnalyzer(logger: logger)
110+
let unused = analyzer.findUnused(declarations: declarations,
111+
in: Array(files.keys),
112+
xibs: [],
113+
using: fileReader)
114+
#expect(unused.count == 1)
115+
}
116+
117+
/// Declarations matched inside a string should not count as usages.
118+
@Test func testUsageAnalyzerFindsUnusedItemsWithString() async throws {
119+
let files: [String: String] = [
120+
"foo.swift": .oneUnusedItemWithString,
121+
]
122+
let fileReader = MockFileReader(files: files)
123+
let parser = SwiftParser(logger: logger)
124+
let declarations = parser.extractDeclarations(in: Array(files.keys),
125+
ignoringItems: [],
126+
using: fileReader)
127+
let analyzer = UsageAnalyzer(logger: logger)
128+
let unused = analyzer.findUnused(declarations: declarations,
129+
in: Array(files.keys),
130+
xibs: [],
131+
using: fileReader)
132+
#expect(unused.count == 1)
133+
}
134+
135+
/// Declarations matched inside a string interpolation should count as usages.
136+
@Test func testUsageAnalyzerFindsNoUnusedItemsWithStringInterpolation() async throws {
137+
let files: [String: String] = [
138+
"foo.swift": .noUnusedItemWithStringInterpolation,
139+
]
140+
let fileReader = MockFileReader(files: files)
141+
let parser = SwiftParser(logger: logger)
142+
let declarations = parser.extractDeclarations(in: Array(files.keys),
143+
ignoringItems: [],
144+
using: fileReader)
145+
let analyzer = UsageAnalyzer(logger: logger)
146+
let unused = analyzer.findUnused(declarations: declarations,
147+
in: Array(files.keys),
148+
xibs: [],
149+
using: fileReader)
150+
#expect(unused.count == 0)
151+
}
152+
}

0 commit comments

Comments
 (0)