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

Commit 1ba8ead

Browse files
committed
Distinguish operators from function implementations
1 parent ce432af commit 1ba8ead

File tree

9 files changed

+227
-9
lines changed

9 files changed

+227
-9
lines changed

Sources/SwiftDoc/Extensions/SwiftSemantics+Extensions.swift

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,3 +27,18 @@ extension Structure: Type {}
2727
extension Unknown: Type {
2828
public var inheritance: [String] { return [] }
2929
}
30+
31+
// MARK: -
32+
33+
extension Operator.Kind: Comparable {
34+
public static func < (lhs: Operator.Kind, rhs: Operator.Kind) -> Bool {
35+
switch (lhs, rhs) {
36+
case (_, .infix):
37+
return true
38+
case (.postfix, _):
39+
return true
40+
default:
41+
return false
42+
}
43+
}
44+
}

Sources/SwiftDoc/Helpers.swift

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ public func route(for symbol: Symbol) -> String {
66

77
public func route(for name: CustomStringConvertible) -> String {
88
return name.description.replacingOccurrences(of: ".", with: "_")
9+
.replacingOccurrences(of: " ", with: "-")
910
}
1011

1112
public func path(for symbol: Symbol, with baseURL: String) -> String {

Sources/SwiftDoc/Interface.swift

Lines changed: 27 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,18 @@ public final class Interface {
1212

1313
self.symbolsGroupedByIdentifier = Dictionary(grouping: symbols, by: { $0.id })
1414
self.symbolsGroupedByQualifiedName = Dictionary(grouping: symbols, by: { $0.id.description })
15-
self.topLevelSymbols = symbols.filter { $0.api is Type || $0.id.pathComponents.isEmpty }
15+
16+
self.topLevelSymbols = symbols.filter { symbol in
17+
if symbol.api is Type || symbol.api is Operator {
18+
return true
19+
}
20+
21+
if let function = symbol.api as? Function, function.isOperator {
22+
return false
23+
}
24+
25+
return symbol.id.pathComponents.isEmpty
26+
}
1627

1728
self.relationships = {
1829
let extensionsByExtendedType: [String: [Extension]] = Dictionary(grouping: symbols.flatMap { $0.context.compactMap { $0 as? Extension } }, by: { $0.extendedType })
@@ -89,6 +100,20 @@ public final class Interface {
89100
return Array(relationships)
90101
}()
91102

103+
self.functionsByOperator = {
104+
var functionsByOperator: [Symbol: Set<Symbol>] = [:]
105+
106+
let functionsGroupedByName = Dictionary(grouping: symbols.filter { $0.api is Function},
107+
by: { $0.api.name })
108+
109+
for `operator` in symbols.filter({ $0.api is Operator }) {
110+
let functions = functionsGroupedByName[`operator`.name] ?? []
111+
functionsByOperator[`operator`] = Set(functions)
112+
}
113+
114+
return functionsByOperator
115+
}()
116+
92117
self.relationshipsBySubject = Dictionary(grouping: relationships, by: { $0.subject.id })
93118
self.relationshipsByObject = Dictionary(grouping: relationships, by: { $0.object.id })
94119
}
@@ -98,6 +123,7 @@ public final class Interface {
98123
public let symbolsGroupedByIdentifier: [Symbol.ID: [Symbol]]
99124
public let symbolsGroupedByQualifiedName: [String: [Symbol]]
100125
public let topLevelSymbols: [Symbol]
126+
public var functionsByOperator: [Symbol: Set<Symbol>]
101127
public var baseClasses: [Symbol] {
102128
symbols.filter { $0.api is Class && typesInherited(by: $0).isEmpty }
103129
}
@@ -115,7 +141,6 @@ public final class Interface {
115141
}
116142

117143
return classClusters
118-
119144
}
120145

121146
public let relationships: [Relationship]

Sources/SwiftDoc/SourceFile.swift

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -168,6 +168,11 @@ public struct SourceFile: Hashable, Codable {
168168
return .skipChildren
169169
}
170170

171+
override func visit(_ node: OperatorDeclSyntax) -> SyntaxVisitorContinueKind {
172+
push(symbol(Operator.self, node))
173+
return .skipChildren
174+
}
175+
171176
override func visit(_ node: PrecedenceGroupDeclSyntax) -> SyntaxVisitorContinueKind {
172177
push(symbol(PrecedenceGroup.self, node))
173178
return .skipChildren

Sources/SwiftDoc/Symbol.swift

Lines changed: 18 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,8 +27,21 @@ public final class Symbol {
2727
return api.name
2828
}
2929

30+
public var kind: String {
31+
switch api {
32+
case let function as Function where function.isOperator:
33+
return "Operator"
34+
default:
35+
return String(describing: type(of: api))
36+
}
37+
}
38+
3039
public private(set) lazy var id: ID = {
31-
Identifier(pathComponents: context.compactMap {
40+
if let `operator` = api as? Operator, `operator`.kind != .infix {
41+
return Identifier(pathComponents: [], name: "\(`operator`.kind) \(`operator`.name)")
42+
}
43+
44+
return Identifier(pathComponents: context.compactMap {
3245
($0 as? Symbol)?.name ?? ($0 as? Extension)?.extendedType
3346
}, name: name)
3447
}()
@@ -38,6 +51,10 @@ public final class Symbol {
3851
return true
3952
}
4053

54+
if api is Operator {
55+
return true
56+
}
57+
4158
if api.modifiers.contains(where: { $0.name == "public" || $0.name == "open" }) {
4259
return true
4360
}

Sources/swift-doc/Subcommands/Generate.swift

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,8 @@ extension SwiftDoc {
6666
pages[route(for: symbol)] = TypePage(module: module, symbol: symbol, baseURL: baseURL, includingChildren: symbolFilter)
6767
case let `typealias` as Typealias:
6868
pages[route(for: `typealias`.name)] = TypealiasPage(module: module, symbol: symbol, baseURL: baseURL)
69+
case is Operator:
70+
pages[route(for: symbol)] = OperatorPage(module: module, symbol: symbol, baseURL: baseURL)
6971
case let function as Function where !function.isOperator:
7072
globals[function.name, default: []] += [symbol]
7173
case let variable as Variable:
Lines changed: 96 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,96 @@
1+
import CommonMarkBuilder
2+
import SwiftDoc
3+
import SwiftMarkup
4+
import SwiftSemantics
5+
import HypertextLiteral
6+
7+
struct OperatorImplementations: Component {
8+
var symbol: Symbol
9+
var module: Module
10+
let baseURL: String
11+
12+
var implementations: [Symbol]
13+
14+
init(of symbol: Symbol, in module: Module, baseURL: String, implementations: [Symbol]) {
15+
self.symbol = symbol
16+
self.module = module
17+
self.baseURL = baseURL
18+
self.implementations = implementations
19+
}
20+
21+
22+
// MARK: - Component
23+
24+
var fragment: Fragment {
25+
guard !implementations.isEmpty else { return Fragment { "" } }
26+
27+
return Fragment {
28+
ForEach(in: implementations) { implementation -> BlockConvertible in
29+
Section {
30+
Heading { implementation.name }
31+
32+
Documentation(for: implementation, in: module, baseURL: baseURL)
33+
}
34+
}
35+
}
36+
}
37+
38+
var html: HypertextLiteral.HTML {
39+
let sections = implementations.compactMap { implementation -> HypertextLiteral.HTML? in
40+
guard let `operator` = symbol.api as? Operator,
41+
let function = implementation.api as? Function
42+
else { return nil }
43+
44+
let heading: String
45+
switch `operator`.kind {
46+
case .infix:
47+
guard function.signature.input.count == 2,
48+
let lhs = function.signature.input.first,
49+
let rhs = function.signature.input.last
50+
else {
51+
return nil
52+
}
53+
54+
heading = [lhs.type, function.name, rhs.type, function.genericWhereClause].compactMap { $0 }.joined(separator: " ")
55+
case .prefix:
56+
guard function.signature.input.count == 2,
57+
let operand = function.signature.input.first
58+
else {
59+
return nil
60+
}
61+
heading = [function.name, operand.type, function.genericWhereClause].compactMap { $0 }.joined(separator: " ")
62+
63+
case .postfix:
64+
guard function.signature.input.count == 2,
65+
let operand = function.signature.input.first
66+
else {
67+
return nil
68+
}
69+
heading = [operand.type, function.name, function.genericWhereClause].compactMap { $0 }.joined(separator: " ")
70+
}
71+
72+
return #"""
73+
<section>
74+
<h3>\#(heading)</h3>
75+
\#(Documentation(for: implementation, in: module, baseURL: baseURL).html)
76+
</section>
77+
"""#
78+
}
79+
80+
guard !sections.isEmpty else { return "" }
81+
82+
return #"""
83+
<section id="implementations">
84+
<h2>Implementations</h2>
85+
\#(sections)
86+
</section>
87+
"""#
88+
}
89+
}
90+
91+
fileprivate extension Function {
92+
var genericWhereClause: String? {
93+
guard !genericRequirements.isEmpty else { return nil }
94+
return "where \(genericRequirements.map { $0.description }.joined(separator: ", "))"
95+
}
96+
}

Sources/swift-doc/Supporting Types/Pages/HomePage.swift

Lines changed: 17 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -34,8 +34,6 @@ struct HomePage: Page {
3434
globalTypealiases.append(symbol)
3535
case is Operator:
3636
operators.append(symbol)
37-
case let function as Function where function.isOperator:
38-
operators.append(symbol)
3937
case is Function:
4038
globalFunctions.append(symbol)
4139
case is Variable:
@@ -44,6 +42,18 @@ struct HomePage: Page {
4442
continue
4543
}
4644
}
45+
46+
classes.sort()
47+
enumerations.sort()
48+
structures.sort()
49+
protocols.sort()
50+
globalTypealiases.sort()
51+
operators.sort(by: { (lhs, rhs) in
52+
guard let lo = lhs.api as? Operator, let ro = rhs.api as? Operator else { return false }
53+
return (lo.kind, lo.name) > (ro.kind, ro.name)
54+
})
55+
globalFunctions.sort()
56+
globalVariables.sort()
4757
}
4858

4959
// MARK: - Page
@@ -57,15 +67,15 @@ struct HomePage: Page {
5767
ForEach(in: [
5868
("Types", classes + enumerations + structures),
5969
("Protocols", protocols),
60-
("Operators", operators),
6170
("Global Typealiases", globalTypealiases),
6271
("Global Functions", globalFunctions),
6372
("Global Variables", globalVariables),
73+
("Operators", operators),
6474
]) { (heading, symbols) in
6575
if (!symbols.isEmpty) {
6676
Heading { heading }
6777

68-
List(of: symbols.sorted()) { symbol in
78+
List(of: symbols) { symbol in
6979
Abstract(for: symbol, baseURL: baseURL).fragment
7080
}
7181
}
@@ -82,15 +92,16 @@ struct HomePage: Page {
8292
("Protocols", protocols),
8393
("Typealiases", globalTypealiases),
8494
("Functions", globalFunctions),
85-
("Variables", globalVariables)
95+
("Variables", globalVariables),
96+
("Operators", operators),
8697
].compactMap { (heading, symbols) -> HypertextLiteral.HTML? in
8798
guard !symbols.isEmpty else { return nil }
8899

89100
return #"""
90101
<section id=\#(heading.lowercased())>
91102
<h2>\#(heading)</h2>
92103
<dl>
93-
\#(symbols.sorted().map { Abstract(for: $0, baseURL: baseURL).html })
104+
\#(symbols.map { Abstract(for: $0, baseURL: baseURL).html })
94105
</dl>
95106
</section>
96107
"""#
Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
import SwiftSemantics
2+
import SwiftDoc
3+
import CommonMarkBuilder
4+
import HypertextLiteral
5+
6+
struct OperatorPage: Page {
7+
let module: Module
8+
let symbol: Symbol
9+
let implementations: [Symbol]
10+
let baseURL: String
11+
12+
init(module: Module, symbol: Symbol, baseURL: String) {
13+
precondition(symbol.api is Operator)
14+
self.module = module
15+
self.symbol = symbol
16+
self.implementations = module.interface.functionsByOperator[symbol]?.sorted() ?? []
17+
self.baseURL = baseURL
18+
}
19+
20+
// MARK: - Page
21+
22+
var title: String {
23+
return symbol.id.description
24+
}
25+
26+
var document: CommonMark.Document {
27+
return CommonMark.Document {
28+
Heading { symbol.id.description }
29+
30+
Documentation(for: symbol, in: module, baseURL: baseURL)
31+
}
32+
}
33+
34+
var html: HypertextLiteral.HTML {
35+
return #"""
36+
<h1>
37+
<small>\#(symbol.kind)</small>
38+
<code class="name">\#(softbreak(symbol.id.description))</code>
39+
</h1>
40+
41+
\#(Documentation(for: symbol, in: module, baseURL: baseURL).html)
42+
\#(OperatorImplementations(of: symbol, in: module, baseURL: baseURL, implementations: implementations).html)
43+
"""#
44+
}
45+
}
46+

0 commit comments

Comments
 (0)