Skip to content
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

Add --include-extended-types flag #34

Merged
merged 5 commits into from
Jan 31, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions IntegrationTests/Package.swift
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ let package = Package(
.copy("Fixtures/MixedTargets"),
.copy("Fixtures/TargetWithDocCCatalog"),
.copy("Fixtures/PackageWithSnippets"),
.copy("Fixtures/LibraryTargetWithExtensionSymbols"),
]
),
]
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
// swift-tools-version: 5.6
//
// This source file is part of the Swift.org open source project
//
// Copyright (c) 2023 Apple Inc. and the Swift project authors
// Licensed under Apache License v2.0 with Runtime Library Exception
//
// See https://swift.org/LICENSE.txt for license information
// See https://swift.org/CONTRIBUTORS.txt for Swift project authors

import Foundation
import PackageDescription

let package = Package(
name: "LibraryTargetWithExtensionSymbols",
targets: [
.target(name: "Library"),
]
)

// We only expect 'swift-docc-plugin' to be a sibling when this package
// is running as part of a test.
//
// This allows the package to compile outside of tests for easier
// test development.
if FileManager.default.fileExists(atPath: "../swift-docc-plugin") {
package.dependencies += [
.package(path: "../swift-docc-plugin"),
]
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
// This source file is part of the Swift.org open source project
//
// Copyright (c) 2023 Apple Inc. and the Swift project authors
// Licensed under Apache License v2.0 with Runtime Library Exception
//
// See https://swift.org/LICENSE.txt for license information
// See https://swift.org/CONTRIBUTORS.txt for Swift project authors

/// This is foo's documentation.
///
/// Foo is a public struct and should be included in documentation.
public struct Foo {
public func foo() {}
}

/// This is the documentation for ``Swift/Array``.
///
/// This is the extension to ``Array`` with the longest documentation
/// comment, thus it is used for doucmenting the extended type in this
/// target.
extension Array {
/// This is the documentation for the ``isArray`` property
/// we added to ``Swift/Array``.
///
/// This is a public extension to an external type and should be included
/// in the documentation.
public var isArray: Bool { true }
}

/// This is the documentation for ``Swift/Int``.
///
/// This is the extension to ``Int`` with the longest documentation
/// comment, thus it is used for doucmenting the extended type in this
/// target.
extension Int {
/// This is the documentation for the ``isArray`` property
/// we added to ``Swift/Int``.
///
/// This is a public extension to an external type and should be included
/// in the documentation.
public var isArray: Bool { false }
}


/// This is the documentation for ``CustomFooConvertible``.
///
/// This is a public protocol and should be included in the documentation.
public protocol CustomFooConvertible {
/// This is the documentation for ``CustomFooConvertible/asFoo``.
///
/// This is a public protocol requirement and should be included in the documentation.
var asFoo: Foo { get }
}

/// This is not used as the documentation comment for ``Swift/Int``
/// as it is shorter than the comment on the other extension to `Int`.
extension Int: CustomFooConvertible {
/// This is the documentation for ``Swift/Int/asFoo``.
///
/// This is a public protocol requirement implementation and should be included in the documentation.
public var asFoo: Foo { Foo() }
}
91 changes: 91 additions & 0 deletions IntegrationTests/Tests/TargetWithSwiftExtensionsTests.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
// This source file is part of the Swift.org open source project
//
// Copyright (c) 2023 Apple Inc. and the Swift project authors
// Licensed under Apache License v2.0 with Runtime Library Exception
//
// See https://swift.org/LICENSE.txt for license information
// See https://swift.org/CONTRIBUTORS.txt for Swift project authors

import Foundation
import XCTest

final class TargetWithSwiftExtensionsTests: ConcurrencyRequiringTestCase {
#if swift(>=5.8)
let supportsIncludingSwiftExtendedTypes = true
#else
let supportsIncludingSwiftExtendedTypes = false
#endif

override func setUpWithError() throws {
try XCTSkipUnless(
supportsIncludingSwiftExtendedTypes,
"The current toolchain does not support symbol graph generation for extended types."
)

try super.setUpWithError()
}

func testGenerateDocumentationWithoutEnablementFlag() throws {
let result = try swiftPackage(
"generate-documentation",
workingDirectory: try setupTemporaryDirectoryForFixture(named: "LibraryTargetWithExtensionSymbols")
)

result.assertExitStatusEquals(0)
XCTAssertEqual(result.referencedDocCArchives.count, 1)

let doccArchiveURL = try XCTUnwrap(result.referencedDocCArchives.first)

let dataDirectoryContents = try filesIn(.dataSubdirectory, of: doccArchiveURL)

XCTAssertEqual(
Set(dataDirectoryContents.map(\.lastTwoPathComponents)),
[
"documentation/library.json",

"library/foo.json",
"foo/foo().json",

"library/customfooconvertible.json",
"customfooconvertible/asfoo.json",
]
)
}

func testGenerateDocumentationWithEnablementFlag() throws {
let result = try swiftPackage(
"generate-documentation",
"--include-extended-types",
workingDirectory: try setupTemporaryDirectoryForFixture(named: "LibraryTargetWithExtensionSymbols")
)

result.assertExitStatusEquals(0)
XCTAssertEqual(result.referencedDocCArchives.count, 1)

let doccArchiveURL = try XCTUnwrap(result.referencedDocCArchives.first)

let dataDirectoryContents = try filesIn(.dataSubdirectory, of: doccArchiveURL)

XCTAssertEqual(
Set(dataDirectoryContents.map(\.lastTwoPathComponents)),
[
"documentation/library.json",
"library/swift.json",

"swift/int.json",
"int/isarray.json",
"int/asfoo.json",
"int/customfooconvertible-implementations.json",

"swift/array.json",
"array/isarray.json",

"library/foo.json",
"foo/foo().json",

"library/customfooconvertible.json",
"customfooconvertible/asfoo.json",
]
)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -38,12 +38,27 @@ extension PackageManager {
for target: SwiftSourceModuleTarget,
context: PluginContext,
verbose: Bool,
snippetExtractor: SnippetExtractor?
snippetExtractor: SnippetExtractor?,
customSymbolGraphOptions: [PluginFlag]
) throws -> DocCSymbolGraphResult {
// First generate the primary symbol graphs containing information about the
// symbols defined in the target itself.

let symbolGraphOptions = target.defaultSymbolGraphOptions(in: context.package)
var symbolGraphOptions = target.defaultSymbolGraphOptions(in: context.package)

// Modify the symbol graph options with the custom ones
for customSymbolGraphOption in customSymbolGraphOptions {
switch customSymbolGraphOption {
case .extendedTypes:
#if swift(>=5.8)
symbolGraphOptions.emitExtensionBlocks = true
#else
print("warning: detected '--include-extended-types' option, which is incompatible with your swift version (required: 5.8)")
#endif
default:
fatalError("error: unknown PluginFlag (\(customSymbolGraphOption.parsedValues.joined(separator: ", "))) detected in symbol graph generation - please create an issue at https://github.com/apple/swift-docc-plugin")
}
}

if verbose {
print("symbol graph options: '\(symbolGraphOptions)'")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,21 @@ extension SwiftSourceModuleTarget {
return PackageManager.SymbolGraphOptions(
minimumAccessLevel: targetMinimumAccessLevel,
includeSynthesized: true,
includeSPI: false
includeSPI: false,
emitExtensionBlocks: false
)
}
}


#if swift(<5.8)
private extension PackageManager.SymbolGraphOptions {
ethan-kusters marked this conversation as resolved.
Show resolved Hide resolved
/// A compatibility layer for lower Swift versions which discards unknown parameters.
init(minimumAccessLevel: PackagePlugin.PackageManager.SymbolGraphOptions.AccessLevel = .public,
includeSynthesized: Bool = false,
includeSPI: Bool = false,
emitExtensionBlocks: Bool) {
self.init(minimumAccessLevel: minimumAccessLevel, includeSynthesized: includeSynthesized, includeSPI: includeSPI)
}
}
#endif
3 changes: 2 additions & 1 deletion Plugins/Swift-DocC Convert/SwiftDocCConvert.swift
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,8 @@ import PackagePlugin
for: target,
context: context,
verbose: verbose,
snippetExtractor: snippetExtractor
snippetExtractor: snippetExtractor,
customSymbolGraphOptions: parsedArguments.symbolGraphArguments
)

if try FileManager.default.contentsOfDirectory(atPath: symbolGraphs.targetSymbolGraphsDirectory.path).isEmpty {
Expand Down
3 changes: 2 additions & 1 deletion Plugins/Swift-DocC Preview/SwiftDocCPreview.swift
Original file line number Diff line number Diff line change
Expand Up @@ -83,7 +83,8 @@ import PackagePlugin
for: target,
context: context,
verbose: verbose,
snippetExtractor: snippetExtractor
snippetExtractor: snippetExtractor,
customSymbolGraphOptions: parsedArguments.symbolGraphArguments
)

if try FileManager.default.contentsOfDirectory(atPath: symbolGraphs.targetSymbolGraphsDirectory.path).isEmpty {
Expand Down
9 changes: 8 additions & 1 deletion Sources/SwiftDocCPluginUtilities/HelpInformation.swift
Original file line number Diff line number Diff line change
Expand Up @@ -43,10 +43,17 @@ public enum HelpInformation {
helpText = previewPluginHelpOverview
}

let supportedPluginFlags = [
var supportedPluginFlags = [
PluginFlag.disableIndex,
]

// stops 'not mutated' warning for Swift 5.7 and lower
supportedPluginFlags += []

#if swift(>=5.8)
supportedPluginFlags += [PluginFlag.extendedTypes]
#endif

theMomax marked this conversation as resolved.
Show resolved Hide resolved
for flag in supportedPluginFlags {
helpText += """
\(flag.parsedValues.sorted().joined(separator: ", "))
Expand Down
72 changes: 68 additions & 4 deletions Sources/SwiftDocCPluginUtilities/ParsedArguments.swift
Original file line number Diff line number Diff line change
Expand Up @@ -21,8 +21,8 @@ public struct ParsedArguments {

/// Returns the arguments that should be passed to `docc` to invoke the given plugin action.
///
/// Merges the arguments provided upon initialization of the parsed arguments
/// with default fallback values for required options that were not provided.
/// Merges the arguments provided upon initialization of the parsed arguments that are relevant
/// to `docc` with default fallback values for required options that were not provided.
///
/// For example, if ParsedArguments is initialized like so:
///
Expand Down Expand Up @@ -90,7 +90,7 @@ public struct ParsedArguments {
symbolGraphDirectoryPath: String,
outputPath: String
) -> Arguments {
var doccArguments = arguments
var doccArguments = arguments.filter(for: .docc)

// Iterate through the flags required for the `docc` invocation
// and append any that are not already present.
Expand Down Expand Up @@ -153,8 +153,19 @@ public struct ParsedArguments {
/// Creates a new set of parsed arguments with the given arguments.
public init(_ arguments: [String]) {
self.arguments = arguments

let symbolGraphArguments = arguments.filter(for: .dumpSymbolGraph)

self.symbolGraphArguments = ParsedArguments.ArgumentConsumer.dumpSymbolGraph.flags.filter { option in
option.parsedValues.contains(where: symbolGraphArguments.contains)
}
}

// Build array with plugin flags that modify the symbol graph generation,
// filtering from the available custom symbol graph options those
// that correspond to the received flags
var symbolGraphArguments: [PluginFlag]

/// The command-line options required by the `docc` tool.
private static let requiredOptions: [CommandLineOption] = [
.fallbackDisplayName,
Expand All @@ -170,6 +181,59 @@ public struct ParsedArguments {
]

private static let argumentsTransformers: [ArgumentsTransforming] = [
PluginFlag.disableIndex
PluginFlag.disableIndex,
PluginFlag.extendedTypes
]
}

private extension ParsedArguments {
enum ArgumentConsumer: CaseIterable {
/// The `docc` command
case docc
/// The `swift package dump-symbol-graph` command
case dumpSymbolGraph

/// Returns the flags applicable to an `ArgumentConsumer`.
///
/// If `flags.isEmpty` is `true`, this `ArgumentConsumer` is assumed to
/// consume all flags not consumed by any of the other `ArgumentConsumer`s.
var flags: [PluginFlag] {
switch self {
case .dumpSymbolGraph:
return [
PluginFlag.extendedTypes
]
case .docc:
return []
}
}
}
}

private extension Arguments {
/// Returns the subset of arguments which are applicable to the given `consumer`.
func filter(for consumer: ParsedArguments.ArgumentConsumer) -> Arguments {
theMomax marked this conversation as resolved.
Show resolved Hide resolved
if !consumer.flags.isEmpty {
// If the consumer can provide a complete list of valid flags,
// we only include elements that are included in one of these flags'
// `parsedValues`, i.e. if one of these flags can be applied to the
// element.
let flagsToInclude = consumer.flags
return self.filter { argument in
flagsToInclude.contains(where: { flag in
flag.parsedValues.contains(argument)
})
}
} else {
// If the consumer cannot provide a complete list of valid flags, (which
// should only happen for the `.docc` case) we return all elements
// that are not applicable to any of the other `ArgumentConsumer`s.
let flagsToExclude = ParsedArguments.ArgumentConsumer.allCases.flatMap(\.flags)
return self.filter { argument in
!flagsToExclude.contains(where: { flag in
flag.parsedValues.contains(argument)
})
}
}
}
}
Loading