Repository: apple/swift-docc (swift)

Issue:
Article automatic curation is non deterministic
Articles get automatically curated in a non-deterministic order when building a docc archive without a top level extension file.

### 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
Curation order should be deterministic. When viewing a navigator built from such and archive I expect it to always have the same node ordering.

### Actual behavior
`FirstCurationRoot` and `SecondCurationRoot` get swapped non-deterministically.
[MultiCuratedSubtree.docc.zip](https://github.com/apple/swift-docc/files/8880683/MultiCuratedSubtree.docc.zip)


### Steps to Reproduce
Run `docc convert` on the provided catalogue

### Swift-DocC Version Information

**Swift-DocC version:** built from main
**Swift Compiler version:** N/A

Pull request:
rdar: //81691423

## Summary

Currently the behavior is not deterministic when we have multiple files with same names, whether it be tutorial or markdown files having same names. Whenever there are two or more files with same name for a type in catalog only one is picked up and rest gets Diagnostics information that its content is being dropped. The diagnostic information existed but it was always picking one of the files not in a specific order, i.e everytime it was picking a random file. The change addresses the issue by adding a sort to the url of the document, so that the behavior is deterministic, i.e the order in which a file is picked, as well as the rest who's content is ignored. 

## Dependencies

No Known dependencies 

## Testing

Steps:
1. Create a project in Xcode, it can be a swift project
2.  Create .docc folder in that.                                                       
3.  have an overview.md at .docc level, i/e /docc/overview.md
4.  also save the same file at /documentation1/overview.md and /documentation2/overview.md 
5. update the contents of documentation1/overview.md and documentation1/overview.md
6. Build documentation.


- [yes ] Added tests
- [yes ] Ran the `./bin/test` script and it succeeded
- [na ] Updated documentation if necessary

Patch:
diff --git a/Sources/SwiftDocC/Infrastructure/DocumentationContext.swift b/Sources/SwiftDocC/Infrastructure/DocumentationContext.swift
index da69f2b4db..820dde5738 100644
--- a/Sources/SwiftDocC/Infrastructure/DocumentationContext.swift
+++ b/Sources/SwiftDocC/Infrastructure/DocumentationContext.swift
@@ -823,7 +823,10 @@ public class DocumentationContext: DocumentationContextDataProviderDelegate {
             throw error
         }
         
-        for analyzedDocument in analyzedDocuments {
+        // to preserve the order of documents by url
+        let analyzedDocumentsSorted = analyzedDocuments.sorted(by: \.0.absoluteString)
+
+        for analyzedDocument in analyzedDocumentsSorted {
             // Store the references we encounter to ensure they're unique. The file name is currently the only part of the URL considered for the topic reference, so collisions may occur.
             let (url, analyzed) = analyzedDocument
 


Test patch:
diff --git a/Tests/SwiftDocCTests/Infrastructure/DocumentationContext/DocumentationContextTests.swift b/Tests/SwiftDocCTests/Infrastructure/DocumentationContext/DocumentationContextTests.swift
index c37bde81cd..eda5edaf7f 100644
--- a/Tests/SwiftDocCTests/Infrastructure/DocumentationContext/DocumentationContextTests.swift
+++ b/Tests/SwiftDocCTests/Infrastructure/DocumentationContext/DocumentationContextTests.swift
@@ -644,15 +644,30 @@ class DocumentationContextTests: XCTestCase {
     
     func testDetectsReferenceCollision() throws {
         let (_, context) = try testBundleAndContext(named: "TestBundleWithDupe")
-        
-        XCTAssert(
-            context.problems.contains { problem in
-                problem.diagnostic.identifier == "org.swift.docc.DuplicateReference"
-                    && problem.diagnostic.localizedSummary == "Redeclaration of 'TestTutorial.tutorial'; this file will be skipped"
-            }
-        )
+
+        let problemWithDuplicate = context.problems.filter { $0.diagnostic.identifier == "org.swift.docc.DuplicateReference" }
+
+        XCTAssertEqual(problemWithDuplicate.count, 1)
+
+        let localizedSummary = try XCTUnwrap(problemWithDuplicate.first?.diagnostic.localizedSummary)
+        XCTAssertEqual(localizedSummary, "Redeclaration of 'TestTutorial.tutorial'; this file will be skipped")
+
     }
     
+    func testDetectsMultipleMDfilesWithSameName() throws {
+        let (_, context) = try testBundleAndContext(named: "TestBundleWithDupMD")
+
+        let problemWithDuplicateReference = context.problems.filter { $0.diagnostic.identifier == "org.swift.docc.DuplicateReference" }
+
+        XCTAssertEqual(problemWithDuplicateReference.count, 2)
+
+        let localizedSummary = try XCTUnwrap(problemWithDuplicateReference.first?.diagnostic.localizedSummary)
+        XCTAssertEqual(localizedSummary, "Redeclaration of \'overview.md\'; this file will be skipped")
+
+        let localizedSummarySecond = try XCTUnwrap(problemWithDuplicateReference[1].diagnostic.localizedSummary)
+        XCTAssertEqual(localizedSummarySecond, "Redeclaration of \'overview.md\'; this file will be skipped")
+    }
+
     func testGraphChecks() throws {
         let workspace = DocumentationWorkspace()
         let context = try DocumentationContext(dataProvider: workspace)
diff --git a/Tests/SwiftDocCTests/Test Bundles/TestBundleWithDupMD.docc/Info.plist b/Tests/SwiftDocCTests/Test Bundles/TestBundleWithDupMD.docc/Info.plist
new file mode 100644
index 0000000000..3042895bd1
--- /dev/null
+++ b/Tests/SwiftDocCTests/Test Bundles/TestBundleWithDupMD.docc/Info.plist
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
+<plist version="1.0">
+<dict>
+	<key>CFBundleName</key>
+	<string>TestBundleWithDupMD</string>
+	<key>CFBundleDisplayName</key>
+	<string>Test Bundle</string>
+	<key>CFBundleIdentifier</key>
+	<string>org.swift.docc.example</string>
+	<key>CFBundleIconFile</key>
+	<string>DocumentationIcon</string>
+	<key>CFBundleIconName</key>
+	<string>DocumentationIcon</string>
+	<key>CFBundlePackageType</key>
+	<string>DOCS</string>
+	<key>CFBundleSignature</key>
+	<string>????</string>
+	<key>CFBundleVersion</key>
+	<string>0.1.0</string>
+	<key>CFBundleShortVersionString</key>
+	<string>0.1.0</string>
+</dict>
+</plist>
diff --git a/Tests/SwiftDocCTests/Test Bundles/TestBundleWithDupMD.docc/documentation/overview.md b/Tests/SwiftDocCTests/Test Bundles/TestBundleWithDupMD.docc/documentation/overview.md
new file mode 100644
index 0000000000..830dec18fd
--- /dev/null
+++ b/Tests/SwiftDocCTests/Test Bundles/TestBundleWithDupMD.docc/documentation/overview.md
@@ -0,0 +1,9 @@
+# Getting Started with Capybara
+
+Create a capybara and assign personality traits and abilities.
+
+## Overview at documentation level
+
+Capybara are complex creatures that require careful creation and a suitable habitat. After creating a capybara, you're responsible for feeding them, providing fulfilling activities, and giving them opportunities to exercise and rest.
+
+<!-- Copyright (c) 2022 Apple Inc and the Swift Project authors. All Rights Reserved. -->
diff --git a/Tests/SwiftDocCTests/Test Bundles/TestBundleWithDupMD.docc/documentation1/overview.md b/Tests/SwiftDocCTests/Test Bundles/TestBundleWithDupMD.docc/documentation1/overview.md
new file mode 100644
index 0000000000..49f97864f8
--- /dev/null
+++ b/Tests/SwiftDocCTests/Test Bundles/TestBundleWithDupMD.docc/documentation1/overview.md
@@ -0,0 +1,9 @@
+# Getting Started with Monkeys
+
+Create a monkey and assign personality traits and abilities.
+
+## Overview at documentation1
+
+Monekys are complex creatures that require careful creation and a suitable habitat. After creating a sloth, you're responsible for feeding them, providing fulfilling activities, and giving them opportunities to exercise and rest. 
+
+<!-- Copyright (c) 2022 Apple Inc and the Swift Project authors. All Rights Reserved. -->
diff --git a/Tests/SwiftDocCTests/Test Bundles/TestBundleWithDupMD.docc/overview.md b/Tests/SwiftDocCTests/Test Bundles/TestBundleWithDupMD.docc/overview.md
new file mode 100644
index 0000000000..aeb88182b4
--- /dev/null
+++ b/Tests/SwiftDocCTests/Test Bundles/TestBundleWithDupMD.docc/overview.md
@@ -0,0 +1,9 @@
+# Getting Started with Monkeys
+
+Create a monkey and assign personality traits and abilities.
+
+## Overview at parent level
+
+Monkeys are complex creatures that require careful creation and a suitable habitat. After creating a sloth, you're responsible for feeding them, providing fulfilling activities, and giving them opportunities to exercise and rest. 
+
+<!-- Copyright (c) 2022 Apple Inc and the Swift Project authors. All Rights Reserved. -->