Skip to content

Commit

Permalink
Merge pull request #31 from nerdsupremacist/diagnostics
Browse files Browse the repository at this point in the history
Diagnostics
  • Loading branch information
nerdsupremacist authored Jan 24, 2021
2 parents aa68137 + b094a6b commit b6dcb1d
Show file tree
Hide file tree
Showing 11 changed files with 138 additions and 36 deletions.
4 changes: 2 additions & 2 deletions .swiftpm/xcode/xcshareddata/xcschemes/Graphaello.xcscheme
Original file line number Diff line number Diff line change
Expand Up @@ -52,14 +52,14 @@
</BuildableProductRunnable>
<CommandLineArguments>
<CommandLineArgument
argument = "codegen --project /Users/mathiasquintero/Projects/MindfulEating/iOS/app --apollo derivedData"
argument = "codegen --project /Users/mathiasquintero/Desktop/Movies --apollo derivedData"
isEnabled = "YES">
</CommandLineArgument>
</CommandLineArguments>
<EnvironmentVariables>
<EnvironmentVariable
key = "BUILD_ROOT"
value = "/Users/mathiasquintero/Library/Developer/Xcode/DerivedData/MindfulEating-akqgqpdhznbxrxebrmxqsqvkqovi/Build/Products"
value = "/Users/mathiasquintero/Library/Developer/Xcode/DerivedData/Movies-apxlimunewwqyhdzxrrdiuocwyez/Build/Products"
isEnabled = "YES">
</EnvironmentVariable>
</EnvironmentVariables>
Expand Down
77 changes: 44 additions & 33 deletions Sources/Graphaello/Commands/Codegen/CodegenCommand.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand All @@ -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
}
}
}

Expand Down
11 changes: 11 additions & 0 deletions Sources/Graphaello/Processing/Model/Struct/Warning.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@

import Foundation

struct Warning: CustomStringConvertible {
let location: Location
let descriptionText: String

var description: String {
return "\(location.locationDescription): warning: \(descriptionText)"
}
}
5 changes: 5 additions & 0 deletions Sources/Graphaello/Processing/Pipeline/BasicPipeline.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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<File>) throws -> [Struct<Stage.Extracted>] {
return try extractor.extract(from: file)
Expand Down Expand Up @@ -44,4 +45,8 @@ struct BasicPipeline: Pipeline {
func generate(prepared: Project.State<Stage.Prepared>, useFormatting: Bool) throws -> String {
return try generator.generate(prepared: prepared, useFormatting: useFormatting)
}

func diagnose(parsed: Struct<Stage.Parsed>) throws -> [Warning] {
return try diagnoser?.diagnose(parsed: parsed) ?? []
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@

import Foundation

extension Array: WarningDiagnoser where Element: WarningDiagnoser {

func diagnose(parsed: Struct<Stage.Parsed>) throws -> [Warning] {
return try flatMap { try $0.diagnose(parsed: parsed) }
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@

import Foundation
import SwiftSyntax
import SourceKittenFramework

private let swiftUIViewProtocols: Set<String> = ["View"]

struct UnusedWarningDiagnoser: WarningDiagnoser {
func diagnose(parsed: Struct<Stage.Parsed>) 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<Stage.Parsed>, from parsed: Struct<Stage.Parsed>) 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<Stage.Parsed>

private(set) var shouldUseSelf = false
private(set) var isUsed = false

init(property: Property<Stage.Parsed>) {
self.property = property
super.init()
}

override func visitPost(_ node: IdentifierExprSyntax) {
if node.identifier.text == property.name {
isUsed = true
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@

import Foundation

protocol WarningDiagnoser {
func diagnose(parsed: Struct<Stage.Parsed>) throws -> [Warning]
}
2 changes: 2 additions & 0 deletions Sources/Graphaello/Processing/Pipeline/Pipeline.swift
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,8 @@ protocol Pipeline {
using apollo: ApolloReference) throws -> Project.State<Stage.Prepared>

func generate(prepared: Project.State<Stage.Prepared>, useFormatting: Bool) throws -> String

func diagnose(parsed: Struct<Stage.Parsed>) throws -> [Warning]
}

extension Pipeline {
Expand Down
3 changes: 2 additions & 1 deletion Sources/Graphaello/Processing/Pipeline/PipelineFactory.swift
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,8 @@ enum PipelineFactory {
cleaner: create(),
assembler: create(),
preparator: create(),
generator: create())
generator: create(),
diagnoser: UnusedWarningDiagnoser())
}

private static func create() -> Extractor {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -32,5 +32,9 @@ extension Pipeline {
func clean(resolved: Project.State<Stage.Resolved>) throws -> Project.State<Stage.Cleaned> {
return resolved.with(structs: try clean(resolved: resolved.structs))
}

func diagnose(parsed: Project.State<Stage.Parsed>) throws -> [Warning] {
return try parsed.structs.flatMap { try diagnose(parsed: $0) }
}

}

0 comments on commit b6dcb1d

Please sign in to comment.