String.addingPercentEncoding(withAllowedCharacters:) の引数

[String.addingPercentEncoding(withAllowedCharacters:)](https://developer.apple.com/documentation/foundation/nsstring/1411946-addingpercentencoding) が何をエンコードするか気になったので調べてみました。
ついでに [URLComponents](https://developer.apple.com/documentation/foundation/urlcomponents) の queryItems の value 部分と、JavaScriptCore の [encodeURI](https://developer.mozilla.org/ja/docs/Web/JavaScript/Reference/Global_Objects/encodeURI) および [encodeURIComponent](https://developer.mozilla.org/ja/docs/Web/JavaScript/Reference/Global_Objects/encodeURIComponent) とも比較してみました。悩ましい。
ちなみに、記号は全部エンコードしちゃおうと思って [.alphanumerics](https://developer.apple.com/documentation/foundation/characterset/1779632-alphanumerics) を使うと、アクセント符号付きのアルファベット (é など) やいわゆる全角英数字がエンコードされないという罠があります。
 
|CharacterSet| |!|"|#|$|%|&|'|(|)|*|+|,|-|.|/|:|;|<|=|>|?|@|[|\|]|^|_|`|{|&#124;|}|~|
|:--|:-:|:-:|:-:|:-:|:-:|:-:|:-:|:-:|:-:|:-:|:-:|:-:|:-:|:-:|:-:|:-:|:-:|:-:|:-:|:-:|:-:|:-:|:-:|:-:|:-:|:-:|:-:|:-:|:-:|:-:|:-:|:-:|:-:|
|urlFragmentAllowed|✅||✅|✅||✅|||||||||||||✅||✅|||✅|✅|✅|✅||✅|✅|✅|✅||
|urlHostAllowed|✅||✅|✅||✅||||||||||✅|||✅||✅|✅|✅||✅||✅||✅|✅|✅|✅||
|urlPasswordAllowed|✅||✅|✅||✅||||||||||✅|✅||✅||✅|✅|✅|✅|✅|✅|✅||✅|✅|✅|✅||
|urlPathAllowed|✅||✅|✅||✅||||||||||||✅|✅||✅|✅||✅|✅|✅|✅||✅|✅|✅|✅||
|urlQueryAllowed|✅||✅|✅||✅|||||||||||||✅||✅|||✅|✅|✅|✅||✅|✅|✅|✅||
|urlUserAllowed|✅||✅|✅||✅||||||||||✅|✅||✅||✅|✅|✅|✅|✅|✅|✅||✅|✅|✅|✅||
|URLComponents|✅||✅|✅||✅|✅||||||||||||✅|✅|✅|||✅|✅|✅|✅||✅|✅|✅|✅||
|encodeURI|✅||✅|||✅|||||||||||||✅||✅|||✅|✅|✅|✅||✅|✅|✅|✅||
|encodeURIComponent|✅||✅|✅|✅|✅|✅|||||✅|✅|||✅|✅|✅|✅|✅|✅|✅|✅|✅|✅|✅|✅||✅|✅|✅|✅||

```swift
import Foundation
import JavaScriptCore

let asciiSymbols = (0x20...0x7e)
    .map { Unicode.Scalar($0) }
    .filter { !CharacterSet.alphanumerics.contains($0) }

let allowdChars = { (str: String) -> String in
    let regexp = try! NSRegularExpression(pattern: "%[0-9A-F][0-9A-F]")
    let range = NSRange(str.startIndex..., in: str)
    return regexp.stringByReplacingMatches(in: str, range: range, withTemplate: "")
}

let urlComponents = { () -> String in
    var comp = URLComponents(string: "https://example.com/")!
    comp.queryItems = [URLQueryItem(name: "q", value: asciiSymbols.map { String($0) }.joined())]
    return allowdChars(comp.url!.absoluteString.components(separatedBy: "=")[1])
}()

let context = JSContext()!
context.evaluateScript("var ascii = ''; for (var i = 0x20; i < 0x7f; i++) ascii += String.fromCharCode(i)")
context.evaluateScript("var result = encodeURI(ascii)")
let encodeURI = allowdChars(context.objectForKeyedSubscript("result")!.toString()!)
context.evaluateScript("var result = encodeURIComponent(ascii)")
let encodeURIComponent = allowdChars(context.objectForKeyedSubscript("result")!.toString()!)

let charsets: [(String, CharacterSet)] = [
    ("urlFragmentAllowed", .urlFragmentAllowed),
    ("urlHostAllowed",     .urlHostAllowed),
    ("urlPasswordAllowed", .urlPasswordAllowed),
    ("urlPathAllowed",     .urlPathAllowed),
    ("urlQueryAllowed",    .urlQueryAllowed),
    ("urlUserAllowed",     .urlUserAllowed),
    ("URLComponents",      CharacterSet(charactersIn: urlComponents)),
    ("encodeURI",          CharacterSet(charactersIn: encodeURI)),
    ("encodeURIComponent", CharacterSet(charactersIn: encodeURIComponent)),
]
print("|CharacterSet|" + asciiSymbols.map { String($0) }.map { ($0 == "|" ? "&#124;" : $0) + "|" }.joined())
print("|:--|" + asciiSymbols.map { _ in ":-:|" }.joined())
for cs in charsets {
    print("|\(cs.0)|" + asciiSymbols.map { (cs.1.contains($0) ? "" : "✅") + "|" }.joined())
}
```