Files
sourcekit-lsp/Sources/SKTestSupport/CheckCoding.swift
Alex Hoppen b2c17c748d Add an extra percent encoding layer when encoding DocumentURIs to LSP requests
The URI standard RFC 3986 is ambiguous about whether percent encoding and their represented characters are considered equivalent. VS Code considers them equivalent and treats them the same:

```js
vscode.Uri.parse("x://a?b=xxxx%3Dyyyy").toString() -> 'x://a?b%3Dxxxx%3Dyyyy'
vscode.Uri.parse("x://a?b=xxxx%3Dyyyy").toString(/*skipEncoding=*/true) -> 'x://a?b=xxxx=yyyy'
```

This causes issues because SourceKit-LSP's macro expansion URLs encoded by URLComponents use `=` do denote the separation of a key and a value in the outer query. The value of the `parent` key may itself contain query items, which use the escaped form '%3D'. Simplified, such a URL may look like `scheme://host?parent=scheme://host?line%3D2`.

But after running this through VS Code's URI type `=` and `%3D` get canonicalized and are indistinguishable.

To avoid this ambiguity, always percent escape the characters we use to distinguish URL query parameters, producing the following URL: `scheme://host?parent%3Dscheme://host%3Fline%253D2`.
2024-08-19 21:41:50 -07:00

101 lines
3.4 KiB
Swift

//===----------------------------------------------------------------------===//
//
// This source file is part of the Swift.org open source project
//
// Copyright (c) 2014 - 2018 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
// See https://swift.org/CONTRIBUTORS.txt for the list of Swift project authors
//
//===----------------------------------------------------------------------===//
import Foundation
import XCTest
/// Checks the encoding of the given value against the given json string and verifies that decoding the json reproduces the original value (according to its equatable conformance).
///
/// - parameter value: The value to encode/decode.
/// - parameter json: The expected json encoding.
package func checkCoding<T: Codable & Equatable>(
_ value: T,
json: String,
file: StaticString = #filePath,
line: UInt = #line
) {
let encoder = JSONEncoder()
encoder.outputFormatting.insert(.prettyPrinted)
encoder.outputFormatting.insert(.sortedKeys)
let data = try! encoder.encode(WrapFragment(value: value))
let wrappedStr = String(data: data, encoding: .utf8)!
/// Strip off WrapFragment encoding `{"value":}` and extra indentation.
let pre = "{\n \"value\" : "
let suff = "\n}"
XCTAssert(wrappedStr.hasPrefix(pre))
XCTAssert(wrappedStr.hasSuffix(suff))
let str = String(wrappedStr.dropFirst(pre.count).dropLast(suff.count))
// Remove extra indentation
.replacingOccurrences(of: "\n ", with: "\n")
// Remove trailing whitespace to normalize between corelibs and Apple Foundation.
.trimmingTrailingWhitespace()
XCTAssertEqual(json, str, file: file, line: line)
let decoder = JSONDecoder()
XCTAssertNoThrow(
try {
let decodedValue = try decoder.decode(WrapFragment<T>.self, from: data).value
XCTAssertEqual(value, decodedValue, file: file, line: line)
}()
)
}
/// JSONEncoder requires the top-level value to be encoded as a JSON container (array or object). Give it one.
private struct WrapFragment<T>: Equatable, Codable where T: Equatable & Codable {
var value: T
}
/// Checks that decoding the given string is equal to the expected value.
///
/// - parameter value: The value to encode/decode.
/// - parameter json: The expected json encoding.
package func checkDecoding<T: Codable & Equatable>(
json: String,
expected value: T,
file: StaticString = #filePath,
line: UInt = #line
) {
let wrappedStr = "{\"value\":\(json)}"
let data = wrappedStr.data(using: .utf8)!
let decoder = JSONDecoder()
let decodedValue = try! decoder.decode(WrapFragment<T>.self, from: data).value
XCTAssertEqual(value, decodedValue, file: file, line: line)
}
package func checkCoding<T>(
_ value: T,
json: String,
userInfo: [CodingUserInfoKey: Any] = [:],
file: StaticString = #filePath,
line: UInt = #line,
body: (T) -> Void
) where T: Codable {
let encoder = JSONEncoder()
encoder.outputFormatting.insert(.prettyPrinted)
encoder.outputFormatting.insert(.sortedKeys)
let data = try! encoder.encode(value)
let str = String(data: data, encoding: .utf8)!
// Remove trailing whitespace to normalize between corelibs and Apple Foundation.
.trimmingTrailingWhitespace()
XCTAssertEqual(json, str, file: file, line: line)
let decoder = JSONDecoder()
decoder.userInfo = userInfo
let decodedValue = try! decoder.decode(T.self, from: data)
body(decodedValue)
}