Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions Sources/CodableMacroPlugin/Attributes/CodedBy.swift
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,8 @@ struct CodedBy: PropertyAttribute {
/// The following conditions are checked by the
/// built diagnoser:
/// * Attached declaration is a variable declaration.
/// * Attached declaration is not a static variable
/// declaration
/// * Macro usage is not duplicated for the same
/// declaration.
/// * This attribute isn't used combined with
Expand All @@ -47,6 +49,7 @@ struct CodedBy: PropertyAttribute {
func diagnoser() -> DiagnosticProducer {
return AggregatedDiagnosticProducer {
expect(syntax: VariableDeclSyntax.self)
attachedToNonStaticVariable()
cantDuplicate()
cantBeCombined(with: IgnoreCoding.self)
}
Expand Down
3 changes: 3 additions & 0 deletions Sources/CodableMacroPlugin/Attributes/Default.swift
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,8 @@ struct Default: PropertyAttribute {
/// The following conditions are checked by the
/// built diagnoser:
/// * Attached declaration is a variable declaration.
/// * Attached declaration is not a static variable
/// declaration
/// * Macro usage is not duplicated for the same
/// declaration.
/// * This attribute isn't used combined with
Expand All @@ -46,6 +48,7 @@ struct Default: PropertyAttribute {
func diagnoser() -> DiagnosticProducer {
return AggregatedDiagnosticProducer {
expect(syntax: VariableDeclSyntax.self)
attachedToNonStaticVariable()
cantDuplicate()
cantBeCombined(with: IgnoreCoding.self)
}
Expand Down
3 changes: 3 additions & 0 deletions Sources/CodableMacroPlugin/Attributes/KeyPath/CodedAt.swift
Original file line number Diff line number Diff line change
Expand Up @@ -37,13 +37,16 @@ struct CodedAt: PropertyAttribute {
/// declaration.
/// * Attached declaration is not a grouped variable
/// declaration.
/// * Attached declaration is not a static variable
/// declaration
/// * This attribute isn't used combined with `CodedIn`
/// and `IgnoreCoding` attribute.
///
/// - Returns: The built diagnoser instance.
func diagnoser() -> DiagnosticProducer {
return AggregatedDiagnosticProducer {
attachedToUngroupedVariable()
attachedToNonStaticVariable()
cantDuplicate()
cantBeCombined(with: CodedIn.self)
cantBeCombined(with: IgnoreCoding.self)
Expand Down
3 changes: 3 additions & 0 deletions Sources/CodableMacroPlugin/Attributes/KeyPath/CodedIn.swift
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,8 @@ struct CodedIn: PropertyAttribute {
/// * Attached declaration is a variable declaration.
/// * Macro usage is not duplicated for the same
/// declaration.
/// * Attached declaration is not a static variable
/// declaration
/// * This attribute isn't used combined with `CodedAt`
/// and `IgnoreCoding` attribute.
///
Expand All @@ -56,6 +58,7 @@ struct CodedIn: PropertyAttribute {
return AggregatedDiagnosticProducer {
expect(syntax: VariableDeclSyntax.self)
cantDuplicate()
attachedToNonStaticVariable()
cantBeCombined(with: CodedAt.self)
cantBeCombined(with: IgnoreCoding.self)
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,14 @@ extension RegistrationAttribute {
guard let decl = member.decl.as(VariableDeclSyntax.self)
else { return }

// The Macro fails to compile if the decl.modifiers.contains
// is directly used in the guard statement. Otherwise it should
// be used as a second condition in guard block above.
let isStatic = decl.modifiers.contains {
$0.name.tokenKind == .keyword(.static)
}
guard !isStatic else { return }

// builder
let builder =
CodingKeys(from: declaration)
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
@_implementationOnly import SwiftDiagnostics
@_implementationOnly import SwiftSyntax
@_implementationOnly import SwiftSyntaxMacros

/// A diagnostic producer type that can validate passed syntax is not a static
/// variable declaration.
///
/// This producer can be used for macro-attributes that must be attached to
/// non static variable declarations.
struct StaticVariableDeclaration<Attr: PropertyAttribute>: DiagnosticProducer {
/// The attribute for which
/// validation performed.
///
/// Uses this attribute name
/// in generated diagnostic
/// messages.
let attr: Attr

/// Creates a static variable declaration validation instance
/// with provided attribute.
///
/// - Parameter attr: The attribute for which
/// validation performed.
/// - Returns: Newly created diagnostic producer.
init(_ attr: Attr) {
self.attr = attr
}

/// Validates and produces diagnostics for the passed syntax
/// in the macro expansion context provided.
///
/// Checks whether provided syntax is a non static variable declaration,
/// for static variable declarations error diagnostics
/// are generated.
///
/// - Parameters:
/// - syntax: The syntax to validate and produce diagnostics for.
/// - context: The macro expansion context diagnostics produced in.
///
/// - Returns: True if syntax fails validation, false otherwise.
@discardableResult
func produce(
for syntax: some SyntaxProtocol,
in context: some MacroExpansionContext
) -> Bool {
// The Macro fails to compile if the .modifiers.contains
// is directly used in the guard statement.
let isStatic = syntax.as(VariableDeclSyntax.self)?
.modifiers.contains { $0.name.tokenKind == .keyword(.static) }
guard isStatic ?? false else { return false }
let message = attr.node.diagnostic(
message:
"@\(attr.name) can't be used with static variables declarations",
id: attr.misuseMessageID,
severity: .error
)
context.diagnose(attr: attr, message: message)
return true
}
}

extension PropertyAttribute {
/// Indicates attribute must be attached to non static variable declaration.
///
/// The created diagnostic producer produces error diagnostic,
/// if attribute is attached to static variable declarations.
///
/// - Returns: Static variable declaration validation diagnostic producer.
func attachedToNonStaticVariable() -> StaticVariableDeclaration<Self> {
return .init(self)
}
}
41 changes: 41 additions & 0 deletions Tests/MetaCodableTests/CodableTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,47 @@ final class CodableTests: XCTestCase {
)
}

func testWithoutAnyCustomizationWithStaticVar() throws {
assertMacroExpansion(
"""
@Codable
struct SomeCodable {
let value: String
static let otherValue: String
public private(set) static var valueWithModifiers: String
}
""",
expandedSource:
"""
struct SomeCodable {
let value: String
static let otherValue: String
public private(set) static var valueWithModifiers: String
}

extension SomeCodable: Decodable {
init(from decoder: any Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
self.value = try container.decode(String.self, forKey: CodingKeys.value)
}
}

extension SomeCodable: Encodable {
func encode(to encoder: any Encoder) throws {
var container = encoder.container(keyedBy: CodingKeys.self)
try container.encode(self.value, forKey: CodingKeys.value)
}
}

extension SomeCodable {
enum CodingKeys: String, CodingKey {
case value = "value"
}
}
"""
)
}

func testOnlyDecodeConformance() throws {
assertMacroExpansion(
"""
Expand Down
76 changes: 76 additions & 0 deletions Tests/MetaCodableTests/CodedAtTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,82 @@ final class CodedAtTests: XCTestCase {
)
}

func testMisuseOnStaticVariableDeclaration() throws {
assertMacroExpansion(
"""
struct SomeCodable {
@CodedAt
static let value: String
}
""",
expandedSource:
"""
struct SomeCodable {
static let value: String
}
""",
diagnostics: [
.init(
id: CodedAt.misuseID,
message:
"@CodedAt can't be used with static variables declarations",
line: 2, column: 5,
fixIts: [
.init(message: "Remove @CodedAt attribute")
]
)
]
)
}

func testMisuseOnStaticWithCodedByDefaultVariableDeclaration() throws {
assertMacroExpansion(
"""
struct SomeCodable {
@Default("some")
@CodedBy(Since1970DateCoder())
@CodedAt
static let value: String
}
""",
expandedSource:
"""
struct SomeCodable {
static let value: String
}
""",
diagnostics: [
.init(
id: Default.misuseID,
message:
"@Default can't be used with static variables declarations",
line: 2, column: 5,
fixIts: [
.init(message: "Remove @Default attribute")
]
),
.init(
id: CodedBy.misuseID,
message:
"@CodedBy can't be used with static variables declarations",
line: 3, column: 5,
fixIts: [
.init(message: "Remove @CodedBy attribute")
]
),
.init(
id: CodedAt.misuseID,
message:
"@CodedAt can't be used with static variables declarations",
line: 4, column: 5,
fixIts: [
.init(message: "Remove @CodedAt attribute")
]
),
]
)
}

func testMisuseInCombinationWithCodedInMacro() throws {
assertMacroExpansion(
"""
Expand Down
28 changes: 28 additions & 0 deletions Tests/MetaCodableTests/CodedInTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,34 @@ final class CodedInTests: XCTestCase {
)
}

func testMisuseOnStaticVariableDeclaration() throws {
assertMacroExpansion(
"""
struct SomeCodable {
@CodedIn
static let value: String
}
""",
expandedSource:
"""
struct SomeCodable {
static let value: String
}
""",
diagnostics: [
.init(
id: CodedIn.misuseID,
message:
"@CodedIn can't be used with static variables declarations",
line: 2, column: 5,
fixIts: [
.init(message: "Remove @CodedIn attribute")
]
)
]
)
}

func testDuplicatedMisuse() throws {
assertMacroExpansion(
"""
Expand Down