Repository: CreateAPI/CreateAPI (swift)

Issue:
Simplify `acronym` related properties
Currently there are four things related to acronyms:

1. The default list (`["url", "id", "html", "ssl", "tls", "https", "http", "dns", "ftp", "api", "uuid", "json"]`)
2. `isReplacingCommonAcronyms` (defaults to `true`)
3. `addedAcronyms`
4. `ignoredAcronyms`

Its done like this to include a default set of acronyms, and then allow customisation by either disabling replacement, ignoring specific or adding new ones.

After thinking it through some more, this could be simplified down to a single configuration option:

```swift
/// A set of acronyms that should be uppercased if detected.
///
/// If you don't want to uppercase any acronyms, set this property to an empty array.
public var uppercaseAcronyms: [String] = ["url", "id", "html", "ssl", "tls", "https", "http", "dns", "ftp", "api", "uuid", "json"]
```

We provide (and document) the default set, so if the user wants to add to it, they can incorporate the set into their configuration file. 

If the user doesn't want to uppercase acronyms, they can set the property to an empty array. 

I can't see any reason not to do this?

Pull request:
- Closes #97 

Prior to this change, there were three different options for managing acronyms. One to enable/disable and another two to add/ignore to/from the default list. 

In this change, I consolidate these three options into a single `acronyms` option. To disable replacement, you set it to an empty array but to modify it, you just maintain complete list yourself.

Patch:
diff --git a/Docs/ConfigOptions.md b/Docs/ConfigOptions.md
index 253f41ee..365ddf69 100644
--- a/Docs/ConfigOptions.md
+++ b/Docs/ConfigOptions.md
@@ -48,9 +48,7 @@ Below you can find the complete documentation for all available options.
 - [generateEnums](#generateenums)
 - [useSwiftyPropertyNames](#useswiftypropertynames)
 - [inlineTypealiases](#inlinetypealiases)
-- [isReplacingCommonAcronyms](#isreplacingcommonacronyms)
-- [addedAcronyms](#addedacronyms)
-- [ignoredAcronyms](#ignoredacronyms)
+- [acronyms](#acronyms)
 - [indentation](#indentation)
 - [spaceWidth](#spacewidth)
 - [pluralizeProperties](#pluralizeproperties)
@@ -149,30 +147,58 @@ For example, `typealias Pets = [Pet]` is inlined as `[Pet]`.
 
 <br/>
 
-## isReplacingCommonAcronyms
+## acronyms
 
-**Type:** Bool<br />
-**Default:** `true`
+**Type:** [String]<br />
+**Default:** `["url", "id", "html", "ssl", "tls", "https", "http", "dns", "ftp", "api", "uuid", "json"]`
 
-For example, `var sourceUrl` becomes `var sourceURL`.
+A list of acronyms that should be uppercased when present in property names.
 
-<br/>
+To disable uppercasing of acronyms, set this property to an empty array.
 
-## addedAcronyms
+<details>
+<summary>Examples</summary>
 
-**Type:** [String]<br />
-**Default:** `[]`
+With the given schema:
 
-Acronyms to add to the default list
+```yaml
+type: object
+properties:
+  user_id:
+    type: integer
+  image_url:
+    type: string
+    format: uri
+  acme_corporation:
+    type: boolean
+```
 
-<br/>
+**No Acronyms**
+```yaml
+acronyms: []
+```
 
-## ignoredAcronyms
+```swift
+var userId: Int
+var imageUrl: URL
+var isAcmeCorporation: Bool
+```
 
-**Type:** [String]<br />
-**Default:** `[]`
+**Custom Acronyms**
+```yaml
+acronyms:
+- id
+- url
+- acme
+```
 
-Acronyms to remove from the default list
+```swift
+var userID: Int
+var imageURL: URL
+var isACMECorporation: Bool
+```
+
+</details>
 
 <br/>
 
diff --git a/Sources/CreateAPI/Generator/Naming.swift b/Sources/CreateAPI/Generator/Naming.swift
index 3044462f..81b73043 100644
--- a/Sources/CreateAPI/Generator/Naming.swift
+++ b/Sources/CreateAPI/Generator/Naming.swift
@@ -57,7 +57,7 @@ struct PropertyName: CustomStringConvertible, Hashable, DeclarationName {
             return self
         }
         let first = words[0]
-        if options.allAcronyms.contains(first.lowercased()) {
+        if options.acronyms.contains(first.lowercased()) {
             string.removeFirst(first.count)
             string = first.uppercased() + string
         }
@@ -172,8 +172,8 @@ extension String {
         // Replace abbreviations (but only at code boundries)
         // WARNING: Depends on isProperty and first lowercase letter (implementation detail)
         // TODO: Refactor
-        if options.isReplacingCommonAcronyms {
-            for acronym in options.allAcronyms {
+        if !options.acronyms.isEmpty {
+            for acronym in options.acronyms {
                 if let range = output.range(of: acronym.capitalizingFirstLetter()),
                    (range.upperBound == output.endIndex || output[range.upperBound].isUppercase || output[range.upperBound] == "s") {
                     output.replaceSubrange(range, with: acronym.uppercased())
diff --git a/Sources/CreateOptions/ConfigOptions+DecodableWithDefault.swift b/Sources/CreateOptions/ConfigOptions+DecodableWithDefault.swift
index 7e0c32dc..eb744e9e 100644
--- a/Sources/CreateOptions/ConfigOptions+DecodableWithDefault.swift
+++ b/Sources/CreateOptions/ConfigOptions+DecodableWithDefault.swift
@@ -10,9 +10,7 @@ extension ConfigOptions: Decodable {
         case generateEnums
         case useSwiftyPropertyNames
         case inlineTypealiases
-        case isReplacingCommonAcronyms
-        case addedAcronyms
-        case ignoredAcronyms
+        case acronyms
         case indentation
         case spaceWidth
         case pluralizeProperties
@@ -53,19 +51,9 @@ extension ConfigOptions: Decodable {
             defaultValue: true
         )
 
-        isReplacingCommonAcronyms = try container.decode(Bool.self,
-            forKey: .isReplacingCommonAcronyms,
-            defaultValue: true
-        )
-
-        addedAcronyms = try container.decode([String].self,
-            forKey: .addedAcronyms,
-            defaultValue: []
-        )
-
-        ignoredAcronyms = try container.decode([String].self,
-            forKey: .ignoredAcronyms,
-            defaultValue: []
+        acronyms = try container.decode([String].self,
+            forKey: .acronyms,
+            defaultValue: ["url", "id", "html", "ssl", "tls", "https", "http", "dns", "ftp", "api", "uuid", "json"]
         )
 
         indentation = try container.decode(ConfigOptions.Indentation.self,
@@ -130,6 +118,9 @@ extension ConfigOptions: Decodable {
                 ("isNaiveDateEnabled", "Use 'useNaiveDate' instead."),
                 ("isUsingIntegersWithPredefinedCapacity", "Use 'useIntegersWithPredefinedCapacity' instead."),
                 ("comments", "Use 'commentOptions' instead."),
+                ("addedAcronyms", "Replaced by 'acronyms'."),
+                ("ignoredAcronyms", "Replaced by 'acronyms'."),
+                ("isReplacingCommonAcronyms", "Replaced by 'acronyms'."),
                 ("isSwiftLintDisabled", "Add to 'fileHeaderComment' instead."),
             ]
         )
diff --git a/Sources/CreateOptions/ConfigOptions.swift b/Sources/CreateOptions/ConfigOptions.swift
index e3c38d4b..8685f78d 100644
--- a/Sources/CreateOptions/ConfigOptions.swift
+++ b/Sources/CreateOptions/ConfigOptions.swift
@@ -22,6 +22,9 @@ import Foundation
 
 // sourcery: document, root, decodableWithDefault
 // sourcery: removed: isSwiftLintDisabled = "Add to 'fileHeaderComment' instead."
+// sourcery: removed: isReplacingCommonAcronyms = "Replaced by 'acronyms'."
+// sourcery: removed: addedAcronyms = "Replaced by 'acronyms'."
+// sourcery: removed: ignoredAcronyms = "Replaced by 'acronyms'."
 /// CreateAPI supports a massive number of customization options to generate the most appropriate source code for your api.
 ///
 /// To use these options, you must define a configuration file that includes these properties. This can be done using either YAML or JSON, for example:
@@ -79,14 +82,54 @@ public struct ConfigOptions: Encodable {
     /// For example, `typealias Pets = [Pet]` is inlined as `[Pet]`.
     public var inlineTypealiases: Bool = true // sourcery: replacementFor = isInliningTypealiases
 
-    /// For example, `var sourceUrl` becomes `var sourceURL`.
-    public var isReplacingCommonAcronyms: Bool = true
-
-    /// Acronyms to add to the default list
-    public var addedAcronyms: [String] = []
-
-    /// Acronyms to remove from the default list
-    public var ignoredAcronyms: [String] = []
+    /// A list of acronyms that should be uppercased when present in property names.
+    ///
+    /// To disable uppercasing of acronyms, set this property to an empty array.
+    ///
+    /// <details>
+    /// <summary>Examples</summary>
+    ///
+    /// With the given schema:
+    ///
+    /// ```yaml
+    /// type: object
+    /// properties:
+    ///   user_id:
+    ///     type: integer
+    ///   image_url:
+    ///     type: string
+    ///     format: uri
+    ///   acme_corporation:
+    ///     type: boolean
+    /// ```
+    ///
+    /// **No Acronyms**
+    /// ```yaml
+    /// acronyms: []
+    /// ```
+    ///
+    /// ```swift
+    /// var userId: Int
+    /// var imageUrl: URL
+    /// var isAcmeCorporation: Bool
+    /// ```
+    ///
+    /// **Custom Acronyms**
+    /// ```yaml
+    /// acronyms:
+    /// - id
+    /// - url
+    /// - acme
+    /// ```
+    ///
+    /// ```swift
+    /// var userID: Int
+    /// var imageURL: URL
+    /// var isACMECorporation: Bool
+    /// ```
+    ///
+    /// </details>
+    public var acronyms: [String] = ["url", "id", "html", "ssl", "tls", "https", "http", "dns", "ftp", "api", "uuid", "json"]
 
     /// Available levels of indentation
     public enum Indentation: String, Codable {
diff --git a/Sources/CreateOptions/GenerateOptions.swift b/Sources/CreateOptions/GenerateOptions.swift
index a6c34aba..2807e624 100644
--- a/Sources/CreateOptions/GenerateOptions.swift
+++ b/Sources/CreateOptions/GenerateOptions.swift
@@ -6,12 +6,6 @@ public final class GenerateOptions {
     /// The options loaded from a **.create-api.yaml** configuration file (or the default options)
     public let configOptions: ConfigOptions
 
-    /// Acronyms used for replacement when `isReplacingCommonAcronyms` is `true`.
-    ///
-    /// A set of all acronyms based on the default list after factoring in the `addedAcronyms` and removing `ignoredAcronyms`.
-    /// Results are ordered so that the longer acronyms come first.
-    public let allAcronyms: [String]
-
     /// Warnings detected when loading a configuration file
     public let warnings: [String]
 
@@ -19,8 +13,10 @@ public final class GenerateOptions {
     public static let `default` = GenerateOptions()
 
     init(configOptions: ConfigOptions = .default, warnings: [String] = []) {
+        var configOptions = configOptions
+        configOptions.acronyms.sort { $0.count > $1.count } // important that longest is found first when searching
+
         self.configOptions = configOptions
-        self.allAcronyms = Self.allAcronyms(including: configOptions.addedAcronyms, excluding: configOptions.ignoredAcronyms)
         self.warnings = warnings
     }
 
@@ -51,15 +47,3 @@ public extension GenerateOptions {
         self.init(configOptions: configOptions, warnings: recorder.issues.map(\.description))
     }
 }
-
-// MARK: - Acronyms
-private extension GenerateOptions {
-    static let defaultAcronyms: Set<String> = ["url", "id", "html", "ssl", "tls", "https", "http", "dns", "ftp", "api", "uuid", "json"]
-
-    static func allAcronyms(including: [String], excluding: [String]) -> [String] {
-        Self.defaultAcronyms
-            .union(including)
-            .subtracting(excluding)
-            .sorted { $0.count > $1.count }
-    }
-}


Test patch:
diff --git a/Tests/CreateAPITests/GenerateOptionsTests.swift b/Tests/CreateAPITests/GenerateOptionsTests.swift
index 0fb83d54..d6c9a04c 100644
--- a/Tests/CreateAPITests/GenerateOptionsTests.swift
+++ b/Tests/CreateAPITests/GenerateOptionsTests.swift
@@ -459,7 +459,7 @@ final class GenerateOptionsTests: GenerateBaseTests {
             "--package", "edgecases-disable-acronyms",
             "--config", config("""
             {
-                "isReplacingCommonAcronyms": false
+                "acronyms": []
             }
             """)
         ])
diff --git a/Tests/CreateAPITests/HelpersTests.swift b/Tests/CreateAPITests/HelpersTests.swift
index 508e5316..1a2dbe7d 100644
--- a/Tests/CreateAPITests/HelpersTests.swift
+++ b/Tests/CreateAPITests/HelpersTests.swift
@@ -43,7 +43,9 @@ final class HelpersTests: XCTestCase {
         
         // Additional acronyms
         do {
-            let options = GenerateOptions(configOptions: ConfigOptions(addedAcronyms: ["nft"]))
+            var configOptions = ConfigOptions.default
+            configOptions.acronyms.append("nft")
+            let options = GenerateOptions(configOptions: configOptions)
             XCTAssertEqual(TypeName(processing: "myNft", options: options).rawValue, "MyNFT")
         }