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

Commit da1189a

Browse files
Lukas-Stuehrkmattt
andauthored
Add option for specifying which access levels are included (#219)
* Generate: Add an option to set the minimal access level of included symbols. This slightly changes the behaviour of the internal data structures which collect information on the symbols (e.g. Interface, Members and Symbol). Now, they contain every symbol, not only the public ones. The decision which symbol to include in the documentation is moved to the generating of the documentation. * Coverage: Add an option to set the minimal access level of included symbols. * Diagram: Add an option to set the minimal access level of included symbols. * Add a better error message for new users. * Update README with information on the new flag. * Provide the new access level option also in the Github action. * Copy editing * Use long flag for minimum access level option * Copy edit minimum access level option help * Copy edit minimum-access-level input description * Add changelog entry for #219 * Copy edit minimumAccessLevel help for diagram command Co-authored-by: Mattt <mattt@me.com>
1 parent cdac212 commit da1189a

File tree

15 files changed

+163
-34
lines changed

15 files changed

+163
-34
lines changed

Changelog.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
1111

1212
- Added end-to-end tests for command-line interface.
1313
#199 by @MaxDesiatov and @mattt.
14+
- Added `--minimum-access-level` option to `generate` and `coverage` commands.
15+
#219 by @Lukas-Stuehrk.
1416

1517
### Fixed
1618

README.md

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -102,6 +102,9 @@ $ apt-get install -y libxml2-dev graphviz
102102
-f, --format <format> The output format (default: commonmark)
103103
--base-url <base-url> The base URL used for all relative URLs in generated
104104
documents. (default: /)
105+
--minimum-access-level <minimum-access-level>
106+
The minimum access level of the symbols which should
107+
be included. (default: public)
105108
-h, --help Show help information.
106109

107110
The `generate` subcommand
@@ -131,6 +134,12 @@ $ Documentation/
131134
└── index.html
132135
```
133136

137+
By default,
138+
`swift-doc` includes only symbols declared as `public` or `open`
139+
in the generated documentation.
140+
To include `internal` or `private` declarations,
141+
pass the `--minimum-access-level` flag with the specified access level.
142+
134143
#### swift-doc coverage
135144

136145
OVERVIEW: Generates documentation coverage statistics for Swift files
@@ -142,6 +151,9 @@ $ Documentation/
142151

143152
OPTIONS:
144153
-o, --output <output> The path for generated report
154+
--minimum-access-level <minimum-access-level>
155+
The minimum access level of the symbols which should
156+
be included. (default: public)
145157
-h, --help Show help information.
146158

147159
The `coverage` subcommand
@@ -190,6 +202,9 @@ please reach out by [opening an Issue][open an issue]!
190202
<inputs> One or more paths to Swift files
191203

192204
OPTIONS:
205+
--minimum-access-level <minimum-access-level>
206+
The minimum access level of the symbols which should
207+
be included. (default: public)
193208
-h, --help Show help information.
194209

195210
The `diagram` subcommand

Sources/SwiftDoc/Interface.swift

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,14 +8,13 @@ public final class Interface {
88

99
public required init(imports: [Import], symbols: [Symbol]) {
1010
self.imports = imports
11-
self.symbols = symbols.filter { $0.isPublic }
11+
self.symbols = symbols
1212

1313
self.symbolsGroupedByIdentifier = Dictionary(grouping: symbols, by: { $0.id })
1414
self.symbolsGroupedByQualifiedName = Dictionary(grouping: symbols, by: { $0.id.description })
1515
self.topLevelSymbols = symbols.filter { $0.api is Type || $0.id.pathComponents.isEmpty }
1616

1717
self.relationships = {
18-
let symbols = symbols.filter { $0.isPublic }
1918
let extensionsByExtendedType: [String: [Extension]] = Dictionary(grouping: symbols.flatMap { $0.context.compactMap { $0 as? Extension } }, by: { $0.extendedType })
2019

2120
var relationships: Set<Relationship> = []

Sources/SwiftDoc/Symbol.swift

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,44 @@ public final class Symbol {
6666
return false
6767
}
6868

69+
public var isPrivate: Bool {
70+
// We always assume that `Unknown` is public.
71+
guard api is Unknown == false else {
72+
return false
73+
}
74+
75+
if api.modifiers.contains(where: { $0.detail == nil && ($0.name == "private" || $0.name == "fileprivate") }) {
76+
return true
77+
}
78+
79+
if let `extension` = `extension`,
80+
`extension`.modifiers.contains(where: { $0.name == "private" || $0.name == "fileprivate" }) {
81+
82+
return api.modifiers.allSatisfy { modifier in
83+
modifier.detail != nil || (modifier.name != "internal" && modifier.name != "public" && modifier.name != "open")
84+
}
85+
}
86+
87+
if let symbol = context.compactMap({ $0 as? Symbol }).last,
88+
symbol.api.modifiers.contains(where: { $0.name == "private" || $0.name == "fileprivate" })
89+
{
90+
switch symbol.api {
91+
case is Enumeration:
92+
return api is Enumeration.Case
93+
case is Protocol:
94+
return api is Function || api is Variable
95+
default:
96+
break
97+
}
98+
}
99+
100+
return false
101+
}
102+
103+
public var isInternal: Bool {
104+
!isPublic && !isPrivate
105+
}
106+
69107
public var isDocumented: Bool {
70108
return documentation?.isEmpty == false
71109
}

Sources/swift-doc/Extensions/DCOV+Extensions.swift

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -18,10 +18,10 @@ extension Entry {
1818
// MARK: -
1919

2020
extension Report {
21-
public init(module: Module) {
21+
public init(module: Module, symbolFilter: (Symbol) -> Bool) {
2222
let entries = module.sourceFiles
2323
.flatMap { $0.symbols }
24-
.filter { $0.isPublic }
24+
.filter(symbolFilter)
2525
.map { Entry($0) }
2626

2727
self.init(entries: entries)

Sources/swift-doc/Subcommands/Coverage.swift

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,10 @@ extension SwiftDoc {
1212
@Option(name: .shortAndLong,
1313
help: "The path for generated report")
1414
var output: String?
15+
16+
@Option(name: .long,
17+
help: "The minimum access level of the symbols considered for coverage statistics.")
18+
var minimumAccessLevel: AccessLevel = .public
1519
}
1620

1721
static var configuration = CommandConfiguration(abstract: "Generates documentation coverage statistics for Swift files")
@@ -21,7 +25,7 @@ extension SwiftDoc {
2125

2226
func run() throws {
2327
let module = try Module(paths: options.inputs)
24-
let report = Report(module: module)
28+
let report = Report(module: module, symbolFilter: options.minimumAccessLevel.includes(symbol:))
2529

2630
if let output = options.output {
2731
let encoder = JSONEncoder()

Sources/swift-doc/Subcommands/Diagram.swift

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -11,23 +11,27 @@ extension SwiftDoc {
1111
struct Options: ParsableArguments {
1212
@Argument(help: "One or more paths to Swift files")
1313
var inputs: [String]
14+
15+
@Option(name: .long,
16+
help: "The minimum access level of the symbols included in the generated diagram.")
17+
var minimumAccessLevel: AccessLevel = .public
1418
}
1519

1620
static var configuration = CommandConfiguration(abstract: "Generates diagram of Swift symbol relationships")
1721

1822
@OptionGroup()
1923
var options: Options
20-
24+
2125
func run() throws {
2226
let module = try Module(paths: options.inputs)
23-
print(diagram(of: module), to: &standardOutput)
27+
print(diagram(of: module, including: options.minimumAccessLevel.includes(symbol:)), to: &standardOutput)
2428
}
2529
}
2630
}
2731

2832
// MARK: -
2933

30-
fileprivate func diagram(of module: Module) -> String {
34+
fileprivate func diagram(of module: Module, including symbolFilter: (Symbol) -> Bool) -> String {
3135
var graph = Graph(directed: true)
3236

3337
for (baseClass, subclasses) in module.interface.classHierarchies {
@@ -61,7 +65,7 @@ fileprivate func diagram(of module: Module) -> String {
6165
}
6266

6367

64-
for symbol in (module.interface.symbols.filter { $0.isPublic && $0.api is Type }) {
68+
for symbol in (module.interface.symbols.filter { $0.api is Type }).filter(symbolFilter) {
6569
let symbolNode = Node("\(symbol.id)")
6670
graph.append(symbolNode)
6771

Sources/swift-doc/Subcommands/Generate.swift

Lines changed: 13 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,10 @@ extension SwiftDoc {
3535
@Option(name: .customLong("base-url"),
3636
help: "The base URL used for all relative URLs in generated documents.")
3737
var baseURL: String = "/"
38+
39+
@Option(name: .long,
40+
help: "The minimum access level of the symbols included in generated documentation.")
41+
var minimumAccessLevel: AccessLevel = .public
3842
}
3943

4044
static var configuration = CommandConfiguration(abstract: "Generates Swift documentation")
@@ -55,10 +59,11 @@ extension SwiftDoc {
5559
var pages: [String: Page] = [:]
5660

5761
var globals: [String: [Symbol]] = [:]
58-
for symbol in module.interface.topLevelSymbols.filter({ $0.isPublic }) {
62+
let symbolFilter = options.minimumAccessLevel.includes(symbol:)
63+
for symbol in module.interface.topLevelSymbols.filter(symbolFilter) {
5964
switch symbol.api {
6065
case is Class, is Enumeration, is Structure, is Protocol:
61-
pages[route(for: symbol)] = TypePage(module: module, symbol: symbol, baseURL: baseURL)
66+
pages[route(for: symbol)] = TypePage(module: module, symbol: symbol, baseURL: baseURL, includingChildren: symbolFilter)
6267
case let `typealias` as Typealias:
6368
pages[route(for: `typealias`.name)] = TypealiasPage(module: module, symbol: symbol, baseURL: baseURL)
6469
case let function as Function where !function.isOperator:
@@ -76,6 +81,9 @@ extension SwiftDoc {
7681

7782
guard !pages.isEmpty else {
7883
logger.warning("No public API symbols were found at the specified path. No output was written.")
84+
if options.minimumAccessLevel == .public {
85+
logger.warning("By default, swift-doc only includes public declarations. Maybe you want to use --minimum-access-level to include non-public declarations?")
86+
}
7987
return
8088
}
8189

@@ -93,11 +101,11 @@ extension SwiftDoc {
93101
} else {
94102
switch format {
95103
case .commonmark:
96-
pages["Home"] = HomePage(module: module, baseURL: baseURL)
97-
pages["_Sidebar"] = SidebarPage(module: module, baseURL: baseURL)
104+
pages["Home"] = HomePage(module: module, baseURL: baseURL, symbolFilter: symbolFilter)
105+
pages["_Sidebar"] = SidebarPage(module: module, baseURL: baseURL, symbolFilter: symbolFilter)
98106
pages["_Footer"] = FooterPage(baseURL: baseURL)
99107
case .html:
100-
pages["Home"] = HomePage(module: module, baseURL: baseURL)
108+
pages["Home"] = HomePage(module: module, baseURL: baseURL, symbolFilter: symbolFilter)
101109
}
102110

103111
try pages.map { $0 }.parallelForEach {
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
import ArgumentParser
2+
import SwiftDoc
3+
4+
enum AccessLevel: String, ExpressibleByArgument {
5+
case `public`
6+
case `internal`
7+
8+
func includes(symbol: Symbol) -> Bool {
9+
switch self {
10+
case .public:
11+
return symbol.isPublic
12+
case .internal:
13+
return symbol.isPublic || symbol.isInternal
14+
}
15+
}
16+
}

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

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -18,14 +18,14 @@ struct Members: Component {
1818
var methods: [Symbol]
1919
var genericallyConstrainedMembers: [[GenericRequirement] : [Symbol]]
2020

21-
init(of symbol: Symbol, in module: Module, baseURL: String) {
21+
init(of symbol: Symbol, in module: Module, baseURL: String, symbolFilter: (Symbol) -> Bool) {
2222
self.symbol = symbol
2323
self.module = module
2424
self.baseURL = baseURL
2525

2626
self.members = module.interface.members(of: symbol)
2727
.filter { $0.extension?.genericRequirements.isEmpty != false }
28-
.filter { $0.isPublic }
28+
.filter(symbolFilter)
2929

3030
self.typealiases = members.filter { $0.api is Typealias }
3131
self.initializers = members.filter { $0.api is Initializer }

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

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,11 +16,11 @@ struct HomePage: Page {
1616
var globalFunctions: [Symbol] = []
1717
var globalVariables: [Symbol] = []
1818

19-
init(module: Module, baseURL: String) {
19+
init(module: Module, baseURL: String, symbolFilter: (Symbol) -> Bool) {
2020
self.module = module
2121
self.baseURL = baseURL
2222

23-
for symbol in module.interface.topLevelSymbols.filter({ $0.isPublic }) {
23+
for symbol in module.interface.topLevelSymbols.filter(symbolFilter) {
2424
switch symbol.api {
2525
case is Class:
2626
classes.append(symbol)

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

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,11 +14,11 @@ struct SidebarPage: Page {
1414
var globalFunctionNames: Set<String> = []
1515
var globalVariableNames: Set<String> = []
1616

17-
init(module: Module, baseURL: String) {
17+
init(module: Module, baseURL: String, symbolFilter: (Symbol) -> Bool) {
1818
self.module = module
1919
self.baseURL = baseURL
2020

21-
for symbol in module.interface.topLevelSymbols.filter({ $0.isPublic }) {
21+
for symbol in module.interface.topLevelSymbols.filter(symbolFilter) {
2222
switch symbol.api {
2323
case is Class:
2424
typeNames.insert(symbol.id.description)

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

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -7,12 +7,14 @@ struct TypePage: Page {
77
let module: Module
88
let symbol: Symbol
99
let baseURL: String
10+
let symbolFilter: (Symbol) -> Bool
1011

11-
init(module: Module, symbol: Symbol, baseURL: String) {
12+
init(module: Module, symbol: Symbol, baseURL: String, includingChildren symbolFilter: @escaping (Symbol) -> Bool) {
1213
precondition(symbol.api is Type)
1314
self.module = module
1415
self.symbol = symbol
1516
self.baseURL = baseURL
17+
self.symbolFilter = symbolFilter
1618
}
1719

1820
// MARK: - Page
@@ -27,7 +29,7 @@ struct TypePage: Page {
2729

2830
Documentation(for: symbol, in: module, baseURL: baseURL)
2931
Relationships(of: symbol, in: module, baseURL: baseURL)
30-
Members(of: symbol, in: module, baseURL: baseURL)
32+
Members(of: symbol, in: module, baseURL: baseURL, symbolFilter: symbolFilter)
3133
Requirements(of: symbol, in: module, baseURL: baseURL)
3234
}
3335
}
@@ -41,7 +43,7 @@ struct TypePage: Page {
4143
4244
\#(Documentation(for: symbol, in: module, baseURL: baseURL).html)
4345
\#(Relationships(of: symbol, in: module, baseURL: baseURL).html)
44-
\#(Members(of: symbol, in: module, baseURL: baseURL).html)
46+
\#(Members(of: symbol, in: module, baseURL: baseURL, symbolFilter: symbolFilter).html)
4547
\#(Requirements(of: symbol, in: module, baseURL: baseURL).html)
4648
"""#
4749
}

0 commit comments

Comments
 (0)