10000 Fix nested type handling by victor-pavlychko · Pull Request #62 · SwiftDocOrg/swift-doc · GitHub
[go: up one dir, main page]

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

Fix nested type handling #62

Merged
merged 9 commits into from
Apr 10, 2020

Conversation

victor-pavlychko
Copy link
Contributor

First of all, thanks for a great tool! 🙂

While running swift-doc with one of my current projects, I noticed a bug with nested type handling.

Consider the following input:

public class Outer { }

extension Outer {
    public enum Inner {
        case Case
    }
}

extension Outer.Inner {
    public static let Ver = 0
}

This snippet triggers the following issues:

  • Case and Ver symbols are mistakenly associated with Outer symbol
  • No "nested types" and "member of" sections are generated

@mattt
Copy link
Contributor
mattt commented Apr 8, 2020

Thanks so much for taking the time to identify this issue and provide a fix, @victor-pavlychko. I really appreciate it!

I'd been holding off on investing in a robust test suite for this functionality, because I thought it might soon be replaced (see #31). But now that I'm more confident that the current, syntactic approach will be sticking around, I'd like to add more unit tests to exercise these kinds of corner cases.

Could you please add a test case to verify the behavior of your patch? The example you used here is great; just a few requested changes to match the conventions of the existing test:

public class C { }

extension C {
    public enum E {
        case c
    }
}

extension C.E {
    public static let tp = 0
}

graph.aspectRatio = 0.125
graph.center = true
graph.overlap = "compress"
guard sections.map(\.symbols.count).reduce(0, +) != 0 else { return "" }
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm still on the fence about Swift's new keypath map syntax. But setting that aside: We don't need to compute the total count to determine if there are any symbols. A better way would be to do let _ = sections.first { !$0.symbols.isEmpty }.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not sure about checking the first item only:

  • I noticed that sections are processed with filter { !$0.symbols.isEmpty } so there should be no empty sections at the moment, we can just test for sections.isEmpty
  • at the same time, I see guard !symbols.isEmpty else { return nil } in the generation code

So I decided to write a more defensive check to be sure that it won't break in the future.

I'd prefer to state that we never have empty sections and simplify/remove all relevant checks.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

To clarify: the use of first doesn't check the first item, but returns as soon as it finds a section with non-empty symbols. But taking another look, you're right: a simple check on sections.isEmpty should be sufficient. The other guard statement you mentioned was from an earlier iteration, and I neglected to remove it.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oh, right, I've misunderstood your initial suggestion. Thanks for clarifying that.

Changing to isEmpty + removed that guard statement.

Comment on lines 76 to 91
var graph = symbol.graph(in: module)
guard !graph.edges.isEmpty else { return "" }

graph.aspectRatio = 0.125
graph.center = true
graph.overlap = "compress"
guard sections.map(\.symbols.count).reduce(0, +) != 0 else { return "" }

let algorithm: LayoutAlgorithm = graph.nodes.count > 3 ? .neato : .dot
var svg: HypertextLiteral.HTML?
var graph = symbol.graph(in: module)
if !graph.edges.isEmpty {
graph.aspectRatio = 0.125
graph.center = true
graph.overlap = "compress"

let algorithm: LayoutAlgorithm = graph.nodes.count > 3 ? .neato : .dot

do {
svg = try HypertextLiteral.HTML(String(data: graph.render(using: algorithm, to: .svg), encoding: .utf8) ?? "")
} catch {
logger.error("\(error)")
do {
svg = try HypertextLiteral.HTML(String(data: graph.render(using: algorithm, to: .svg), encoding: .utf8) ?? "")
} catch {
logger.error("\(error)")
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this has gotten complex enough to warrant extraction into a separate helper method / property (e.g. var svg: HyperTextLiteral.HTML. What do you think?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Agree, moved to graphHTML property.

}

return #"""
<section id="relationships">
<h2 hidden>Relationships</h2>
<figure>
\#(svg ?? "")
\#(svg.flatMap { svg in
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not sure what flatMap is providing here as compared to the original, but that could just be me having trouble reading the diff.

Copy link
Contributor Author
@victor-pavlychko victor-pavlychko Apr 8, 2020

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I moved the <figure> block inside flatMap so it is not rendered when the svg data is missing.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah, got it. Thanks for clarifying.

@@ -55,9 +55,9 @@ public final class Interface: Codable {
public private(set) lazy var relationships: [Relationship] = {
var relationships: Set<Relationship> = []
for symbol in symbols {
let `extension` = symbol.context.compactMap({ $0 as? Extension }).first
let owner = symbol.context.last(where: { $0 is Extension || $0 is Symbol })
Copy link
Contributor
@mattt mattt Apr 8, 2020

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not sure owner is the right name for this. Can we come up with something better? (If not, we can go with something inelegant but precise, like lastExtensionOrSymbolContext)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I can also propose lastDeclarationScope, what do you think?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Perfect!

@victor-pavlychko
Copy link
Contributor Author

Thanks for the quick feedback. I have added a test for SwiftDoc library functionality. As for the swift-doc rendering code, the tests are also there, but, I have disabled them for now. It looks like SPM can't test executable targets.

@kean
Copy link
Contributor
kean commented Apr 8, 2020

@victor-pavlychko, I was going to work on the same thing but you beat me to it 😭

As for the swift-doc rendering code, the tests are also there, but, I have disabled them for now. It looks like SPM can't test executable targets.

I was wondering the same thing. Regardless of whether it can or can't test executables, it might be worth moving all of this functionality to SwiftDoc library.

@kean
Copy link
Contributor
kean commented Apr 8, 2020

I performed manual testing, the changes in this branch fix the issue #25

@mattt
Copy link
Contributor
mattt commented Apr 8, 2020

@kean: Regardless of whether it can or can't test executables, it might be worth moving all of this functionality to SwiftDoc library.

I'm still not 100% on the current shape and spelling of the SwiftDoc module, but I'm pretty confident that anything to do with rendering pages should be in a separate module.

While we're waiting on end-to-end tests for the executable, could we write tests that @testable import swift-doc? (I don't think I've ever tried to do anything with the module of an executable, so I'm genuinely curious). If not, then I think the next best thing for now would be to move everything but the commands themselves into a new module, and test the rendering behavior through that directly.

@victor-pavlychko
Copy link
Contributor Author

@mattt: While we're waiting on end-to-end tests for the executable, could we write tests that @testable import swift-doc? (I don't think I've ever tried to do anything with the module of an executable, so I'm genuinely curious).

Tried that: it complies swift files and then fails to link with the swift-doc module throwing a bunch of unresolved external errors.

I suggest leaving those test methods disabled for now and enabling them once the functionality is moved somewhere.

- rename owner -> lastDeclarationScope
- improve checks in Relationships renderer
@victor-pavlychko victor-pavlychko requested a review from mattt April 9, 2020 18:48
Copy link
Contributor
@mattt mattt left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Great work, @victor-pavlychko! Thanks for incorporating my feedback and answering my questions. I'm really happy to see this merged for the next release.

If you don't mind me asking for one more thing: Could you please update the Changelog to add an entry for this fix (sorry for not mentioning this until now; this is a new process I've been trying since #65, and am still figuring out how to document / automate everything)

@mattt mattt merged commit bac2610 into SwiftDocOrg:master Apr 10, 2020
@mattt
Copy link
Contributor
mattt commented Apr 10, 2020

Perfect. Thanks again, @victor-pavlychko! 🎉

@victor-pavlychko victor-pavlychko deleted the fix/nested-types branch April 10, 2020 22:31
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants
0