Skip to content

Commit 98e5fc0

Browse files
Update JSON output schema (#7)
1 parent 1cf0c79 commit 98e5fc0

File tree

6 files changed

+88
-79
lines changed

6 files changed

+88
-79
lines changed

Sources/SwiftTypeAdoptionReporter/FastStrategy.swift

Lines changed: 22 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -27,14 +27,22 @@ public class FastStrategy: Strategy {
2727
self.verbose = verbose
2828
}
2929

30-
public func findUsageCounts() throws -> [String : Int] {
30+
public func findUsageCounts() throws -> [String : TypeUsage] {
31+
fileCounts = [:]
3132
usageCounts = [:]
3233

3334
for path in paths {
3435
try visit(fileOrDirectory: path)
3536
}
3637

37-
return usageCounts
38+
var typeUsages: [String: TypeUsage] = [:]
39+
for type in types {
40+
typeUsages[type] = TypeUsage(
41+
fileCount: fileCounts[type]?.count ?? 0,
42+
usageCount: usageCounts[type] ?? 0
43+
)
44+
}
45+
return typeUsages
3846
}
3947

4048
enum Error: Swift.Error {
@@ -46,7 +54,9 @@ public class FastStrategy: Strategy {
4654
private let moduleName: String?
4755
private let paths: [AbsolutePath]
4856
private let verbose: Bool
57+
private var fileCounts: [String: Set<AbsolutePath>] = [:]
4958
private var usageCounts: [String: Int] = [:]
59+
private var currentFile: AbsolutePath?
5060

5161
/**
5262
* - Parameters:
@@ -92,18 +102,9 @@ public class FastStrategy: Strategy {
92102
throw Error.noSuchFileOrDirectory
93103
}
94104

95-
let parsedSource = try SyntaxParser.parse(file.asURL)
96-
var visitor = self
97-
parsedSource.walk(&visitor)
98-
}
105+
currentFile = file
99106

100-
/**
101-
* - Parameters:
102-
* - sourceString: String directly containing Swift source code.
103-
* - reporter: Reporter in which to store report.
104-
*/
105-
private func visit(sourceString: String) throws {
106-
let parsedSource = try SyntaxParser.parse(source: sourceString)
107+
let parsedSource = try SyntaxParser.parse(file.asURL)
107108
var visitor = self
108109
parsedSource.walk(&visitor)
109110
}
@@ -270,14 +271,15 @@ extension FastStrategy: SyntaxVisitor {
270271
print(token.verboseDescription)
271272
}
272273

273-
let key: String
274-
if let moduleName = moduleName {
275-
key = "\(moduleName).\(identifier)"
276-
} else {
277-
key = identifier
278-
}
274+
usageCounts[identifier] = (usageCounts[identifier] ?? 0) + 1
279275

280-
usageCounts[key] = (usageCounts[key] ?? 0) + 1
276+
if let currentFile = currentFile {
277+
if fileCounts[identifier] != nil {
278+
fileCounts[identifier]?.insert(currentFile)
279+
} else {
280+
fileCounts[identifier] = [currentFile]
281+
}
282+
}
281283
}
282284
}
283285

Sources/SwiftTypeAdoptionReporter/HumanReadableReportFormatter.swift

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -16,12 +16,14 @@ public class HumanReadableReportFormatter: ReportFormatter {
1616
public init() {
1717
}
1818

19-
public func format(_ usageCounts: [String: Int]) -> String {
19+
public func format(_ usageCounts: [String: TypeUsage]) -> String {
2020
let sortedUsageCounts = usageCounts.sorted(by: { $0.key < $1.key })
2121

2222
var output = ""
23-
for (componentName, usageCount) in sortedUsageCounts {
24-
output += "\(componentName) used \(usageCount) time\(usageCount != 1 ? "s" : "").\n"
23+
for (componentName, typeUsage) in sortedUsageCounts {
24+
let fileCount = typeUsage.fileCount
25+
let usageCount = typeUsage.usageCount
26+
output += "\(componentName) used \(usageCount) time\(usageCount != 1 ? "s" : "") in \(fileCount) file\(fileCount != 1 ? "s" : "").\n"
2527
}
2628
return output.trimmingCharacters(in: .whitespacesAndNewlines)
2729
}

Sources/SwiftTypeAdoptionReporter/JSONReportFormatter.swift

Lines changed: 16 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,13 +14,27 @@
1414

1515
import Foundation
1616

17+
private struct EncodableTypeUsage: Encodable {
18+
let name: String
19+
let fileCount: Int
20+
let usageCount: Int
21+
}
22+
1723
public class JSONReportFormatter: ReportFormatter {
1824
public init() {
1925
}
2026

21-
public func format(_ usageCounts: [String: Int]) -> String {
27+
public func format(_ usageCounts: [String: TypeUsage]) -> String {
28+
let encodableTypeUsages = usageCounts.map({ name, typeUsage in
29+
EncodableTypeUsage(
30+
name: name,
31+
fileCount: typeUsage.fileCount,
32+
usageCount: typeUsage.usageCount
33+
)
34+
})
35+
2236
let encoder = JSONEncoder()
23-
let data = try! encoder.encode(usageCounts)
37+
let data = try! encoder.encode(encodableTypeUsages)
2438
return String(data: data, encoding: .utf8)!
2539
}
2640
}

Sources/SwiftTypeAdoptionReporter/ReportFormatter.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,5 +13,5 @@
1313
// limitations under the License.
1414

1515
public protocol ReportFormatter: AnyObject {
16-
func format(_ usageCounts: [String: Int]) -> String
16+
func format(_ usageCounts: [String: TypeUsage]) -> String
1717
}

Sources/SwiftTypeAdoptionReporter/Strategy.swift

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,14 @@
1212
// See the License for the specific language governing permissions and
1313
// limitations under the License.
1414

15+
public struct TypeUsage {
16+
/// Number of files this type was used in
17+
let fileCount: Int
18+
19+
/// Number of times this type was used
20+
let usageCount: Int
21+
}
22+
1523
public protocol Strategy: AnyObject {
16-
func findUsageCounts() throws -> [String: Int]
24+
func findUsageCounts() throws -> [String: TypeUsage]
1725
}

Tests/SwiftTypeAdoptionReporterTests/FastStrategyTests.swift

Lines changed: 35 additions & 52 deletions
Original file line numberDiff line numberDiff line change
@@ -63,9 +63,8 @@ final class SwiftTypeAdoptionReporterTests: XCTestCase {
6363
class MyView: UIView {
6464
}
6565
"""
66-
let expected: [String: Int] = [:]
67-
let types = ["UIView"]
68-
verify(expected: expected, types: types, for: sourceString)
66+
let expected: [String: Int] = ["UIView": 0]
67+
verify(expected: expected, for: sourceString)
6968
}
7069

7170
/// *Do not* count declaration of the component itself
@@ -75,9 +74,8 @@ final class SwiftTypeAdoptionReporterTests: XCTestCase {
7574
class UIView {
7675
}
7776
"""
78-
let expected: [String: Int] = [:]
79-
let types = ["UIView"]
80-
verify(expected: expected, types: types, for: sourceString)
77+
let expected: [String: Int] = ["UIView": 0]
78+
verify(expected: expected, for: sourceString)
8179
}
8280

8381
/// *Do not* include references inside the component itself
@@ -88,9 +86,8 @@ final class SwiftTypeAdoptionReporterTests: XCTestCase {
8886
let _ = UILabel()
8987
}
9088
"""
91-
let expected: [String: Int] = [:]
92-
let types = ["UIView", "UILabel"]
93-
verify(expected: expected, types: types, for: sourceString)
89+
let expected: [String: Int] = ["UIView": 0, "UILabel": 0]
90+
verify(expected: expected, for: sourceString)
9491
}
9592

9693
/// *Do* include references inside non-component class
@@ -114,9 +111,8 @@ final class SwiftTypeAdoptionReporterTests: XCTestCase {
114111
struct UIView {
115112
}
116113
"""
117-
let expected: [String: Int] = [:]
118-
let types = ["UIView"]
119-
verify(expected: expected, types: types, for: sourceString)
114+
let expected: [String: Int] = ["UIView": 0]
115+
verify(expected: expected, for: sourceString)
120116
}
121117

122118
/// *Do not* count declaraction of extensions on the component
@@ -126,48 +122,43 @@ final class SwiftTypeAdoptionReporterTests: XCTestCase {
126122
extension UIView {
127123
}
128124
"""
129-
let expected: [String: Int] = [:]
130-
let types = ["UIView"]
131-
verify(expected: expected, types: types, for: sourceString)
125+
let expected: [String: Int] = ["UIView": 0]
126+
verify(expected: expected, for: sourceString)
132127
}
133128

134129
/// *Do* count function calls relative to a provided module name
135130
func testFunctionCallWithModuleName() {
136131
let sourceString = "let _ = MyModule.Foo()"
137132
let expected = [
138-
"MyModule.Foo": 1,
133+
"Foo": 1,
139134
]
140-
let types = ["Foo"]
141135
let moduleName = "MyModule"
142-
verify(expected: expected, types: types, for: sourceString, moduleName: moduleName)
136+
verify(expected: expected, for: sourceString, moduleName: moduleName)
143137
}
144138

145139
/// *Do not* count function calls relative to a containing module/class if no module name provided
146140
func testNamespacedFunctionCallWithoutModuleName() {
147141
let sourceString = "let _ = Foo.Bar()"
148-
let expected: [String: Int] = [:]
149-
let types = ["Bar"]
150-
verify(expected: expected, types: types, for: sourceString)
142+
let expected: [String: Int] = ["Bar": 0]
143+
verify(expected: expected, for: sourceString)
151144
}
152145

153146
/// *Do not* count function calls relative to a containing module/class other than the provided module name
154147
func testNamespacedFunctionCallInNonModule() {
155148
let sourceString = "let _ = NotMyModule.Foo()"
156-
let expected: [String: Int] = [:]
157-
let types = ["Foo"]
149+
let expected: [String: Int] = ["Foo": 0]
158150
let moduleName = "MyModule"
159-
verify(expected: expected, types: types, for: sourceString, moduleName: moduleName)
151+
verify(expected: expected, for: sourceString, moduleName: moduleName)
160152
}
161153

162154
/// *Do* count property references relative to a provided module name
163155
func testPropertyReferenceWithModuleName() {
164156
let sourceString = "let _ = MyModule.Foo.bar"
165157
let expected = [
166-
"MyModule.Foo": 1,
158+
"Foo": 1,
167159
]
168-
let types = ["Foo"]
169160
let moduleName = "MyModule"
170-
verify(expected: expected, types: types, for: sourceString, moduleName: moduleName)
161+
verify(expected: expected, for: sourceString, moduleName: moduleName)
171162
}
172163

173164
/// *Do* count property references with no module name
@@ -182,27 +173,24 @@ final class SwiftTypeAdoptionReporterTests: XCTestCase {
182173
/// *Do not* count property references relative to a containing module/class if no module name provided
183174
func testNamespacedPropertyReferenceWithoutModuleName() {
184175
let sourceString = "let _ = Foo.Bar.baz"
185-
let expected: [String: Int] = [:]
186-
let types = ["Bar"]
187-
verify(expected: expected, types: types, for: sourceString)
176+
let expected: [String: Int] = ["Bar": 0]
177+
verify(expected: expected, for: sourceString)
188178
}
189179

190180
/// *Do not* count property references relative to a containing module/class other than the provided module name
191181
func testNamespacedPropertyReferenceInNonModule() {
192182
let sourceString = "let _ = NotMyModule.Foo.bar"
193-
let expected: [String: Int] = [:]
194-
let types = ["Foo"]
183+
let expected: [String: Int] = ["Foo": 0]
195184
let moduleName = "MyModule"
196-
verify(expected: expected, types: types, for: sourceString, moduleName: moduleName)
185+
verify(expected: expected, for: sourceString, moduleName: moduleName)
197186
}
198187

199188
/// *Do not* count property references relative to the provided module name in addition to some other module/class
200189
func testPropertyReferenceInModuleInNonModule() {
201190
let sourceString = "let _ = NotMyModule.MyModule.Foo.bar"
202-
let expected: [String: Int] = [:]
203-
let types = ["Foo"]
191+
let expected: [String: Int] = ["Foo": 0]
204192
let moduleName = "MyModule"
205-
verify(expected: expected, types: types, for: sourceString, moduleName: moduleName)
193+
verify(expected: expected, for: sourceString, moduleName: moduleName)
206194
}
207195

208196
/// *Do* count references inside another function call
@@ -217,17 +205,15 @@ final class SwiftTypeAdoptionReporterTests: XCTestCase {
217205
/// *Do not* count contents of a comment
218206
func testComment() {
219207
let sourceString = "// This is a comment about UIView"
220-
let expected: [String: Int] = [:]
221-
let types = ["UIView"]
222-
verify(expected: expected, types: types, for: sourceString)
208+
let expected: [String: Int] = ["UIView": 0]
209+
verify(expected: expected, for: sourceString)
223210
}
224211

225212
/// *Do not* count contents of a string
226213
func testString() {
227214
let sourceString = "\"UIView\""
228-
let expected: [String: Int] = [:]
229-
let types = ["UIView"]
230-
verify(expected: expected, types: types, for: sourceString)
215+
let expected: [String: Int] = ["UIView": 0]
216+
verify(expected: expected, for: sourceString)
231217
}
232218

233219
/// *Do* count references in string interpolation
@@ -240,25 +226,22 @@ final class SwiftTypeAdoptionReporterTests: XCTestCase {
240226
/// *Do not* count type casting
241227
func testTypeCast() {
242228
let sourceString = "foo as UIView"
243-
let expected: [String: Int] = [:]
244-
let types = ["UIView"]
245-
verify(expected: expected, types: types, for: sourceString)
229+
let expected: [String: Int] = ["UIView": 0]
230+
verify(expected: expected, for: sourceString)
246231
}
247232

248233
/// *Do not* count optional type casting
249234
func testOptionalTypeCasting() {
250235
let sourceString = "foo as? UIView"
251-
let expected: [String: Int] = [:]
252-
let types = ["UIView"]
253-
verify(expected: expected, types: types, for: sourceString)
236+
let expected: [String: Int] = ["UIView": 0]
237+
verify(expected: expected, for: sourceString)
254238
}
255239

256240
/// *Do not* count force casting
257241
func testForceCasting() {
258242
let sourceString = "foo as! UIView"
259-
let expected: [String: Int] = [:]
260-
let types = ["UIView"]
261-
verify(expected: expected, types: types, for: sourceString)
243+
let expected: [String: Int] = ["UIView": 0]
244+
verify(expected: expected, for: sourceString)
262245
}
263246

264247
/// *Do* count function calls that are arguments to a chained function call
@@ -295,7 +278,7 @@ final class SwiftTypeAdoptionReporterTests: XCTestCase {
295278
paths: [tmpFile.path]
296279
)
297280

298-
let actual = try strategy.findUsageCounts()
281+
let actual = (try strategy.findUsageCounts()).mapValues({ $0.usageCount })
299282
XCTAssertEqual(expected, actual, file: file, line: line)
300283
} catch {
301284
XCTFail("Failed with error \(error.localizedDescription)", file: file, line: line)

0 commit comments

Comments
 (0)