diff --git a/Sources/DocCDocumentation/DocCDocumentation.docc/Info.plist b/Sources/DocCDocumentation/DocCDocumentation.docc/Info.plist
index e30a6cc54..48a63989c 100644
--- a/Sources/DocCDocumentation/DocCDocumentation.docc/Info.plist
+++ b/Sources/DocCDocumentation/DocCDocumentation.docc/Info.plist
@@ -22,5 +22,7 @@
0.1.0
CFBundleVersion
0.1.0
+ CDDefaultModuleKind
+ Tool
diff --git a/Sources/SwiftDocC/Infrastructure/DocumentationContext.swift b/Sources/SwiftDocC/Infrastructure/DocumentationContext.swift
index f9011086e..f3fa30280 100644
--- a/Sources/SwiftDocC/Infrastructure/DocumentationContext.swift
+++ b/Sources/SwiftDocC/Infrastructure/DocumentationContext.swift
@@ -1096,13 +1096,17 @@ public class DocumentationContext: DocumentationContextDataProviderDelegate {
// Create a module symbol
let moduleIdentifier = SymbolGraph.Symbol.Identifier(precise: moduleName, interfaceLanguage: SourceLanguage.swift.id)
+
+ // Use the default module kind for this bundle if one was provided,
+ // otherwise fall back to 'Framework'
+ let moduleKindDisplayName = bundle.info.defaultModuleKind ?? "Framework"
let moduleSymbol = SymbolGraph.Symbol(
identifier: moduleIdentifier,
names: SymbolGraph.Symbol.Names(title: moduleName, navigator: nil, subHeading: nil, prose: nil),
pathComponents: [moduleName],
docComment: nil,
accessLevel: SymbolGraph.Symbol.AccessControl(rawValue: "public"),
- kind: SymbolGraph.Symbol.Kind(parsedIdentifier: .module, displayName: "Framework"),
+ kind: SymbolGraph.Symbol.Kind(parsedIdentifier: .module, displayName: moduleKindDisplayName),
mixins: [:])
let moduleSymbolReference = SymbolReference(moduleName, interfaceLanguage: .swift, symbol: moduleSymbol)
moduleReference = ResolvedTopicReference(symbolReference: moduleSymbolReference, moduleName: moduleName, bundle: bundle)
diff --git a/Sources/SwiftDocC/Infrastructure/Workspace/DocumentationBundle+Info.swift b/Sources/SwiftDocC/Infrastructure/Workspace/DocumentationBundle+Info.swift
index b1ff0846d..ab09400b7 100644
--- a/Sources/SwiftDocC/Infrastructure/Workspace/DocumentationBundle+Info.swift
+++ b/Sources/SwiftDocC/Infrastructure/Workspace/DocumentationBundle+Info.swift
@@ -30,6 +30,9 @@ extension DocumentationBundle {
/// The default availability for the various modules in the bundle.
public var defaultAvailability: DefaultAvailability?
+ /// The default kind for the various modules in the bundle.
+ public var defaultModuleKind: String?
+
/// The keys that must be present in an Info.plist file in order for doc compilation to proceed.
static let requiredKeys: Set = [.displayName, .identifier, .version]
@@ -39,6 +42,7 @@ extension DocumentationBundle {
case version = "CFBundleVersion"
case defaultCodeListingLanguage = "CDDefaultCodeListingLanguage"
case defaultAvailability = "CDAppleDefaultAvailability"
+ case defaultModuleKind = "CDDefaultModuleKind"
var argumentName: String? {
switch self {
@@ -50,6 +54,8 @@ extension DocumentationBundle {
return "--fallback-bundle-version"
case .defaultCodeListingLanguage:
return "--default-code-listing-language"
+ case .defaultModuleKind:
+ return "--fallback-default-module-kind"
case .defaultAvailability:
return nil
}
@@ -161,6 +167,7 @@ extension DocumentationBundle {
// Finally, decode the optional keys if they're present.
self.defaultCodeListingLanguage = try decodeOrFallbackIfPresent(String.self, with: .defaultCodeListingLanguage)
+ self.defaultModuleKind = try decodeOrFallbackIfPresent(String.self, with: .defaultModuleKind)
self.defaultAvailability = try decodeOrFallbackIfPresent(DefaultAvailability.self, with: .defaultAvailability)
}
@@ -169,12 +176,14 @@ extension DocumentationBundle {
identifier: String,
version: Version,
defaultCodeListingLanguage: String? = nil,
+ defaultModuleKind: String? = nil,
defaultAvailability: DefaultAvailability? = nil
) {
self.displayName = displayName
self.identifier = identifier
self.version = version
self.defaultCodeListingLanguage = defaultCodeListingLanguage
+ self.defaultModuleKind = defaultModuleKind
self.defaultAvailability = defaultAvailability
}
}
@@ -198,6 +207,7 @@ extension BundleDiscoveryOptions {
fallbackIdentifier: String? = nil,
fallbackVersion: String? = nil,
fallbackDefaultCodeListingLanguage: String? = nil,
+ fallbackDefaultModuleKind: String? = nil,
fallbackDefaultAvailability: DefaultAvailability? = nil,
additionalSymbolGraphFiles: [URL] = []
) {
@@ -220,6 +230,8 @@ extension BundleDiscoveryOptions {
value = fallbackDefaultCodeListingLanguage
case .defaultAvailability:
value = fallbackDefaultAvailability
+ case .defaultModuleKind:
+ value = fallbackDefaultModuleKind
}
guard let unwrappedValue = value else {
diff --git a/Sources/SwiftDocCUtilities/ArgumentParsing/ActionExtensions/ConvertAction+CommandInitialization.swift b/Sources/SwiftDocCUtilities/ArgumentParsing/ActionExtensions/ConvertAction+CommandInitialization.swift
index 44eff0bc1..45ac51069 100644
--- a/Sources/SwiftDocCUtilities/ArgumentParsing/ActionExtensions/ConvertAction+CommandInitialization.swift
+++ b/Sources/SwiftDocCUtilities/ArgumentParsing/ActionExtensions/ConvertAction+CommandInitialization.swift
@@ -49,6 +49,7 @@ extension ConvertAction {
fallbackIdentifier: convert.fallbackBundleIdentifier,
fallbackVersion: convert.fallbackBundleVersion,
fallbackDefaultCodeListingLanguage: convert.defaultCodeListingLanguage,
+ fallbackDefaultModuleKind: convert.fallbackDefaultModuleKind,
additionalSymbolGraphFiles: additionalSymbolGraphFiles
)
diff --git a/Sources/SwiftDocCUtilities/ArgumentParsing/Subcommands/Convert.swift b/Sources/SwiftDocCUtilities/ArgumentParsing/Subcommands/Convert.swift
index 56118a990..05b8b5e7a 100644
--- a/Sources/SwiftDocCUtilities/ArgumentParsing/Subcommands/Convert.swift
+++ b/Sources/SwiftDocCUtilities/ArgumentParsing/Subcommands/Convert.swift
@@ -155,6 +155,14 @@ extension Docc {
help: "A fallback default language for code listings if no value is provided in the documentation bundle's Info.plist file."
)
public var defaultCodeListingLanguage: String?
+
+ @Option(
+ help: """
+ A fallback default module kind if no value is provided \
+ in the documentation bundle's Info.plist file.
+ """
+ )
+ public var fallbackDefaultModuleKind: String?
/// A user-provided location where the convert action writes the built documentation.
@Option(
diff --git a/Tests/SwiftDocCTests/Infrastructure/DocumentationBundleInfoTests.swift b/Tests/SwiftDocCTests/Infrastructure/DocumentationBundleInfoTests.swift
index a3d12a363..d0d092648 100644
--- a/Tests/SwiftDocCTests/Infrastructure/DocumentationBundleInfoTests.swift
+++ b/Tests/SwiftDocCTests/Infrastructure/DocumentationBundleInfoTests.swift
@@ -159,6 +159,8 @@ class DocumentationBundleInfoTests: XCTestCase {
CDDefaultCodeListingLanguage
swift
+ CDDefaultModuleKind
+ Executable
CFBundleDisplayName
ShapeKit
CFBundleIdentifier
@@ -196,6 +198,7 @@ class DocumentationBundleInfoTests: XCTestCase {
fallbackIdentifier: "swift.org.Identifier",
fallbackVersion: "1.0.0",
fallbackDefaultCodeListingLanguage: "swift",
+ fallbackDefaultModuleKind: "Executable",
fallbackDefaultAvailability: DefaultAvailability(
with: [
"MyModule": [
@@ -216,6 +219,7 @@ class DocumentationBundleInfoTests: XCTestCase {
identifier: "swift.org.Identifier",
version: Version(arrayLiteral: 1,0,0),
defaultCodeListingLanguage: "swift",
+ defaultModuleKind: "Executable",
defaultAvailability: DefaultAvailability(
with: [
"MyModule": [
@@ -236,6 +240,7 @@ class DocumentationBundleInfoTests: XCTestCase {
identifier: "swift.org.Identifier",
version: Version(arrayLiteral: 1,0,0),
defaultCodeListingLanguage: "swift",
+ defaultModuleKind: "Executable",
defaultAvailability: DefaultAvailability(
with: [
"MyModule": [
diff --git a/Tests/SwiftDocCTests/Infrastructure/DocumentationContextTests.swift b/Tests/SwiftDocCTests/Infrastructure/DocumentationContextTests.swift
index 4023f3723..8fc42a75d 100644
--- a/Tests/SwiftDocCTests/Infrastructure/DocumentationContextTests.swift
+++ b/Tests/SwiftDocCTests/Infrastructure/DocumentationContextTests.swift
@@ -2972,6 +2972,15 @@ let expected = """
// Verify the solution proposes the expected absolute link replacement.
XCTAssertEqual(problem.possibleSolutions[0].replacements[0].replacement, "")
}
+
+ func testCustomModuleKind() throws {
+ let (bundle, context) = try testBundleAndContext(named: "BundleWithExecutableModuleKind")
+ XCTAssertEqual(bundle.info.defaultModuleKind, "Executable")
+
+ let moduleSymbol = try XCTUnwrap(context.symbolIndex["ExampleDocumentedExecutable"]?.symbol)
+ XCTAssertEqual(moduleSymbol.kind.identifier.identifier, "module")
+ XCTAssertEqual(moduleSymbol.kind.displayName, "Executable")
+ }
}
func assertEqualDumps(_ lhs: String, _ rhs: String, file: StaticString = #file, line: UInt = #line) {
diff --git a/Tests/SwiftDocCTests/Test Bundles/BundleWithExecutableModuleKind.docc/ExampleDocumentedExecutable.symbols.json b/Tests/SwiftDocCTests/Test Bundles/BundleWithExecutableModuleKind.docc/ExampleDocumentedExecutable.symbols.json
new file mode 100644
index 000000000..e2dcc8641
--- /dev/null
+++ b/Tests/SwiftDocCTests/Test Bundles/BundleWithExecutableModuleKind.docc/ExampleDocumentedExecutable.symbols.json
@@ -0,0 +1,202 @@
+{
+ "metadata": {
+ "formatVersion": {
+ "major": 0,
+ "minor": 5,
+ "patch": 3
+ },
+ "generator": "Apple Swift version 5.5 (swiftlang-1300.0.29.1 clang-1300.0.28.1)"
+ },
+ "module": {
+ "name": "ExampleDocumentedExecutable",
+ "platform": {
+ "architecture": "arm64",
+ "vendor": "apple",
+ "operatingSystem": {
+ "name": "macosx",
+ "minimumVersion": {
+ "major": 10,
+ "minor": 10,
+ "patch": 0
+ }
+ }
+ }
+ },
+ "symbols": [
+ {
+ "kind": {
+ "identifier": "swift.struct",
+ "displayName": "Structure"
+ },
+ "identifier": {
+ "precise": "s:27ExampleDocumentedExecutableAAV",
+ "interfaceLanguage": "swift"
+ },
+ "pathComponents": [
+ "ExampleDocumentedExecutable"
+ ],
+ "names": {
+ "title": "ExampleDocumentedExecutable",
+ "navigator": [
+ {
+ "kind": "identifier",
+ "spelling": "ExampleDocumentedExecutable"
+ }
+ ],
+ "subHeading": [
+ {
+ "kind": "keyword",
+ "spelling": "struct"
+ },
+ {
+ "kind": "text",
+ "spelling": " "
+ },
+ {
+ "kind": "identifier",
+ "spelling": "ExampleDocumentedExecutable"
+ }
+ ]
+ },
+ "docComment": {
+ "lines": [
+ {
+ "range": {
+ "start": {
+ "line": 0,
+ "character": 4
+ },
+ "end": {
+ "line": 0,
+ "character": 55
+ }
+ },
+ "text": "This is a description of what this executable does."
+ }
+ ]
+ },
+ "declarationFragments": [
+ {
+ "kind": "attribute",
+ "spelling": "@main"
+ },
+ {
+ "kind": "text",
+ "spelling": " "
+ },
+ {
+ "kind": "keyword",
+ "spelling": "struct"
+ },
+ {
+ "kind": "text",
+ "spelling": " "
+ },
+ {
+ "kind": "identifier",
+ "spelling": "ExampleDocumentedExecutable"
+ }
+ ],
+ "accessLevel": "internal",
+ "location": {
+ "uri": "file:///Users/demo/Downloads/ExampleDocumentedExecutable/Sources/ExampleDocumentedExecutable/ExampleDocumentedExecutable.swift",
+ "position": {
+ "line": 2,
+ "character": 14
+ }
+ }
+ },
+ {
+ "kind": {
+ "identifier": "swift.type.method",
+ "displayName": "Type Method"
+ },
+ "identifier": {
+ "precise": "s:27ExampleDocumentedExecutableAAV4mainyyFZ",
+ "interfaceLanguage": "swift"
+ },
+ "pathComponents": [
+ "ExampleDocumentedExecutable",
+ "main()"
+ ],
+ "names": {
+ "title": "main()",
+ "subHeading": [
+ {
+ "kind": "keyword",
+ "spelling": "static"
+ },
+ {
+ "kind": "text",
+ "spelling": " "
+ },
+ {
+ "kind": "keyword",
+ "spelling": "func"
+ },
+ {
+ "kind": "text",
+ "spelling": " "
+ },
+ {
+ "kind": "identifier",
+ "spelling": "main"
+ },
+ {
+ "kind": "text",
+ "spelling": "()"
+ }
+ ]
+ },
+ "functionSignature": {
+ "returns": [
+ {
+ "kind": "text",
+ "spelling": "()"
+ }
+ ]
+ },
+ "declarationFragments": [
+ {
+ "kind": "keyword",
+ "spelling": "static"
+ },
+ {
+ "kind": "text",
+ "spelling": " "
+ },
+ {
+ "kind": "keyword",
+ "spelling": "func"
+ },
+ {
+ "kind": "text",
+ "spelling": " "
+ },
+ {
+ "kind": "identifier",
+ "spelling": "main"
+ },
+ {
+ "kind": "text",
+ "spelling": "()"
+ }
+ ],
+ "accessLevel": "internal",
+ "location": {
+ "uri": "file:///Users/username/Downloads/ExampleDocumentedExecutable/Sources/ExampleDocumentedExecutable/ExampleDocumentedExecutable.swift",
+ "position": {
+ "line": 3,
+ "character": 23
+ }
+ }
+ }
+ ],
+ "relationships": [
+ {
+ "kind": "memberOf",
+ "source": "s:27ExampleDocumentedExecutableAAV4mainyyFZ",
+ "target": "s:27ExampleDocumentedExecutableAAV"
+ }
+ ]
+}
\ No newline at end of file
diff --git a/Tests/SwiftDocCTests/Test Bundles/BundleWithExecutableModuleKind.docc/Info.plist b/Tests/SwiftDocCTests/Test Bundles/BundleWithExecutableModuleKind.docc/Info.plist
new file mode 100644
index 000000000..ae42f7629
--- /dev/null
+++ b/Tests/SwiftDocCTests/Test Bundles/BundleWithExecutableModuleKind.docc/Info.plist
@@ -0,0 +1,16 @@
+
+
+
+
+ CFBundleName
+ ExampleDocumentedExecutable
+ CFBundleDisplayName
+ My Executable
+ CFBundleIdentifier
+ org.swift.Executable
+ CFBundleVersion
+ 0.1.0
+ CDDefaultModuleKind
+ Executable
+
+