Skip to content

Commit

Permalink
Import statements update (w/tests) (apollographql/apollo-ios-dev#245)
Browse files Browse the repository at this point in the history
  • Loading branch information
AnthonyMDev authored and gh-action-runner committed Jan 25, 2024
1 parent 99aa12d commit 0ca6620
Show file tree
Hide file tree
Showing 7 changed files with 109 additions and 16 deletions.
4 changes: 2 additions & 2 deletions Package.resolved
Original file line number Diff line number Diff line change
Expand Up @@ -23,8 +23,8 @@
"kind" : "remoteSourceControl",
"location" : "https://github.com/apple/swift-collections",
"state" : {
"revision" : "937e904258d22af6e447a0b72c0bc67583ef64a2",
"version" : "1.0.4"
"revision" : "d029d9d39c87bed85b1c50adee7c41795261a192",
"version" : "1.0.6"
}
}
],
Expand Down
2 changes: 1 addition & 1 deletion Package.swift
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ let package = Package(
.upToNextMajor(from: "1.0.0")),
.package(
url: "https://github.com/apple/swift-collections",
.upToNextMajor(from: "1.0.0")),
.upToNextMajor(from: "1.0.6")),
.package(
url: "https://github.com/apple/swift-argument-parser.git",
.upToNextMajor(from: "1.2.0")),
Expand Down
4 changes: 3 additions & 1 deletion Sources/ApolloCodegenLib/Templates/FragmentTemplate.swift
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,9 @@ struct FragmentTemplate: TemplateRenderer {

let config: ApolloCodegen.ConfigurationContext

let target: TemplateTarget = .operationFile
var target: TemplateTarget {
.operationFile(moduleImports: fragment.definition.moduleImports)
}

func renderBodyTemplate(
nonFatalErrorRecorder: ApolloCodegen.NonFatalError.Recorder
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,9 @@ struct LocalCacheMutationDefinitionTemplate: OperationTemplateRenderer {

let config: ApolloCodegen.ConfigurationContext

let target: TemplateTarget = .operationFile
var target: TemplateTarget {
.operationFile(moduleImports: operation.definition.moduleImports)
}

func renderBodyTemplate(
nonFatalErrorRecorder: ApolloCodegen.NonFatalError.Recorder
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,9 @@ struct OperationDefinitionTemplate: OperationTemplateRenderer {

let config: ApolloCodegen.ConfigurationContext

let target: TemplateTarget = .operationFile
var target: TemplateTarget {
.operationFile(moduleImports: operation.definition.moduleImports)
}

func renderBodyTemplate(
nonFatalErrorRecorder: ApolloCodegen.NonFatalError.Recorder
Expand Down Expand Up @@ -45,7 +47,7 @@ struct OperationDefinitionTemplate: OperationTemplateRenderer {
""")
}

private func OperationDeclaration() -> TemplateString {
return """
\(accessControlModifier(for: .parent))\
Expand Down
36 changes: 30 additions & 6 deletions Sources/ApolloCodegenLib/Templates/TemplateRenderer.swift
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import TemplateString
import OrderedCollections

// MARK: TemplateRenderer

Expand All @@ -7,7 +8,7 @@ enum TemplateTarget: Equatable {
/// Used in schema types files; enum, input object, union, etc.
case schemaFile(type: SchemaFileType)
/// Used in operation files; query, mutation, fragment, etc.
case operationFile
case operationFile(moduleImports: OrderedSet<String>? = nil)
/// Used in files that define a module; Swift Package Manager, etc.
case moduleFile
/// Used in test mock files; schema object `Mockable` extensions
Expand All @@ -23,7 +24,7 @@ enum TemplateTarget: Equatable {
case customScalar
case inputObject

var namespaceComponent: String? {
var namespaceComponent: String? {
switch self {
case .schemaMetadata, .enum, .customScalar, .inputObject, .schemaConfiguration:
return nil
Expand Down Expand Up @@ -102,10 +103,17 @@ extension TemplateRenderer {

let body = {
switch target {
case let .schemaFile(type): return renderSchemaFile(type, errorRecorder)
case .operationFile: return renderOperationFile(errorRecorder)
case .moduleFile: return renderModuleFile(errorRecorder)
case .testMockFile: return renderTestMockFile(errorRecorder)
case let .schemaFile(type):
return renderSchemaFile(type, errorRecorder)

case let .operationFile(moduleImports):
return renderOperationFile(moduleImports, errorRecorder)

case .moduleFile:
return renderModuleFile(errorRecorder)

case .testMockFile:
return renderTestMockFile(errorRecorder)
}
}()

Expand Down Expand Up @@ -153,12 +161,14 @@ extension TemplateRenderer {
}

private func renderOperationFile(
_ moduleImports: OrderedSet<String>?,
_ errorRecorder: ApolloCodegen.NonFatalError.Recorder
) -> String {
TemplateString(
"""
\(ifLet: renderHeaderTemplate(nonFatalErrorRecorder: errorRecorder), { "\($0)\n" })
\(ImportStatementTemplate.Operation.template(for: config))
\(ifLet: moduleImports, { "\(ModuleImportStatementTemplate.template(moduleImports: $0))" })
\(if: config.output.operations.isInModule && !config.output.schemaTypes.isInModule,
renderBodyTemplate(nonFatalErrorRecorder: errorRecorder)
Expand Down Expand Up @@ -350,6 +360,20 @@ struct ImportStatementTemplate {
}
}

/// Provides the format to import additional Swift modules required by the template type.
/// These are custom import statements defined using the `@import(module:)` directive.
struct ModuleImportStatementTemplate {

static func template(
moduleImports: OrderedSet<String>
) -> TemplateString {
return """
\(moduleImports.map { "import \($0)" }.joined(separator: "\n"))
"""
}

}

fileprivate extension ApolloCodegenConfiguration {
var schemaModuleName: String {
switch output.schemaTypes.moduleType {
Expand Down
69 changes: 66 additions & 3 deletions Sources/GraphQLCompiler/CompilationResult.swift
Original file line number Diff line number Diff line change
@@ -1,12 +1,14 @@
import JavaScriptCore
import TemplateString
import OrderedCollections

/// The output of the frontend compiler.
public final class CompilationResult: JavaScriptObjectDecodable {

/// String constants used to match JavaScriptObject instances.
fileprivate enum Constants {
enum DirectiveNames {
static let Import = "import"
static let LocalCacheMutation = "apollo_client_ios_localCacheMutation"
static let Defer = "defer"
}
Expand Down Expand Up @@ -108,6 +110,8 @@ public final class CompilationResult: JavaScriptObjectDecodable {
public let filePath: String

public let isLocalCacheMutation: Bool

public let moduleImports: OrderedSet<String>

static func fromJSValue(_ jsValue: JSValue, bridge: isolated JavaScriptBridge) -> Self {
self.init(
Expand Down Expand Up @@ -145,6 +149,9 @@ public final class CompilationResult: JavaScriptObjectDecodable {
self.filePath = filePath
self.isLocalCacheMutation = directives?
.contains { $0.name == Constants.DirectiveNames.LocalCacheMutation } ?? false

self.moduleImports = OperationDefinition.getImportModuleNames(directives: directives,
referencedFragments: referencedFragments)
}

public var debugDescription: String {
Expand All @@ -158,6 +165,17 @@ public final class CompilationResult: JavaScriptObjectDecodable {
public static func ==(lhs: OperationDefinition, rhs: OperationDefinition) -> Bool {
return lhs.name == rhs.name
}

private static func getImportModuleNames(directives: [Directive]?,
referencedFragments: [FragmentDefinition]) -> OrderedSet<String> {
let referencedImports: [String] = referencedFragments
.flatMap { $0.moduleImports }
let directiveImports: [String] = directives?
.compactMap { ImportDirective(directive: $0)?.moduleName } ?? []
var orderedImports = OrderedSet(referencedImports + directiveImports)
orderedImports.sort()
return orderedImports
}
}

public enum OperationType: String, Equatable, Sendable, JavaScriptValueDecodable {
Expand Down Expand Up @@ -214,15 +232,19 @@ public final class CompilationResult: JavaScriptObjectDecodable {
public var isLocalCacheMutation: Bool {
directives?.contains { $0.name == Constants.DirectiveNames.LocalCacheMutation } ?? false
}

public let moduleImports: OrderedSet<String>

init(_ jsValue: JSValue, bridge: isolated JavaScriptBridge) {
self.name = jsValue["name"]
self.directives = .fromJSValue(jsValue["directives"], bridge: bridge)
self.type = .fromJSValue(jsValue["typeCondition"], bridge: bridge)
self.selectionSet = .fromJSValue(jsValue["selectionSet"], bridge: bridge)
self.directives = .fromJSValue(jsValue["directives"], bridge: bridge)
self.referencedFragments = .fromJSValue(jsValue["referencedFragments"], bridge: bridge)
self.source = jsValue["source"]
self.filePath = jsValue["filePath"]
self.moduleImports = FragmentDefinition.getImportModuleNames(directives: directives,
referencedFragments: referencedFragments)
}

/// Initializer to be used for creating mock objects in tests only.
Expand All @@ -242,6 +264,8 @@ public final class CompilationResult: JavaScriptObjectDecodable {
self.referencedFragments = referencedFragments
self.source = source
self.filePath = filePath
self.moduleImports = FragmentDefinition.getImportModuleNames(directives: directives,
referencedFragments: referencedFragments)
}

public var debugDescription: String {
Expand All @@ -255,6 +279,18 @@ public final class CompilationResult: JavaScriptObjectDecodable {
public static func ==(lhs: FragmentDefinition, rhs: FragmentDefinition) -> Bool {
return lhs.name == rhs.name
}

private static func getImportModuleNames(directives: [Directive]?,
referencedFragments: [FragmentDefinition]) -> OrderedSet<String> {
let referencedImports: [String] = referencedFragments
.flatMap { $0.moduleImports }
let directiveImports: [String] = directives?
.compactMap { ImportDirective(directive: $0)?.moduleName } ?? []

var orderedImports = OrderedSet(referencedImports + directiveImports)
orderedImports.sort()
return orderedImports
}
}

public final class SelectionSet:
Expand Down Expand Up @@ -527,7 +563,7 @@ public final class CompilationResult: JavaScriptObjectDecodable {
}
}

public struct Argument:
public struct Argument:
JavaScriptObjectDecodable, Sendable, Hashable {
public let name: String

Expand Down Expand Up @@ -650,7 +686,34 @@ public final class CompilationResult: JavaScriptObjectDecodable {
return string
}
}


fileprivate struct ImportDirective: Hashable, CustomDebugStringConvertible, Sendable, Equatable {
/// String constants used to match JavaScriptObject instances.
enum Constants {
enum ArgumentNames {
static let Module = "module"
}
}

let moduleName: String

init?(directive: Directive) {
guard directive.name == CompilationResult.Constants.DirectiveNames.Import else {
return nil
}
guard let moduleArgument = directive.arguments?.first(
where: { $0.name == Constants.ArgumentNames.Module }),
case let .string(moduleValue) = moduleArgument.value
else {
return nil
}
moduleName = moduleValue
}

var debugDescription: String {
return "@import(module: \"\(moduleName)\")"
}
}
}

fileprivate protocol Deferrable { }
Expand Down

0 comments on commit 0ca6620

Please sign in to comment.