From e2b26f96841d39700bc591993f5ed1fa25a84d81 Mon Sep 17 00:00:00 2001 From: Joseph Duffy Date: Wed, 31 Jan 2024 18:56:15 -0800 Subject: [PATCH 1/5] Ignore files that cannot be reasonably tested --- codecov.yml | 3 +++ 1 file changed, 3 insertions(+) create mode 100644 codecov.yml diff --git a/codecov.yml b/codecov.yml new file mode 100644 index 0000000..3ea489c --- /dev/null +++ b/codecov.yml @@ -0,0 +1,3 @@ +ignore: + - "HashableMacro/Sources/HashableMacroMacros/CustomHashablePlugin.swift" + - "HashableMacro/Tests/HashableMacroTests/HashableMacroAPITests.swift" From 785bdaf5d3b468374291ea768f5701081fd10f70 Mon Sep 17 00:00:00 2001 From: Joseph Duffy Date: Wed, 31 Jan 2024 19:09:17 -0800 Subject: [PATCH 2/5] Add more macro expansion tests --- .../HashableMacroMacros/HashableMacro.swift | 2 +- .../HashableMacroTests.swift | 610 +++++++++++++++++- 2 files changed, 610 insertions(+), 2 deletions(-) diff --git a/Sources/HashableMacroMacros/HashableMacro.swift b/Sources/HashableMacroMacros/HashableMacro.swift index 8fbc7dc..b1a9f41 100644 --- a/Sources/HashableMacroMacros/HashableMacro.swift +++ b/Sources/HashableMacroMacros/HashableMacro.swift @@ -589,7 +589,7 @@ public struct HashableMacro: ExtensionMacro { comparisons = InfixOperatorExprSyntax( leftOperand: existingComparisons, operator: BinaryOperatorExprSyntax( - leadingTrivia: .newline.appending(Trivia.spaces(8)), + leadingTrivia: .newline.appending(Trivia.spaces(4)), operator: .binaryOperator("&&") ), rightOperand: comparison diff --git a/Tests/HashableMacroTests/HashableMacroTests.swift b/Tests/HashableMacroTests/HashableMacroTests.swift index 79bd521..4e06e2a 100644 --- a/Tests/HashableMacroTests/HashableMacroTests.swift +++ b/Tests/HashableMacroTests/HashableMacroTests.swift @@ -296,7 +296,7 @@ final class HashableMacroTests: XCTestCase { #endif } - func testTypeWithExplicitHashableConformation() throws { + func testTypeWithExplicitHashableConformance() throws { #if canImport(HashableMacroMacros) assertMacro(testMacros) { """ @@ -466,6 +466,39 @@ final class HashableMacroTests: XCTestCase { #endif } + func testStructWithAllExcludedProperties() throws { + #if canImport(HashableMacroMacros) + assertMacro(testMacros) { + """ + @Hashable(_disableNSObjectSubclassSupport: true) + struct TestStruct { + @NotHashed + var hashedProperty: String + } + """ + } expansion: { + """ + struct TestStruct { + var hashedProperty: String + } + + extension TestStruct { + func hash(into hasher: inout Hasher) { + } + } + + extension TestStruct { + static func ==(lhs: TestStruct, rhs: TestStruct) -> Bool { + return true + } + } + """ + } + #else + throw XCTSkip("Macros are only supported when running tests for the host platform") + #endif + } + func testPublicFinalType() throws { #if canImport(HashableMacroMacros) assertMacro(testMacros) { @@ -500,6 +533,74 @@ final class HashableMacroTests: XCTestCase { #endif } + func testExplicitFinalHashInto() throws { + #if canImport(HashableMacroMacros) + assertMacro(testMacros) { + """ + @Hashable(_disableNSObjectSubclassSupport: true, finalHashInto: true) + public class TestClass { + @Hashed + var hashedProperty: String + } + """ + } expansion: { + """ + public class TestClass { + var hashedProperty: String + } + + extension TestClass { + public final func hash(into hasher: inout Hasher) { + hasher.combine(self.hashedProperty) + } + } + + extension TestClass { + public static func ==(lhs: TestClass, rhs: TestClass) -> Bool { + return lhs.hashedProperty == rhs.hashedProperty + } + } + """ + } + #else + throw XCTSkip("Macros are only supported when running tests for the host platform") + #endif + } + + func testExplicitNotFinalHashInto() throws { + #if canImport(HashableMacroMacros) + assertMacro(testMacros) { + """ + @Hashable(_disableNSObjectSubclassSupport: true, finalHashInto: false) + public class TestClass { + @Hashed + var hashedProperty: String + } + """ + } expansion: { + """ + public class TestClass { + var hashedProperty: String + } + + extension TestClass { + public func hash(into hasher: inout Hasher) { + hasher.combine(self.hashedProperty) + } + } + + extension TestClass { + public static func ==(lhs: TestClass, rhs: TestClass) -> Bool { + return lhs.hashedProperty == rhs.hashedProperty + } + } + """ + } + #else + throw XCTSkip("Macros are only supported when running tests for the host platform") + #endif + } + func testComputedProperty() throws { #if canImport(HashableMacroMacros) assertMacro(testMacros) { @@ -538,6 +639,99 @@ final class HashableMacroTests: XCTestCase { #endif } + func testComputedPropertyWithExplicitGet() throws { + #if canImport(HashableMacroMacros) + assertMacro(testMacros) { + """ + @Hashable(_disableNSObjectSubclassSupport: true) + struct TypeWithComputedPropertt { + @Hashed + var hashedProperty: String + + var computedProperty: String { + get { + "computed" + } + } + } + """ + } expansion: { + """ + struct TypeWithComputedPropertt { + var hashedProperty: String + + var computedProperty: String { + get { + "computed" + } + } + } + + extension TypeWithComputedPropertt { + func hash(into hasher: inout Hasher) { + hasher.combine(self.hashedProperty) + } + } + + extension TypeWithComputedPropertt { + static func ==(lhs: TypeWithComputedPropertt, rhs: TypeWithComputedPropertt) -> Bool { + return lhs.hashedProperty == rhs.hashedProperty + } + } + """ + } + #else + throw XCTSkip("Macros are only supported when running tests for the host platform") + #endif + } + + func testStoredPropertyWithDidSet() throws { + #if canImport(HashableMacroMacros) + assertMacro(testMacros) { + """ + @Hashable(_disableNSObjectSubclassSupport: true) + struct TypeWithComputedPropertt { + var hashedProperty: String + + var otherHashedProperty: String { + didSet { + // ... do something + } + } + } + """ + } expansion: { + """ + struct TypeWithComputedPropertt { + var hashedProperty: String + + var otherHashedProperty: String { + didSet { + // ... do something + } + } + } + + extension TypeWithComputedPropertt { + func hash(into hasher: inout Hasher) { + hasher.combine(self.hashedProperty) + hasher.combine(self.otherHashedProperty) + } + } + + extension TypeWithComputedPropertt { + static func ==(lhs: TypeWithComputedPropertt, rhs: TypeWithComputedPropertt) -> Bool { + return lhs.hashedProperty == rhs.hashedProperty + && lhs.otherHashedProperty == rhs.otherHashedProperty + } + } + """ + } + #else + throw XCTSkip("Macros are only supported when running tests for the host platform") + #endif + } + func testMixedHashedNotHashedDiagnostic() throws { #if canImport(HashableMacroMacros) assertMacro(testMacros) { @@ -598,5 +792,419 @@ final class HashableMacroTests: XCTestCase { throw XCTSkip("Macros are only supported when running tests for the host platform") #endif } + + func testDirectNSObjectSubclass() throws { + #if canImport(HashableMacroMacros) + assertMacro(testMacros) { + """ + @Hashable(_disableNSObjectSubclassSupport: false) + class TestClass: NSObject { + @Hashed + var hashedProperty: String + + @Hashed + var secondHashedProperty: String + + var notHashedProperty: String + } + """ + } expansion: { + """ + class TestClass: NSObject { + var hashedProperty: String + var secondHashedProperty: String + + var notHashedProperty: String + } + + extension TestClass { + override var hash: Int { + var hasher = Hasher() + hasher.combine(self.hashedProperty) + hasher.combine(self.secondHashedProperty) + return hasher.finalize() + } + } + + extension TestClass { + override func isEqual(_ object: Any?) -> Bool { + guard let object else { + return false + } + guard type(of: self) == type(of: object) else { + return false + } + guard let object = object as? Self else { + return false + } + return self.hashedProperty == object.hashedProperty + && self.secondHashedProperty == object.secondHashedProperty + } + } + """ + } + #else + throw XCTSkip("Macros are only supported when running tests for the host platform") + #endif + } + + func testDirectNSObjectSubclass_neverCallSuper() throws { + #if canImport(HashableMacroMacros) + assertMacro(testMacros) { + """ + @Hashable(_disableNSObjectSubclassSupport: false, nsObjectSubclassBehaviour: .neverCallSuper) + class TestClass: NSObject { + @Hashed + var hashedProperty: String + + var notHashedProperty: String + } + """ + } expansion: { + """ + class TestClass: NSObject { + var hashedProperty: String + + var notHashedProperty: String + } + + extension TestClass { + override var hash: Int { + var hasher = Hasher() + hasher.combine(self.hashedProperty) + return hasher.finalize() + } + } + + extension TestClass { + override func isEqual(_ object: Any?) -> Bool { + guard let object else { + return false + } + guard type(of: self) == type(of: object) else { + return false + } + guard let object = object as? Self else { + return false + } + return self.hashedProperty == object.hashedProperty + } + } + """ + } + #else + throw XCTSkip("Macros are only supported when running tests for the host platform") + #endif + } + + func testDirectNSObjectSubclass_callSuperUnlessDirectSubclass() throws { + #if canImport(HashableMacroMacros) + assertMacro(testMacros) { + """ + @Hashable(_disableNSObjectSubclassSupport: false, nsObjectSubclassBehaviour: .callSuperUnlessDirectSubclass) + class TestClass: NSObject { + @Hashed + var hashedProperty: String + + var notHashedProperty: String + } + """ + } expansion: { + """ + class TestClass: NSObject { + var hashedProperty: String + + var notHashedProperty: String + } + + extension TestClass { + override var hash: Int { + var hasher = Hasher() + hasher.combine(self.hashedProperty) + return hasher.finalize() + } + } + + extension TestClass { + override func isEqual(_ object: Any?) -> Bool { + guard let object else { + return false + } + guard type(of: self) == type(of: object) else { + return false + } + guard let object = object as? Self else { + return false + } + return self.hashedProperty == object.hashedProperty + } + } + """ + } + #else + throw XCTSkip("Macros are only supported when running tests for the host platform") + #endif + } + + func testDirectNSObjectSubclass_alwaysCallSuper() throws { + #if canImport(HashableMacroMacros) + assertMacro(testMacros) { + """ + @Hashable(_disableNSObjectSubclassSupport: false, nsObjectSubclassBehaviour: .alwaysCallSuper) + class TestClass: NSObject { + @Hashed + var hashedProperty: String + + var notHashedProperty: String + } + """ + } expansion: { + """ + class TestClass: NSObject { + var hashedProperty: String + + var notHashedProperty: String + } + + extension TestClass { + override var hash: Int { + var hasher = Hasher() + hasher.combine(super.hash) + hasher.combine(self.hashedProperty) + return hasher.finalize() + } + } + + extension TestClass { + override func isEqual(_ object: Any?) -> Bool { + guard let object else { + return false + } + guard type(of: self) == type(of: object) else { + return false + } + guard super.isEqual(object) else { + return false + } + guard let object = object as? Self else { + return false + } + return self.hashedProperty == object.hashedProperty + } + } + """ + } + #else + throw XCTSkip("Macros are only supported when running tests for the host platform") + #endif + } + + func testIndirectNSObjectSubclass() throws { + #if canImport(HashableMacroMacros) + assertMacro(testMacros) { + """ + @Hashable(_disableNSObjectSubclassSupport: false) + class TestClass: UIView { + @Hashed + var hashedProperty: String + + var notHashedProperty: String + } + """ + } expansion: { + """ + class TestClass: UIView { + var hashedProperty: String + + var notHashedProperty: String + } + + extension TestClass { + override var hash: Int { + var hasher = Hasher() + hasher.combine(super.hash) + hasher.combine(self.hashedProperty) + return hasher.finalize() + } + } + + extension TestClass { + override func isEqual(_ object: Any?) -> Bool { + guard let object else { + return false + } + guard type(of: self) == type(of: object) else { + return false + } + guard super.isEqual(object) else { + return false + } + guard let object = object as? Self else { + return false + } + return self.hashedProperty == object.hashedProperty + } + } + """ + } + #else + throw XCTSkip("Macros are only supported when running tests for the host platform") + #endif + } + + func testIndirectNSObjectSubclass_neverCallSuper() throws { + #if canImport(HashableMacroMacros) + assertMacro(testMacros) { + """ + @Hashable(_disableNSObjectSubclassSupport: false, nsObjectSubclassBehaviour: .neverCallSuper) + class TestClass: UIView { + @Hashed + var hashedProperty: String + + var notHashedProperty: String + } + """ + } expansion: { + """ + class TestClass: UIView { + var hashedProperty: String + + var notHashedProperty: String + } + + extension TestClass { + override var hash: Int { + var hasher = Hasher() + hasher.combine(self.hashedProperty) + return hasher.finalize() + } + } + + extension TestClass { + override func isEqual(_ object: Any?) -> Bool { + guard let object else { + return false + } + guard type(of: self) == type(of: object) else { + return false + } + guard let object = object as? Self else { + return false + } + return self.hashedProperty == object.hashedProperty + } + } + """ + } + #else + throw XCTSkip("Macros are only supported when running tests for the host platform") + #endif + } + + func testIndirectNSObjectSubclass_callSuperUnlessDirectSubclass() throws { + #if canImport(HashableMacroMacros) + assertMacro(testMacros) { + """ + @Hashable(_disableNSObjectSubclassSupport: false, nsObjectSubclassBehaviour: .callSuperUnlessDirectSubclass) + class TestClass: UIView { + @Hashed + var hashedProperty: String + + var notHashedProperty: String + } + """ + } expansion: { + """ + class TestClass: UIView { + var hashedProperty: String + + var notHashedProperty: String + } + + extension TestClass { + override var hash: Int { + var hasher = Hasher() + hasher.combine(super.hash) + hasher.combine(self.hashedProperty) + return hasher.finalize() + } + } + + extension TestClass { + override func isEqual(_ object: Any?) -> Bool { + guard let object else { + return false + } + guard type(of: self) == type(of: object) else { + return false + } + guard super.isEqual(object) else { + return false + } + guard let object = object as? Self else { + return false + } + return self.hashedProperty == object.hashedProperty + } + } + """ + } + #else + throw XCTSkip("Macros are only supported when running tests for the host platform") + #endif + } + + func testIndirectNSObjectSubclass_alwaysCallSuper() throws { + #if canImport(HashableMacroMacros) + assertMacro(testMacros) { + """ + @Hashable(_disableNSObjectSubclassSupport: false, nsObjectSubclassBehaviour: .alwaysCallSuper) + class TestClass: UIView { + @Hashed + var hashedProperty: String + + var notHashedProperty: String + } + """ + } expansion: { + """ + class TestClass: UIView { + var hashedProperty: String + + var notHashedProperty: String + } + + extension TestClass { + override var hash: Int { + var hasher = Hasher() + hasher.combine(super.hash) + hasher.combine(self.hashedProperty) + return hasher.finalize() + } + } + + extension TestClass { + override func isEqual(_ object: Any?) -> Bool { + guard let object else { + return false + } + guard type(of: self) == type(of: object) else { + return false + } + guard super.isEqual(object) else { + return false + } + guard let object = object as? Self else { + return false + } + return self.hashedProperty == object.hashedProperty + } + } + """ + } + #else + throw XCTSkip("Macros are only supported when running tests for the host platform") + #endif + } } #endif From fb1a216526ef325750acefa0344ddebd151d4f3d Mon Sep 17 00:00:00 2001 From: Joseph Duffy Date: Wed, 31 Jan 2024 19:48:59 -0800 Subject: [PATCH 3/5] =?UTF-8?q?Don=E2=80=99t=20run=20NSObject=20tests=20wh?= =?UTF-8?q?en=20Objective-C=20is=20not=20available?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../HashableMacroTests.swift | 32 +++++++++++++++++++ 1 file changed, 32 insertions(+) diff --git a/Tests/HashableMacroTests/HashableMacroTests.swift b/Tests/HashableMacroTests/HashableMacroTests.swift index 4e06e2a..99a50ca 100644 --- a/Tests/HashableMacroTests/HashableMacroTests.swift +++ b/Tests/HashableMacroTests/HashableMacroTests.swift @@ -795,6 +795,7 @@ final class HashableMacroTests: XCTestCase { func testDirectNSObjectSubclass() throws { #if canImport(HashableMacroMacros) + #if canImport(ObjectiveC) assertMacro(testMacros) { """ @Hashable(_disableNSObjectSubclassSupport: false) @@ -844,12 +845,16 @@ final class HashableMacroTests: XCTestCase { """ } #else + throw XCTSkip("This expansion requires Objective-C") + #endif + #else throw XCTSkip("Macros are only supported when running tests for the host platform") #endif } func testDirectNSObjectSubclass_neverCallSuper() throws { #if canImport(HashableMacroMacros) + #if canImport(ObjectiveC) assertMacro(testMacros) { """ @Hashable(_disableNSObjectSubclassSupport: false, nsObjectSubclassBehaviour: .neverCallSuper) @@ -893,12 +898,16 @@ final class HashableMacroTests: XCTestCase { """ } #else + throw XCTSkip("This expansion requires Objective-C") + #endif + #else throw XCTSkip("Macros are only supported when running tests for the host platform") #endif } func testDirectNSObjectSubclass_callSuperUnlessDirectSubclass() throws { #if canImport(HashableMacroMacros) + #if canImport(ObjectiveC) assertMacro(testMacros) { """ @Hashable(_disableNSObjectSubclassSupport: false, nsObjectSubclassBehaviour: .callSuperUnlessDirectSubclass) @@ -942,12 +951,16 @@ final class HashableMacroTests: XCTestCase { """ } #else + throw XCTSkip("This expansion requires Objective-C") + #endif + #else throw XCTSkip("Macros are only supported when running tests for the host platform") #endif } func testDirectNSObjectSubclass_alwaysCallSuper() throws { #if canImport(HashableMacroMacros) + #if canImport(ObjectiveC) assertMacro(testMacros) { """ @Hashable(_disableNSObjectSubclassSupport: false, nsObjectSubclassBehaviour: .alwaysCallSuper) @@ -995,12 +1008,16 @@ final class HashableMacroTests: XCTestCase { """ } #else + throw XCTSkip("This expansion requires Objective-C") + #endif + #else throw XCTSkip("Macros are only supported when running tests for the host platform") #endif } func testIndirectNSObjectSubclass() throws { #if canImport(HashableMacroMacros) + #if canImport(ObjectiveC) assertMacro(testMacros) { """ @Hashable(_disableNSObjectSubclassSupport: false) @@ -1048,12 +1065,16 @@ final class HashableMacroTests: XCTestCase { """ } #else + throw XCTSkip("This expansion requires Objective-C") + #endif + #else throw XCTSkip("Macros are only supported when running tests for the host platform") #endif } func testIndirectNSObjectSubclass_neverCallSuper() throws { #if canImport(HashableMacroMacros) + #if canImport(ObjectiveC) assertMacro(testMacros) { """ @Hashable(_disableNSObjectSubclassSupport: false, nsObjectSubclassBehaviour: .neverCallSuper) @@ -1097,12 +1118,16 @@ final class HashableMacroTests: XCTestCase { """ } #else + throw XCTSkip("This expansion requires Objective-C") + #endif + #else throw XCTSkip("Macros are only supported when running tests for the host platform") #endif } func testIndirectNSObjectSubclass_callSuperUnlessDirectSubclass() throws { #if canImport(HashableMacroMacros) + #if canImport(ObjectiveC) assertMacro(testMacros) { """ @Hashable(_disableNSObjectSubclassSupport: false, nsObjectSubclassBehaviour: .callSuperUnlessDirectSubclass) @@ -1150,12 +1175,16 @@ final class HashableMacroTests: XCTestCase { """ } #else + throw XCTSkip("This expansion requires Objective-C") + #endif + #else throw XCTSkip("Macros are only supported when running tests for the host platform") #endif } func testIndirectNSObjectSubclass_alwaysCallSuper() throws { #if canImport(HashableMacroMacros) + #if canImport(ObjectiveC) assertMacro(testMacros) { """ @Hashable(_disableNSObjectSubclassSupport: false, nsObjectSubclassBehaviour: .alwaysCallSuper) @@ -1203,6 +1232,9 @@ final class HashableMacroTests: XCTestCase { """ } #else + throw XCTSkip("This expansion requires Objective-C") + #endif + #else throw XCTSkip("Macros are only supported when running tests for the host platform") #endif } From 191babc276e939881597cf9a9aea8a47223f0d9c Mon Sep 17 00:00:00 2001 From: Joseph Duffy Date: Wed, 31 Jan 2024 20:21:42 -0800 Subject: [PATCH 4/5] Use CODECOV_TOKEN to upload code coverage to Codecov --- .github/workflows/tests.yml | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 667ce58..d8cafe8 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -41,6 +41,8 @@ jobs: - name: Upload coverage to Codecov uses: codecov/codecov-action@v3 + env: + CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }} with: files: ${{ join(fromJSON(steps.convert-coverage.outputs.files), ',') }} fail_ci_if_error: true @@ -102,6 +104,8 @@ jobs: - name: Upload coverage to Codecov uses: codecov/codecov-action@v3 + env: + CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }} with: files: ${{ join(fromJSON(steps.convert-coverage.outputs.files), ',') }} fail_ci_if_error: true From ef8e69c84ed61c0615dbb40375263da0d5645c37 Mon Sep 17 00:00:00 2001 From: Joseph Duffy Date: Wed, 31 Jan 2024 20:25:12 -0800 Subject: [PATCH 5/5] Remove duplicate `env` key --- .github/workflows/tests.yml | 2 -- 1 file changed, 2 deletions(-) diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index d8cafe8..9980449 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -109,8 +109,6 @@ jobs: with: files: ${{ join(fromJSON(steps.convert-coverage.outputs.files), ',') }} fail_ci_if_error: true - env: - CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }} linux_tests: name: Swift ${{ matrix.swift }} on ${{ matrix.os }}