Skip to content
This repository was archived by the owner on Jun 1, 2023. It is now read-only.

Commit 868ded0

Browse files
committed
Add GraphViz package dependency
Replace ad-hoc implementation of DOT diagram encoding
1 parent 85836c0 commit 868ded0

File tree

4 files changed

+75
-52
lines changed

4 files changed

+75
-52
lines changed

Package.resolved

Lines changed: 9 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Package.swift

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6,18 +6,19 @@ import PackageDescription
66
let package = Package(
77
name: "swift-doc",
88
dependencies: [
9+
.package(url: "https://github.com/apple/swift-syntax.git", .revision("swift-5.2-DEVELOPMENT-SNAPSHOT-2020-03-09-a")),
10+
.package(url: "https://github.com/SwiftDocOrg/SwiftSemantics.git", .branch("swift-5.2")),
911
.package(url: "https://github.com/SwiftDocOrg/CommonMark.git", .branch("master")),
1012
.package(url: "https://github.com/SwiftDocOrg/SwiftMarkup.git", .upToNextMinor(from: "0.0.4")),
11-
.package(url: "https://github.com/SwiftDocOrg/SwiftSemantics.git", .branch("swift-5.2")),
12-
.package(url: "https://github.com/apple/swift-syntax.git", .revision("swift-5.2-DEVELOPMENT-SNAPSHOT-2020-03-09-a")),
13+
.package(url: "https://github.com/SwiftDocOrg/GraphViz.git", .upToNextMinor(from: "0.0.1")),
1314
.package(url: "https://github.com/apple/swift-argument-parser", .upToNextMinor(from: "0.0.2")),
1415
],
1516
targets: [
1617
// Targets are the basic building blocks of a package. A target can define a module or a test suite.
1718
// Targets can depend on other targets in this package, and on products in packages which this package depends on.
1819
.target(
1920
name: "swift-doc",
20-
dependencies: ["ArgumentParser", "SwiftDoc", "SwiftSemantics", "SwiftMarkup", "CommonMarkBuilder", "DCOV"]
21+
dependencies: ["ArgumentParser", "SwiftDoc", "SwiftSemantics", "SwiftMarkup", "CommonMarkBuilder", "DCOV", "GraphViz"]
2122
),
2223
.target(
2324
name: "DCOV",

Sources/SwiftDoc/Interface.swift

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,23 @@ public final class Interface: Codable {
2323

2424
public private(set) lazy var baseClasses: [Symbol] = {
2525
return symbols.filter { $0.declaration is Class &&
26-
typesInherited(by: $0).isEmpty }
26+
typesInherited(by: $0).isEmpty }
27+
}()
28+
29+
public private(set) lazy var classHierarchies: [Symbol: Set<Symbol>] = {
30+
var classClusters: [Symbol: Set<Symbol>] = [:]
31+
32+
for baseClass in baseClasses {
33+
var superclasses = Set(CollectionOfOne(baseClass))
34+
35+
while !superclasses.isEmpty {
36+
let subclasses = Set(superclasses.flatMap { typesInheriting(from: $0) }.filter { $0.isPublic })
37+
defer { superclasses = subclasses }
38+
classClusters[baseClass, default: []].formUnion(subclasses)
39+
}
40+
}
41+
42+
return classClusters
2743
}()
2844

2945
public private(set) lazy var relationships: [Relationship] = {

Sources/swift-doc/Subcommands/Diagram.swift

Lines changed: 45 additions & 48 deletions
Original file line numberDiff line numberDiff line change
@@ -2,84 +2,81 @@ import ArgumentParser
22
import Foundation
33
import SwiftDoc
44
import SwiftSemantics
5+
import GraphViz
6+
import DOT
57

68
extension SwiftDoc {
79
struct Diagram: ParsableCommand {
810
struct Options: ParsableArguments {
911
@Argument(help: "One or more paths to Swift files")
1012
var inputs: [String]
1113
}
12-
14+
1315
static var configuration = CommandConfiguration(abstract: "Generates diagram of Swift symbol relationships")
14-
16+
1517
@OptionGroup()
1618
var options: Options
17-
19+
1820
func run() throws {
1921
let module = try Module(paths: options.inputs)
20-
print(GraphViz.diagram(of: module), to: &standardOutput)
22+
print(diagram(of: module), to: &standardOutput)
2123
}
2224
}
2325
}
2426

2527
// MARK: -
2628

27-
enum GraphViz {
28-
static func diagram(of module: Module) -> String {
29-
var lines: [String] = []
29+
fileprivate func diagram(of module: Module) -> String {
30+
var graph = Graph(directed: true)
31+
32+
for (baseClass, subclasses) in module.interface.classHierarchies {
33+
var subgraph = Subgraph(id: "cluster_\(baseClass.id.description.replacingOccurrences(of: ".", with: "_"))")
3034

31-
var classClusters: [Symbol: Set<Symbol>] = [:]
32-
for baseClass in module.interface.baseClasses {
33-
var superclasses = Set(CollectionOfOne(baseClass))
35+
for subclass in subclasses {
36+
var subclassNode = Node("\(subclass.id)")
37+
subclassNode.shape = .box
3438

35-
while !superclasses.isEmpty {
36-
let subclasses = Set(superclasses.flatMap { module.interface.typesInheriting(from: $0) }
37-
.filter { $0.isPublic })
38-
defer { superclasses = subclasses }
39-
classClusters[baseClass, default: []].formUnion(subclasses)
39+
if subclass.declaration.modifiers.contains(where: { $0.name == "final" }) {
40+
subclassNode.strokeWidth = 2.0
4041
}
41-
}
4242

43-
for (baseClass, cluster) in classClusters {
44-
var clusterLines: [String] = []
45-
46-
for subclass in cluster {
47-
if subclass.declaration.modifiers.contains(where: { $0.name == "final" }) {
48-
clusterLines.append(#""\#(subclass.id)" [shape=box,peripheries=2];"#)
49-
} else {
50-
clusterLines.append(#""\#(subclass.id)" [shape=box];"#)
51-
}
52-
53-
for superclass in module.interface.typesInherited(by: subclass) {
54-
clusterLines.append(#""\#(subclass.id)" -> "\#(superclass.id)";"#)
55-
}
56-
}
43+
subgraph.append(subclassNode)
44+
45+
for superclass in module.interface.typesInherited(by: subclass) {
46+
let superclassNode = Node("\(superclass.id)")
47+
subgraph.append(superclassNode)
5748

58-
if cluster.count > 1 {
59-
clusterLines = (
60-
["", "subgraph cluster_\(baseClass.id.description.replacingOccurrences(of: ".", with: "_")) {"] +
61-
clusterLines.map { $0.indented() } +
62-
["}", ""]
63-
)
49+
let edge = Edge(from: subclassNode, to: superclassNode)
50+
subgraph.append(edge)
6451
}
65-
66-
lines.append(contentsOf: clusterLines)
6752
}
53+
54+
if subclasses.count > 1 {
55+
graph.append(subgraph)
56+
} else {
57+
subgraph.nodes.forEach { graph.append($0) }
58+
subgraph.edges.forEach { graph.append($0) }
59+
}
60+
}
61+
6862

69-
lines.append("")
63+
for symbol in (module.interface.symbols.filter { $0.isPublic && $0.declaration is Type }) {
64+
let symbolNode = Node("\(symbol.id)")
65+
graph.append(symbolNode)
7066

71-
for symbol in (module.interface.symbols.filter { $0.isPublic && $0.declaration is Type }) {
72-
for inherited in module.interface.typesConformed(by: symbol) {
73-
lines.append(#""\#(symbol.id)" -> "\#(inherited.id)";"#)
74-
}
67+
for inherited in module.interface.typesConformed(by: symbol) {
68+
let inheritedNode = Node("\(inherited.id.description)")
69+
let edge = Edge(from: symbolNode, to: inheritedNode)
70+
71+
graph.append(inheritedNode)
72+
graph.append(edge)
7573
}
74+
}
7675

77-
lines = ["digraph \(module.name) {"] +
78-
lines.map { $0.indented() } +
79-
["}"]
76+
let encoder = DOTEncoder()
77+
let dot = encoder.encode(graph)
8078

81-
return lines.joined(separator: "\n")
82-
}
79+
return dot
8380
}
8481

8582
// MARK: -

0 commit comments

Comments
 (0)