Skip to content

Commit

Permalink
feat: added decoding/encoding ignore attributes (#16)
Browse files Browse the repository at this point in the history
* feat: added decoding/encoding ignore attributes
allowed explicit encoding with coding attributes

* test: added integration tests with other macros
  • Loading branch information
soumyamahunt committed Sep 20, 2023
1 parent 12f3177 commit 94855a0
Show file tree
Hide file tree
Showing 47 changed files with 1,998 additions and 570 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -13,13 +13,13 @@ extension SyntaxProtocol {
/// - Returns: All the attributes of provided type.
func attributes<A: Attribute>(for type: A.Type) -> [A] {
guard
case .choices(let choices) = Self.structure
case .choices(let choices) = DeclSyntax.structure
else { return [] }

let declSyntaxChoice = choices.first { choice in
if case .node(let type) = choice {
return type is AttributableDeclSyntax.Type
&& self.is(type)
&& self.is(type)
} else {
return false
}
Expand Down
37 changes: 18 additions & 19 deletions Sources/CodableMacroPlugin/Attributes/Attribute.swift
Original file line number Diff line number Diff line change
Expand Up @@ -20,23 +20,17 @@ protocol Attribute: AttachedMacro {
/// - Parameter node: The attribute syntax to create with.
/// - Returns: Newly created attribute instance.
init?(from node: AttributeSyntax)
/// Validates this attribute is used properly with the declaration provided.
/// Builds diagnoser that can validate this macro
/// attached declaration.
///
/// This type checks the attribute usage doesn't violate any conditions
/// and produces diagnostics for such violations in the macro expansion
/// context provided.
/// All the usage conditions are provided to built
/// diagnoser to check violations in attached
/// declaration in the macro expansion context
/// provided. Diagnostics are produced in case of
/// any violation.
///
/// - Parameters:
/// - declaration: The declaration this macro attribute is attached to.
/// - context: The macro expansion context validation performed in.
///
/// - Returns: True if attribute usage satisfies all conditions,
/// false otherwise.
@discardableResult
func validate(
declaration: some DeclSyntaxProtocol,
in context: some MacroExpansionContext
) -> Bool
/// - Returns: The built diagnoser instance.
func diagnoser() -> DiagnosticProducer
}

extension Attribute {
Expand All @@ -55,16 +49,21 @@ extension Attribute {

/// Message id for misuse of this attribute.
///
/// This attribute can must be removed or its usage condition must be satisfied.
/// This attribute can must be removed or its usage condition
/// must be satisfied.
var misuseMessageID: MessageID { .messageID("\(id)-misuse") }
/// Message id for unnecessary usage of this attribute.
///
/// This attribute can be omitted in such scenario and the final result will still be the same.
/// This attribute can be omitted in such scenario and the
/// final result will still be the same.
var unusedMessageID: MessageID { .messageID("\(id)-unused") }

/// Checks whether this attribute is applied more than once to provided declaration.
/// Checks whether this attribute is applied more than once to
/// provided declaration.
///
/// - Parameter declaration: The declaration this macro attribute
/// is attached to.
///
/// - Parameter declaration: The declaration this macro attribute is attached to.
/// - Returns: Whether this attribute is applied more than once.
func isDuplicated(in declaration: some SyntaxProtocol) -> Bool {
return declaration.attributes(for: Self.self).count > 1
Expand Down
51 changes: 16 additions & 35 deletions Sources/CodableMacroPlugin/Attributes/Codable.swift
Original file line number Diff line number Diff line change
Expand Up @@ -35,43 +35,19 @@ struct Codable: Attribute {
self.node = node
}

/// Validates this attribute is used properly with the declaration provided.
/// Builds diagnoser that can validate this macro
/// attached declaration.
///
/// The declaration has to be a `struct` declaration, otherwise validation fails
/// and diagnostics created with `misuseMessageID`.
/// Builds diagnoser that validates attached declaration
/// is `struct` declaration and macro usage is not
/// duplicated for the same declaration.
///
/// - Parameters:
/// - declaration: The declaration this macro attribute is attached to.
/// - context: The macro expansion context validation performed in.
///
/// - Returns: True if attribute usage satisfies all conditions,
/// false otherwise.
@discardableResult
func validate(
declaration: some SyntaxProtocol,
in context: some MacroExpansionContext
) -> Bool {
var diagnostics: [(MetaCodableMessage, [FixIt])] = []

if !declaration.is(StructDeclSyntax.self) {
let message = node.diagnostic(
message: "@\(name) only works for struct declarations",
id: misuseMessageID,
severity: .error
)
diagnostics.append((message, [message.fixItByRemove]))
/// - Returns: The built diagnoser instance.
func diagnoser() -> DiagnosticProducer {
return AggregatedDiagnosticProducer {
expect(syntax: StructDeclSyntax.self)
cantDuplicate()
}

for (message, fixes) in diagnostics {
context.diagnose(
.init(
node: Syntax(node),
message: message,
fixIts: fixes
)
)
}
return diagnostics.isEmpty
}
}

Expand Down Expand Up @@ -117,7 +93,9 @@ extension Codable: ConformanceMacro, MemberMacro {
in context: some MacroExpansionContext
) throws -> [DeclSyntax] {
// validate proper use
guard Self(from: node)!.validate(declaration: declaration, in: context)
guard
!Self(from: node)!.diagnoser()
.produce(for: declaration, in: context)
else { return [] }

let options = Registrar.Options(modifiers: declaration.modifiers)
Expand All @@ -137,6 +115,9 @@ extension Codable: ConformanceMacro, MemberMacro {
OptionalRegistrationBuilder(base: CodedBy(from: decl))
OptionalRegistrationBuilder(base: Default(from: decl))
InitializationRegistrationBuilder<AnyVariable>()
ConditionalCodingBuilder<
InitializationVariable<AnyVariable<RequiredInitialization>>
>()
}

// register
Expand Down
68 changes: 17 additions & 51 deletions Sources/CodableMacroPlugin/Attributes/CodedAt.swift
Original file line number Diff line number Diff line change
Expand Up @@ -12,19 +12,6 @@ struct CodedAt: PropertyAttribute {
/// during initialization.
let node: AttributeSyntax

/// The attribute types this attribute can't be combined with.
///
/// If any of the attribute type that is covered in this is applied in the same
/// declaration as this attribute is attached, then diagnostics generated
/// to remove this attribute.
///
/// - Note: This attribute can't be combined with `CodedIn`
/// macro-attribute.
var cantBeCombinedWith: [PropertyAttribute.Type] {
return [
CodedIn.self
]
}
/// Creates a new instance with the provided node.
///
/// The initializer fails to create new instance if the name
Expand All @@ -40,47 +27,26 @@ struct CodedAt: PropertyAttribute {
self.node = node
}

/// Validates this attribute is used properly with the declaration provided.
/// Builds diagnoser that can validate this macro
/// attached declaration.
///
/// The following conditions are checked for validations:
/// The following conditions are checked by the
/// built diagnoser:
/// * Attached declaration is a variable declaration.
/// * Attached declaration is not a grouped variable declaration.
/// * This attribute isn't used combined with `CodedIn` attribute.
///
/// - Parameters:
/// - declaration: The declaration this macro attribute is attached to.
/// - context: The macro expansion context validation performed in.
/// * Macro usage is not duplicated for the same
/// declaration.
/// * Attached declaration is not a grouped variable
/// declaration.
/// * This attribute isn't used combined with `CodedIn`
/// and `IgnoreCoding` attribute.
///
/// - Returns: True if attribute usage satisfies all conditions,
/// false otherwise.
@discardableResult
func validate(
declaration: some SyntaxProtocol,
in context: some MacroExpansionContext
) -> Bool {
let result = performBasicValidation(of: declaration, in: context)
var diagnostics: [(MetaCodableMessage, [FixIt])] = []

if declaration.as(VariableDeclSyntax.self)?.bindings.count ?? 0 > 1 {
let message = node.diagnostic(
message:
"@\(name) can't be used with grouped variables declaration",
id: misuseMessageID,
severity: .error
)
diagnostics.append((message, [message.fixItByRemove]))
}

for (message, fixes) in diagnostics {
context.diagnose(
.init(
node: Syntax(node),
message: message,
fixIts: fixes
)
)
/// - Returns: The built diagnoser instance.
func diagnoser() -> DiagnosticProducer {
return AggregatedDiagnosticProducer {
attachedToUngroupedVariable()
cantDuplicate()
cantBeCombined(with: CodedIn.self)
cantBeCombined(with: IgnoreCoding.self)
}

return result && diagnostics.isEmpty
}
}
20 changes: 20 additions & 0 deletions Sources/CodableMacroPlugin/Attributes/CodedBy.swift
Original file line number Diff line number Diff line change
Expand Up @@ -24,4 +24,24 @@ struct CodedBy: PropertyAttribute {
else { return nil }
self.node = node
}

/// Builds diagnoser that can validate this macro
/// attached declaration.
///
/// The following conditions are checked by the
/// built diagnoser:
/// * Attached declaration is a variable declaration.
/// * Macro usage is not duplicated for the same
/// declaration.
/// * This attribute isn't used combined with
/// `IgnoreCoding` attribute.
///
/// - Returns: The built diagnoser instance.
func diagnoser() -> DiagnosticProducer {
return AggregatedDiagnosticProducer {
expect(syntax: VariableDeclSyntax.self)
cantDuplicate()
cantBeCombined(with: IgnoreCoding.self)
}
}
}
68 changes: 16 additions & 52 deletions Sources/CodableMacroPlugin/Attributes/CodedIn.swift
Original file line number Diff line number Diff line change
Expand Up @@ -15,19 +15,6 @@ struct CodedIn: PropertyAttribute {
/// during initialization.
let node: AttributeSyntax

/// The attribute types this attribute can't be combined with.
///
/// If any of the attribute type that is covered in this is applied in the same
/// declaration as this attribute is attached, then diagnostics generated
/// to remove this attribute.
///
/// - Note: This attribute can't be combined with `CodedAt`
/// macro-attribute.
var cantBeCombinedWith: [PropertyAttribute.Type] {
return [
CodedAt.self
]
}
/// Creates a new instance with the provided node.
///
/// The initializer fails to create new instance if the name
Expand All @@ -53,47 +40,24 @@ struct CodedIn: PropertyAttribute {
self.inDefaultMode = true
}

/// Validates this attribute is used properly with the declaration provided.
/// Builds diagnoser that can validate this macro
/// attached declaration.
///
/// The following conditions are checked for validations:
/// The following conditions are checked by the
/// built diagnoser:
/// * Attached declaration is a variable declaration.
/// * This attribute isn't used combined with `CodedAt` attribute.
///
/// Warning is generated if this attribute is used without any arguments,
/// but validation is success for this scenario.
///
/// - Parameters:
/// - declaration: The declaration this macro attribute is attached to.
/// - context: The macro expansion context validation performed in.
///
/// - Returns: True if attribute usage satisfies all conditions,
/// false otherwise.
@discardableResult
func validate(
declaration: some SyntaxProtocol,
in context: some MacroExpansionContext
) -> Bool {
var diagnostics: [(MetaCodableMessage, [FixIt])] = []

if node.argument?.as(TupleExprElementListSyntax.self)?.first == nil {
let message = node.diagnostic(
message: "Unnecessary use of @\(name) without arguments",
id: unusedMessageID,
severity: .warning
)
diagnostics.append((message, [message.fixItByRemove]))
/// * Macro usage is not duplicated for the same
/// declaration.
/// * This attribute isn't used combined with `CodedAt`
/// and `IgnoreCoding` attribute.
///
/// - Returns: The built diagnoser instance.
func diagnoser() -> DiagnosticProducer {
return AggregatedDiagnosticProducer {
expect(syntax: VariableDeclSyntax.self)
cantDuplicate()
cantBeCombined(with: CodedAt.self)
cantBeCombined(with: IgnoreCoding.self)
}

for (message, fixes) in diagnostics {
context.diagnose(
.init(
node: Syntax(node),
message: message,
fixIts: fixes
)
)
}

return performBasicValidation(of: declaration, in: context)
}
}
20 changes: 20 additions & 0 deletions Sources/CodableMacroPlugin/Attributes/Default.swift
Original file line number Diff line number Diff line change
Expand Up @@ -24,4 +24,24 @@ struct Default: PropertyAttribute {
else { return nil }
self.node = node
}

/// Builds diagnoser that can validate this macro
/// attached declaration.
///
/// The following conditions are checked by the
/// built diagnoser:
/// * Attached declaration is a variable declaration.
/// * Macro usage is not duplicated for the same
/// declaration.
/// * This attribute isn't used combined with
/// `IgnoreCoding` attribute.
///
/// - Returns: The built diagnoser instance.
func diagnoser() -> DiagnosticProducer {
return AggregatedDiagnosticProducer {
expect(syntax: VariableDeclSyntax.self)
cantDuplicate()
cantBeCombined(with: IgnoreCoding.self)
}
}
}

0 comments on commit 94855a0

Please sign in to comment.