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

Commit 083b37a

Browse files
authored
Merge branch 'master' into reda/css-build
2 parents 9b309ba + c757fff commit 083b37a

File tree

5 files changed

+192
-28
lines changed

5 files changed

+192
-28
lines changed

Changelog.md

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,16 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
1212
- Added `--base-url` option.
1313
#65 by @kean.
1414

15+
### Fixed
16+
17+
- Fixed relationship handling for members of nested types.
18+
#62 by @victor-pavlychko.
19+
- Fixed rendering of type relationships section when no graph data is available.
20+
#62 by @victor-pavlychko.
21+
- Fixed rendering of protocol requirements in the HTML version.
22+
#76 by @victor-pavlychko.
23+
24+
1525
## [1.0.0-beta.2] - 2020-04-08
1626

1727
### Changed

Sources/SwiftDoc/Interface.swift

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -55,9 +55,9 @@ public final class Interface: Codable {
5555
public private(set) lazy var relationships: [Relationship] = {
5656
var relationships: Set<Relationship> = []
5757
for symbol in symbols {
58-
let `extension` = symbol.context.compactMap({ $0 as? Extension }).first
58+
let lastDeclarationScope = symbol.context.last(where: { $0 is Extension || $0 is Symbol })
5959

60-
if let container = symbol.context.compactMap({ $0 as? Symbol }).last {
60+
if let container = lastDeclarationScope as? Symbol {
6161
let predicate: Relationship.Predicate
6262

6363
switch container.api {
@@ -74,7 +74,7 @@ public final class Interface: Codable {
7474
relationships.insert(Relationship(subject: symbol, predicate: predicate, object: container))
7575
}
7676

77-
if let `extension` = `extension` {
77+
if let `extension` = lastDeclarationScope as? Extension {
7878
if let extended = symbols.first(where: { $0.api is Type && $0.id.matches(`extension`.extendedType) }) {
7979

8080
let predicate: Relationship.Predicate

Sources/swift-doc/Supporting Types/Components/Relationships.swift

Lines changed: 26 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,23 @@ struct Relationships: Component {
3636
self.inheritedTypes = module.interface.typesInherited(by: symbol) + module.interface.typesConformed(by: symbol)
3737
}
3838

39+
var graphHTML: HypertextLiteral.HTML? {
40+
var graph = symbol.graph(in: module)
41+
guard !graph.edges.isEmpty else { return nil }
42+
43+
graph.aspectRatio = 0.125
44+
graph.center = true
45+
graph.overlap = "compress"
46+
47+
let algorithm: LayoutAlgorithm = graph.nodes.count > 3 ? .neato : .dot
48+
49+
do {
50+
return try HypertextLiteral.HTML(String(data: graph.render(using: algorithm, to: .svg), encoding: .utf8) ?? "")
51+
} catch {
52+
logger.error("\(error)")
53+
return nil
54+
}
55+
}
3956

4057
var sections: [(title: String, symbols: [Symbol])] {
4158
return [
@@ -73,33 +90,21 @@ struct Relationships: Component {
7390
}
7491

7592
var html: HypertextLiteral.HTML {
76-
var graph = symbol.graph(in: module)
77-
guard !graph.edges.isEmpty else { return "" }
78-
79-
graph.aspectRatio = 0.125
80-
graph.center = true
81-
graph.overlap = "compress"
82-
83-
let algorithm: LayoutAlgorithm = graph.nodes.count > 3 ? .neato : .dot
84-
var svg: HypertextLiteral.HTML?
85-
86-
do {
87-
svg = try HypertextLiteral.HTML(String(data: graph.render(using: algorithm, to: .svg), encoding: .utf8) ?? "")
88-
} catch {
89-
logger.error("\(error)")
90-
}
93+
guard !sections.isEmpty else { return "" }
9194

9295
return #"""
9396
<section id="relationships">
9497
<h2 hidden>Relationships</h2>
95-
<figure>
96-
\#(svg ?? "")
98+
\#(graphHTML.flatMap { (graphHTML) -> HypertextLiteral.HTML in
99+
return #"""
100+
<figure>
101+
\#(graphHTML)
97102

98-
<figcaption hidden>Inheritance graph for \#(symbol.id).</figcaption>
99-
</figure>
103+
<figcaption hidden>Inheritance graph for \#(symbol.id).</figcaption>
104+
</figure>
105+
"""#
106+
} ?? "")
100107
\#(sections.compactMap { (heading, symbols) -> HypertextLiteral.HTML? in
101-
guard !symbols.isEmpty else { return nil }
102-
103108
let partitioned = symbols.filter { !($0.api is Unknown) } + symbols.filter { ($0.api is Unknown) }
104109

105110
return #"""

Sources/swift-doc/Supporting Types/Components/Requirements.swift

Lines changed: 26 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -13,13 +13,16 @@ struct Requirements: Component {
1313
self.module = module
1414
}
1515

16+
var sections: [(title: String, requirements: [Symbol])] {
17+
return [
18+
("Requirements", module.interface.requirements(of: symbol)),
19+
("Optional Requirements", module.interface.optionalRequirements(of: symbol))
20+
].filter { !$0.requirements.isEmpty }
21+
}
22+
1623
// MARK: - Component
1724

1825
var fragment: Fragment {
19-
let sections: [(title: String, requirements: [Symbol])] = [
20-
("Requirements", module.interface.requirements(of: symbol)),
21-
("Optional Requirements", module.interface.optionalRequirements(of: symbol))
22-
].filter { !$0.requirements.isEmpty}
2326
guard !sections.isEmpty else { return Fragment { "" } }
2427

2528
return Fragment {
@@ -37,7 +40,26 @@ struct Requirements: Component {
3740

3841
var html: HypertextLiteral.HTML {
3942
return #"""
43+
\#(sections.map { section -> HypertextLiteral.HTML in
44+
#"""
45+
<section id=\#(section.title.lowercased())>
46+
<h2>\#(section.title)</h2>
47+
48+
\#(section.requirements.map { member -> HypertextLiteral.HTML in
49+
let descriptor = String(describing: type(of: symbol.api)).lowercased()
4050

51+
return #"""
52+
<div role="article" class="\#(descriptor)" id=\#(member.id.description.lowercased().replacingOccurrences(of: " ", with: "-"))>
53+
<h3>
54+
<code>\#(softbreak(member.name))</code>
55+
</h3>
56+
\#(Documentation(for: member, in: module).html)
57+
</div>
58+
"""#
59+
})
60+
</section>
61+
"""#
62+
})
4163
"""#
4264
}
4365
}
Lines changed: 127 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,127 @@
1+
import XCTest
2+
3+
import SwiftDoc
4+
import SwiftSemantics
5+
import struct SwiftSemantics.Protocol
6+
import SwiftSyntax
7+
8+
final class NestedTypesTests: XCTestCase {
9+
func testNestedTypes() throws {
10+
let source = #"""
11+
public class C { }
12+
13+
extension C {
14+
public enum E {
15+
case c
16+
}
17+
}
18+
19+
extension C.E {
20+
public static let tp = 0
21+
}
22+
"""#
23+
24+
let url = try temporaryFile(contents: source)
25+
let sourceFile = try SourceFile(file: url, relativeTo: url.deletingLastPathComponent())
26+
let module = Module(name: "Module", sourceFiles: [sourceFile])
27+
28+
XCTAssertEqual(sourceFile.symbols.count, 4)
29+
30+
// `class C`
31+
let `class` = sourceFile.symbols[0]
32+
XCTAssert(`class`.api is Class)
33+
34+
// `enum E`
35+
let `enum` = sourceFile.symbols[1]
36+
XCTAssert(`enum`.api is Enumeration)
37+
38+
// `case c`
39+
let `case` = sourceFile.symbols[2]
40+
XCTAssert(`case`.api is Enumeration.Case)
41+
42+
// `let tp`
43+
let `let` = sourceFile.symbols[3]
44+
XCTAssert(`let`.api is Variable)
45+
46+
// `class C` contains `enum E`
47+
let classRelationships = try XCTUnwrap(module.interface.relationshipsByObject[`class`.id])
48+
XCTAssertEqual(classRelationships.count, 1)
49+
XCTAssertTrue(classRelationships.allSatisfy({ $0.predicate == .memberOf }))
50+
XCTAssertEqual(Set(classRelationships.map({ $0.subject.id })), Set([`enum`.id]))
51+
52+
// `enum C` contains `case c` and `let tp`
53+
let enumRelationships = try XCTUnwrap(module.interface.relationshipsByObject[`enum`.id])
54+
XCTAssertEqual(enumRelationships.count, 2)
55+
XCTAssertTrue(enumRelationships.allSatisfy({ $0.predicate == .memberOf }))
56+
XCTAssertEqual(Set(enumRelationships.map({ $0.subject.id })), Set([`case`.id, `let`.id]))
57+
58+
// `case c` and `let tp` have no relationships
59+
XCTAssertNil(module.interface.relationshipsByObject[`case`.id])
60+
XCTAssertNil(module.interface.relationshipsByObject[`let`.id])
61+
62+
// no other relationships present in module
63+
XCTAssertEqual(
64+
module.interface.relationships.count,
65+
[classRelationships, enumRelationships].joined().count
66+
)
67+
}
68+
69+
#if false // Disabling tests for `swift-doc` code, executable targers are not testable.
70+
71+
func testRelationshipsSectionWithNestedTypes() throws {
72+
let source = #"""
73+
public class C {
74+
public enum E {
75+
}
76+
}
77+
"""#
78+
79+
let url = try temporaryFile(contents: source)
80+
let sourceFile = try SourceFile(file: url, relativeTo: url.deletingLastPathComponent())
81+
let module = Module(name: "Module", sourceFiles: [sourceFile])
82+
83+
// `class C`
84+
let `class` = sourceFile.symbols[0]
85+
XCTAssert(`class`.api is Class)
86+
87+
// `enum E`
88+
let `enum` = sourceFile.symbols[1]
89+
XCTAssert(`enum`.api is Enumeration)
90+
91+
let classRelationships = Relationships(of: `class`, in: module)
92+
XCTAssertNotEqual(classRelationships.html, "")
93+
94+
let enumRelationships = Relationships(of: `enum`, in: module)
95+
XCTAssertNotEqual(enumRelationships.html, "")
96+
}
97+
98+
func testNoRelationshipsSection() throws {
99+
let source = #"""
100+
public class C {
101+
}
102+
103+
public enum E {
104+
}
105+
"""#
106+
107+
let url = try temporaryFile(contents: source)
108+
let sourceFile = try SourceFile(file: url, relativeTo: url.deletingLastPathComponent())
109+
let module = Module(name: "Module", sourceFiles: [sourceFile])
110+
111+
// `class C`
112+
let `class` = sourceFile.symbols[0]
113+
XCTAssert(`class`.api is Class)
114+
115+
// `enum E`
116+
let `enum` = sourceFile.symbols[1]
117+
XCTAssert(`enum`.api is Enumeration)
118+
119+
let classRelationships = Relationships(of: `class`, in: module)
120+
XCTAssertEqual(classRelationships.html, "")
121+
122+
let enumRelationships = Relationships(of: `enum`, in: module)
123+
XCTAssertEqual(enumRelationships.html, "")
124+
}
125+
126+
#endif
127+
}

0 commit comments

Comments
 (0)