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 + +