-
Notifications
You must be signed in to change notification settings - Fork 128
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Support @Metadata
and @DeprecationSummary
in documentation comments
#1107
Support @Metadata
and @DeprecationSummary
in documentation comments
#1107
Conversation
@swift-ci please test |
Woo! Do the directives apply recursively? For instance, if I have: /// @Metadata {
/// @Available(Swift, introduced: 6.1)
/// }
struct S {
var x
} Will |
Good question. As of this PR, the changes don't get applied recursively. You need to annotate every member. Longer term I think it would make sense for availability to be carried over to child members (like in Swift), but we'd want a way to support annotating an API as unavailable so that you can opt out of the inferred availability. Currently the Available directive only supports introduced and deprecated versions. |
I think for our own purposes (Swift Testing) we can just make sure all symbols are explicitly annotated, but let's make sure to track that enhancement somewhere as it's probably not a scalable solution long-term. 🤔 |
bundle: bundle, | ||
context: context, | ||
problems: &problems | ||
) as! (Metadata?, [BlockDirective]) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why the need to force cast here?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It was because the method returned any Markup
and we needed to force cast the second element of the tuple so that it can be assigned to docCommentDirectives
. But with your suggestion of making the markupElements
argument inout
, this is no longer needed.
import Markdown | ||
|
||
/// A utlity type for parsing directives from markup. | ||
struct DirectiveParser<Directive: AutomaticDirectiveConvertible> { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
minor: If this doesn't have any state, should it be an enum with static methods instead?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I typically prefer set these as structs in case we want to add configuration arguments to the initialiser, e.g., to customise the parsing behaviour. In this case it doesn't matter though, because the API is not public and we can switch it from an enum to a struct if/when we need to.
bundle: DocumentationBundle, | ||
context: DocumentationContext |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why are we adding the bundle
and context
parameters here?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We need to pass them all the way down to DirectiveParser
because the Metadata
initialiser needs it.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Franklin and I talked about this some more offline. Ideally the directives wouldn't need the full context because it creates a cyclic dependency. They really only need some mechanism to load data but changing that is way out of scope for this PR.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
+1 to this. I think there should be a better way to do it without having to pass the full context and bundle. This does not look correct for initialising a symbol.
/// and diagnoses subsequent instances. | ||
func parseSingleDirective<MarkupElements: Sequence<Markup>>( | ||
_ directiveType: Directive.Type, | ||
from markupElements: MarkupElements, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Considering how this is called, how do you feel about making the markup inout
instead of returning remaining markup?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
That's a great suggestion, thanks! Initially I though we'd run into trouble because we sometimes call this function with [any Markup]
and sometimes with [any BlockDirective]
and I thought that'd be problematic with type erasure and any Markup
not conforming to Markup
, but we can actually just make this function take inout [any Markup]
, so I did that.
in context: DocumentationContext, | ||
problems: inout [Problem] | ||
) { | ||
let invalidDirectives: [(any AutomaticDirectiveConvertible)?] = [ |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Would it be better to have a list of supported directives rather than the unsupported directives here? I imagine that that would need to be updated less frequently.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I went back and forth on this. If we were to specify the valid ones, we'd still need to somehow get the values of the invalid ones in order to produce a diagnostic. I couldn't find a good way to get an array of the valid ones easily. We could maybe use keyPaths.values
and find a way to generically access the relevant properties, but that seems too complex.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I like the idea of having the valid ones, If not we will have to remember to add it to the list, which could be forgotten and lead to errors.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We'd need to list the valid ones as well anyway, unfortunately.
func parseSingleDirective<MarkupElements: Sequence<Markup>>( | ||
_ directiveType: Directive.Type, | ||
from markupElements: MarkupElements, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
FYI: You can express this more concisely using some
func parseSingleDirective<MarkupElements: Sequence<Markup>>( | |
_ directiveType: Directive.Type, | |
from markupElements: MarkupElements, | |
func parseSingleDirective( | |
_ directiveType: Directive.Type, | |
from markupElements: some Sequence<Markup>, |
@@ -416,10 +420,7 @@ class MetadataTests: XCTestCase { | |||
let document = Document(parsing: source, options: [.parseBlockDirectives, .parseSymbolLinks]) | |||
let (bundle, context) = try testBundleAndContext(named: "TestBundle") | |||
|
|||
var analyzer = SemanticAnalyzer(source: nil, context: context, bundle: bundle) | |||
_ = analyzer.visit(document) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why removing the analysis in this test and any problems raised from that?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We were parsing the document twice for some reason, and producing the diagnostics twice. I didn't see a reason for us needing to do that.
docComment | ||
.components( | ||
separatedBy: .newlines | ||
) | ||
.enumerated() | ||
.map { arg -> SymbolGraph.LineList.Line in | ||
var ( | ||
index, | ||
line | ||
) = arg |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nit: I find this excessive use of newlines hard to read. AFAICT not based on width—because .map { arg -> SymbolGraph.LineList.Line in
is wider than .components(separatedBy: .newlines)
—so I'm assuming that it's a purely stylistic choice.
docComment | |
.components( | |
separatedBy: .newlines | |
) | |
.enumerated() | |
.map { arg -> SymbolGraph.LineList.Line in | |
var ( | |
index, | |
line | |
) = arg | |
docComment | |
.components(separatedBy: .newlines) | |
.enumerated() | |
.map { index, line in |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes, I agree. I had used Xcode's multi-line format here but it's a bit excessive here.
XCTAssert(problems.isEmpty) | ||
|
||
XCTAssertEqual( | ||
try XCTUnwrap( |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
FYI: This XCTWrap
isn't necessary because you can compare an optional value with a non-optional value.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ah that's right, thanks!
@@ -56,6 +56,17 @@ to add additional URLs for a page. | |||
> Note: Starting with version 6.0, @Redirected is supported as a child directive of @Metadata. In | |||
previous versions, @Redirected must be used as a top level directive. | |||
|
|||
### Usage in documentation comments | |||
|
|||
If you wish, you can specify `@Metadata` configuration in documentation comments for the following customizations: |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't think we use "if you wish" in any other user-facing documentation
If you wish, you can specify `@Metadata` configuration in documentation comments for the following customizations: | |
You can specify the following `@Metadata` configuration in an in-source documentation comment: |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks, I agree. I prefer your more concise version.
7a71608
to
f10b1a4
Compare
@swift-ci please test |
@@ -24,7 +24,7 @@ import Markdown | |||
/// } | |||
/// ``` | |||
/// | |||
/// You can use the `@DeprecationSummary` directive top-level in both articles and documentation extension files. | |||
/// You can use the `@DeprecationSummary` directive top-level in articles, documentation extension files, or documentation comments. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
FYI: you need to run swift run generate-symbol-graph
for this change to be reflected in the published documentation.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ah yes, thanks. I updated this in the latest commit.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It would also be good to add an "Earlier Versions" aside, like on the @Available documentation to say that before Swift-DocC 6.2 (or 6.1 if we plan on cherry-picking this), @DeprecationSummary isn't supported in documentation comments.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Good idea. Updated, thanks! I also realised that CustomMetadata
is actually intentionally hidden from documentation (although I'm not sure why), so I removed its reference in this document.
f10b1a4
to
4f75d2f
Compare
bundle: DocumentationBundle, | ||
context: DocumentationContext |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
+1 to this. I think there should be a better way to do it without having to pass the full context and bundle. This does not look correct for initialising a symbol.
in context: DocumentationContext, | ||
problems: inout [Problem] | ||
) { | ||
let invalidDirectives: [(any AutomaticDirectiveConvertible)?] = [ |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I like the idea of having the valid ones, If not we will have to remember to add it to the list, which could be forgotten and lead to errors.
titleHeading, | ||
] + (redirects ?? []) | ||
+ supportedLanguages | ||
+ pageImages |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
why the change on code style here?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can you clarify what you mean here? This is all new code
4f75d2f
to
8b4a0ea
Compare
@@ -492,7 +492,7 @@ class SymbolTests: XCTestCase { | |||
XCTAssertEqual(withRedirectInArticle.redirects, nil) | |||
|
|||
XCTAssertEqual(problems.first?.diagnostic.identifier, "org.swift.docc.UnsupportedDocCommentDirective") | |||
XCTAssertEqual(problems.first?.diagnostic.range?.lowerBound.line, 3) | |||
XCTAssertEqual(problems.first?.diagnostic.range?.lowerBound.line, 15) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This test seems like it's finding another directive in some other symbol. Please update the test to
- don't build more content that it needs to
- warn about some unsupported directive that's created in this test.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It's referring to the @Redirected
directive in the test, but I can see how that's confusing. I had made makeDocumentationNodeForSymbol
add an arbitrary offset of 12 to the doc comment's line numbers, but I changed it so that it takes the value as a parameter instead for the specific tests I wrote.
) | ||
return .init(text: line, range: range) | ||
}) | ||
let newDocComment = SymbolGraph.LineList( |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is it possible to use makeLineList(docComment:startOffset:url:)
here instead?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes, thanks!
> Note: Don't specify an `@Metadata` directive in both the symbol's documentation comment and its documentation extension file. | ||
If you have one in each, DocC will pick the one in the documentation extension and discard the one in the documentation | ||
comment. | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It would be good to add an "Earlier Versions" aside, like on the @Available documentation to say that before Swift-DocC 6.2 (or 6.1 if we plan on cherry-picking this), @metadata isn't supported in documentation comments.
8b4a0ea
to
3511925
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
LGTM
3bf6dd7
to
0fc804c
Compare
Adds support for authoring `@Metadata` directives in documentation comments, for convenience. For some children of `@Metadata`, like `@Available`, it's a bit inconvenient to need to create a documentation extension just to specify availability for a symbol. This commit allows you to do that in documentation comments instead. A few notes on the behavior: - Only the `@Available` and `@CustomMetadata` are supported as child directives of `@Metadata` in documentation comments. DocC ignores other directives and emits a warnings for their uses. - If you author a `@Metadata` directive both in the symbol's documentation comment and documentation extension file, DocC will pick the one in the documentation extension, and ignore the one in the documentation comment and emit a warning. On the implementation: - While `AutomaticDirectiveConvertible` provides nice utilities for parsing arguments and child directives, its benefits can't be leveraged when the directive you're parsing in a top-level document, e.g., an article or documentation comment. This commits adds a `DirectiveParser` utility type which takes a list of Markup elements and parses the first one that matches the directive type you're looking for. - Emitting a diagnostic for a directive in a documentation comment at the right source location is complicated. The codebase already has utilities to offset a diagnostic's source range which makes this computation easier, and this commit adds non-mutating variants of those APIs. rdar://138395778
Adds support for authoring `@DeprecationSummary` messages in documentation comments. This is useful when you want to provide a formatted documentation summary for an API that's deprecated, but don't want to create a documentation extension file in order to do just that. It turns out that DocC already supported this, but emitted a warning whenever you did so. This commit removes that warning.
Adds support for `@Comment` directives in documentation comments. They don't hurt anyone, so might as well allow them.
0fc804c
to
a5c523e
Compare
@swift-ci please test |
Bug/issue #, if applicable: rdar://138395778
Summary
This PR adds the following behavior changes:
@Metadata
directives in documentation comments. Some notes:Only the
@Available
and@CustomMetadata
are supported as child directives of@Metadata
in documentation comments. DocC ignores other directives and emits a warnings for their uses:If you author a
@Metadata
directive both in the symbol's documentation comment and documentation extension file, DocC will pick the one in the documentation extension, and ignore the one in the documentation comment and emit a warning.(Second commit) Support
@DeprecationSummary
in doc comments. Adds support for authoring@DeprecationSummary
messages in documentation comments. This is useful when you want to provide a formatted documentation summary for an API that's deprecated, but don't want to create a documentation extension file in order to do just that.(Third commit) Support for
@Comment
in documentation comments. They are innocuous, so may as well support them.Dependencies
None.
Testing
In a Swift package, define
@Metadata
,@DeprecationSummary
, and@Comment
directives in documentation comments and verify that the rendered output matches your expectations.Also, author invalid uses of these directives to trigger the diagnostics described above.
Checklist
./bin/test
script and it succeeded