-
Notifications
You must be signed in to change notification settings - Fork 304
Add a syntactic "Add Codable structs from JSON" code action #1205
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Conversation
@swift-ci please test |
@swift-ci please test Windows |
1 similar comment
@swift-ci please test Windows |
@swift-ci please test Linux |
1 similar comment
@swift-ci please test Linux |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Copied a few review comments from #1169 😉
Sources/SourceKitLSP/Swift/CodeActions/ConvertJSONToCodableStruct.swift
Outdated
Show resolved
Hide resolved
Sources/SourceKitLSP/Swift/CodeActions/ConvertJSONToCodableStruct.swift
Outdated
Show resolved
Hide resolved
Sources/SourceKitLSP/Swift/CodeActions/ConvertJSONToCodableStruct.swift
Outdated
Show resolved
Hide resolved
} | ||
|
||
extension ConvertJSONToCodableStruct { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Let’s just shove them all together instead of having extension
s of the same type within the same file.
} | |
extension ConvertJSONToCodableStruct { |
Same once more a few lines below.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I want the protocol-conforming extensions separate, but other than that---sure
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is no longer a protocol-conforming extension 😜 Also, I’m not generally a fan of putting protocol conformances in extensions. I think it makes it harder to reason about which protocols a type conforms to.
Sources/SourceKitLSP/Swift/CodeActions/ConvertJSONToCodableStruct.swift
Outdated
Show resolved
Hide resolved
} | ||
|
||
extension Array where Element == Any { | ||
fileprivate func unwrap(_ depth: Int) -> Dictionary<String, Any>? { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Could you add a comment of what unwrap
does?
let uri = DocumentURI.for(.swift) | ||
let positions = testClient.openDocument( | ||
""" | ||
1️⃣{ |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nitpick
1️⃣{ | |
1️⃣{ |
Also, 2 spaces indentation would be nice here for consistency.
public func assertStringsEqualWithDiff( | ||
_ actual: String, | ||
_ expected: String, | ||
_ message: String = "", | ||
additionalInfo: @autoclosure () -> String? = nil, | ||
file: StaticString = #filePath, | ||
line: UInt = #line | ||
) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If we’re adding the string diff functions, could you put them in SKTestSupport
so that other tests can also use them?
|
||
// Use `CollectionDifference` on supported platforms to get `diff`-like line-based output. On | ||
// older platforms, fall back to simple string comparison. | ||
if #available(macOS 10.15, *) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The #available
check isn’t needed since sourcekit-lsp targets macOS 13.
Add a syntactic action that takes JSON pasted into a Swift file or placed in a string literal, then turns it into a set of Codable structs that can represent the JSON. Our typical example starts like this: ``` { "name": "Produce", "shelves": [ { "name": "Discount Produce", "product": { "name": "Banana", "points": 200, "description": "A banana that's perfectly ripe." } } ] } ``` and turns into this: ```swift struct JSONValue: Codable { var name: String var shelves: [Shelves] struct Shelves: Codable { var name: String var product: Product struct Product: Codable { var description: String var name: String var points: Double } } } ``` When converting to JSON, we attempt to reason about multiple JSON objects on the same level to detect when there are optional fields, due to either an explicit null or due to the absence of fields in some of the JSON objects that are conceptually stored together. The refactoring itself would live down in the swift-syntax package if not for its dependency on Foundation. We'll move it when appropriate.
f1566e5
to
e54c99e
Compare
@swift-ci please test |
@swift-ci please test Windows |
I just went ahead and rewrote this with a completely new structure that does merging of like elements in the whole tree. When merging a field w/ a missing value (or one explicitly marked |
@swift-ci please test |
@swift-ci please test Windows |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
That’s a lot shorter and simpler and even implements merging of structs 🤩
I have a few, mostly nitpicky, comments.
Sources/SourceKitLSP/Swift/CodeActions/ConvertJSONToCodableStruct.swift
Outdated
Show resolved
Hide resolved
""" | ||
1️⃣{ | ||
"name": "Produce", | ||
"shelves": [ |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think right now it’s only possible to invoke the refactoring from the opening {
. I think it would be a lot more discoverable if you could invoke it from anywhere within the JSON.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
No, the refactoring can be invoked from elsewhere in the closure. The test harness doesn't make it easy to check for places other than the start, though.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hmm, I see what's going on. The parser isn't treating the whole blob as a single closure, so we sometimes end up in the "unexpected" area. I don't have a great fix at the moment.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Could you file an issue for that?
Sources/SourceKitLSP/Swift/CodeActions/ConvertJSONToCodableStruct.swift
Outdated
Show resolved
Hide resolved
Sources/SourceKitLSP/Swift/CodeActions/ConvertJSONToCodableStruct.swift
Outdated
Show resolved
Hide resolved
} | ||
|
||
extension ConvertJSONToCodableStruct { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is no longer a protocol-conforming extension 😜 Also, I’m not generally a fan of putting protocol conformances in extensions. I think it makes it harder to reason about which protocols a type conforms to.
Sources/SourceKitLSP/Swift/CodeActions/ConvertJSONToCodableStruct.swift
Outdated
Show resolved
Hide resolved
init(value: Any) { | ||
switch value { | ||
case let string as String: | ||
if string == "true" || string == "false" { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Interesting that JSONSerialization
doesn’t deserialize those to an NSNumber
. That does make me wonder: Does JSONSerialization
deserialize "null"
as NSNull
or also as a string with value null
? I don’t think we’ve got a test for that.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It leaves them all as strings. A "null"
is a dreadful thing because it messes with the merging logic.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is there any case that does produce an NSNull
then?
// Print any nested types. | ||
for (typeName, object) in nestedTypes { | ||
MemberBlockItemSyntax( | ||
leadingTrivia: (typeName == nestedTypes.first?.0) ? .newlines(2) : .newline, | ||
decl: object.asDeclSyntax(name: typeName) | ||
) | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I know this is a subjective opinion, but I would always put the nested types before the members.
Sources/SourceKitLSP/Swift/CodeActions/ConvertJSONToCodableStruct.swift
Outdated
Show resolved
Hide resolved
@swift-ci please test |
@swift-ci please test Windows |
Add a syntactic action that takes JSON pasted into a Swift file or
placed in a string literal, then turns it into a set of Codable
structs that can represent the JSON. Our typical example starts like
this:
and turns into this:
The refactoring itself would live down in the swift-syntax package if
not for its dependency on Foundation. We'll move as appropriate.