Skip to content

Improve handling of lists when exporting markdown#1543

Open
jrturton wants to merge 2 commits into
swiftlang:mainfrom
jrturton:179135030/resolve-links-in-list-items
Open

Improve handling of lists when exporting markdown#1543
jrturton wants to merge 2 commits into
swiftlang:mainfrom
jrturton:179135030/resolve-links-in-list-items

Conversation

@jrturton

@jrturton jrturton commented Jun 11, 2026

Copy link
Copy Markdown
Contributor

Bug/issue #, if applicable:

179135030

Summary

  • Links in list items should get resolved to title + link (179135030)

Ordered and unordered lists were getting directly output using format() unless we were processing a linked list (like a topics section). This bypassed the link resolution / formatting steps that happen when visiting a link.

Changed the processing of lists to find and convert any links or symbol links found in the list item.

Dependencies

N/A

Testing

Create source documentation with article and symbol links inside ordered or unordered lists, and within standard paragraphs of prose. Run convert on the documentation with the --enable-experimental-markdown-output flag enabled. Examine the markdown produced in the docc archive. The links should be formatted the same in the list output as they are in the normal paragraph output.

Checklist

Make sure you check off the following items. If they cannot be completed, provide a reason.

  • Added tests
  • Ran the ./bin/test script and it succeeded
  • Updated documentation if necessary (N/A)

- Links in list items should get resolved to title + link (179135030)
- Term lists shouldn't render with the word "term" in them (166128254)
@jrturton jrturton requested a review from a team as a code owner June 11, 2026 13:49

@d-ronnqvist d-ronnqvist left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Hi, it appears that this is addressing two issues that aren't related to each other (and that seemingly doesn't modify the same lines of code). Can you please open one PR per issue so that it' easier to review the changes?

@d-ronnqvist

Copy link
Copy Markdown
Contributor

Regarding the "Testing" section of the PR description;
This section is meant to contain instructions describing how a reviewer can manually try out and verify the changes. It is expected that all PRs add automated tests to cover their changes. Please fill out the PR template (testing excerpt below) in both PRs.

Describe how a reviewer can test the functionality of your PR. Provide test content to test with if applicable.

Steps:

  • Provide setup instructions.
  • Explain in detail how the functionality can be tested.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Please use Swift Testing for all new tests. See the "Adding new tests" section of the contributing document for more details.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

I was planning to migrate all the tests to swift testing as a subsequent PR to this (now these?), to reduce noise - is that OK or would you prefer me to add another file with just the new tests in it?

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

The contributing document offers some guidance on this; specifically that you can create a new test suite in (even in the same file) that contains only the Swift Testing test(s):

Updating existing tests

If you're updating an existing test case with additional logic, we appreciate if you also modernize that test while updating it, but we don't expect it. If the test case is part of a large file, you can create new test suite which contains just the test case that you're modernizing.

If you modernize an existing test case, consider not only the syntactical differences between Swift Testing and XCTest, but also if there are any Swift Testing features or other changes that would make the test case easier to read, maintain, or debug.

Comment on lines +202 to +206
Tests the appearance of nested lists

## Overview

This is a list:

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

minor: This markup is never verified in the output and can be removed.

Suggested change
Tests the appearance of nested lists
## Overview
This is a list:

Comment on lines +212 to +217

## Topics

### No more links

Empty section

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

minor: This markup is never verified in the output and can be removed.

Suggested change
## Topics
### No more links
Empty section

This is a list of things that have links:

- You can use ``MarkdownSymbol`` to do interesting things
- You can't use other things

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

minor: This markup is never verified in the output and can be removed.

Suggested change
- You can't use other things

Comment on lines +171 to +175
## Topics

### No more links

Empty section

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

minor: This markup is never verified in the output and can be removed.

Suggested change
## Topics
### No more links
Empty section

Comment on lines +155 to +160
Tests the appearance of inline and linked lists

## Overview

These are some interesting links that are in list items:

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

minor: This markup is never verified in the output and can be removed.

Suggested change
Tests the appearance of inline and linked lists
## Overview
These are some interesting links that are in list items:

Comment on lines +165 to +166
These are some interesting links that are in ordered list items:

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

minor: This markup is never verified in the output and can be removed.

Suggested change
These are some interesting links that are in ordered list items:

}

mutating func visitSymbolLink(_ symbolLink: SymbolLink) {
mutating func convertSymbolLink(_ symbolLink: SymbolLink) -> (link: any InlineMarkup, abstract: (any Markup)?)? {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

minor: As far as I can tell, this is an implementation details that other types aren't expected to call.

Also, AFAICT this method doesn't mutate the walker.

Suggested change
mutating func convertSymbolLink(_ symbolLink: SymbolLink) -> (link: any InlineMarkup, abstract: (any Markup)?)? {
private func convertSymbolLink(_ symbolLink: SymbolLink) -> (link: any InlineMarkup, abstract: (any Markup)?)? {


mutating func visitLink(_ link: Link) {

mutating func convertLink(_ link: Link) -> (link: Link, abstract: (any Markup)?) {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

minor: As far as I can tell, this is an implementation details that other types aren't expected to call.

Also, AFAICT this method doesn't mutate the walker.

Suggested change
mutating func convertLink(_ link: Link) -> (link: Link, abstract: (any Markup)?) {
private func convertLink(_ link: Link) -> (link: Link, abstract: (any Markup)?) {

return List(newItems)
}

mutating func convertListItem(_ item: ListItem) -> ListItem {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

minor: As far as I can tell, this is an implementation details that other types aren't expected to call.

Also, AFAICT this method doesn't mutate the walker.

Suggested change
mutating func convertListItem(_ item: ListItem) -> ListItem {
private func convertListItem(_ item: ListItem) -> ListItem {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

This method could also benefit from a brief comment explaining what it does and why. From the signature all I can tell is that it converts a list item to the same type of list item.

Comment on lines +149 to +162
var newComponents: [any InlineMarkup] = []
for inlineChild in paragraph.inlineChildren {
if let link = inlineChild as? Link {
let (converted, _) = convertLink(link)
newComponents.append(converted)
} else if let symbolLink = inlineChild as? SymbolLink {
if let (converted, _) = convertSymbolLink(symbolLink) {
newComponents.append(converted)
}
} else {
newComponents.append(inlineChild)
}
}
newChildren.append(Paragraph(newComponents))

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

minor: It might make this implementation easier to read if this was extracted to a private function (or even an inner function. That would make it clearer at a glance that the outer loop is switching over the element type and calling different convert functions for each type.

Comment on lines +143 to +166
var newChildren: [any BlockMarkup] = []
for child in item.blockChildren {
if let nestedList = child as? any ListItemContainer {
let converted = convertList(nestedList)
newChildren.append(converted)
} else if let paragraph = child as? Paragraph {
var newComponents: [any InlineMarkup] = []
for inlineChild in paragraph.inlineChildren {
if let link = inlineChild as? Link {
let (converted, _) = convertLink(link)
newComponents.append(converted)
} else if let symbolLink = inlineChild as? SymbolLink {
if let (converted, _) = convertSymbolLink(symbolLink) {
newComponents.append(converted)
}
} else {
newComponents.append(inlineChild)
}
}
newChildren.append(Paragraph(newComponents))
} else {
newChildren.append(child)
}
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

There are a couple patterns hiding in this implementation that I feel make it harder to read than necessary.

Both loops behave like map (but is slower because it doesn't preemptively reserve capacity for the known number of elements) and both if-else expressions behave like a switch over a value.

It might be easier to read this implementation if it leveraged those language constructs.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants