Skip to content

Commit

Permalink
fix: Escape deprecation messages (#2951)
Browse files Browse the repository at this point in the history
  • Loading branch information
calvincestari authored Apr 12, 2023
1 parent 3aa8812 commit 58f6d3f
Show file tree
Hide file tree
Showing 7 changed files with 473 additions and 131 deletions.
12 changes: 8 additions & 4 deletions Apollo.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
19E9F6AC26D58A9A003AB80E /* OperationMessageIdCreatorTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 19E9F6AA26D58A92003AB80E /* OperationMessageIdCreatorTests.swift */; };
19E9F6B526D6BF25003AB80E /* OperationMessageIdCreator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 19E9F6A826D5867E003AB80E /* OperationMessageIdCreator.swift */; };
2EE7FFD0276802E30035DC39 /* CacheKeyConstructionTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2EE7FFCF276802E30035DC39 /* CacheKeyConstructionTests.swift */; };
534A754528EB21D6003291BE /* TemplateString+AvailabilityDeprecated.swift in Sources */ = {isa = PBXBuildFile; fileRef = 534A754428EB21D6003291BE /* TemplateString+AvailabilityDeprecated.swift */; };
534A754528EB21D6003291BE /* TemplateString+DeprecationMessage.swift in Sources */ = {isa = PBXBuildFile; fileRef = 534A754428EB21D6003291BE /* TemplateString+DeprecationMessage.swift */; };
54DDB0921EA045870009DD99 /* InMemoryNormalizedCache.swift in Sources */ = {isa = PBXBuildFile; fileRef = 54DDB0911EA045870009DD99 /* InMemoryNormalizedCache.swift */; };
5AC6CA4322AAF7B200B7C94D /* GraphQLHTTPMethod.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5AC6CA4222AAF7B200B7C94D /* GraphQLHTTPMethod.swift */; };
5BB2C0232380836100774170 /* VersionNumberTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5BB2C0222380836100774170 /* VersionNumberTests.swift */; };
Expand Down Expand Up @@ -709,6 +709,7 @@
E608A52B280905C2001BE656 /* WSProtocolTestsBase.swift in Sources */ = {isa = PBXBuildFile; fileRef = E608A528280905C2001BE656 /* WSProtocolTestsBase.swift */; };
E608A52D280905E9001BE656 /* SubscriptionTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = E608A52C280905E9001BE656 /* SubscriptionTests.swift */; };
E60AE2EE27E3FC6C003C093A /* TemplateRenderer.swift in Sources */ = {isa = PBXBuildFile; fileRef = E60AE2ED27E3FC6C003C093A /* TemplateRenderer.swift */; };
E60F457A29E4CFB800E60A04 /* TemplateString_DeprecationMessage_Tests.swift in Sources */ = {isa = PBXBuildFile; fileRef = E60F457929E4CFB800E60A04 /* TemplateString_DeprecationMessage_Tests.swift */; };
E610D8D7278EA2390023E495 /* EnumFileGenerator.swift in Sources */ = {isa = PBXBuildFile; fileRef = E610D8D6278EA2390023E495 /* EnumFileGenerator.swift */; };
E610D8D9278EA2560023E495 /* EnumFileGeneratorTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = E610D8D8278EA2560023E495 /* EnumFileGeneratorTests.swift */; };
E610D8DB278EB0900023E495 /* InterfaceFileGenerator.swift in Sources */ = {isa = PBXBuildFile; fileRef = E610D8DA278EB0900023E495 /* InterfaceFileGenerator.swift */; };
Expand Down Expand Up @@ -1119,7 +1120,7 @@
19E9F6A826D5867E003AB80E /* OperationMessageIdCreator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OperationMessageIdCreator.swift; sourceTree = "<group>"; };
19E9F6AA26D58A92003AB80E /* OperationMessageIdCreatorTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OperationMessageIdCreatorTests.swift; sourceTree = "<group>"; };
2EE7FFCF276802E30035DC39 /* CacheKeyConstructionTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CacheKeyConstructionTests.swift; sourceTree = "<group>"; };
534A754428EB21D6003291BE /* TemplateString+AvailabilityDeprecated.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "TemplateString+AvailabilityDeprecated.swift"; sourceTree = "<group>"; };
534A754428EB21D6003291BE /* TemplateString+DeprecationMessage.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "TemplateString+DeprecationMessage.swift"; sourceTree = "<group>"; };
54DDB0911EA045870009DD99 /* InMemoryNormalizedCache.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = InMemoryNormalizedCache.swift; sourceTree = "<group>"; };
5AC6CA4222AAF7B200B7C94D /* GraphQLHTTPMethod.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = GraphQLHTTPMethod.swift; sourceTree = "<group>"; };
5BB2C0222380836100774170 /* VersionNumberTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VersionNumberTests.swift; sourceTree = "<group>"; };
Expand Down Expand Up @@ -1878,6 +1879,7 @@
E608A528280905C2001BE656 /* WSProtocolTestsBase.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = WSProtocolTestsBase.swift; sourceTree = "<group>"; };
E608A52C280905E9001BE656 /* SubscriptionTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SubscriptionTests.swift; sourceTree = "<group>"; };
E60AE2ED27E3FC6C003C093A /* TemplateRenderer.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TemplateRenderer.swift; sourceTree = "<group>"; };
E60F457929E4CFB800E60A04 /* TemplateString_DeprecationMessage_Tests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TemplateString_DeprecationMessage_Tests.swift; sourceTree = "<group>"; };
E610D8D6278EA2390023E495 /* EnumFileGenerator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EnumFileGenerator.swift; sourceTree = "<group>"; };
E610D8D8278EA2560023E495 /* EnumFileGeneratorTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EnumFileGeneratorTests.swift; sourceTree = "<group>"; };
E610D8DA278EB0900023E495 /* InterfaceFileGenerator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InterfaceFileGenerator.swift; sourceTree = "<group>"; };
Expand Down Expand Up @@ -2509,7 +2511,7 @@
E674DB40274C0A9B009BB90E /* Glob.swift */,
DE5FD600276923620033EE23 /* TemplateString.swift */,
DED5B35A286CF16600AE6BFF /* TemplateString+CodegenConfiguration.swift */,
534A754428EB21D6003291BE /* TemplateString+AvailabilityDeprecated.swift */,
534A754428EB21D6003291BE /* TemplateString+DeprecationMessage.swift */,
DE31A437276A78140020DC44 /* Templates */,
E6E3BBDC276A8D6200E5218B /* FileGenerators */,
);
Expand Down Expand Up @@ -2949,6 +2951,7 @@
DE9CEAF2282C6AC300959AF9 /* TemplateRenderer_TestMockFile_Tests.swift */,
E64F7EB927A085D90059C021 /* UnionTemplateTests.swift */,
DED5B358286CEA0900AE6BFF /* TemplateString_Documentation_Tests.swift */,
E60F457929E4CFB800E60A04 /* TemplateString_DeprecationMessage_Tests.swift */,
);
path = Templates;
sourceTree = "<group>";
Expand Down Expand Up @@ -4933,7 +4936,7 @@
E6B42D0927A472A700A3BD58 /* SwiftPackageManagerModuleTemplate.swift in Sources */,
DE100B1A28872D0F00BE11C2 /* Documentation.docc in Sources */,
E6D90D07278FA595009CAC5D /* InputObjectFileGenerator.swift in Sources */,
534A754528EB21D6003291BE /* TemplateString+AvailabilityDeprecated.swift in Sources */,
534A754528EB21D6003291BE /* TemplateString+DeprecationMessage.swift in Sources */,
E64F7EB827A0854E0059C021 /* UnionTemplate.swift in Sources */,
E60AE2EE27E3FC6C003C093A /* TemplateRenderer.swift in Sources */,
9BCA8C0926618226004FF2F6 /* UntypedGraphQLRequestBodyCreator.swift in Sources */,
Expand Down Expand Up @@ -5097,6 +5100,7 @@
E6D90D0D278FFE35009CAC5D /* SchemaMetadataFileGeneratorTests.swift in Sources */,
E6B42D0B27A4746800A3BD58 /* SchemaModuleFileGeneratorTests.swift in Sources */,
9B68F0552416B33300E97318 /* LineByLineComparison.swift in Sources */,
E60F457A29E4CFB800E60A04 /* TemplateString_DeprecationMessage_Tests.swift in Sources */,
DE09066F27A4713F00211300 /* OperationDefinitionTemplateTests.swift in Sources */,
DE09F9C6270269F800795949 /* OperationDefinitionTemplate_DocumentType_Tests.swift in Sources */,
9B4751AD2575B5070001FB87 /* PluralizerTests.swift in Sources */,
Expand Down

This file was deleted.

61 changes: 61 additions & 0 deletions Sources/ApolloCodegenLib/TemplateString+DeprecationMessage.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
extension TemplateString.StringInterpolation {

mutating func appendInterpolation(
deprecationReason: String?,
config: ApolloCodegen.ConfigurationContext
) {
guard
config.options.warningsOnDeprecatedUsage == .include,
let escapedDeprecationReason = deprecationReason?.escapedSwiftStringSpecialCharacters()
else {
removeLineIfEmpty()
return
}

appendInterpolation("""
@available(*, deprecated, message: \"\(escapedDeprecationReason)\")
""")
}

mutating func appendInterpolation(
field: String,
argument: String,
warningReason: String
) {
let escapedWarningReason = warningReason.escapedSwiftStringSpecialCharacters()

appendInterpolation("""
#warning("Argument '\(argument)' of field '\(field)' is deprecated. \
Reason: '\(escapedWarningReason)'")
""")
}
}

extension String {
/// Replaces specific escaped characters so they are written into the rendered deprecation
/// message as escaped characters to be correctly rendered to the user in an Xcode warning
/// (e.g., `\"` becomes `\\\"`).
///
/// String literals can include the following special characters: `\0` (null character),
/// `\\` (backslash), `\t` (horizontal tab), `\n` (line feed), `\r` (carriage return),
/// `\"` (double quotation mark) and `\'` (single quotation mark).
func escapedSwiftStringSpecialCharacters() -> String {
var escapedString = String()
escapedString.reserveCapacity(self.count)

forEach { character in
switch (character) {
case "\0": escapedString.append(#"\0"#)
case "\\": escapedString.append(#"\\"#)
case "\t": escapedString.append(#"\t"#)
case "\n": escapedString.append(#"\n"#)
case "\r": escapedString.append(#"\r"#)
case "\"": escapedString.append(#"\""#)
case "\'": escapedString.append(#"\'"#)
default: escapedString.append(character)
}
}

return escapedString
}
}
2 changes: 1 addition & 1 deletion Sources/ApolloCodegenLib/Templates/EnumTemplate.swift
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ struct EnumTemplate: TemplateRenderer {
\(if: shouldRenderDocumentation, "\(forceDocumentation: graphqlEnumValue.documentation)")
\(ifLet: graphqlEnumValue.deprecationReason, { """
\(if: shouldRenderDocumentation, "///")
\(forceDocumentation: "**Deprecated**: \($0)")
\(forceDocumentation: "**Deprecated**: \($0.escapedSwiftStringSpecialCharacters())")
""" })
\(caseDefinition(for: graphqlEnumValue))
"""
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -187,8 +187,7 @@ struct SelectionSetTemplate {
return """
\(if: deprecatedArguments != nil && !deprecatedArguments.unsafelyUnwrapped.isEmpty, """
\(deprecatedArguments.unsafelyUnwrapped.map { """
#warning("Argument '\($0.arg)' of field '\($0.field)' is deprecated. \
Reason: '\($0.reason)'")
\(field: $0.field, argument: $0.arg, warningReason: $0.reason)
"""})
""")
\(selectionsTemplate)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5982,100 +5982,6 @@ class SelectionSetTemplateTests: XCTestCase {
expect(actual).to(equalLineByLine(expected, atLine: 14, ignoringExtraLines: true))
}


func test__render_fieldAccessors__givenWarningsOnDeprecatedUsage_include_hasDeprecatedField_withMultilineDocumentation_shouldGenerateWarningBelowDocumentationWithMultilineLiteral() throws {
// given
schemaSDL = """
type Query {
allAnimals: [Animal!]
}
type Animal {
"This field is a string."
string: String! @deprecated(reason: "Cause I\\nsaid so!")
}
""" // Escaping the backslash is required to allow the frontend to parse correctly this string.
// Removing the escape leads to a "unterminated string literal" error when parsing the schema.

document = """
query TestOperation {
allAnimals {
string
}
}
"""

let expected = #"""
/// This field is a string.
@available(*, deprecated, message: """
Cause I
said so!
""")
public var string: String { __data["string"] }
"""#

// when
try buildSubjectAndOperation(
schemaDocumentation: .include,
warningsOnDeprecatedUsage: .include
)
let allAnimals = try XCTUnwrap(
operation[field: "query"]?[field: "allAnimals"] as? IR.EntityField
)

let actual = subject.render(field: allAnimals)

// then
expect(actual).to(equalLineByLine(expected, atLine: 14, ignoringExtraLines: true))
}

func test__render_fieldAccessors__givenWarningsOnDeprecatedUsage_include_hasDeprecatedField_withMultilineDocumentation_includingEmptyLine_shouldGenerateWarningBelowDocumentationWithMultilineLiteral() throws {
// given
schemaSDL = """
type Query {
allAnimals: [Animal!]
}
type Animal {
"This field is a string."
string: String! @deprecated(reason: "Cause I\\n\\nsaid so!")
}
""" // Escaping the backslash is required to allow the frontend to parse correctly this string.
// Removing the escape leads to a "unterminated string literal" error when parsing the schema.

document = """
query TestOperation {
allAnimals {
string
}
}
"""

let expected = #"""
/// This field is a string.
@available(*, deprecated, message: """
Cause I
said so!
""")
public var string: String { __data["string"] }
"""#

// when
try buildSubjectAndOperation(
schemaDocumentation: .include,
warningsOnDeprecatedUsage: .include
)
let allAnimals = try XCTUnwrap(
operation[field: "query"]?[field: "allAnimals"] as? IR.EntityField
)

let actual = subject.render(field: allAnimals)

// then
expect(actual).to(equalLineByLine(expected, atLine: 14, ignoringExtraLines: true))
}

func test__render_fieldAccessors__givenWarningsOnDeprecatedUsage_exclude_hasDeprecatedField_shouldNotGenerateWarning() throws {
// given
schemaSDL = """
Expand Down
Loading

0 comments on commit 58f6d3f

Please sign in to comment.