diff --git a/Sources/SwiftDocC/Infrastructure/Symbol Graph/ExtendedTypeFormatTransformation.swift b/Sources/SwiftDocC/Infrastructure/Symbol Graph/ExtendedTypeFormatTransformation.swift index 482b165120..ced7403b29 100644 --- a/Sources/SwiftDocC/Infrastructure/Symbol Graph/ExtendedTypeFormatTransformation.swift +++ b/Sources/SwiftDocC/Infrastructure/Symbol Graph/ExtendedTypeFormatTransformation.swift @@ -322,7 +322,7 @@ extension ExtendedTypeFormatTransformation { newMixins[SymbolGraph.Symbol.Swift.Extension.mixinKey] = swiftExtension } - if let declarationFragments = extensionBlockSymbol[mixin: SymbolGraph.Symbol.DeclarationFragments.self]?.declarationFragments { + if let declarationFragments = extensionBlockSymbol[mixin: SymbolGraph.Symbol.DeclarationFragments.self]?.declarationFragments, declarationFragments.count >= 3 { var prefixWithoutWhereClause: [SymbolGraph.Symbol.DeclarationFragments.Fragment] = Array(declarationFragments[..<3]) outer: for fragment in declarationFragments[3...] { diff --git a/Tests/SwiftDocCTests/Infrastructure/ReferenceResolverTests.swift b/Tests/SwiftDocCTests/Infrastructure/ReferenceResolverTests.swift index ae7cbf750c..bd0d4f304f 100644 --- a/Tests/SwiftDocCTests/Infrastructure/ReferenceResolverTests.swift +++ b/Tests/SwiftDocCTests/Infrastructure/ReferenceResolverTests.swift @@ -535,7 +535,19 @@ class ReferenceResolverTests: XCTestCase { let renderReference = try XCTUnwrap(renderNode.references[boolReference]) XCTAssert(renderReference is UnresolvedRenderReference) } - + + func testExtensionWithEmptyDeclarationFragments() throws { + let (bundle, context) = try testBundleAndContext(named: "ModuleWithEmptyDeclarationFragments") + + let node = try context.entity(with: ResolvedTopicReference(bundleIdentifier: bundle.identifier, path: "/documentation/ModuleWithEmptyDeclarationFragments", sourceLanguage: .swift)) + var translator = RenderNodeTranslator(context: context, bundle: bundle, identifier: node.reference, source: nil) + let renderNode = translator.visit(node.semantic as! Symbol) as! RenderNode + + // Despite having an extension to Float, there are no symbols added by that extension, so + // the resulting documentation should be empty + XCTAssertEqual(renderNode.topicSections.count, 0) + } + struct TestExternalReferenceResolver: ExternalReferenceResolver { var bundleIdentifier = "com.external.testbundle" var expectedReferencePath = "/externally/resolved/path" diff --git a/Tests/SwiftDocCTests/Test Bundles/ModuleWithEmptyDeclarationFragments.docc/Info.plist b/Tests/SwiftDocCTests/Test Bundles/ModuleWithEmptyDeclarationFragments.docc/Info.plist new file mode 100644 index 0000000000..ee39c93e17 --- /dev/null +++ b/Tests/SwiftDocCTests/Test Bundles/ModuleWithEmptyDeclarationFragments.docc/Info.plist @@ -0,0 +1,14 @@ + + + + + CFBundleVersion + 0.1.0 + CFBundleIdentifier + org.swift.docc.example + CFBundleDisplayName + Module with empty declaration fragments + CFBundleName + ModuleWithEmptyDeclarationFragments + + diff --git a/Tests/SwiftDocCTests/Test Bundles/ModuleWithEmptyDeclarationFragments.docc/ModuleWithEmptyDeclarationFragments.md b/Tests/SwiftDocCTests/Test Bundles/ModuleWithEmptyDeclarationFragments.docc/ModuleWithEmptyDeclarationFragments.md new file mode 100644 index 0000000000..19874aa7d2 --- /dev/null +++ b/Tests/SwiftDocCTests/Test Bundles/ModuleWithEmptyDeclarationFragments.docc/ModuleWithEmptyDeclarationFragments.md @@ -0,0 +1,10 @@ +# ``ModuleWithEmptyDeclarationFragments`` + +This module contains a single extension to `Float`, which was generated with an empty declaration. + +## Overview + +The purpose of this test fixture is to ensure that Swift-DocC does not crash when encountering these +erroneous symbol graphs on extension block symbols. + + diff --git a/Tests/SwiftDocCTests/Test Bundles/ModuleWithEmptyDeclarationFragments.docc/ModuleWithEmptyDeclarationFragments.symbols.json b/Tests/SwiftDocCTests/Test Bundles/ModuleWithEmptyDeclarationFragments.docc/ModuleWithEmptyDeclarationFragments.symbols.json new file mode 100644 index 0000000000..a0d0c954cf --- /dev/null +++ b/Tests/SwiftDocCTests/Test Bundles/ModuleWithEmptyDeclarationFragments.docc/ModuleWithEmptyDeclarationFragments.symbols.json @@ -0,0 +1,26 @@ +{ + "metadata": { + "formatVersion": { + "major": 0, + "minor": 6, + "patch": 0 + }, + "generator": "Swift 5.9" + }, + "module": { + "name": "ModuleWithEmptyDeclarationFragments", + "platform": { + "architecture": "x86_64", + "vendor": "apple", + "operatingSystem": { + "name": "macosx", + "minimumVersion": { + "major": 12, + "minor": 4 + } + } + } + }, + "symbols": [], + "relationships": [] +} diff --git a/Tests/SwiftDocCTests/Test Bundles/ModuleWithEmptyDeclarationFragments.docc/ModuleWithEmptyDeclarationFragments@Swift.symbols.json b/Tests/SwiftDocCTests/Test Bundles/ModuleWithEmptyDeclarationFragments.docc/ModuleWithEmptyDeclarationFragments@Swift.symbols.json new file mode 100644 index 0000000000..e34e22667a --- /dev/null +++ b/Tests/SwiftDocCTests/Test Bundles/ModuleWithEmptyDeclarationFragments.docc/ModuleWithEmptyDeclarationFragments@Swift.symbols.json @@ -0,0 +1,95 @@ +{ + "metadata": { + "formatVersion": { + "major": 0, + "minor": 6, + "patch": 0 + }, + "generator": "Swift 5.9" + }, + "module": { + "name": "ModuleWithEmptyDeclarationFragments", + "platform": { + "architecture": "x86_64", + "vendor": "apple", + "operatingSystem": { + "name": "macosx", + "minimumVersion": { + "major": 12, + "minor": 4 + } + } + } + }, + "symbols": [ + { + "kind": { + "identifier": "swift.extension", + "displayName": "Extension" + }, + "identifier": { + "precise": "s:e:s:Sf10FoundationE4_argSfvp", + "interfaceLanguage": "swift" + }, + "pathComponents": [ + "Float" + ], + "names": { + "title": "Float", + "navigator": [ + { + "kind": "identifier", + "spelling": "Float" + } + ], + "subHeading": [] + }, + "swiftExtension": { + "extendedModule": "Swift", + "typeKind": "swift.struct" + }, + "declarationFragments": [], + "accessLevel": "public", + "availability": [ + { + "domain": "macOS", + "introduced": { + "major": 12 + } + }, + { + "domain": "watchOS", + "introduced": { + "major": 8 + } + }, + { + "domain": "iOS", + "introduced": { + "major": 15 + } + }, + { + "domain": "tvOS", + "introduced": { + "major": 15 + } + } + ] + } + ], + "relationships": [ + { + "kind": "extensionTo", + "source": "s:e:s:Sf10FoundationE4_argSfvp", + "target": "s:Sf", + "targetFallback": "Swift.Float" + }, + { + "kind": "conformsTo", + "source": "s:e:s:Sf10FoundationE4_argSfvp", + "target": "s:SQ", + "targetFallback": "Swift.Equatable" + } + ] +}