-
Notifications
You must be signed in to change notification settings - Fork 5
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #19 from MetalheadSanya/diagnostic
Append diagnostic for correct errors for unsupported features
- Loading branch information
Showing
11 changed files
with
850 additions
and
42 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
18 changes: 18 additions & 0 deletions
18
Sources/SwiftMockMacros/Diagnostic/AssociatedTypeDiagnostic.swift
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,18 @@ | ||
// | ||
// AssociatedTypeDiagnostic.swift | ||
// | ||
// | ||
// Created by Alexandr Zalutskiy on 19/10/2023. | ||
// | ||
|
||
import SwiftCompilerPlugin | ||
import SwiftDiagnostics | ||
import SwiftSyntax | ||
|
||
extension Diagnostic { | ||
static func validateAssociatedTypeDecl(_ declaration: AssociatedTypeDeclSyntax) throws { | ||
let diagnostic = Diagnostic(node: declaration, message: DiagnosticMessage.associatedtypeIsNotSupported) | ||
throw DiagnosticError(diagnostic: diagnostic) | ||
} | ||
} | ||
|
129 changes: 129 additions & 0 deletions
129
Sources/SwiftMockMacros/Diagnostic/DiagnosticError.swift
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,129 @@ | ||
// | ||
// DiagnosticMessage.swift | ||
// | ||
// | ||
// Created by Alexandr Zalutskiy on 17/10/2023. | ||
// | ||
|
||
import SwiftDiagnostics | ||
|
||
struct DiagnosticError: Error { | ||
let diagnostic: Diagnostic | ||
} | ||
|
||
enum DiagnosticMessage: String, SwiftDiagnostics.DiagnosticMessage { | ||
case notAProtocol | ||
case `private` | ||
case filePrivate | ||
// FIXME: add support for internal protocols | ||
case notAPublicProtocol | ||
// TODO: support for attributes | ||
case attributesIsNotSupported | ||
// TODO: support primary associated types | ||
case primaryAssociatedTypesIsNotSupported | ||
case inheritanceIsNotSupported | ||
|
||
// TODO: support for generic parameters | ||
case genericParametersIsNotSupported | ||
// TODO: support for rethrows | ||
case rethrowsIsNotSupported | ||
// TODO: support for reasync | ||
case reasyncIsNotSupported | ||
|
||
// TODO: support for associated types | ||
case associatedtypeIsNotSupported | ||
|
||
case propertyAccessorsNotSpecified | ||
case propertyAccessorMustBeGetOrSet | ||
// TODO: support for get async in properties | ||
case asyncPropertiesIsNotSupported | ||
// TODO: support for get throws in properties | ||
case throwsPropertiesIsNotSupported | ||
|
||
case unknownEffectSpecifierInPropertyDeclaration | ||
|
||
var severity: DiagnosticSeverity { | ||
switch self { | ||
case .notAProtocol: | ||
return .error | ||
case .private: | ||
return .error | ||
case .filePrivate: | ||
return .error | ||
case .notAPublicProtocol: | ||
return .error | ||
case .attributesIsNotSupported: | ||
return .error | ||
case .primaryAssociatedTypesIsNotSupported: | ||
return .error | ||
case .inheritanceIsNotSupported: | ||
return .error | ||
|
||
case .genericParametersIsNotSupported: | ||
return .error | ||
case .rethrowsIsNotSupported: | ||
return .error | ||
case .reasyncIsNotSupported: | ||
return .error | ||
|
||
case .associatedtypeIsNotSupported: | ||
return .error | ||
|
||
case .propertyAccessorsNotSpecified: | ||
return .error | ||
case .propertyAccessorMustBeGetOrSet: | ||
return .error | ||
case .asyncPropertiesIsNotSupported: | ||
return .error | ||
case .throwsPropertiesIsNotSupported: | ||
return .error | ||
case .unknownEffectSpecifierInPropertyDeclaration: | ||
return .error | ||
} | ||
} | ||
|
||
var message: String { | ||
switch self { | ||
case .notAProtocol: | ||
return "'@Mock' can only be applied to a 'protocol'" | ||
case .private: | ||
return "'@Mock' cannot be applied to a 'private protocol'" | ||
case .filePrivate: | ||
return "'@Mock' cannot be applied to a 'fileprivate protocol'" | ||
case .notAPublicProtocol: | ||
return "'@Mock' can only be applied to a 'public protocol'" | ||
case .attributesIsNotSupported: | ||
return "'@Mock' doesn't support attributes" | ||
case .primaryAssociatedTypesIsNotSupported: | ||
return "'@Mock' cannot be applied to a 'protocol' with primary associated types" | ||
case .inheritanceIsNotSupported: | ||
return "'@Mock' can only be applied to a non-inherited 'protocol'" | ||
|
||
case .genericParametersIsNotSupported: | ||
return "'@Mock' doesn't support generic parameters" | ||
case .rethrowsIsNotSupported: | ||
return "'@Mock' doesn't support rethrows methods" | ||
case .reasyncIsNotSupported: | ||
return "'@Mock' doesn't support reasync methods" | ||
|
||
case .associatedtypeIsNotSupported: | ||
return "'@Mock' doesn't support associatedtypes" | ||
|
||
case .propertyAccessorsNotSpecified: | ||
return "Property in protocol must have explicit { get } or { get set } specifier" | ||
case .propertyAccessorMustBeGetOrSet: | ||
return "Expected get or set in a protocol property" | ||
|
||
case .asyncPropertiesIsNotSupported: | ||
return "'@Mock' doesn't support async properties" | ||
case .throwsPropertiesIsNotSupported: | ||
return "'@Mock' doesn't support throws properties" | ||
case .unknownEffectSpecifierInPropertyDeclaration: | ||
return "'@Mock' found unknown effect specifier in property declaration, please report in issue in GitHub" | ||
} | ||
} | ||
|
||
var diagnosticID: MessageID { | ||
MessageID(domain: "SwiftMockMacros", id: rawValue) | ||
} | ||
} |
36 changes: 36 additions & 0 deletions
36
Sources/SwiftMockMacros/Diagnostic/FunctionDiagnostic.swift
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,36 @@ | ||
// | ||
// FunctionDiagnostic.swift | ||
// | ||
// | ||
// Created by Alexandr Zalutskiy on 18/10/2023. | ||
// | ||
|
||
import SwiftCompilerPlugin | ||
import SwiftDiagnostics | ||
import SwiftSyntax | ||
|
||
extension Diagnostic { | ||
static func validateFunctionDecl(_ declaration: FunctionDeclSyntax) throws { | ||
if let attribute = declaration.attributes.first { | ||
let diagnostic = Diagnostic(node: attribute, message: DiagnosticMessage.attributesIsNotSupported) | ||
throw DiagnosticError(diagnostic: diagnostic) | ||
} | ||
|
||
if let genericParameterClause = declaration.genericParameterClause { | ||
let diagnostic = Diagnostic(node: genericParameterClause, message: DiagnosticMessage.genericParametersIsNotSupported) | ||
throw DiagnosticError(diagnostic: diagnostic) | ||
} | ||
|
||
if let effectSpecifiers = declaration.signature.effectSpecifiers { | ||
if let asyncSpecifier = effectSpecifiers.asyncSpecifier, asyncSpecifier.isReasync { | ||
let diagnostic = Diagnostic(node: asyncSpecifier, message: DiagnosticMessage.reasyncIsNotSupported) | ||
throw DiagnosticError(diagnostic: diagnostic) | ||
} | ||
|
||
if let throwsSpecifier = effectSpecifiers.throwsSpecifier, throwsSpecifier.isRethrows { | ||
let diagnostic = Diagnostic(node: throwsSpecifier, message: DiagnosticMessage.rethrowsIsNotSupported) | ||
throw DiagnosticError(diagnostic: diagnostic) | ||
} | ||
} | ||
} | ||
} |
51 changes: 51 additions & 0 deletions
51
Sources/SwiftMockMacros/Diagnostic/PropertyDiagnostic.swift
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,51 @@ | ||
// | ||
// PropertyDiagnostic.swift | ||
// | ||
// | ||
// Created by Alexandr Zalutskiy on 19/10/2023. | ||
// | ||
|
||
import SwiftCompilerPlugin | ||
import SwiftDiagnostics | ||
import SwiftSyntax | ||
|
||
extension Diagnostic { | ||
static func validatePropertyDeclaration(_ declaration: VariableDeclSyntax) throws { | ||
for binding in declaration.bindings { | ||
// Protocols must contains accessor block by language design | ||
guard let accessorBlock = binding.accessorBlock else { | ||
let diagnostic = Diagnostic(node: declaration, message: DiagnosticMessage.propertyAccessorsNotSpecified) | ||
throw DiagnosticError(diagnostic: diagnostic) | ||
} | ||
// Protocols must contains accessor block by language design | ||
guard case let .`accessors`(accessorList) = accessorBlock.accessors else { | ||
let diagnostic = Diagnostic( | ||
node: declaration, | ||
message: DiagnosticMessage.propertyAccessorsNotSpecified | ||
) | ||
throw DiagnosticError(diagnostic: diagnostic) | ||
} | ||
|
||
for accessorDecl in accessorList { | ||
let accessorSpecifier = accessorDecl.accessorSpecifier | ||
guard accessorSpecifier.isGet || accessorSpecifier.isSet else { | ||
let diagnostic = Diagnostic(node: accessorSpecifier, message: DiagnosticMessage.propertyAccessorMustBeGetOrSet) | ||
throw DiagnosticError(diagnostic: diagnostic) | ||
} | ||
|
||
if let effectSpecifiers = accessorDecl.effectSpecifiers { | ||
if let asyncSpecifier = effectSpecifiers.asyncSpecifier { | ||
let diagnostic = Diagnostic(node: asyncSpecifier, message: DiagnosticMessage.asyncPropertiesIsNotSupported) | ||
throw DiagnosticError(diagnostic: diagnostic) | ||
} | ||
if let throwsSpecifier = effectSpecifiers.throwsSpecifier { | ||
let diagnostic = Diagnostic(node: throwsSpecifier, message: DiagnosticMessage.throwsPropertiesIsNotSupported) | ||
throw DiagnosticError(diagnostic: diagnostic) | ||
} | ||
let diagnostic = Diagnostic(node: effectSpecifiers, message: DiagnosticMessage.unknownEffectSpecifierInPropertyDeclaration) | ||
throw DiagnosticError(diagnostic: diagnostic) | ||
} | ||
} | ||
} | ||
} | ||
} |
84 changes: 84 additions & 0 deletions
84
Sources/SwiftMockMacros/Diagnostic/ProtocolDiagnostic.swift
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,84 @@ | ||
// | ||
// ProtocolDiagnostic.swift | ||
// | ||
// | ||
// Created by Alexandr Zalutskiy on 18/10/2023. | ||
// | ||
|
||
import SwiftCompilerPlugin | ||
import SwiftDiagnostics | ||
import SwiftSyntax | ||
|
||
extension Diagnostic { | ||
static func extractProtocolDecl(_ declaration: some DeclSyntaxProtocol) throws -> ProtocolDeclSyntax { | ||
guard let declaration = declaration.as(ProtocolDeclSyntax.self) else { | ||
let diagnostic = Diagnostic(node: declaration, message: DiagnosticMessage.notAProtocol) | ||
throw DiagnosticError(diagnostic: diagnostic) | ||
} | ||
|
||
return declaration | ||
} | ||
|
||
static func validateProtocolDecl(_ declaration: ProtocolDeclSyntax) throws { | ||
if let modifier = declaration.modifiers.first(where: { $0.name.text == privateModifier.name.text }) { | ||
let diagnostic = Diagnostic(node: modifier, message: DiagnosticMessage.private) | ||
throw DiagnosticError(diagnostic: diagnostic) | ||
} | ||
if let modifier = declaration.modifiers.first(where: { $0.name.text == fileprivateModifier.name.text }) { | ||
let diagnostic = Diagnostic(node: modifier, message: DiagnosticMessage.filePrivate) | ||
throw DiagnosticError(diagnostic: diagnostic) | ||
} | ||
|
||
// Start | ||
// FIXME: add support for internal protocols | ||
// They can be used using `@testable import` | ||
if !declaration.modifiers.contains(where: { $0.name.text == publicModifier.name.text }) { | ||
if let modifier = declaration.modifiers.first { | ||
let diagnostic = Diagnostic(node: modifier, message: DiagnosticMessage.notAPublicProtocol) | ||
throw DiagnosticError(diagnostic: diagnostic) | ||
} else { | ||
let diagnostic = Diagnostic(node: declaration.protocolKeyword, message: DiagnosticMessage.notAPublicProtocol) | ||
throw DiagnosticError(diagnostic: diagnostic) | ||
} | ||
} | ||
// End | ||
|
||
// TODO: support for attributes | ||
if let attribute = declaration.attributes.first(where: { !$0.isMock }) { | ||
let diagnostic = Diagnostic(node: attribute, message: DiagnosticMessage.attributesIsNotSupported) | ||
throw DiagnosticError(diagnostic: diagnostic) | ||
} | ||
|
||
// TODO: support for primary associated types | ||
if let primaryAssociatedTypeClause = declaration.primaryAssociatedTypeClause { | ||
let diagnostic = Diagnostic(node: primaryAssociatedTypeClause, message: DiagnosticMessage.primaryAssociatedTypesIsNotSupported) | ||
throw DiagnosticError(diagnostic: diagnostic) | ||
} | ||
|
||
if let inheritanceClause = declaration.inheritanceClause, inheritanceClause.inheritedTypes.contains(where: { !$0.type.isAnyObject }) { | ||
let diagnostic = Diagnostic(node: inheritanceClause, message: DiagnosticMessage.inheritanceIsNotSupported) | ||
throw DiagnosticError(diagnostic: diagnostic) | ||
} | ||
|
||
for member in declaration.memberBlock.members { | ||
if let functionDecl = member.decl.as(FunctionDeclSyntax.self) { | ||
try Diagnostic.validateFunctionDecl(functionDecl) | ||
} else if let associatedTypeDecl = member.decl.as(AssociatedTypeDeclSyntax.self) { | ||
try Diagnostic.validateAssociatedTypeDecl(associatedTypeDecl) | ||
} else if let variableDeclaration = member.decl.as(VariableDeclSyntax.self) { | ||
try Diagnostic.validatePropertyDeclaration(variableDeclaration) | ||
} | ||
} | ||
} | ||
} | ||
|
||
private extension AttributeListSyntax.Element { | ||
var isMock: Bool { | ||
switch self { | ||
case .attribute(let attribute): | ||
return attribute.attributeName.isMock | ||
case .ifConfigDecl: | ||
return false | ||
} | ||
} | ||
} |
Oops, something went wrong.