Repository: apple/swift-docc (swift)

Issue:
`convert` and `preview` commands crash on certain Markdown pages
### Description

When rendering Swift compiler documentation the diagnostics formatting code crashes on certain Markdown pages.

### Checklist

- [X] If possible, I've reproduced the issue using the `main` branch of this package.
- [X] This issue hasn't been addressed in an [existing GitHub issue](https://github.com/apple/swift-docc/issues).

### Expected Behavior

No crashes.

### Actual behavior

`Swift/StringCharacterView.swift:158: Fatal error: String index is out of bounds`

### Steps To Reproduce

1. Clone https://github.com/apple/swift.git
2. Install a recent development snapshot of Swift, I'm using `swift-DEVELOPMENT-SNAPSHOT-2023-07-23-a`.
3. Run DocC from this snapshot this way (assuming `swift` is the clone from step 1):
```
docc preview --allow-arbitrary-catalog-directories swift/docs
```

### Swift-DocC Version Information

swift-DEVELOPMENT-SNAPSHOT-2023-07-23-a

### Swift Compiler Version Information

```shell
Apple Swift version 5.9-dev (LLVM 9b562f55c38e378, Swift b4ee68bd37c4f7d)
Target: arm64-apple-macosx14.0
```

Pull request description:
Bug/issue #, if applicable: 

Close #729 and rdar://79991171

## Summary

If the range’s source is not matched with the resolver’s source, we should not emit a problem

## Dependencies

None

## Testing

See `func testDuplicatedDiagnosticForExtensionFile()` in `MarkupReferenceResolverTests.swift`

## Checklist

Make sure you check off the following items. If they cannot be completed, provide a reason.

- [x] Added tests
- [x] Ran the `./bin/test` script and it succeeded
- [x] Updated documentation if necessary

Patch:
diff --git a/Sources/SwiftDocC/Semantics/MarkupReferenceResolver.swift b/Sources/SwiftDocC/Semantics/MarkupReferenceResolver.swift
index b60a37aae8..1dc257dc3d 100644
--- a/Sources/SwiftDocC/Semantics/MarkupReferenceResolver.swift
+++ b/Sources/SwiftDocC/Semantics/MarkupReferenceResolver.swift
@@ -80,6 +80,13 @@ struct MarkupReferenceResolver: MarkupRewriter {
             return resolved
             
         case .failure(let unresolved, let error):
+            if let rangeLowerBoundSource = range?.lowerBound.source,
+               let rangeUpperBoundSource = range?.upperBound.source,
+               let source = source,
+               source != rangeLowerBoundSource || source != rangeUpperBoundSource {
+                return nil
+            }
+
             if let callback = problemForUnresolvedReference,
                let problem = callback(unresolved, source, range, fromSymbolLink, error.message) {
                 problems.append(problem)


Test patch:
diff --git a/Tests/SwiftDocCTests/Semantics/MarkupReferenceResolverTests.swift b/Tests/SwiftDocCTests/Semantics/MarkupReferenceResolverTests.swift
index 7601c52974..f49860731c 100644
--- a/Tests/SwiftDocCTests/Semantics/MarkupReferenceResolverTests.swift
+++ b/Tests/SwiftDocCTests/Semantics/MarkupReferenceResolverTests.swift
@@ -1,7 +1,7 @@
 /*
  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-2023 Apple Inc. and the Swift project authors
  Licensed under Apache License v2.0 with Runtime Library Exception
 
  See https://swift.org/LICENSE.txt for license information
@@ -27,4 +27,14 @@ class MarkupReferenceResolverTests: XCTestCase {
         _ = resolver.visit(document)
         XCTAssertEqual(0, resolver.problems.count)
     }
+
+    func testDuplicatedDiagnosticForExtensionFile() throws {
+        let (_, context) = try testBundleAndContext(named: "ExtensionArticleBundle")
+        // Before #733, symbols with documentation extension files emitted duplicated problems:
+        // - one with a source location in the in-source documentation comment
+        // - one with a source location in the documentation extension file.
+        // The source range was only valid for one of these diagnostics. This resulted in an index out of range crash in DefaultDiagnosticConsoleFormatter when displaying line that caused the problem to the user
+        XCTAssertEqual(1, context.problems.count)
+        XCTAssertEqual("Server.md", context.problems.first?.diagnostic.source?.lastPathComponent)
+    }
 }
diff --git a/Tests/SwiftDocCTests/Test Bundles/ExtensionArticleBundle.docc/Server.md b/Tests/SwiftDocCTests/Test Bundles/ExtensionArticleBundle.docc/Server.md
new file mode 100644
index 0000000000..2d9f3382f8
--- /dev/null
+++ b/Tests/SwiftDocCTests/Test Bundles/ExtensionArticleBundle.docc/Server.md
@@ -0,0 +1,6 @@
+# ``Server``
+
+<!-- The correct signature is ``Server/echo(_:)``-->
+Check this awesome function ``Server/echo()``
+
+<!-- Copyright (c) 2023 Apple Inc and the Swift Project authors. All Rights Reserved. -->
diff --git a/Tests/SwiftDocCTests/Test Bundles/ExtensionArticleBundle.docc/ServerKit.symbols.json b/Tests/SwiftDocCTests/Test Bundles/ExtensionArticleBundle.docc/ServerKit.symbols.json
new file mode 100644
index 0000000000..c32bf91413
--- /dev/null
+++ b/Tests/SwiftDocCTests/Test Bundles/ExtensionArticleBundle.docc/ServerKit.symbols.json
@@ -0,0 +1,130 @@
+{
+    "metadata": {
+        "formatVersion": { "major": 0, "minor": 6, "patch": 0 },
+        "generator": "Apple Swift version 5.9 (swiftlang-5.9.0.128.108 clang-1500.0.40.1)"
+    },
+    "module": {
+        "name": "ServerKit",
+        "platform": {
+            "architecture": "arm64",
+            "vendor": "apple",
+            "operatingSystem": {
+                "name": "ios",
+                "minimumVersion": { "major": 12, "minor": 0 }
+            }
+        }
+    },
+    "symbols": [
+        {
+            "kind": {
+                "identifier": "swift.method",
+                "displayName": "Instance Method"
+            },
+            "identifier": {
+                "precise": "s:9ServerKit0A0C4echoyySSF",
+                "interfaceLanguage": "swift"
+            },
+            "pathComponents": ["Server", "echo(_:)"],
+            "names": {
+                "title": "echo(_:)",
+                "subHeading": [
+                    { "kind": "keyword", "spelling": "func" },
+                    { "kind": "text", "spelling": " " },
+                    { "kind": "identifier", "spelling": "echo" },
+                    { "kind": "text", "spelling": "(" },
+                    {
+                        "kind": "typeIdentifier",
+                        "spelling": "String",
+                        "preciseIdentifier": "s:SS"
+                    },
+                    { "kind": "text", "spelling": ")" }
+                ]
+            },
+            "functionSignature": {
+                "parameters": [
+                    {
+                        "name": "message",
+                        "declarationFragments": [
+                            { "kind": "identifier", "spelling": "message" },
+                            { "kind": "text", "spelling": ": " },
+                            {
+                                "kind": "typeIdentifier",
+                                "spelling": "String",
+                                "preciseIdentifier": "s:SS"
+                            }
+                        ]
+                    }
+                ],
+                "returns": [{ "kind": "text", "spelling": "()" }]
+            },
+            "declarationFragments": [
+                { "kind": "keyword", "spelling": "func" },
+                { "kind": "text", "spelling": " " },
+                { "kind": "identifier", "spelling": "echo" },
+                { "kind": "text", "spelling": "(" },
+                { "kind": "externalParam", "spelling": "_" },
+                { "kind": "text", "spelling": " " },
+                { "kind": "internalParam", "spelling": "message" },
+                { "kind": "text", "spelling": ": " },
+                {
+                    "kind": "typeIdentifier",
+                    "spelling": "String",
+                    "preciseIdentifier": "s:SS"
+                },
+                { "kind": "text", "spelling": ")" }
+            ],
+            "accessLevel": "public",
+            "location": {
+                "uri": "file:///tmp/Server.swift",
+                "position": { "line": 2, "character": 16 }
+            }
+        },
+        {
+            "kind": { "identifier": "swift.class", "displayName": "Class" },
+            "identifier": {
+                "precise": "s:9ServerKit0A0C",
+                "interfaceLanguage": "swift"
+            },
+            "pathComponents": ["Server"],
+            "names": {
+                "title": "Server",
+                "navigator": [{ "kind": "identifier", "spelling": "Server" }],
+                "subHeading": [
+                    { "kind": "keyword", "spelling": "class" },
+                    { "kind": "text", "spelling": " " },
+                    { "kind": "identifier", "spelling": "Server" }
+                ]
+            },
+            "docComment": {
+                "uri": "file:///tmp/Server.swift",
+                "module": "ServerKit",
+                "lines": [
+                    {
+                        "range": {
+                            "start": { "line": 0, "character": 4 },
+                            "end": { "line": 0, "character": 15 }
+                        },
+                        "text": "Hello World"
+                    }
+                ]
+            },
+            "declarationFragments": [
+                { "kind": "keyword", "spelling": "class" },
+                { "kind": "text", "spelling": " " },
+                { "kind": "identifier", "spelling": "Server" }
+            ],
+            "accessLevel": "public",
+            "location": {
+                "uri": "file:///tmp/Server.swift",
+                "position": { "line": 1, "character": 13 }
+            }
+        }
+    ],
+    "relationships": [
+        {
+            "kind": "memberOf",
+            "source": "s:9ServerKit0A0C4echoyySSF",
+            "target": "s:9ServerKit0A0C"
+        }
+    ]
+}