Skip to content
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

[codegen] import directive #236

Closed
Closed
Show file tree
Hide file tree
Changes from 9 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ class TemplateRenderer_OperationFile_Tests: XCTestCase {
}

private func buildSubject(config: ApolloCodegenConfiguration = .mock()) -> MockFileTemplate {
MockFileTemplate(target: .operationFile, config: ApolloCodegen.ConfigurationContext(config: config))
MockFileTemplate(target: .operationFile(), config: ApolloCodegen.ConfigurationContext(config: config))
}

// MARK: Render Target .operationFile Tests
Expand Down
4 changes: 2 additions & 2 deletions apollo-ios-codegen/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"
Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

OrderedSet was not Sendable (resulting in a warning) in version 1.0.4 so i updated collections to 1.0.6 as it was fixed there.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That's great. If we need that patch version in order for this to compile correctly, then we should probably make it the minimum version number in the Package.swift file too.

}
}
],
Expand Down
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
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 @@ -103,7 +104,7 @@ extension TemplateRenderer {
let body = {
switch target {
case let .schemaFile(type): return renderSchemaFile(type, errorRecorder)
case .operationFile: return renderOperationFile(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 +154,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: ModuleImportStatementTemplate.template(moduleImports: moduleImports), { "\($0)" })

\(if: config.output.operations.isInModule && !config.output.schemaTypes.isInModule,
renderBodyTemplate(nonFatalErrorRecorder: errorRecorder)
Expand Down Expand Up @@ -350,6 +353,20 @@ struct ImportStatementTemplate {
}
}

/// Provides the format to import additional Swift modules required by the template type.
struct ModuleImportStatementTemplate {

static func template(
moduleImports: OrderedSet<String>?
) -> TemplateString? {
guard let moduleImports else { return nil }
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 apollo-ios-codegen/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
.map { $0.moduleImports }
.flatMap { $0 }
let directiveImports: [String] = directives?.compactMap { ImportDirective(directive: $0)?.moduleName } ?? []
var ordered = OrderedSet(referencedImports + directiveImports)
ordered.sort()
return ordered
}
}

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]?,
Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I can add this as a global function in order to avoid any code duplication. For now i left this helper inside the corresponding class.

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

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

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