diff --git a/.swiftpm/xcode/xcshareddata/xcschemes/Graphaello.xcscheme b/.swiftpm/xcode/xcshareddata/xcschemes/Graphaello.xcscheme
index 6423813..6397964 100644
--- a/.swiftpm/xcode/xcshareddata/xcschemes/Graphaello.xcscheme
+++ b/.swiftpm/xcode/xcshareddata/xcschemes/Graphaello.xcscheme
@@ -52,14 +52,14 @@
diff --git a/Sources/Graphaello/Commands/Codegen/CodegenCommand.swift b/Sources/Graphaello/Commands/Codegen/CodegenCommand.swift
index c6ed18c..542cd5b 100644
--- a/Sources/Graphaello/Commands/Codegen/CodegenCommand.swift
+++ b/Sources/Graphaello/Commands/Codegen/CodegenCommand.swift
@@ -48,6 +48,12 @@ class CodegenCommand : Command {
Console.print(title: "๐ Parsing Paths From Structs:")
let parsed = try pipeline.parse(extracted: extracted)
+
+ let warnings = try pipeline.diagnose(parsed: parsed)
+ warnings.forEach { warning in
+ print(warning.description)
+ }
+
Console.print(result: "Found \(parsed.structs.count) structs with values from GraphQL")
parsed.structs.forEach { parsed in
Console.print(result: "\(inverse: parsed.name)", indentation: 2)
@@ -70,46 +76,51 @@ class CodegenCommand : Command {
cache[.lastRunHash] = hashValue
}
- Console.print(title: "๐ Validating Paths against API definitions:")
- let validated = try pipeline.validate(parsed: parsed)
- Console.print(result: "Checked \(validated.graphQLPaths.count) fields")
+ do {
+ Console.print(title: "๐ Validating Paths against API definitions:")
+ let validated = try pipeline.validate(parsed: parsed)
+ Console.print(result: "Checked \(validated.graphQLPaths.count) fields")
- Console.print(title: "๐งฐ Resolving Fragments and Queries:")
- let resolved = try pipeline.resolve(validated: validated)
+ Console.print(title: "๐งฐ Resolving Fragments and Queries:")
+ let resolved = try pipeline.resolve(validated: validated)
- Console.print(result: "Resolved \(resolved.allQueries.count) Queries:")
- resolved.allQueries.forEach { query in
- Console.print(result: "\(inverse: query.name)", indentation: 2)
- }
+ Console.print(result: "Resolved \(resolved.allQueries.count) Queries:")
+ resolved.allQueries.forEach { query in
+ Console.print(result: "\(inverse: query.name)", indentation: 2)
+ }
- Console.print(result: "Resolved \(resolved.allFragments.count) Fragments:")
- resolved.allFragments.forEach { fragment in
- Console.print(result: "\(inverse: fragment.name)", indentation: 2)
- }
+ Console.print(result: "Resolved \(resolved.allFragments.count) Fragments:")
+ resolved.allFragments.forEach { fragment in
+ Console.print(result: "\(inverse: fragment.name)", indentation: 2)
+ }
- Console.print(title: "๐งน Cleaning Queries and Fragments:")
- let cleaned = try pipeline.clean(resolved: resolved)
+ Console.print(title: "๐งน Cleaning Queries and Fragments:")
+ let cleaned = try pipeline.clean(resolved: resolved)
- Console.print(title: "โ๏ธ Generating Swift Code:")
-
- Console.print(title: "๐จ Writing GraphQL Code", indentation: 1)
- let assembled = try pipeline.assemble(cleaned: cleaned)
-
- Console.print(title: "๐ Delegating some stuff to Apollo codegen", indentation: 1)
- let prepared = try pipeline.prepare(assembled: assembled, using: apollo)
-
- Console.print(title: "๐ Bundling it all together", indentation: 1)
-
- let autoGeneratedFile = try pipeline.generate(prepared: prepared, useFormatting: !skipFormatting)
-
- Console.print(result: "Generated \(autoGeneratedFile.components(separatedBy: "\n").count) lines of code")
- Console.print(result: "You're welcome ๐", indentation: 2)
+ Console.print(title: "โ๏ธ Generating Swift Code:")
+
+ Console.print(title: "๐จ Writing GraphQL Code", indentation: 1)
+ let assembled = try pipeline.assemble(cleaned: cleaned)
+
+ Console.print(title: "๐ Delegating some stuff to Apollo codegen", indentation: 1)
+ let prepared = try pipeline.prepare(assembled: assembled, using: apollo)
- Console.print(title: "๐พ Saving Autogenerated Code")
- try project.writeFile(name: "Graphaello.swift", content: autoGeneratedFile)
+ Console.print(title: "๐ Bundling it all together", indentation: 1)
- Console.print("")
- Console.print(title: "โ
Done")
+ let autoGeneratedFile = try pipeline.generate(prepared: prepared, useFormatting: !skipFormatting)
+
+ Console.print(result: "Generated \(autoGeneratedFile.components(separatedBy: "\n").count) lines of code")
+ Console.print(result: "You're welcome ๐", indentation: 2)
+
+ Console.print(title: "๐พ Saving Autogenerated Code")
+ try project.writeFile(name: "Graphaello.swift", content: autoGeneratedFile)
+
+ Console.print("")
+ Console.print(title: "โ
Done")
+ } catch {
+ cache?[.lastRunHash] = nil
+ throw error
+ }
}
}
diff --git a/Sources/Graphaello/Processing/Model/Struct/Warning.swift b/Sources/Graphaello/Processing/Model/Struct/Warning.swift
new file mode 100644
index 0000000..13b9b81
--- /dev/null
+++ b/Sources/Graphaello/Processing/Model/Struct/Warning.swift
@@ -0,0 +1,11 @@
+
+import Foundation
+
+struct Warning: CustomStringConvertible {
+ let location: Location
+ let descriptionText: String
+
+ var description: String {
+ return "\(location.locationDescription): warning: \(descriptionText)"
+ }
+}
diff --git a/Sources/Graphaello/Processing/Pipeline/BasicPipeline.swift b/Sources/Graphaello/Processing/Pipeline/BasicPipeline.swift
index c16bde1..a026f83 100644
--- a/Sources/Graphaello/Processing/Pipeline/BasicPipeline.swift
+++ b/Sources/Graphaello/Processing/Pipeline/BasicPipeline.swift
@@ -10,6 +10,7 @@ struct BasicPipeline: Pipeline {
let assembler: Assembler
let preparator: Preparator
let generator: Generator
+ var diagnoser: WarningDiagnoser? = nil
func extract(from file: WithTargets) throws -> [Struct] {
return try extractor.extract(from: file)
@@ -44,4 +45,8 @@ struct BasicPipeline: Pipeline {
func generate(prepared: Project.State, useFormatting: Bool) throws -> String {
return try generator.generate(prepared: prepared, useFormatting: useFormatting)
}
+
+ func diagnose(parsed: Struct) throws -> [Warning] {
+ return try diagnoser?.diagnose(parsed: parsed) ?? []
+ }
}
diff --git a/Sources/Graphaello/Processing/Pipeline/Component/1. Extraction/SourceCode/SourceCode+decode.swift b/Sources/Graphaello/Processing/Pipeline/Component/1. Extraction/SourceCode/SourceCode+decode.swift
index 89d08f4..b1221df 100644
--- a/Sources/Graphaello/Processing/Pipeline/Component/1. Extraction/SourceCode/SourceCode+decode.swift
+++ b/Sources/Graphaello/Processing/Pipeline/Component/1. Extraction/SourceCode/SourceCode+decode.swift
@@ -75,6 +75,10 @@ extension SourceCode {
return SwiftDeclarationAttributeKind(rawValue: try decode(key: "key.attribute")) ?? ._custom
}
+ func usr() throws -> String {
+ return try decode(key: .usr)
+ }
+
}
extension SourceCode {
diff --git a/Sources/Graphaello/Processing/Pipeline/Diagnostics/Array+WarningDiagnoser.swift b/Sources/Graphaello/Processing/Pipeline/Diagnostics/Array+WarningDiagnoser.swift
new file mode 100644
index 0000000..6a3b5f2
--- /dev/null
+++ b/Sources/Graphaello/Processing/Pipeline/Diagnostics/Array+WarningDiagnoser.swift
@@ -0,0 +1,10 @@
+
+import Foundation
+
+extension Array: WarningDiagnoser where Element: WarningDiagnoser {
+
+ func diagnose(parsed: Struct) throws -> [Warning] {
+ return try flatMap { try $0.diagnose(parsed: parsed) }
+ }
+
+}
diff --git a/Sources/Graphaello/Processing/Pipeline/Diagnostics/UnusedWarningDiagnoser.swift b/Sources/Graphaello/Processing/Pipeline/Diagnostics/UnusedWarningDiagnoser.swift
new file mode 100644
index 0000000..b5e7738
--- /dev/null
+++ b/Sources/Graphaello/Processing/Pipeline/Diagnostics/UnusedWarningDiagnoser.swift
@@ -0,0 +1,48 @@
+
+import Foundation
+import SwiftSyntax
+import SourceKittenFramework
+
+private let swiftUIViewProtocols: Set = ["View"]
+
+struct UnusedWarningDiagnoser: WarningDiagnoser {
+ func diagnose(parsed: Struct) throws -> [Warning] {
+ guard !swiftUIViewProtocols.intersection(parsed.inheritedTypes).isEmpty else {
+ return []
+ }
+
+ return try parsed.properties.flatMap { try diagnose(property: $0, from: parsed) }
+ }
+
+ private func diagnose(property: Property, from parsed: Struct) throws -> [Warning] {
+ guard property.graphqlPath != nil,
+ property.name != "id" else { return [] }
+
+ let verifier = UsageVerifier(property: property)
+ let syntax = try parsed.code.syntaxTree()
+
+ verifier.walk(syntax)
+
+ guard !verifier.isUsed else { return [] }
+ return [Warning(location: property.code.location,
+ descriptionText: "Unused Property `\(property.name)` belongs to a View and is fetching data from GraphQL. This can be wasteful. Consider using it or removing the property.")]
+ }
+}
+
+class UsageVerifier: SyntaxVisitor {
+ let property: Property
+
+ private(set) var shouldUseSelf = false
+ private(set) var isUsed = false
+
+ init(property: Property) {
+ self.property = property
+ super.init()
+ }
+
+ override func visitPost(_ node: IdentifierExprSyntax) {
+ if node.identifier.text == property.name {
+ isUsed = true
+ }
+ }
+}
diff --git a/Sources/Graphaello/Processing/Pipeline/Diagnostics/WarningDiagnoser.swift b/Sources/Graphaello/Processing/Pipeline/Diagnostics/WarningDiagnoser.swift
new file mode 100644
index 0000000..6c60884
--- /dev/null
+++ b/Sources/Graphaello/Processing/Pipeline/Diagnostics/WarningDiagnoser.swift
@@ -0,0 +1,6 @@
+
+import Foundation
+
+protocol WarningDiagnoser {
+ func diagnose(parsed: Struct) throws -> [Warning]
+}
diff --git a/Sources/Graphaello/Processing/Pipeline/Pipeline.swift b/Sources/Graphaello/Processing/Pipeline/Pipeline.swift
index fdfd8f3..b58c25e 100644
--- a/Sources/Graphaello/Processing/Pipeline/Pipeline.swift
+++ b/Sources/Graphaello/Processing/Pipeline/Pipeline.swift
@@ -14,6 +14,8 @@ protocol Pipeline {
using apollo: ApolloReference) throws -> Project.State
func generate(prepared: Project.State, useFormatting: Bool) throws -> String
+
+ func diagnose(parsed: Struct) throws -> [Warning]
}
extension Pipeline {
diff --git a/Sources/Graphaello/Processing/Pipeline/PipelineFactory.swift b/Sources/Graphaello/Processing/Pipeline/PipelineFactory.swift
index baca3d2..52a4e1e 100644
--- a/Sources/Graphaello/Processing/Pipeline/PipelineFactory.swift
+++ b/Sources/Graphaello/Processing/Pipeline/PipelineFactory.swift
@@ -11,7 +11,8 @@ enum PipelineFactory {
cleaner: create(),
assembler: create(),
preparator: create(),
- generator: create())
+ generator: create(),
+ diagnoser: UnusedWarningDiagnoser())
}
private static func create() -> Extractor {
diff --git a/Sources/Graphaello/Processing/Project/Project+State+Pipeline.swift b/Sources/Graphaello/Processing/Project/Project+State+Pipeline.swift
index f3dfd53..114c3de 100644
--- a/Sources/Graphaello/Processing/Project/Project+State+Pipeline.swift
+++ b/Sources/Graphaello/Processing/Project/Project+State+Pipeline.swift
@@ -32,5 +32,9 @@ extension Pipeline {
func clean(resolved: Project.State) throws -> Project.State {
return resolved.with(structs: try clean(resolved: resolved.structs))
}
+
+ func diagnose(parsed: Project.State) throws -> [Warning] {
+ return try parsed.structs.flatMap { try diagnose(parsed: $0) }
+ }
}