From 7582c7d83804a63a6840a8a3725a353716d62d1b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?David=20R=C3=B6nnqvist?= Date: Fri, 6 Sep 2024 11:06:04 +0200 Subject: [PATCH 01/11] Refine command line argument types to define flag/option specific names MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This means that flag/option specific API can leverage types instead of including "flag" or "option" in the API's name. This also means that a named option requires a value to create an argument. Co-authored-by: Sofía Rodríguez --- .../Swift-DocC Convert/SwiftDocCConvert.swift | 6 +- .../CommandLineArgument.swift | 61 +++++++++++++++---- .../CommandLineArguments.swift | 41 ++++++++----- .../ParsedPluginArguments.swift | 2 +- .../ParsedArguments.swift | 40 ++++++------ .../CommandLineArgumentsTests.swift | 31 +++++----- 6 files changed, 118 insertions(+), 63 deletions(-) diff --git a/Plugins/Swift-DocC Convert/SwiftDocCConvert.swift b/Plugins/Swift-DocC Convert/SwiftDocCConvert.swift index 35762e5..eea86a0 100644 --- a/Plugins/Swift-DocC Convert/SwiftDocCConvert.swift +++ b/Plugins/Swift-DocC Convert/SwiftDocCConvert.swift @@ -211,11 +211,11 @@ import PackagePlugin var mergeCommandArguments = ["merge"] mergeCommandArguments.append(contentsOf: intermediateDocumentationArchives.map(\.standardizedFileURL.path)) - mergeCommandArguments.append(contentsOf: [DocCArguments.outputPath.preferred, combinedArchiveOutput.path]) + mergeCommandArguments.append(contentsOf: [DocCArguments.outputPath.names.preferred, combinedArchiveOutput.path]) if let doccFeatures, doccFeatures.contains(.synthesizedLandingPageName) { - mergeCommandArguments.append(contentsOf: [DocCArguments.synthesizedLandingPageName.preferred, context.package.displayName]) - mergeCommandArguments.append(contentsOf: [DocCArguments.synthesizedLandingPageKind.preferred, "Package"]) + mergeCommandArguments.append(contentsOf: [DocCArguments.synthesizedLandingPageName.names.preferred, context.package.displayName]) + mergeCommandArguments.append(contentsOf: [DocCArguments.synthesizedLandingPageKind.names.preferred, "Package"]) } // Remove the combined archive if it already exists diff --git a/Sources/SwiftDocCPluginUtilities/CommandLineArguments/CommandLineArgument.swift b/Sources/SwiftDocCPluginUtilities/CommandLineArguments/CommandLineArgument.swift index 524142e..284b868 100644 --- a/Sources/SwiftDocCPluginUtilities/CommandLineArguments/CommandLineArgument.swift +++ b/Sources/SwiftDocCPluginUtilities/CommandLineArguments/CommandLineArgument.swift @@ -43,18 +43,57 @@ public struct CommandLineArgument { case option(value: String) } - /// Creates a new command line flag with the given names. - /// - Parameters: - /// - names: The names for the new command line flag. - public static func flag(_ names: Names) -> Self { - .init(names: names, kind: .flag) + // Only create arguments from flags or options (with a value) + + init(_ flag: Flag) { + names = flag.names + kind = .flag + } + + init(_ option: Option, value: String) { + names = option.names + kind = .option(value: value) + } +} + +extension CommandLineArgument { + /// A flag argument without an associated value. + /// + /// For example: `"--some-flag"`. + public struct Flag { + public var names: Names + + /// Creates a new command line flag + /// + /// - Parameters: + /// - preferred: The preferred name for this flag. + /// - alternatives: A collection of alternative names for this flag. + public init(preferred: String, alternatives: Set = []) { + // This is duplicating the `Names` parameters to offer a nicer initializer for the common case. + names = .init(preferred: preferred, alternatives: alternatives) + } } - /// Creates a new command option with the given names and associated value. - /// - Parameters: - /// - names: The names for the new command line option. - /// - value: The value that's associated with this command line option. - public static func option(_ names: Names, value: String) -> Self { - .init(names: names, kind: .option(value: value)) + /// An option argument that will eventually associated with a value. + /// + /// For example: `"--some-option", "value"` or `"--some-option=value"`. + public struct Option { + public var names: Names + + /// Creates a new command line option. + /// + /// - Parameters: + /// - preferred: The preferred name for this option. + /// - alternatives: A collection of alternative names for this option. + public init(preferred: String, alternatives: Set = []) { + // This is duplicating the `Names` parameters to offer a nicer initializer for the common case. + self.init( + Names(preferred: preferred, alternatives: alternatives) + ) + } + + init(_ names: Names) { + self.names = names + } } } diff --git a/Sources/SwiftDocCPluginUtilities/CommandLineArguments/CommandLineArguments.swift b/Sources/SwiftDocCPluginUtilities/CommandLineArguments/CommandLineArguments.swift index 8463ea3..73085f4 100644 --- a/Sources/SwiftDocCPluginUtilities/CommandLineArguments/CommandLineArguments.swift +++ b/Sources/SwiftDocCPluginUtilities/CommandLineArguments/CommandLineArguments.swift @@ -41,8 +41,9 @@ public struct CommandLineArguments { /// /// - Parameter names: The names of a command line option. /// - Returns: The extracted values for this command line option, in the order that they appear in the arguments list. - public mutating func extractOption(named names: CommandLineArgument.Names) -> [String] { + public mutating func extract(_ option: CommandLineArgument.Option) -> [String] { var values = [String]() + let names = option.names for (index, argument) in remainingOptionsOrFlags.indexed().reversed() { guard let suffix = names.suffixAfterMatchingNamesWith(argument: argument) else { @@ -92,12 +93,24 @@ public struct CommandLineArguments { // MARK: Insert - /// Inserts a command line argument into the arguments list unless it already exists. - /// - Parameter argument: The command line argument (flag or option) to insert. + /// Inserts a command line option into the arguments list unless it already exists. + /// - Parameters: + /// - argument: The command line option to insert. + /// - matchOptionWithAnyValue: If `true`, an option argument will be considered a match even + /// - Returns: `true` if the argument was already present in the arguments list; otherwise, `false`. + @discardableResult + public mutating func insertIfMissing(_ option: CommandLineArgument.Option, value: String) -> Bool { + remainingOptionsOrFlags.appendIfMissing(.init(option, value: value)) + } + + /// Inserts a command line flag into the arguments list unless it already exists. + /// - Parameters: + /// - argument: The command line flag to insert. + /// - matchOptionWithAnyValue: If `true`, an option argument will be considered a match even /// - Returns: `true` if the argument was already present in the arguments list; otherwise, `false`. @discardableResult - public mutating func insertIfMissing(_ argument: CommandLineArgument) -> Bool { - remainingOptionsOrFlags.appendIfMissing(argument) + public mutating func insertIfMissing(_ flag: CommandLineArgument.Flag) -> Bool { + remainingOptionsOrFlags.appendIfMissing(.init(flag)) } /// Adds a raw string argument to the start of the arguments list @@ -107,13 +120,13 @@ public struct CommandLineArguments { /// Inserts a command line argument into the arguments list, overriding any existing values. /// - Parameters: - /// - argument: The command line argument (flag or option) to insert. - /// - newValue: The new value for this command line argument. + /// - argument: The command line argument (flag or option) to insert. + /// - newValue: The new value for this command line argument. /// - Returns: `true` if the argument was already present in the arguments list; otherwise, `false`. @discardableResult - public mutating func overrideOrInsertOption(named names: CommandLineArgument.Names, newValue: String) -> Bool { - let didRemoveArguments = !extractOption(named: names).isEmpty - remainingOptionsOrFlags.append(.option(names, value: newValue)) + public mutating func overrideOrInsert(_ option: CommandLineArgument.Option, newValue: String) -> Bool { + let didRemoveArguments = !extract(option).isEmpty + remainingOptionsOrFlags.append(.init(option, value: newValue)) return didRemoveArguments } } @@ -126,7 +139,7 @@ private extension ArraySlice { /// - Returns: `true` if the argument was already present in the slice; otherwise, `false`. @discardableResult mutating func appendIfMissing(_ argument: CommandLineArgument) -> Bool { - guard !contains(argument.names) else { + guard !contains(argument) else { return true } append(argument) @@ -144,10 +157,10 @@ private extension ArraySlice { } } - /// Checks if the slice contains any of the given names. - func contains(_ names: CommandLineArgument.Names) -> Bool { + /// Checks if the slice contains the given argument. + func contains(_ argument: CommandLineArgument) -> Bool { contains(where: { - names.suffixAfterMatchingNamesWith(argument: $0) != nil + argument.names.suffixAfterMatchingNamesWith(argument: $0) != nil }) } } diff --git a/Sources/SwiftDocCPluginUtilities/CommandLineArguments/ParsedPluginArguments.swift b/Sources/SwiftDocCPluginUtilities/CommandLineArguments/ParsedPluginArguments.swift index 1abcbbf..4bcb179 100644 --- a/Sources/SwiftDocCPluginUtilities/CommandLineArguments/ParsedPluginArguments.swift +++ b/Sources/SwiftDocCPluginUtilities/CommandLineArguments/ParsedPluginArguments.swift @@ -50,6 +50,6 @@ private extension CommandLineArguments { } mutating func extractOption(_ flag: DocumentedFlag) -> String? { - extractOption(named: flag.names).last + extract(.init(flag.names)).last } } diff --git a/Sources/SwiftDocCPluginUtilities/ParsedArguments.swift b/Sources/SwiftDocCPluginUtilities/ParsedArguments.swift index 76aa488..cf06448 100644 --- a/Sources/SwiftDocCPluginUtilities/ParsedArguments.swift +++ b/Sources/SwiftDocCPluginUtilities/ParsedArguments.swift @@ -16,14 +16,14 @@ struct ParsedArguments { init(_ rawArguments: [String]) { var arguments = CommandLineArguments(rawArguments) - outputDirectory = arguments.extractOption(named: DocCArguments.outputPath).last.map { + outputDirectory = arguments.extract(DocCArguments.outputPath).last.map { URL(fileURLWithPath: $0, isDirectory: true).standardizedFileURL } pluginArguments = .init(extractingFrom: &arguments) symbolGraphArguments = .init(extractingFrom: &arguments) - assert(arguments.extractOption(named: DocCArguments.outputPath).isEmpty, + assert(arguments.extract(DocCArguments.outputPath).isEmpty, "There shouldn't be any output path argument left in the remaining DocC arguments.") self.arguments = arguments } @@ -103,20 +103,20 @@ struct ParsedArguments { var arguments = self.arguments if !pluginArguments.disableLMDBIndex { - arguments.insertIfMissing(.flag(DocCArguments.emitLMDBIndex)) + arguments.insertIfMissing(DocCArguments.emitLMDBIndex) } - arguments.insertIfMissing(.option(DocCArguments.fallbackDisplayName, value: targetName)) - arguments.insertIfMissing(.option(DocCArguments.fallbackBundleIdentifier, value: targetName)) + arguments.insertIfMissing(DocCArguments.fallbackDisplayName, value: targetName) + arguments.insertIfMissing(DocCArguments.fallbackBundleIdentifier, value: targetName) - arguments.insertIfMissing(.option(DocCArguments.additionalSymbolGraphDirectory, value: symbolGraphDirectoryPath)) - arguments.insertIfMissing(.option(DocCArguments.outputPath, value: outputPath)) + arguments.insertIfMissing(DocCArguments.additionalSymbolGraphDirectory, value: symbolGraphDirectoryPath) + arguments.insertIfMissing(DocCArguments.outputPath, value: outputPath) if pluginArguments.enableCombinedDocumentation { - arguments.insertIfMissing(.flag(DocCArguments.enableExternalLinkSupport)) + arguments.insertIfMissing(DocCArguments.enableExternalLinkSupport) for dependencyArchivePath in dependencyArchivePaths { - arguments.insertIfMissing(.option(DocCArguments.externalLinkDependency, value: dependencyArchivePath)) + arguments.insertIfMissing(DocCArguments.externalLinkDependency, value: dependencyArchivePath) } } @@ -124,7 +124,7 @@ struct ParsedArguments { case .library: break case .executable: - arguments.insertIfMissing(.option(DocCArguments.fallbackDefaultModuleKind, value: "Command-line Tool")) + arguments.insertIfMissing(DocCArguments.fallbackDefaultModuleKind, value: "Command-line Tool") } // If we were given a catalog path, prepend it to the set of arguments. @@ -140,42 +140,42 @@ enum DocCArguments { /// A fallback value for the bundle display name, if the documentation catalog doesn't specify one or if the build has no symbol information. /// /// The plugin defines this name so that it can pass a default value for older versions of `docc` which require this. - static let fallbackDisplayName = CommandLineArgument.Names( + static let fallbackDisplayName = CommandLineArgument.Option( preferred: "--fallback-display-name" ) /// A fallback value for the bundle identifier, if the documentation catalog doesn't specify one or if the build has no symbol information. /// /// The plugin defines this name so that it can pass a default value for older versions of `docc` which require this. - static let fallbackBundleIdentifier = CommandLineArgument.Names( + static let fallbackBundleIdentifier = CommandLineArgument.Option( preferred: "--fallback-bundle-identifier" ) /// A fallback value for the "module kind" display name, if the documentation catalog doesn't specify one. /// /// The plugin defines this name so that it can pass a default value when building documentation for executable targets. - static let fallbackDefaultModuleKind = CommandLineArgument.Names( + static let fallbackDefaultModuleKind = CommandLineArgument.Option( preferred: "--fallback-default-module-kind" ) /// A directory of symbol graph files that DocC will use as input when building documentation. /// /// The plugin defines this name so that it can pass a default value. - static let additionalSymbolGraphDirectory = CommandLineArgument.Names( + static let additionalSymbolGraphDirectory = CommandLineArgument.Option( preferred: "--additional-symbol-graph-dir" ) /// Configures DocC to include a LMDB representation of the navigator index in the output. /// /// The plugin defines this name so that it can pass this flag by default. - static let emitLMDBIndex = CommandLineArgument.Names( + static let emitLMDBIndex = CommandLineArgument.Flag( preferred: "--emit-lmdb-index" ) /// The directory where DocC will write the built documentation archive. /// /// The plugin defines this name so that it can intercept it and support building documentation for multiple targets within one package build command. - static let outputPath = CommandLineArgument.Names( + static let outputPath = CommandLineArgument.Option( preferred: "--output-path", alternatives: ["--output-dir", "-o"] ) @@ -183,28 +183,28 @@ enum DocCArguments { /// A DocC feature flag to enable support for linking to documentation dependencies. /// /// The plugin defines this name so that it can specify documentation dependencies based on target dependencies when building combined documentation for multiple targets. - static let enableExternalLinkSupport = CommandLineArgument.Names( + static let enableExternalLinkSupport = CommandLineArgument.Flag( preferred: "--enable-experimental-external-link-support" ) /// A DocC flag that specifies a dependency DocC archive that the current build can link to. /// /// The plugin defines this name so that it can specify documentation dependencies based on target dependencies when building combined documentation for multiple targets. - static let externalLinkDependency = CommandLineArgument.Names( + static let externalLinkDependency = CommandLineArgument.Option( preferred: "--dependency" ) /// A DocC flag for the "merge" command that specifies a custom display name for the synthesized landing page. /// /// The plugin defines this name so that it can specify the package name as the display name of the default landing page when building combined documentation for multiple targets. - static let synthesizedLandingPageName = CommandLineArgument.Names( + static let synthesizedLandingPageName = CommandLineArgument.Option( preferred: "--synthesized-landing-page-name" ) /// A DocC flag for the "merge" command that specifies a custom kind for the synthesized landing page. /// /// The plugin defines this name so that it can specify "Package" as the kind of the default landing page when building combined documentation for multiple targets. - static let synthesizedLandingPageKind = CommandLineArgument.Names( + static let synthesizedLandingPageKind = CommandLineArgument.Option( preferred: "--synthesized-landing-page-kind" ) } diff --git a/Tests/SwiftDocCPluginUtilitiesTests/CommandLineArgumentsTests.swift b/Tests/SwiftDocCPluginUtilitiesTests/CommandLineArgumentsTests.swift index 2b98f24..631dbac 100644 --- a/Tests/SwiftDocCPluginUtilitiesTests/CommandLineArgumentsTests.swift +++ b/Tests/SwiftDocCPluginUtilitiesTests/CommandLineArgumentsTests.swift @@ -11,6 +11,9 @@ import SwiftDocCPluginUtilities import XCTest final class CommandLineArgumentsTests: XCTestCase { + typealias Flag = CommandLineArgument.Flag + typealias Option = CommandLineArgument.Option + func testExtractingRawFlagsAndOptions() { var arguments = CommandLineArguments(["--some-flag", "--this-option", "this-value", "--another-option", "another-value", "--", "--some-literal-value"]) @@ -35,24 +38,24 @@ final class CommandLineArgumentsTests: XCTestCase { // Insert - XCTAssertTrue(arguments.insertIfMissing(.flag(.init(preferred: "--some-flag"))), "Already contains '--some-flag'") + XCTAssertTrue(arguments.insertIfMissing(Flag(preferred: "--some-flag")), "Already contains '--some-flag'") XCTAssertEqual(arguments.remainingArguments, ["--some-flag", "--this-option", "this-value", "--", "--some-literal-value"]) - XCTAssertFalse(arguments.insertIfMissing(.flag(.init(preferred: "--other-flag"))), "Didn't previously contain '--other-flag'") + XCTAssertFalse(arguments.insertIfMissing(Flag(preferred: "--other-flag")), "Didn't previously contain '--other-flag'") XCTAssertEqual(arguments.remainingArguments, ["--some-flag", "--this-option", "this-value", "--other-flag", "--", "--some-literal-value"]) - XCTAssertTrue(arguments.insertIfMissing(.option(.init(preferred: "--this-option"), value: "new-value")), "Already contains '--this-option'") + XCTAssertTrue(arguments.insertIfMissing(Option(preferred: "--this-option"), value: "new-value"), "Already contains '--this-option'") XCTAssertEqual(arguments.remainingArguments, ["--some-flag", "--this-option", "this-value", "--other-flag", "--", "--some-literal-value"]) - XCTAssertFalse(arguments.insertIfMissing(.option(.init(preferred: "--another-option"), value: "another-value")), "Didn't previously contain '--another-option'") + XCTAssertFalse(arguments.insertIfMissing(Option(preferred: "--another-option"), value: "another-value"), "Didn't previously contain '--another-option'") XCTAssertEqual(arguments.remainingArguments, ["--some-flag", "--this-option", "this-value", "--other-flag", "--another-option", "another-value", "--", "--some-literal-value"]) // Override - XCTAssertTrue(arguments.overrideOrInsertOption(named: .init(preferred: "--this-option"), newValue: "new-value"), "Already contains '--this-option'") + XCTAssertTrue(arguments.overrideOrInsert(.init(preferred: "--this-option"), newValue: "new-value"), "Already contains '--this-option'") XCTAssertEqual(arguments.remainingArguments, ["--some-flag", "--other-flag", "--another-option", "another-value", "--this-option", "new-value", "--", "--some-literal-value"]) - XCTAssertFalse(arguments.overrideOrInsertOption(named: .init(preferred: "--yet-another-option"), newValue: "another-new-value"), "Didn't previously contain '--yet-another-option'") + XCTAssertFalse(arguments.overrideOrInsert(.init(preferred: "--yet-another-option"), newValue: "another-new-value"), "Didn't previously contain '--yet-another-option'") XCTAssertEqual(arguments.remainingArguments, ["--some-flag", "--other-flag", "--another-option", "another-value", "--this-option", "new-value", "--yet-another-option", "another-new-value", "--", "--some-literal-value"]) } @@ -61,8 +64,8 @@ final class CommandLineArgumentsTests: XCTestCase { do { var arguments = CommandLineArguments(["--spelling-one", "value-one", "--spelling-two=value-two", "-s", "value-three", "-s=value-four", "--spelling-one", "value-five", "--", "--spelling-one", "value-six"]) - let extractedValues = arguments.extractOption(named: - .init(preferred: "--spelling-one", alternatives: ["--spelling-two", "-s"]) + let extractedValues = arguments.extract( + CommandLineArgument.Option(preferred: "--spelling-one", alternatives: ["--spelling-two", "-s"]) ) XCTAssertEqual(extractedValues, ["value-one", "value-two", "value-three", "value-four", "value-five"]) XCTAssertEqual(arguments.remainingArguments, ["--", "--spelling-one", "value-six"]) @@ -98,11 +101,11 @@ final class CommandLineArgumentsTests: XCTestCase { var arguments = CommandLineArguments(existing + ["--other-flag"]) let original = arguments.remainingArguments - let option = CommandLineArgument.option(.init(preferred: "--spelling-one", alternatives: ["--spelling-two", "-s"]), value: "new-value") - XCTAssertTrue(arguments.insertIfMissing(option)) + let option = CommandLineArgument.Option(preferred: "--spelling-one", alternatives: ["--spelling-two", "-s"]) + XCTAssertTrue(arguments.insertIfMissing(option, value: "new-value")) XCTAssertEqual(arguments.remainingArguments, original) - XCTAssertTrue(arguments.overrideOrInsertOption(named: option.names, newValue: "new-value")) + XCTAssertTrue(arguments.overrideOrInsert(option, newValue: "new-value")) XCTAssertEqual(arguments.remainingArguments, ["--other-flag", "--spelling-one", "new-value"]) } @@ -111,17 +114,17 @@ final class CommandLineArgumentsTests: XCTestCase { var arguments = CommandLineArguments([existing, "--other-flag"]) let original = arguments.remainingArguments - XCTAssertTrue(arguments.insertIfMissing(.flag(.init(preferred: "--spelling-one", alternatives: ["--spelling-two", "-s"])))) + XCTAssertTrue(arguments.insertIfMissing(Flag(preferred: "--spelling-one", alternatives: ["--spelling-two", "-s"]))) XCTAssertEqual(arguments.remainingArguments, original) } } } -extension CommandLineArguments { +private extension CommandLineArguments { // MARK: Extract raw mutating func extractOption(rawName: String) -> [String] { - extractOption(named: .init(preferred: rawName)) + extract(CommandLineArgument.Option(preferred: rawName)) } mutating func extractFlag(rawName: String) -> [Bool] { From a9db6935eca3f1e67f29b7ddbf0c747da6d82632 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?David=20R=C3=B6nnqvist?= Date: Fri, 6 Sep 2024 11:22:05 +0200 Subject: [PATCH 02/11] Support defining an option that supports an array of values. MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Also, define `--dependency` as an array-of-values option. Co-authored-by: Sofía Rodríguez --- .../CommandLineArgument.swift | 27 +++++-- .../CommandLineArguments.swift | 42 ++++++++-- .../ParsedArguments.swift | 3 +- .../CommandLineArgumentsTests.swift | 76 +++++++++++++++++++ 4 files changed, 136 insertions(+), 12 deletions(-) diff --git a/Sources/SwiftDocCPluginUtilities/CommandLineArguments/CommandLineArgument.swift b/Sources/SwiftDocCPluginUtilities/CommandLineArguments/CommandLineArgument.swift index 284b868..671118d 100644 --- a/Sources/SwiftDocCPluginUtilities/CommandLineArguments/CommandLineArgument.swift +++ b/Sources/SwiftDocCPluginUtilities/CommandLineArguments/CommandLineArgument.swift @@ -40,7 +40,7 @@ public struct CommandLineArgument { /// An option argument with an associated value. /// /// For example: `"--some-option", "value"` or `"--some-option=value"`. - case option(value: String) + case option(value: String, kind: Option.Kind) } // Only create arguments from flags or options (with a value) @@ -52,7 +52,7 @@ public struct CommandLineArgument { init(_ option: Option, value: String) { names = option.names - kind = .option(value: value) + kind = .option(value: value, kind: option.kind) } } @@ -61,9 +61,10 @@ extension CommandLineArgument { /// /// For example: `"--some-flag"`. public struct Flag { + /// The names of this command line flag. public var names: Names - /// Creates a new command line flag + /// Creates a new command line flag. /// /// - Parameters: /// - preferred: The preferred name for this flag. @@ -78,22 +79,36 @@ extension CommandLineArgument { /// /// For example: `"--some-option", "value"` or `"--some-option=value"`. public struct Option { + /// The names of this command line option. public var names: Names + /// The kind of value for this command line option. + public var kind: Kind + + /// A kind of value(s) that a command line option supports. + public enum Kind { + /// An option that supports a single value. + case singleValue + /// An option that supports an array of different values. + case arrayOfValues + } /// Creates a new command line option. /// /// - Parameters: /// - preferred: The preferred name for this option. /// - alternatives: A collection of alternative names for this option. - public init(preferred: String, alternatives: Set = []) { + /// - kind: The kind of value(s) that this option supports. + public init(preferred: String, alternatives: Set = [], kind: Kind = .singleValue) { // This is duplicating the `Names` parameters to offer a nicer initializer for the common case. self.init( - Names(preferred: preferred, alternatives: alternatives) + Names(preferred: preferred, alternatives: alternatives), + kind: kind ) } - init(_ names: Names) { + init(_ names: Names, kind: Kind = .singleValue) { self.names = names + self.kind = kind } } } diff --git a/Sources/SwiftDocCPluginUtilities/CommandLineArguments/CommandLineArguments.swift b/Sources/SwiftDocCPluginUtilities/CommandLineArguments/CommandLineArguments.swift index 73085f4..ee17ed3 100644 --- a/Sources/SwiftDocCPluginUtilities/CommandLineArguments/CommandLineArguments.swift +++ b/Sources/SwiftDocCPluginUtilities/CommandLineArguments/CommandLineArguments.swift @@ -152,16 +152,48 @@ private extension ArraySlice { switch argument.kind { case .flag: append(argument.names.preferred) - case .option(let value): + case .option(let value, _): append(contentsOf: [argument.names.preferred, value]) } } - /// Checks if the slice contains the given argument. + /// Checks if the slice contains the given argument (flag or argument). func contains(_ argument: CommandLineArgument) -> Bool { - contains(where: { - argument.names.suffixAfterMatchingNamesWith(argument: $0) != nil - }) + let names = argument.names + guard case .option(let value, .arrayOfValues) = argument.kind else { + // When matching flags or single-value options, it's sufficient to check if the slice contains any of the names. + // + // The slice is considered to contain the single-value option, no matter what the existing value is. + // This is used to avoid repeating an single-value option with a different value. + return contains(where: { + names.suffixAfterMatchingNamesWith(argument: $0) != nil + }) + } + + // When matching options that support arrays of values, it's necessary to check the existing option's value. + // + // The slice is only considered to contain the array-of-values option, if the new value is found. + // This is used to allow array-of-values options to insert multiple different values into the arguments. + for (argumentIndex, argument) in indexed() { + guard let suffix = names.suffixAfterMatchingNamesWith(argument: argument) else { + continue + } + + // "--option-name", "value" + if suffix.first != "=" { + let indexAfter = index(after: argumentIndex) + if indexAfter < endIndex, self[indexAfter] == value { + return true + } + } + + // "--option-name=value" + else if suffix.dropFirst(/* the equal sign */) == value { + return true + } + } + // Non of the existing options match the new value. + return false } } diff --git a/Sources/SwiftDocCPluginUtilities/ParsedArguments.swift b/Sources/SwiftDocCPluginUtilities/ParsedArguments.swift index cf06448..088320b 100644 --- a/Sources/SwiftDocCPluginUtilities/ParsedArguments.swift +++ b/Sources/SwiftDocCPluginUtilities/ParsedArguments.swift @@ -191,7 +191,8 @@ enum DocCArguments { /// /// The plugin defines this name so that it can specify documentation dependencies based on target dependencies when building combined documentation for multiple targets. static let externalLinkDependency = CommandLineArgument.Option( - preferred: "--dependency" + preferred: "--dependency", + kind: .arrayOfValues ) /// A DocC flag for the "merge" command that specifies a custom display name for the synthesized landing page. diff --git a/Tests/SwiftDocCPluginUtilitiesTests/CommandLineArgumentsTests.swift b/Tests/SwiftDocCPluginUtilitiesTests/CommandLineArgumentsTests.swift index 631dbac..03efd43 100644 --- a/Tests/SwiftDocCPluginUtilitiesTests/CommandLineArgumentsTests.swift +++ b/Tests/SwiftDocCPluginUtilitiesTests/CommandLineArgumentsTests.swift @@ -59,6 +59,82 @@ final class CommandLineArgumentsTests: XCTestCase { XCTAssertEqual(arguments.remainingArguments, ["--some-flag", "--other-flag", "--another-option", "another-value", "--this-option", "new-value", "--yet-another-option", "another-new-value", "--", "--some-literal-value"]) } + func testInsertSameSingleValueOptionWithDifferentValues() { + let optionName = "--some-option" + let optionNameAlternate = "--some-option-alternate" + let option = CommandLineArgument.Option( + preferred: optionName, + alternatives: [optionNameAlternate], + kind: .singleValue // the default + ) + + for initialArguments in [ + ["\(optionName)=first"], + [optionNameAlternate, "second"], + ] { + var arguments = CommandLineArguments(initialArguments) + + XCTAssertTrue(arguments.insertIfMissing(option, value: "first"), "The arguments already contains some other value for this option") + XCTAssertEqual(arguments.remainingArguments, initialArguments) + + XCTAssertTrue(arguments.insertIfMissing(option, value: "second"), "The arguments already contains some other value for this option") + XCTAssertEqual(arguments.remainingArguments, initialArguments) + + XCTAssertTrue(arguments.insertIfMissing(option, value: "third"), "The arguments already contains some other value for this option") + XCTAssertEqual(arguments.remainingArguments, initialArguments) + } + } + + func testInsertSameArrayOfValuesOptionWithDifferentValues() { + let optionName = "--some-option" + let optionNameAlternate = "--some-option-alternate" + let option = CommandLineArgument.Option( + preferred: optionName, + alternatives: [optionNameAlternate], + kind: .arrayOfValues + ) + + var arguments = CommandLineArguments([ + "\(optionName)=first", + optionNameAlternate, "second", + ]) + + XCTAssertTrue(arguments.insertIfMissing(option, value: "first"), "The arguments already contains this value (using the preferred option spelling)") + XCTAssertEqual(arguments.remainingArguments, [ + "\(optionName)=first", + optionNameAlternate, "second", + ]) + + XCTAssertTrue(arguments.insertIfMissing(option, value: "second"), "The arguments already contains this value (using the alternate option spelling)") + XCTAssertEqual(arguments.remainingArguments, [ + "\(optionName)=first", + optionNameAlternate, "second", + ]) + + XCTAssertFalse(arguments.insertIfMissing(option, value: "third"), "This value is new (doesn't exist among the arguments yet)") + XCTAssertEqual(arguments.remainingArguments, [ + "\(optionName)=first", + optionNameAlternate, "second", + optionName, "third", + ]) + + XCTAssertFalse(arguments.insertIfMissing(option, value: "fourth"), "Third value us also new ... (doesn't exist among the arguments yet)") + XCTAssertEqual(arguments.remainingArguments, [ + "\(optionName)=first", + optionNameAlternate, "second", + optionName, "third", + optionName, "fourth", + ]) + + XCTAssertTrue(arguments.insertIfMissing(option, value: "fourth"), "... but now it exists (after inserting it above)") + XCTAssertEqual(arguments.remainingArguments, [ + "\(optionName)=first", + optionNameAlternate, "second", + optionName, "third", + optionName, "fourth", + ]) + } + func testExtractDifferentArgumentSpellings() { // Options do { From 5cb96940438fdab510945675db6e50bb8017eb3e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?David=20R=C3=B6nnqvist?= Date: Fri, 6 Sep 2024 11:23:29 +0200 Subject: [PATCH 03/11] Fix a logic bug where an alternate argument spellings with common prefixes would sometimes not extract a value. --- .../CommandLineArguments/CommandLineArguments.swift | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/Sources/SwiftDocCPluginUtilities/CommandLineArguments/CommandLineArguments.swift b/Sources/SwiftDocCPluginUtilities/CommandLineArguments/CommandLineArguments.swift index ee17ed3..e027049 100644 --- a/Sources/SwiftDocCPluginUtilities/CommandLineArguments/CommandLineArguments.swift +++ b/Sources/SwiftDocCPluginUtilities/CommandLineArguments/CommandLineArguments.swift @@ -51,8 +51,14 @@ public struct CommandLineArguments { } defer { remainingOptionsOrFlags.remove(at: index) } + // "--option-name=value" + if suffix.first == "=" { + + values.append(String(suffix.dropFirst(/* the equal sign */))) + } + // "--option-name", "value" - if suffix.isEmpty { + else { let indexAfter = remainingOptionsOrFlags.index(after: index) if indexAfter < remainingOptionsOrFlags.endIndex { values.append(remainingOptionsOrFlags[indexAfter]) @@ -60,11 +66,6 @@ public struct CommandLineArguments { remainingOptionsOrFlags.remove(at: indexAfter) } } - - // "--option-name=value" - else if suffix.first == "=" { - values.append(String(suffix.dropFirst(/* the equal sign */))) - } } return values.reversed() // The values are gathered in reverse order From 444988d5a0d4984ccff62cbc288f6990992c34e4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?David=20R=C3=B6nnqvist?= Date: Fri, 6 Sep 2024 11:57:42 +0200 Subject: [PATCH 04/11] Move the inverse names configuration into CommandLineArgument.Flag --- .../Swift-DocC Convert/SwiftDocCConvert.swift | 2 +- .../CommandLineArgument.swift | 6 ++- .../CommandLineArguments.swift | 6 ++- .../ParsedPluginArguments.swift | 26 ++++++--- ...tedFlag.swift => DocumentedArgument.swift} | 54 ++++++++++++++----- .../HelpInformation.swift | 16 +++--- .../CommandLineArgumentsTests.swift | 20 +++---- 7 files changed, 87 insertions(+), 43 deletions(-) rename Sources/SwiftDocCPluginUtilities/DocumentedPluginFlags/{DocumentedFlag.swift => DocumentedArgument.swift} (70%) diff --git a/Plugins/Swift-DocC Convert/SwiftDocCConvert.swift b/Plugins/Swift-DocC Convert/SwiftDocCConvert.swift index eea86a0..dbd34ce 100644 --- a/Plugins/Swift-DocC Convert/SwiftDocCConvert.swift +++ b/Plugins/Swift-DocC Convert/SwiftDocCConvert.swift @@ -46,7 +46,7 @@ import PackagePlugin if isCombinedDocumentationEnabled, doccFeatures?.contains(.linkDependencies) == false { // The developer uses the combined documentation plugin flag with a DocC version that doesn't support combined documentation. Diagnostics.error(""" - Unsupported use of '\(DocumentedFlag.enableCombinedDocumentation.names.preferred)'. \ + Unsupported use of '\(DocumentedArgument.enableCombinedDocumentation.names.preferred)'. \ DocC version at '\(doccExecutableURL.path)' doesn't support combined documentation. """) return diff --git a/Sources/SwiftDocCPluginUtilities/CommandLineArguments/CommandLineArgument.swift b/Sources/SwiftDocCPluginUtilities/CommandLineArguments/CommandLineArgument.swift index 671118d..f217e5f 100644 --- a/Sources/SwiftDocCPluginUtilities/CommandLineArguments/CommandLineArgument.swift +++ b/Sources/SwiftDocCPluginUtilities/CommandLineArguments/CommandLineArgument.swift @@ -63,15 +63,19 @@ extension CommandLineArgument { public struct Flag { /// The names of this command line flag. public var names: Names + /// The negative names for this flag, if any. + public var inverseNames: CommandLineArgument.Names? /// Creates a new command line flag. /// /// - Parameters: /// - preferred: The preferred name for this flag. /// - alternatives: A collection of alternative names for this flag. - public init(preferred: String, alternatives: Set = []) { + /// - inverseNames: The negative names for this flag, if any. + public init(preferred: String, alternatives: Set = [], inverseNames: CommandLineArgument.Names? = nil) { // This is duplicating the `Names` parameters to offer a nicer initializer for the common case. names = .init(preferred: preferred, alternatives: alternatives) + self.inverseNames = inverseNames } } diff --git a/Sources/SwiftDocCPluginUtilities/CommandLineArguments/CommandLineArguments.swift b/Sources/SwiftDocCPluginUtilities/CommandLineArguments/CommandLineArguments.swift index e027049..defc67e 100644 --- a/Sources/SwiftDocCPluginUtilities/CommandLineArguments/CommandLineArguments.swift +++ b/Sources/SwiftDocCPluginUtilities/CommandLineArguments/CommandLineArguments.swift @@ -79,10 +79,12 @@ public struct CommandLineArguments { /// - positiveNames: The positive names for a command line flag. /// - negativeNames: The negative names for this command line flag, if any. /// - Returns: The extracted values for this command line flag. - public mutating func extractFlag(named positiveNames: CommandLineArgument.Names, inverseNames negativeNames: CommandLineArgument.Names? = nil) -> [Bool] { - var values = [Bool]() + public mutating func extract(_ flag: CommandLineArgument.Flag) -> [Bool] { + let positiveNames = flag.names + let negativeNames = flag.inverseNames let allNamesToCheck = positiveNames.all.union(negativeNames?.all ?? []) + var values = [Bool]() for (index, argument) in remainingOptionsOrFlags.indexed().reversed() where allNamesToCheck.contains(argument) { remainingOptionsOrFlags.remove(at: index) diff --git a/Sources/SwiftDocCPluginUtilities/CommandLineArguments/ParsedPluginArguments.swift b/Sources/SwiftDocCPluginUtilities/CommandLineArguments/ParsedPluginArguments.swift index 4bcb179..962c397 100644 --- a/Sources/SwiftDocCPluginUtilities/CommandLineArguments/ParsedPluginArguments.swift +++ b/Sources/SwiftDocCPluginUtilities/CommandLineArguments/ParsedPluginArguments.swift @@ -18,13 +18,13 @@ struct ParsedPluginArguments { /// Creates a new plugin arguments container by extracting the known plugin values from a command line argument list. init(extractingFrom arguments: inout CommandLineArguments) { enableCombinedDocumentation = arguments.extractFlag(.enableCombinedDocumentation) ?? false - disableLMDBIndex = arguments.extractFlag(.disableLMDBIndex) ?? false - verbose = arguments.extractFlag(.verbose) ?? false - help = arguments.extractFlag(named: Self.help).last ?? false + disableLMDBIndex = arguments.extractFlag(.disableLMDBIndex) ?? false + verbose = arguments.extractFlag(.verbose) ?? false + help = arguments.extract(Self.help).last ?? false } /// A common command line tool flag to print the help text instead of running the command. - static let help = CommandLineArgument.Names( + static let help = CommandLineArgument.Flag( preferred: "--help", alternatives: ["-h"] ) } @@ -45,11 +45,21 @@ struct ParsedSymbolGraphArguments { } private extension CommandLineArguments { - mutating func extractFlag(_ flag: DocumentedFlag) -> Bool? { - extractFlag(named: flag.names, inverseNames: flag.inverseNames).last + mutating func extractFlag(_ flag: DocumentedArgument) -> Bool? { + guard case .flag(let flag) = flag.argument else { + assertionFailure("Unexpectedly used flag-only API with an option \(flag.argument)") + return nil + } + return extract(flag).last } - mutating func extractOption(_ flag: DocumentedFlag) -> String? { - extract(.init(flag.names)).last + mutating func extractOption(_ flag: DocumentedArgument) -> String? { + guard case .option(let option) = flag.argument else { + assertionFailure("Unexpectedly used option-only API with a flag \(flag.argument)") + return nil + } + + assert(option.kind == .singleValue, "Unexpectedly used single-value option API with an array-of-values option \(option)") + return extract(option).last } } diff --git a/Sources/SwiftDocCPluginUtilities/DocumentedPluginFlags/DocumentedFlag.swift b/Sources/SwiftDocCPluginUtilities/DocumentedPluginFlags/DocumentedArgument.swift similarity index 70% rename from Sources/SwiftDocCPluginUtilities/DocumentedPluginFlags/DocumentedFlag.swift rename to Sources/SwiftDocCPluginUtilities/DocumentedPluginFlags/DocumentedArgument.swift index 861b1c6..634a2c0 100644 --- a/Sources/SwiftDocCPluginUtilities/DocumentedPluginFlags/DocumentedFlag.swift +++ b/Sources/SwiftDocCPluginUtilities/DocumentedPluginFlags/DocumentedArgument.swift @@ -9,27 +9,53 @@ /// A documented command-line flag for the plugin. /// /// This may include some flags that the plugin forwards to the symbol graph extract tool or to DocC. -struct DocumentedFlag { +struct DocumentedArgument { + /// A command line argument (flag or option) that is wrapped with documentation. + enum Argument { + case flag(CommandLineArgument.Flag) + case option(CommandLineArgument.Option) + } + + /// The command line argument (flag or option) that this documentation applies to. + var argument: Argument + /// The positive names for this flag - var names: CommandLineArgument.Names - /// The possible negative names for this flag, if any. - var inverseNames: CommandLineArgument.Names? + var names: CommandLineArgument.Names { + switch argument { + case .flag(let flag): + return flag.names + case .option(let option): + return option.names + } + } /// A short user-facing description of this flag. var abstract: String /// An expanded user-facing description of this flag. var discussion: String? + + init(flag: CommandLineArgument.Flag, abstract: String, discussion: String? = nil) { + self.argument = .flag(flag) + self.abstract = abstract + self.discussion = discussion + } + + init(option: CommandLineArgument.Option, abstract: String, discussion: String? = nil) { + self.argument = .option(option) + self.abstract = abstract + self.discussion = discussion + } } // MARK: Plugin flags -extension DocumentedFlag { +extension DocumentedArgument { /// A plugin feature flag to enable building combined documentation for multiple targets. /// /// - Note: This flag requires that the `docc` executable supports ``Feature/linkDependencies``. static let enableCombinedDocumentation = Self( - names: .init(preferred: "--enable-experimental-combined-documentation"), + flag: .init(preferred: "--enable-experimental-combined-documentation"), abstract: "Create a combined DocC archive with all generated documentation.", discussion: """ Experimental feature that allows targets to link to pages in their dependencies and that \ @@ -39,7 +65,7 @@ extension DocumentedFlag { /// A plugin feature flag to skip adding the `--emit-lmdb-index` flag, that the plugin adds by default. static let disableLMDBIndex = Self( - names: .init(preferred: "--disable-indexing", alternatives: ["--no-indexing"]), + flag: .init(preferred: "--disable-indexing", alternatives: ["--no-indexing"]), abstract: "Disable indexing for the produced DocC archive.", discussion: """ Produces a DocC archive that is best-suited for hosting online but incompatible with Xcode. @@ -48,7 +74,7 @@ extension DocumentedFlag { /// A plugin feature flag to enable verbose logging. static let verbose = Self( - names: .init(preferred: "--verbose"), + flag: .init(preferred: "--verbose"), abstract: "Increase verbosity to include informational output.", discussion: nil ) @@ -58,7 +84,7 @@ extension DocumentedFlag { // MARK: Symbol graph flags -extension DocumentedFlag { +extension DocumentedArgument { /// Include or exclude extended types in documentation archives. /// /// Enables/disables the extension block symbol format when calling the dump symbol graph API. @@ -66,8 +92,10 @@ extension DocumentedFlag { /// - Note: This flag is only available starting from Swift 5.8. It should be hidden from the `--help` command for lower toolchain versions. /// However, we do not hide the flag entirely, because this enables us to give a more precise warning when accidentally used with Swift 5.7 or lower. static let extendedTypes = Self( - names: .init(preferred: "--include-extended-types"), - inverseNames: .init(preferred: "--exclude-extended-types"), + flag: .init( + preferred: "--include-extended-types", + inverseNames: .init(preferred: "--exclude-extended-types") + ), abstract: "Control whether extended types from other modules are shown in the produced DocC archive. (default: --\(Self.defaultExtendedTypesValue ? "include" : "exclude")-extended-types)", discussion: "Allows documenting symbols that a target adds to its dependencies." ) @@ -76,7 +104,7 @@ extension DocumentedFlag { /// /// `--experimental-skip-synthesized-symbols` produces a DocC archive without compiler-synthesized symbols. static let skipSynthesizedSymbols = Self( - names: .init(preferred: "--experimental-skip-synthesized-symbols"), + flag: .init(preferred: "--experimental-skip-synthesized-symbols"), abstract: "Exclude synthesized symbols from the generated documentation.", discussion: """ Experimental feature that produces a DocC archive without compiler synthesized symbols. @@ -85,7 +113,7 @@ extension DocumentedFlag { /// The minimum access level that the symbol graph extractor will emit symbols for static let minimumAccessLevel = Self( - names: .init(preferred: "--symbol-graph-minimum-access-level"), + option: .init(preferred: "--symbol-graph-minimum-access-level"), abstract: "Include symbols with this access level or more.", discussion: """ Supported access level values are: `open`, `public`, `internal`, `private`, `fileprivate` diff --git a/Sources/SwiftDocCPluginUtilities/HelpInformation.swift b/Sources/SwiftDocCPluginUtilities/HelpInformation.swift index 56c8f7b..4813322 100644 --- a/Sources/SwiftDocCPluginUtilities/HelpInformation.swift +++ b/Sources/SwiftDocCPluginUtilities/HelpInformation.swift @@ -44,13 +44,13 @@ public enum HelpInformation { } var supportedPluginFlags = [ - DocumentedFlag.disableLMDBIndex, - DocumentedFlag.verbose, + DocumentedArgument.disableLMDBIndex, + DocumentedArgument.verbose, ] let doccFeatures = (try? DocCFeatures(doccExecutable: doccExecutableURL)) ?? .init() if doccFeatures.contains(.linkDependencies) { - supportedPluginFlags.insert(DocumentedFlag.enableCombinedDocumentation, at: 1) + supportedPluginFlags.insert(DocumentedArgument.enableCombinedDocumentation, at: 1) } for flag in supportedPluginFlags { @@ -59,11 +59,11 @@ public enum HelpInformation { helpText += "\nSYMBOL GRAPH OPTIONS:\n" var supportedSymbolGraphFlags = [ - DocumentedFlag.skipSynthesizedSymbols, - DocumentedFlag.minimumAccessLevel, + DocumentedArgument.skipSynthesizedSymbols, + DocumentedArgument.minimumAccessLevel, ] #if swift(>=5.8) - supportedSymbolGraphFlags.insert(DocumentedFlag.extendedTypes, at: 1) + supportedSymbolGraphFlags.insert(DocumentedArgument.extendedTypes, at: 1) #else // stops 'not mutated' warning for Swift 5.7 and lower supportedPluginFlags += [] @@ -129,10 +129,10 @@ public enum HelpInformation { """ } -private extension DocumentedFlag { +private extension DocumentedArgument { var helpDescription: String { var flagListText = names.listForHelpDescription - if let inverseNames { + if case .flag(let flag) = argument, let inverseNames = flag.inverseNames { flagListText += " / \(inverseNames.listForHelpDescription)" } diff --git a/Tests/SwiftDocCPluginUtilitiesTests/CommandLineArgumentsTests.swift b/Tests/SwiftDocCPluginUtilitiesTests/CommandLineArgumentsTests.swift index 03efd43..b5d5048 100644 --- a/Tests/SwiftDocCPluginUtilitiesTests/CommandLineArgumentsTests.swift +++ b/Tests/SwiftDocCPluginUtilitiesTests/CommandLineArgumentsTests.swift @@ -140,9 +140,9 @@ final class CommandLineArgumentsTests: XCTestCase { do { var arguments = CommandLineArguments(["--spelling-one", "value-one", "--spelling-two=value-two", "-s", "value-three", "-s=value-four", "--spelling-one", "value-five", "--", "--spelling-one", "value-six"]) - let extractedValues = arguments.extract( - CommandLineArgument.Option(preferred: "--spelling-one", alternatives: ["--spelling-two", "-s"]) - ) + let extractedValues = arguments.extract(Option( + preferred: "--spelling-one", alternatives: ["--spelling-two", "-s"] + )) XCTAssertEqual(extractedValues, ["value-one", "value-two", "value-three", "value-four", "value-five"]) XCTAssertEqual(arguments.remainingArguments, ["--", "--spelling-one", "value-six"]) } @@ -151,9 +151,9 @@ final class CommandLineArgumentsTests: XCTestCase { do { var arguments = CommandLineArguments(["--spelling-one", "--spelling-two", "-s", "--spelling-one", "--", "--spelling-one"]) - let extractedValues = arguments.extractFlag(named: - .init(preferred: "--spelling-one", alternatives: ["--spelling-two", "-s"]) - ) + let extractedValues = arguments.extract(Flag( + preferred: "--spelling-one", alternatives: ["--spelling-two", "-s"] + )) XCTAssertEqual(extractedValues, [true, true, true, true]) XCTAssertEqual(arguments.remainingArguments, ["--", "--spelling-one"]) } @@ -162,10 +162,10 @@ final class CommandLineArgumentsTests: XCTestCase { do { var arguments = CommandLineArguments(["--spelling-one", "--spelling-two", "--negative-spelling-one", "--negative-spelling-two", "-s", "--spelling-one", "-ns", "--", "--spelling-one", "--negative-spelling-two"]) - let extractedValues = arguments.extractFlag( - named: .init(preferred: "--spelling-one", alternatives: ["--spelling-two", "-s"]), + let extractedValues = arguments.extract(Flag( + preferred: "--spelling-one", alternatives: ["--spelling-two", "-s"], inverseNames: .init(preferred: "--negative-spelling-one", alternatives: ["--negative-spelling-two", "-ns"]) - ) + )) XCTAssertEqual(extractedValues, [true, true, false, false, true, true, false]) XCTAssertEqual(arguments.remainingArguments, ["--", "--spelling-one", "--negative-spelling-two"]) } @@ -204,6 +204,6 @@ private extension CommandLineArguments { } mutating func extractFlag(rawName: String) -> [Bool] { - extractFlag(named: .init(preferred: rawName)) + extract(CommandLineArgument.Flag(preferred: rawName)) } } From 185aab7365cfd3f4ddd0a1323e35e834b82ed1a0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?David=20R=C3=B6nnqvist?= Date: Fri, 6 Sep 2024 14:18:20 +0200 Subject: [PATCH 05/11] Add integration test that verifies that targets are passed all their dependencies --- IntegrationTests/Package.swift | 1 + .../Tests/CombinedDocumentationTests.swift | 99 +++++++++++++++++++ .../TargetsWithDependencies/Package.swift | 42 ++++++++ .../Sources/InnerFirst/SomeFile.swift | 13 +++ .../Sources/InnerSecond/SomeFile.swift | 13 +++ .../Sources/NestedInner/SomeFile.swift | 13 +++ .../Sources/Outer/SomeFile.swift | 13 +++ 7 files changed, 194 insertions(+) create mode 100644 IntegrationTests/Tests/CombinedDocumentationTests.swift create mode 100644 IntegrationTests/Tests/Fixtures/TargetsWithDependencies/Package.swift create mode 100644 IntegrationTests/Tests/Fixtures/TargetsWithDependencies/Sources/InnerFirst/SomeFile.swift create mode 100644 IntegrationTests/Tests/Fixtures/TargetsWithDependencies/Sources/InnerSecond/SomeFile.swift create mode 100644 IntegrationTests/Tests/Fixtures/TargetsWithDependencies/Sources/NestedInner/SomeFile.swift create mode 100644 IntegrationTests/Tests/Fixtures/TargetsWithDependencies/Sources/Outer/SomeFile.swift diff --git a/IntegrationTests/Package.swift b/IntegrationTests/Package.swift index 0d7513a..6743f88 100644 --- a/IntegrationTests/Package.swift +++ b/IntegrationTests/Package.swift @@ -28,6 +28,7 @@ let package = Package( .copy("Fixtures/SingleTestTarget"), .copy("Fixtures/SingleExecutableTarget"), .copy("Fixtures/MixedTargets"), + .copy("Fixtures/TargetsWithDependencies"), .copy("Fixtures/TargetWithDocCCatalog"), .copy("Fixtures/PackageWithSnippets"), .copy("Fixtures/PackageWithConformanceSymbols"), diff --git a/IntegrationTests/Tests/CombinedDocumentationTests.swift b/IntegrationTests/Tests/CombinedDocumentationTests.swift new file mode 100644 index 0000000..91d3001 --- /dev/null +++ b/IntegrationTests/Tests/CombinedDocumentationTests.swift @@ -0,0 +1,99 @@ +// This source file is part of the Swift.org open source project +// +// Copyright (c) 2024 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 XCTest + +final class CombinedDocumentationTests: ConcurrencyRequiringTestCase { + func testCombinedDocumentation() throws { +#if compiler(>=6.0) + let result = try swiftPackage( + "generate-documentation", + "--enable-experimental-combined-documentation", + "--verbose", // Necessary to see the 'docc convert' calls in the log and verify their parameters. + workingDirectory: try setupTemporaryDirectoryForFixture(named: "TargetsWithDependencies") + ) + + result.assertExitStatusEquals(0) + let outputArchives = result.referencedDocCArchives + XCTAssertEqual(outputArchives.count, 1) + XCTAssertEqual(outputArchives.map(\.lastPathComponent), [ + "TargetsWithDependencies.doccarchive", + ]) + + // Verify that the combined archive contains all target's documentation + + let combinedArchiveURL = try XCTUnwrap(outputArchives.first) + let combinedDataDirectoryContents = try filesIn(.dataSubdirectory, of: combinedArchiveURL) + .map(\.relativePath) + .sorted() + + XCTAssertEqual(combinedDataDirectoryContents, [ + "documentation.json", + "documentation/innerfirst.json", + "documentation/innerfirst/somethingpublic.json", + "documentation/innersecond.json", + "documentation/innersecond/somethingpublic.json", + "documentation/nestedinner.json", + "documentation/nestedinner/somethingpublic.json", + "documentation/outer.json", + "documentation/outer/somethingpublic.json", + ]) + + // Verify that each 'docc convert' call was passed the expected dependencies + + let doccConvertCalls = result.standardOutput + .components(separatedBy: .newlines) + .filter { line in + line.hasPrefix("docc invocation: '") && line.utf8.contains("docc convert ".utf8) + }.map { line in + line.trimmingCharacters(in: CharacterSet(charactersIn: "'")) + .components(separatedBy: .whitespaces) + .drop(while: { $0 != "convert" }) + } + + XCTAssertEqual(doccConvertCalls.count, 4) + + func extractDependencyArchives(targetName: String, file: StaticString = #filePath, line: UInt = #line) throws -> [String] { + let arguments = try XCTUnwrap( + doccConvertCalls.first(where: { $0.contains(["--fallback-display-name", targetName]) }), + file: file, line: line + ) + var dependencyPaths: [URL] = [] + + var remaining = arguments[...] + while !remaining.isEmpty { + remaining = remaining.drop(while: { $0 != "--dependency" }).dropFirst(/* the '--dependency' element */) + if let path = remaining.popFirst() { + dependencyPaths.append(URL(fileURLWithPath: path)) + } + } + + return dependencyPaths.map { $0.lastPathComponent }.sorted() + } + // Outer + // ├─ InnerFirst + // ╰─ InnerSecond + // ╰─ NestedInner + + XCTAssertEqual(try extractDependencyArchives(targetName: "Outer"), [ + "InnerFirst.doccarchive", + "InnerSecond.doccarchive", + ], "The outer target has depends on both inner targets") + + XCTAssertEqual(try extractDependencyArchives(targetName: "InnerFirst"), [], "The first inner target has no dependencies") + + XCTAssertEqual(try extractDependencyArchives(targetName: "InnerSecond"), [ + "NestedInner.doccarchive", + ], "The second inner target has depends on the nested inner target") + + XCTAssertEqual(try extractDependencyArchives(targetName: "NestedInner"), [], "The nested inner target has no dependencies") +#else + XCTSkip("This test requires a Swift-DocC version that support the link-dependencies feature") +#endif + } +} diff --git a/IntegrationTests/Tests/Fixtures/TargetsWithDependencies/Package.swift b/IntegrationTests/Tests/Fixtures/TargetsWithDependencies/Package.swift new file mode 100644 index 0000000..07d3091 --- /dev/null +++ b/IntegrationTests/Tests/Fixtures/TargetsWithDependencies/Package.swift @@ -0,0 +1,42 @@ +// swift-tools-version: 5.7 +// +// This source file is part of the Swift.org open source project +// +// Copyright (c) 2024 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: "TargetsWithDependencies", + targets: [ + // Outer + // ├─ InnerFirst + // ╰─ InnerSecond + // ╰─ NestedInner + .target(name: "Outer", dependencies: [ + "InnerFirst", + "InnerSecond", + ]), + .target(name: "InnerFirst"), + .target(name: "InnerSecond", dependencies: [ + "NestedInner" + ]), + .target(name: "NestedInner"), + ] +) + +// 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"), + ] +} diff --git a/IntegrationTests/Tests/Fixtures/TargetsWithDependencies/Sources/InnerFirst/SomeFile.swift b/IntegrationTests/Tests/Fixtures/TargetsWithDependencies/Sources/InnerFirst/SomeFile.swift new file mode 100644 index 0000000..9f5fa32 --- /dev/null +++ b/IntegrationTests/Tests/Fixtures/TargetsWithDependencies/Sources/InnerFirst/SomeFile.swift @@ -0,0 +1,13 @@ +// This source file is part of the Swift.org open source project +// +// Copyright (c) 2024 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 a public struct and should be included in the documentation for this library. +public struct SomethingPublic {} + +/// This is an internal struct and should not be included in the documentation for this library. +struct SomethingInternal {} diff --git a/IntegrationTests/Tests/Fixtures/TargetsWithDependencies/Sources/InnerSecond/SomeFile.swift b/IntegrationTests/Tests/Fixtures/TargetsWithDependencies/Sources/InnerSecond/SomeFile.swift new file mode 100644 index 0000000..9f5fa32 --- /dev/null +++ b/IntegrationTests/Tests/Fixtures/TargetsWithDependencies/Sources/InnerSecond/SomeFile.swift @@ -0,0 +1,13 @@ +// This source file is part of the Swift.org open source project +// +// Copyright (c) 2024 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 a public struct and should be included in the documentation for this library. +public struct SomethingPublic {} + +/// This is an internal struct and should not be included in the documentation for this library. +struct SomethingInternal {} diff --git a/IntegrationTests/Tests/Fixtures/TargetsWithDependencies/Sources/NestedInner/SomeFile.swift b/IntegrationTests/Tests/Fixtures/TargetsWithDependencies/Sources/NestedInner/SomeFile.swift new file mode 100644 index 0000000..9f5fa32 --- /dev/null +++ b/IntegrationTests/Tests/Fixtures/TargetsWithDependencies/Sources/NestedInner/SomeFile.swift @@ -0,0 +1,13 @@ +// This source file is part of the Swift.org open source project +// +// Copyright (c) 2024 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 a public struct and should be included in the documentation for this library. +public struct SomethingPublic {} + +/// This is an internal struct and should not be included in the documentation for this library. +struct SomethingInternal {} diff --git a/IntegrationTests/Tests/Fixtures/TargetsWithDependencies/Sources/Outer/SomeFile.swift b/IntegrationTests/Tests/Fixtures/TargetsWithDependencies/Sources/Outer/SomeFile.swift new file mode 100644 index 0000000..9f5fa32 --- /dev/null +++ b/IntegrationTests/Tests/Fixtures/TargetsWithDependencies/Sources/Outer/SomeFile.swift @@ -0,0 +1,13 @@ +// This source file is part of the Swift.org open source project +// +// Copyright (c) 2024 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 a public struct and should be included in the documentation for this library. +public struct SomethingPublic {} + +/// This is an internal struct and should not be included in the documentation for this library. +struct SomethingInternal {} From 7609abd00ed6406f3ddfcd16e890392091fe6c19 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?David=20R=C3=B6nnqvist?= Date: Fri, 6 Sep 2024 14:20:56 +0200 Subject: [PATCH 06/11] Use the CommandLineArguments type to construct the merge arguments --- Plugins/Swift-DocC Convert/SwiftDocCConvert.swift | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/Plugins/Swift-DocC Convert/SwiftDocCConvert.swift b/Plugins/Swift-DocC Convert/SwiftDocCConvert.swift index dbd34ce..27d41e0 100644 --- a/Plugins/Swift-DocC Convert/SwiftDocCConvert.swift +++ b/Plugins/Swift-DocC Convert/SwiftDocCConvert.swift @@ -209,20 +209,21 @@ import PackagePlugin combinedArchiveOutput = URL(fileURLWithPath: context.pluginWorkDirectory.appending(combinedArchiveName).string) } - var mergeCommandArguments = ["merge"] - mergeCommandArguments.append(contentsOf: intermediateDocumentationArchives.map(\.standardizedFileURL.path)) - mergeCommandArguments.append(contentsOf: [DocCArguments.outputPath.names.preferred, combinedArchiveOutput.path]) + var mergeCommandArguments = CommandLineArguments( + ["merge"] + intermediateDocumentationArchives.map(\.standardizedFileURL.path) + ) + mergeCommandArguments.insertIfMissing(DocCArguments.outputPath, value: combinedArchiveOutput.path) if let doccFeatures, doccFeatures.contains(.synthesizedLandingPageName) { - mergeCommandArguments.append(contentsOf: [DocCArguments.synthesizedLandingPageName.names.preferred, context.package.displayName]) - mergeCommandArguments.append(contentsOf: [DocCArguments.synthesizedLandingPageKind.names.preferred, "Package"]) + mergeCommandArguments.insertIfMissing(DocCArguments.synthesizedLandingPageName, value: context.package.displayName) + mergeCommandArguments.insertIfMissing(DocCArguments.synthesizedLandingPageKind, value: "Package") } // Remove the combined archive if it already exists try? FileManager.default.removeItem(at: combinedArchiveOutput) // Create a new combined archive - let process = try Process.run(doccExecutableURL, arguments: mergeCommandArguments) + let process = try Process.run(doccExecutableURL, arguments: mergeCommandArguments.remainingArguments) process.waitUntilExit() print(""" From d779f00bc74979730bf4f8abbdf91ca291df488b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?David=20R=C3=B6nnqvist?= Date: Fri, 6 Sep 2024 14:51:35 +0200 Subject: [PATCH 07/11] Print the 'docc merge' call when the `--verbose` flag is passed --- Plugins/Swift-DocC Convert/SwiftDocCConvert.swift | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/Plugins/Swift-DocC Convert/SwiftDocCConvert.swift b/Plugins/Swift-DocC Convert/SwiftDocCConvert.swift index 27d41e0..db5ba3a 100644 --- a/Plugins/Swift-DocC Convert/SwiftDocCConvert.swift +++ b/Plugins/Swift-DocC Convert/SwiftDocCConvert.swift @@ -222,6 +222,11 @@ import PackagePlugin // Remove the combined archive if it already exists try? FileManager.default.removeItem(at: combinedArchiveOutput) + if verbose { + let arguments = mergeCommandArguments.remainingArguments.joined(separator: " ") + print("docc invocation: '\(doccExecutableURL.path) \(arguments)'") + } + // Create a new combined archive let process = try Process.run(doccExecutableURL, arguments: mergeCommandArguments.remainingArguments) process.waitUntilExit() From 77af20f06b84a2023aaa27b7e9ac7e6eee30ac54 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?David=20R=C3=B6nnqvist?= Date: Fri, 6 Sep 2024 14:52:04 +0200 Subject: [PATCH 08/11] Fix an unrelated code warning in Swift 5.7 --- Sources/SwiftDocCPluginUtilities/HelpInformation.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Sources/SwiftDocCPluginUtilities/HelpInformation.swift b/Sources/SwiftDocCPluginUtilities/HelpInformation.swift index 4813322..83087c0 100644 --- a/Sources/SwiftDocCPluginUtilities/HelpInformation.swift +++ b/Sources/SwiftDocCPluginUtilities/HelpInformation.swift @@ -66,7 +66,7 @@ public enum HelpInformation { supportedSymbolGraphFlags.insert(DocumentedArgument.extendedTypes, at: 1) #else // stops 'not mutated' warning for Swift 5.7 and lower - supportedPluginFlags += [] + supportedSymbolGraphFlags += [] #endif for flag in supportedSymbolGraphFlags { From cdbbe08d503f2c1703312122368bfabb6705c278 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?David=20R=C3=B6nnqvist?= Date: Fri, 6 Sep 2024 14:53:44 +0200 Subject: [PATCH 09/11] Fix an unrelated code warning and avoid undefined behavior in snippet-extract --- Sources/snippet-extract/Utility/URL+Status.swift | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/Sources/snippet-extract/Utility/URL+Status.swift b/Sources/snippet-extract/Utility/URL+Status.swift index 883db24..aed81b2 100644 --- a/Sources/snippet-extract/Utility/URL+Status.swift +++ b/Sources/snippet-extract/Utility/URL+Status.swift @@ -1,6 +1,6 @@ // This source file is part of the Swift.org open source project // -// Copyright (c) 2022 Apple Inc. and the Swift project authors +// Copyright (c) 2022-2024 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 @@ -16,7 +16,7 @@ extension URL { var isDirectory: Bool { var isADirectory: ObjCBool = false - FileManager.default.fileExists(atPath: self.path, isDirectory: &isADirectory) - return isADirectory.boolValue + return FileManager.default.fileExists(atPath: self.path, isDirectory: &isADirectory) + && isADirectory.boolValue } } From 703f680766c0f8735cd377994a26ad94fc222cce Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?David=20R=C3=B6nnqvist?= Date: Tue, 10 Sep 2024 13:23:26 +0200 Subject: [PATCH 10/11] Correctly document updated parameters MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Sofía Rodríguez --- .../CommandLineArguments.swift | 26 ++++++++----------- .../DocumentedArgument.swift | 12 ++++----- .../ParsedArguments.swift | 20 +++++++------- 3 files changed, 27 insertions(+), 31 deletions(-) diff --git a/Sources/SwiftDocCPluginUtilities/CommandLineArguments/CommandLineArguments.swift b/Sources/SwiftDocCPluginUtilities/CommandLineArguments/CommandLineArguments.swift index defc67e..68010f6 100644 --- a/Sources/SwiftDocCPluginUtilities/CommandLineArguments/CommandLineArguments.swift +++ b/Sources/SwiftDocCPluginUtilities/CommandLineArguments/CommandLineArguments.swift @@ -35,11 +35,11 @@ public struct CommandLineArguments { // MARK: Extract - /// Extracts the values for the command line option with the given names. + /// Extracts the values for the given command line option. /// /// Upon return, the arguments list no longer contains any elements that match any spelling of this command line option or its values. /// - /// - Parameter names: The names of a command line option. + /// - Parameter option: The command line option to extract values for. /// - Returns: The extracted values for this command line option, in the order that they appear in the arguments list. public mutating func extract(_ option: CommandLineArgument.Option) -> [String] { var values = [String]() @@ -71,13 +71,11 @@ public struct CommandLineArguments { return values.reversed() // The values are gathered in reverse order } - /// Extracts the values for the command line flag with the given names. + /// Extracts the values for the command line flag. /// /// Upon return, the arguments list no longer contains any elements that match any spelling of this command line flag. /// - /// - Parameters: - /// - positiveNames: The positive names for a command line flag. - /// - negativeNames: The negative names for this command line flag, if any. + /// - Parameter flag: The command line flag to extract values for. /// - Returns: The extracted values for this command line flag. public mutating func extract(_ flag: CommandLineArgument.Flag) -> [Bool] { let positiveNames = flag.names @@ -98,8 +96,8 @@ public struct CommandLineArguments { /// Inserts a command line option into the arguments list unless it already exists. /// - Parameters: - /// - argument: The command line option to insert. - /// - matchOptionWithAnyValue: If `true`, an option argument will be considered a match even + /// - option: The command line option to insert. + /// - value: The value for this option. /// - Returns: `true` if the argument was already present in the arguments list; otherwise, `false`. @discardableResult public mutating func insertIfMissing(_ option: CommandLineArgument.Option, value: String) -> Bool { @@ -107,9 +105,7 @@ public struct CommandLineArguments { } /// Inserts a command line flag into the arguments list unless it already exists. - /// - Parameters: - /// - argument: The command line flag to insert. - /// - matchOptionWithAnyValue: If `true`, an option argument will be considered a match even + /// - Parameter flag: The command line flag to insert. /// - Returns: `true` if the argument was already present in the arguments list; otherwise, `false`. @discardableResult public mutating func insertIfMissing(_ flag: CommandLineArgument.Flag) -> Bool { @@ -121,10 +117,10 @@ public struct CommandLineArguments { remainingOptionsOrFlags.insert(rawArgument, at: 0) } - /// Inserts a command line argument into the arguments list, overriding any existing values. + /// Inserts a command line option with a new value into the arguments list, overriding any existing values. /// - Parameters: - /// - argument: The command line argument (flag or option) to insert. - /// - newValue: The new value for this command line argument. + /// - option: The command line option to insert. + /// - newValue: The new value for this option. /// - Returns: `true` if the argument was already present in the arguments list; otherwise, `false`. @discardableResult public mutating func overrideOrInsert(_ option: CommandLineArgument.Option, newValue: String) -> Bool { @@ -160,7 +156,7 @@ private extension ArraySlice { } } - /// Checks if the slice contains the given argument (flag or argument). + /// Checks if the slice contains the given argument (flag or option). func contains(_ argument: CommandLineArgument) -> Bool { let names = argument.names guard case .option(let value, .arrayOfValues) = argument.kind else { diff --git a/Sources/SwiftDocCPluginUtilities/DocumentedPluginFlags/DocumentedArgument.swift b/Sources/SwiftDocCPluginUtilities/DocumentedPluginFlags/DocumentedArgument.swift index 634a2c0..0d1efcc 100644 --- a/Sources/SwiftDocCPluginUtilities/DocumentedPluginFlags/DocumentedArgument.swift +++ b/Sources/SwiftDocCPluginUtilities/DocumentedPluginFlags/DocumentedArgument.swift @@ -6,9 +6,9 @@ // See https://swift.org/LICENSE.txt for license information // See https://swift.org/CONTRIBUTORS.txt for Swift project authors -/// A documented command-line flag for the plugin. +/// A documented command-line argument for the plugin. /// -/// This may include some flags that the plugin forwards to the symbol graph extract tool or to DocC. +/// This may include some arguments (flags or options) that the plugin forwards to the symbol graph extract tool or to DocC. struct DocumentedArgument { /// A command line argument (flag or option) that is wrapped with documentation. enum Argument { @@ -29,10 +29,10 @@ struct DocumentedArgument { } } - /// A short user-facing description of this flag. + /// A short user-facing description of this argument (flag or option). var abstract: String - /// An expanded user-facing description of this flag. + /// An expanded user-facing description of this argument (flag or option). var discussion: String? init(flag: CommandLineArgument.Flag, abstract: String, discussion: String? = nil) { @@ -85,7 +85,7 @@ extension DocumentedArgument { // MARK: Symbol graph flags extension DocumentedArgument { - /// Include or exclude extended types in documentation archives. + /// A plugin feature flag to either include or exclude extended types in documentation archives. /// /// Enables/disables the extension block symbol format when calling the dump symbol graph API. /// @@ -100,7 +100,7 @@ extension DocumentedArgument { discussion: "Allows documenting symbols that a target adds to its dependencies." ) - /// Exclude synthesized symbols from the generated documentation. + /// A plugin feature flag to exclude synthesized symbols from the generated documentation. /// /// `--experimental-skip-synthesized-symbols` produces a DocC archive without compiler-synthesized symbols. static let skipSynthesizedSymbols = Self( diff --git a/Sources/SwiftDocCPluginUtilities/ParsedArguments.swift b/Sources/SwiftDocCPluginUtilities/ParsedArguments.swift index 088320b..a76e5f3 100644 --- a/Sources/SwiftDocCPluginUtilities/ParsedArguments.swift +++ b/Sources/SwiftDocCPluginUtilities/ParsedArguments.swift @@ -139,42 +139,42 @@ struct ParsedArguments { enum DocCArguments { /// A fallback value for the bundle display name, if the documentation catalog doesn't specify one or if the build has no symbol information. /// - /// The plugin defines this name so that it can pass a default value for older versions of `docc` which require this. + /// The plugin defines this option so that it can pass a default value for older versions of `docc` which require this. static let fallbackDisplayName = CommandLineArgument.Option( preferred: "--fallback-display-name" ) /// A fallback value for the bundle identifier, if the documentation catalog doesn't specify one or if the build has no symbol information. /// - /// The plugin defines this name so that it can pass a default value for older versions of `docc` which require this. + /// The plugin defines this option so that it can pass a default value for older versions of `docc` which require this. static let fallbackBundleIdentifier = CommandLineArgument.Option( preferred: "--fallback-bundle-identifier" ) /// A fallback value for the "module kind" display name, if the documentation catalog doesn't specify one. /// - /// The plugin defines this name so that it can pass a default value when building documentation for executable targets. + /// The plugin defines this option so that it can pass a default value when building documentation for executable targets. static let fallbackDefaultModuleKind = CommandLineArgument.Option( preferred: "--fallback-default-module-kind" ) /// A directory of symbol graph files that DocC will use as input when building documentation. /// - /// The plugin defines this name so that it can pass a default value. + /// The plugin defines this option so that it can pass a default value. static let additionalSymbolGraphDirectory = CommandLineArgument.Option( preferred: "--additional-symbol-graph-dir" ) /// Configures DocC to include a LMDB representation of the navigator index in the output. /// - /// The plugin defines this name so that it can pass this flag by default. + /// The plugin defines this flag so that it can pass this flag by default. static let emitLMDBIndex = CommandLineArgument.Flag( preferred: "--emit-lmdb-index" ) /// The directory where DocC will write the built documentation archive. /// - /// The plugin defines this name so that it can intercept it and support building documentation for multiple targets within one package build command. + /// The plugin defines this option so that it can intercept it and support building documentation for multiple targets within one package build command. static let outputPath = CommandLineArgument.Option( preferred: "--output-path", alternatives: ["--output-dir", "-o"] @@ -182,14 +182,14 @@ enum DocCArguments { /// A DocC feature flag to enable support for linking to documentation dependencies. /// - /// The plugin defines this name so that it can specify documentation dependencies based on target dependencies when building combined documentation for multiple targets. + /// The plugin defines this flag so that it can specify documentation dependencies based on target dependencies when building combined documentation for multiple targets. static let enableExternalLinkSupport = CommandLineArgument.Flag( preferred: "--enable-experimental-external-link-support" ) /// A DocC flag that specifies a dependency DocC archive that the current build can link to. /// - /// The plugin defines this name so that it can specify documentation dependencies based on target dependencies when building combined documentation for multiple targets. + /// The plugin defines this option so that it can specify documentation dependencies based on target dependencies when building combined documentation for multiple targets. static let externalLinkDependency = CommandLineArgument.Option( preferred: "--dependency", kind: .arrayOfValues @@ -197,14 +197,14 @@ enum DocCArguments { /// A DocC flag for the "merge" command that specifies a custom display name for the synthesized landing page. /// - /// The plugin defines this name so that it can specify the package name as the display name of the default landing page when building combined documentation for multiple targets. + /// The plugin defines this option so that it can specify the package name as the display name of the default landing page when building combined documentation for multiple targets. static let synthesizedLandingPageName = CommandLineArgument.Option( preferred: "--synthesized-landing-page-name" ) /// A DocC flag for the "merge" command that specifies a custom kind for the synthesized landing page. /// - /// The plugin defines this name so that it can specify "Package" as the kind of the default landing page when building combined documentation for multiple targets. + /// The plugin defines this option so that it can specify "Package" as the kind of the default landing page when building combined documentation for multiple targets. static let synthesizedLandingPageKind = CommandLineArgument.Option( preferred: "--synthesized-landing-page-kind" ) From e99acd7e12dcfc2e5423ed47de56ff02050cdfec Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?David=20R=C3=B6nnqvist?= Date: Tue, 10 Sep 2024 13:30:55 +0200 Subject: [PATCH 11/11] Fix other unrelated documentation warnings --- Sources/Snippets/Model/Snippet.swift | 7 +++---- Sources/snippet-extract/Utility/SymbolGraph+Snippet.swift | 8 +++++--- 2 files changed, 8 insertions(+), 7 deletions(-) diff --git a/Sources/Snippets/Model/Snippet.swift b/Sources/Snippets/Model/Snippet.swift index cd5481f..7e06bf2 100644 --- a/Sources/Snippets/Model/Snippet.swift +++ b/Sources/Snippets/Model/Snippet.swift @@ -1,6 +1,6 @@ // This source file is part of the Swift.org open source project // -// Copyright (c) 2022 Apple Inc. and the Swift project authors +// Copyright (c) 2022-2024 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 @@ -12,7 +12,7 @@ import Foundation /// /// A *snippet* is a short, focused code example that can be shown with little to no context or prose. public struct Snippet { - /// The ``URL`` of the source file for this snippet. + /// The URL of the source file for this snippet. public var sourceFile: URL /// A short abstract explaining what the snippet does. @@ -39,8 +39,7 @@ public struct Snippet { /// Create a Swift snippet by parsing a file. /// - /// - parameter sourceURL: The URL of the file to parse. - /// - parameter syntax: The name of the syntax of the source file if known. + /// - Parameter sourceFile: The URL of the file to parse. public init(parsing sourceFile: URL) throws { let source = try String(contentsOf: sourceFile) self.init(parsing: source, sourceFile: sourceFile) diff --git a/Sources/snippet-extract/Utility/SymbolGraph+Snippet.swift b/Sources/snippet-extract/Utility/SymbolGraph+Snippet.swift index e122d79..34b2ae8 100644 --- a/Sources/snippet-extract/Utility/SymbolGraph+Snippet.swift +++ b/Sources/snippet-extract/Utility/SymbolGraph+Snippet.swift @@ -1,6 +1,6 @@ // This source file is part of the Swift.org open source project // -// Copyright (c) 2022 Apple Inc. and the Swift project authors +// Copyright (c) 2022-2024 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 @@ -11,9 +11,11 @@ import Snippets import struct SymbolKit.SymbolGraph extension SymbolGraph.Symbol { - /// Create a ``SymbolGraph.Symbol`` from a ``Snippet``. + /// Create a symbol for a given snippet. /// - /// - parameter moduleName: The name to use for the package name in the snippet symbol's precise identifier. + /// - Parameters: + /// - snippet: The snippet to create a symbol for. + /// - moduleName: The name to use for the package name in the snippet symbol's precise identifier. public init(_ snippet: Snippets.Snippet, moduleName: String) throws { let basename = snippet.sourceFile.deletingPathExtension().lastPathComponent let identifier = SymbolGraph.Symbol.Identifier(precise: "$snippet__\(moduleName).\(basename)", interfaceLanguage: "swift")