Skip to content

Commit

Permalink
Improve test coverage calculation
Browse files Browse the repository at this point in the history
  • Loading branch information
dfed committed Jul 7, 2024
1 parent 90c8b98 commit af4c468
Show file tree
Hide file tree
Showing 12 changed files with 299 additions and 137 deletions.
30 changes: 12 additions & 18 deletions Sources/SafeDICore/Errors/FixableInjectableError.swift
Original file line number Diff line number Diff line change
Expand Up @@ -41,38 +41,32 @@ public enum FixableInjectableError: DiagnosticError {
// MARK: - InjectableDiagnosticMessage

private struct InjectableDiagnosticMessage: DiagnosticMessage {
var diagnosticID: MessageID {
MessageID(domain: "\(Self.self)", id: error.description)
}

var severity: DiagnosticSeverity {
switch error {
init(error: FixableInjectableError) {
diagnosticID = MessageID(domain: "\(Self.self)", id: error.description)
severity = switch error {
case .unexpectedMutable:
.error
}
message = error.description
}

var message: String {
error.description
}

let error: FixableInjectableError
let diagnosticID: MessageID
let severity: DiagnosticSeverity
let message: String
}

// MARK: - InjectableFixItMessage

private struct InjectableFixItMessage: FixItMessage {
var message: String {
switch error {
init(error: FixableInjectableError) {
message = switch error {
case .unexpectedMutable:
"Replace `var` with `let`"
}
fixItID = MessageID(domain: "\(Self.self)", id: error.description)
}

var fixItID: MessageID {
MessageID(domain: "\(Self.self)", id: error.description)
}

let error: FixableInjectableError
let message: String
let fixItID: MessageID
}
}
30 changes: 12 additions & 18 deletions Sources/SafeDICore/Errors/FixableInstantiableError.swift
Original file line number Diff line number Diff line change
Expand Up @@ -75,12 +75,9 @@ public enum FixableInstantiableError: DiagnosticError {
// MARK: - InstantiableDiagnosticMessage

private struct InstantiableDiagnosticMessage: DiagnosticMessage {
var diagnosticID: MessageID {
MessageID(domain: "\(Self.self)", id: error.description)
}

var severity: DiagnosticSeverity {
switch error {
init(error: FixableInstantiableError) {
diagnosticID = MessageID(domain: "\(Self.self)", id: error.description)
severity = switch error {
case .missingInstantiableConformance,
.missingRequiredInstantiateMethod,
.missingAttributes,
Expand All @@ -94,20 +91,19 @@ public enum FixableInstantiableError: DiagnosticError {
.missingRequiredInitializer:
.error
}
message = error.description
}

var message: String {
error.description
}

let error: FixableInstantiableError
let diagnosticID: MessageID
let severity: DiagnosticSeverity
let message: String
}

// MARK: - InstantiableFixItMessage

private struct InstantiableFixItMessage: FixItMessage {
var message: String {
switch error {
init(error: FixableInstantiableError) {
message = switch error {
case .missingInstantiableConformance:
"Declare conformance to `Instantiable`"
case let .missingRequiredInstantiateMethod(typeName):
Expand All @@ -131,12 +127,10 @@ public enum FixableInstantiableError: DiagnosticError {
case .missingRequiredInitializer:
"Add required initializer"
}
fixItID = MessageID(domain: "\(Self.self)", id: error.description)
}

var fixItID: MessageID {
MessageID(domain: "\(Self.self)", id: error.description)
}

let error: FixableInstantiableError
let message: String
let fixItID: MessageID
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -23,14 +23,11 @@ import SwiftSyntax
extension ImportDeclSyntax {
// MARK: Public

public var asImportStatement: ImportStatement? {
guard let moduleName = path.first?.name.text else {
return nil
}
return ImportStatement(
public var asImportStatement: ImportStatement {
ImportStatement(
attribute: attribute,
kind: kind,
moduleName: moduleName,
moduleName: path.first?.name.text ?? "",
type: path
.map(\.name.text)
.dropFirst()
Expand Down
99 changes: 42 additions & 57 deletions Sources/SafeDICore/Generators/DependencyTreeGenerator.swift
Original file line number Diff line number Diff line change
Expand Up @@ -105,7 +105,9 @@ public final class DependencyTreeGenerator {
.map(\.asSource)
.joined(separator: " -> "))
"""
}.joined(separator: "\n"))
}
.sorted()
.joined(separator: "\n"))
"""
case let .instantiableHasForwardedProperty(property, instantiable, parent):
"Property `\(property.asSource)` on \(parent.concreteInstantiable.asSource) has at least one @\(Dependency.Source.forwardedRawValue) property. Property should instead be of type `\(Dependency.instantiatorType)<\(instantiable.concreteInstantiable.asSource)>`."
Expand Down Expand Up @@ -136,11 +138,7 @@ public final class DependencyTreeGenerator {
}
}

struct UnfulfillableProperty: Hashable, Comparable {
static func < (lhs: DependencyTreeGenerator.DependencyTreeGeneratorError.UnfulfillableProperty, rhs: DependencyTreeGenerator.DependencyTreeGeneratorError.UnfulfillableProperty) -> Bool {
lhs.property < rhs.property
}

struct UnfulfillableProperty: Hashable {
let property: Property
let instantiable: Instantiable
let parentStack: [TypeDescription]
Expand All @@ -153,32 +151,25 @@ public final class DependencyTreeGenerator {
private let typeDescriptionToFulfillingInstantiableMap: [TypeDescription: Instantiable]
private var rootScopeGenerators: [ScopeGenerator] {
get throws {
if let _rootScopeGenerators {
return _rootScopeGenerators
} else {
let rootScopeGenerators: [ScopeGenerator] = try {
try validateReachableTypeDescriptions()

let typeDescriptionToScopeMap = try createTypeDescriptionToScopeMapping()
try validatePropertiesAreFulfillable(typeDescriptionToScopeMap: typeDescriptionToScopeMap)
return try rootInstantiables
.sorted()
.compactMap {
try typeDescriptionToScopeMap[$0]?.createScopeGenerator(
for: nil,
propertyStack: [],
erasedToConcreteExistential: false
)
}
}()
_rootScopeGenerators = rootScopeGenerators
return rootScopeGenerators
}
let rootScopeGenerators: [ScopeGenerator] = try {
try validateReachableTypeDescriptions()

let typeDescriptionToScopeMap = try createTypeDescriptionToScopeMapping()
try validatePropertiesAreFulfillable(typeDescriptionToScopeMap: typeDescriptionToScopeMap)
return try rootInstantiables
.sorted()
.compactMap {
try typeDescriptionToScopeMap[$0]?.createScopeGenerator(
for: nil,
propertyStack: [],
erasedToConcreteExistential: false
)
}
}()
return rootScopeGenerators
}
}

private var _rootScopeGenerators: [ScopeGenerator]?

private var imports: String {
importStatements
.reduce(into: [String: Set<ImportStatement>]()) { partialResult, importStatement in
Expand Down Expand Up @@ -265,12 +256,9 @@ public final class DependencyTreeGenerator {
// Create the mapping.
let typeDescriptionToScopeMap: [TypeDescription: Scope] = reachableTypeDescriptions
.reduce(into: [TypeDescription: Scope]()) { partialResult, typeDescription in
guard let instantiable = typeDescriptionToFulfillingInstantiableMap[typeDescription] else {
// We can't find an instantiable for this type.
// This is bad, but we handle this error in `validateReachableTypeDescriptions()`.
return
}
guard partialResult[instantiable.concreteInstantiable] == nil else {
guard let instantiable = typeDescriptionToFulfillingInstantiableMap[typeDescription],
partialResult[instantiable.concreteInstantiable] == nil
else {
// We've already created a scope for this `instantiable`. Skip.
return
}
Expand All @@ -286,29 +274,26 @@ public final class DependencyTreeGenerator {
switch dependency.source {
case let .instantiated(_, erasedToConcreteExistential):
let instantiatedType = dependency.asInstantiatedType
guard
let instantiable = typeDescriptionToFulfillingInstantiableMap[instantiatedType],
let instantiatedScope = typeDescriptionToScopeMap[instantiatedType]
else {
assertionFailure("Invalid state. Could not look up info for \(instantiatedType)")
continue
}
let type = dependency.property.propertyType
if type.isConstant {
guard instantiable.dependencies.filter(\.isForwarded).isEmpty else {
throw DependencyTreeGeneratorError
.instantiableHasForwardedProperty(
property: dependency.property,
instantiableWithForwardedProperty: instantiable,
parent: scope.instantiable
)
if let instantiable = typeDescriptionToFulfillingInstantiableMap[instantiatedType],
let instantiatedScope = typeDescriptionToScopeMap[instantiatedType]
{
let type = dependency.property.propertyType
if type.isConstant {
guard instantiable.dependencies.filter(\.isForwarded).isEmpty else {
throw DependencyTreeGeneratorError
.instantiableHasForwardedProperty(
property: dependency.property,
instantiableWithForwardedProperty: instantiable,
parent: scope.instantiable
)
}
}
scope.propertiesToGenerate.append(.instantiated(
dependency.property,
instantiatedScope,
erasedToConcreteExistential: erasedToConcreteExistential
))
}
scope.propertiesToGenerate.append(.instantiated(
dependency.property,
instantiatedScope,
erasedToConcreteExistential: erasedToConcreteExistential
))
case let .aliased(fulfillingProperty, erasedToConcreteExistential):
scope.propertiesToGenerate.append(.aliased(
dependency.property,
Expand Down Expand Up @@ -461,7 +446,7 @@ public final class DependencyTreeGenerator {
}

if !unfulfillableProperties.isEmpty {
throw DependencyTreeGeneratorError.unfulfillableProperties(unfulfillableProperties.sorted())
throw DependencyTreeGeneratorError.unfulfillableProperties(Array(unfulfillableProperties))
}
}

Expand Down
31 changes: 1 addition & 30 deletions Sources/SafeDICore/Models/TypeDescription.swift
Original file line number Diff line number Diff line change
Expand Up @@ -222,28 +222,6 @@ public enum TypeDescription: Codable, Hashable, Comparable, Sendable {
}
}

var isUnknown: Bool {
switch self {
case .any,
.array,
.attributed,
.closure,
.composition,
.dictionary,
.implicitlyUnwrappedOptional,
.metatype,
.nested,
.optional,
.simple,
.some,
.tuple,
.void:
false
case .unknown:
true
}
}

var strippingGenerics: TypeDescription {
switch self {
case let .simple(name, _):
Expand Down Expand Up @@ -423,8 +401,6 @@ extension ExprSyntax {
if memberAccessExpr.declName.baseName.text == "self" {
if let base = memberAccessExpr.base {
return base.typeDescription
} else {
return .unknown(text: memberAccessExpr.trimmedDescription)
}
} else {
if let base = memberAccessExpr.base {
Expand All @@ -440,8 +416,6 @@ extension ExprSyntax {
generics: []
)
}
} else {
return .unknown(text: memberAccessExpr.trimmedDescription)
}
}
} else if let genericExpr = GenericSpecializationExprSyntax(self) {
Expand Down Expand Up @@ -508,8 +482,6 @@ extension ExprSyntax {
doesThrow: arrow.effectSpecifiers?.throwsSpecifier != nil,
returnType: returnType.typeDescription
)
} else {
return .unknown(text: trimmedDescription)
}
} else if let optionalChainingExpr = OptionalChainingExprSyntax(self) {
return .optional(optionalChainingExpr.expression.typeDescription)
Expand All @@ -529,9 +501,8 @@ extension ExprSyntax {
key: onlyElement.key.typeDescription,
value: onlyElement.value.typeDescription
)
} else {
return .unknown(text: trimmedDescription)
}
return .unknown(text: trimmedDescription)
}
}

Expand Down
4 changes: 1 addition & 3 deletions Sources/SafeDICore/Visitors/FileVisitor.swift
Original file line number Diff line number Diff line change
Expand Up @@ -43,9 +43,7 @@ public final class FileVisitor: SyntaxVisitor {
}

public override func visit(_ node: ImportDeclSyntax) -> SyntaxVisitorContinueKind {
if let importStatement = node.asImportStatement {
imports.append(importStatement)
}
imports.append(node.asImportStatement)
return .skipChildren
}

Expand Down
24 changes: 24 additions & 0 deletions Tests/SafeDICoreTests/TypeDescriptionTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -796,3 +796,27 @@ final class TypeDescriptionTests: XCTestCase {
}
}
}

extension TypeDescription {
var isUnknown: Bool {
switch self {
case .any,
.array,
.attributed,
.closure,
.composition,
.dictionary,
.implicitlyUnwrappedOptional,
.metatype,
.nested,
.optional,
.simple,
.some,
.tuple,
.void:
false
case .unknown:
true
}
}
}
Loading

0 comments on commit af4c468

Please sign in to comment.