From 370d44b6a620732501d178f2b68b15068256646f Mon Sep 17 00:00:00 2001 From: Sebastian Stehle Date: Wed, 29 Mar 2023 00:29:49 +0200 Subject: [PATCH 1/6] feat: changed CompareStrategy to return CompareResult * Ensure that the correct diff is returned. * Use records. * Move kind to beginning of text. * Fix compilation error and test for custom diff. * Update src/AngleSharp.Diffing/Core/CompareResult.cs Co-authored-by: Egil Hansen * Update src/AngleSharp.Diffing/Core/Comparison.cs Co-authored-by: Egil Hansen * Update src/AngleSharp.Diffing/Core/AttributeComparison.cs Co-authored-by: Egil Hansen * 1. Rename CompareResultDecision 2. Move method inside type. * Rename method. * Get rid of StylesOrder * More records. * refactor: move IsExternalInit to folder matching namespace * add comment to explain use of ReferenceEquals * Move types into two files * simplify attrdiff object hierarchi * remove diff type check * clean up using statements * refactor: IsSameOrSkip as property * refactor: normalize tests * feat: add CommentComparer tests * nodes do not have different closing tags, elements do, so switch to using ElementDiff when comparing elements * fix: override ComparisonSource.ToString for better output * fix: use unspecified when no diff is returned * override ToString in AttributeComparisonSource * bump version number --------- Co-authored-by: Egil Hansen --- CHANGELOG.md | 5 + .../Core/HtmlDifferenceEngineTest.cs | 109 +++++++++++++++--- .../DiffingTestBase.cs | 6 + .../AttributeComparerTest.cs | 29 +++-- .../BooleanAttributeComparerTest.cs | 14 ++- .../ClassAttributeComparerTest.cs | 28 ++++- .../IgnoreAttributeComparerTest.cs | 14 +++ .../OrderingStyleAttributeComparerTest.cs | 21 +++- .../StyleAttributeComparerTest.cs | 16 ++- .../CommentStrategies/CommentComparerTest.cs | 53 +++++++++ .../Strategies/DiffingStrategyPipelineTest.cs | 58 +++++----- .../ElementStrategies/ElementComparerTest.cs | 40 +++++-- .../StyleSheetTextNodeComparerTest.cs | 5 +- .../TextNodeComparerTest.cs | 34 +++--- .../Core/AttributeComparison.cs | 38 +----- .../Core/AttributeComparisonSource.cs | 15 ++- .../Core/CompareDecision.cs | 34 ++++++ src/AngleSharp.Diffing/Core/CompareResult.cs | 47 ++++---- src/AngleSharp.Diffing/Core/Comparison.cs | 39 +------ .../Core/ComparisonSource.cs | 11 +- src/AngleSharp.Diffing/Core/Diffs/AttrDiff.cs | 13 ++- .../Core/Diffs/AttrDiffKind.cs | 20 ++++ .../Core/Diffs/CommentDiff.cs | 14 +++ src/AngleSharp.Diffing/Core/Diffs/DiffBase.cs | 31 +---- .../Core/Diffs/ElementDiff.cs | 21 ++++ .../Core/Diffs/ElementDiffKind.cs | 20 ++++ .../Core/Diffs/MissingAttrDiff.cs | 3 +- .../Core/Diffs/MissingDiffBase.cs | 25 +--- .../Core/Diffs/MissingNodeDiff.cs | 3 +- src/AngleSharp.Diffing/Core/Diffs/NodeDiff.cs | 5 +- .../Core/Diffs/StylesheetDiff.cs | 14 +++ src/AngleSharp.Diffing/Core/Diffs/TextDiff.cs | 14 +++ .../Core/Diffs/UnexpectedAttrDiff.cs | 3 +- .../Core/Diffs/UnexpectedDiffBase.cs | 24 +--- .../Core/Diffs/UnexpectedNodeDiff.cs | 3 +- .../Core/HtmlDifferenceEngine.cs | 22 ++-- .../UnexpectedDOMTreeStructureException.cs | 4 +- .../AttributeStrategies/AttributeComparer.cs | 6 +- .../BooleanAttributeComparer.cs | 8 +- .../ClassAttributeComparer.cs | 8 +- .../IgnoreAttributeComparer.cs | 2 +- .../OrderingStyleAttributeComparer.cs | 16 +-- .../StyleAttributeComparer.cs | 16 +-- .../CommentStrategies/CommentComparer.cs | 12 +- .../ElementStrategies/ElementComparer.cs | 16 ++- .../StyleSheetTextNodeComparer.cs | 8 +- .../TextNodeStrategies/TextNodeComparer.cs | 18 +-- .../IsExternalInit.cs | 66 +++++++++++ src/Directory.Build.props | 2 +- 49 files changed, 688 insertions(+), 345 deletions(-) create mode 100644 src/AngleSharp.Diffing.Tests/Strategies/CommentStrategies/CommentComparerTest.cs create mode 100644 src/AngleSharp.Diffing/Core/CompareDecision.cs create mode 100644 src/AngleSharp.Diffing/Core/Diffs/AttrDiffKind.cs create mode 100644 src/AngleSharp.Diffing/Core/Diffs/CommentDiff.cs create mode 100644 src/AngleSharp.Diffing/Core/Diffs/ElementDiff.cs create mode 100644 src/AngleSharp.Diffing/Core/Diffs/ElementDiffKind.cs create mode 100644 src/AngleSharp.Diffing/Core/Diffs/StylesheetDiff.cs create mode 100644 src/AngleSharp.Diffing/Core/Diffs/TextDiff.cs create mode 100644 src/AngleSharp.Diffing/System.Runtime.CompilerServices/IsExternalInit.cs diff --git a/CHANGELOG.md b/CHANGELOG.md index 9120658..c352737 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,8 @@ +# 0.18.2 + +- Changed `CompareStrategy` such that it now can control the `IDiff` type that should be returned in case a difference is found in a comparison. This allows a comparer to embed additional context in the `IDiff` object. By [@SebastianStehle](https://github.com/SebastianStehle). +- Changed `ElementComparer` to skip comparing two nodes of different types. By [@SebastianStehle](https://github.com/SebastianStehle). + # 0.18.1 - Fixed element comparer such that it can strictly check if the closing tags in the source markup is the same. diff --git a/src/AngleSharp.Diffing.Tests/Core/HtmlDifferenceEngineTest.cs b/src/AngleSharp.Diffing.Tests/Core/HtmlDifferenceEngineTest.cs index 6fc8cc6..7f8ae45 100644 --- a/src/AngleSharp.Diffing.Tests/Core/HtmlDifferenceEngineTest.cs +++ b/src/AngleSharp.Diffing.Tests/Core/HtmlDifferenceEngineTest.cs @@ -103,17 +103,46 @@ public void WhenNodesAreDifferentADiffIsReturned() var results = sut.Compare(nodes, nodes).ToList(); results.Count.ShouldBe(3); - results[0].ShouldBeOfType().ShouldSatisfyAllConditions( + results[0].ShouldBeAssignableTo().ShouldSatisfyAllConditions( diff => diff.Control.Node.NodeName.ShouldBe("P"), diff => diff.Result.ShouldBe(DiffResult.Different), diff => diff.Target.ShouldBe(DiffTarget.Element) ); - results[1].ShouldBeOfType().ShouldSatisfyAllConditions( + results[1].ShouldBeAssignableTo().ShouldSatisfyAllConditions( diff => diff.Control.Node.NodeName.ShouldBe("#comment"), diff => diff.Result.ShouldBe(DiffResult.Different), diff => diff.Target.ShouldBe(DiffTarget.Comment) ); - results[2].ShouldBeOfType().ShouldSatisfyAllConditions( + results[2].ShouldBeAssignableTo().ShouldSatisfyAllConditions( + diff => diff.Control.Node.NodeName.ShouldBe("#text"), + diff => diff.Result.ShouldBe(DiffResult.Different), + diff => diff.Target.ShouldBe(DiffTarget.Text) + ); + } + + [Fact(DisplayName = "When matched control/test nodes are different, a custom diff is returned")] + public void WhenNodesAreDifferentADiffIsReturnedWithCustomDiff() + { + var nodes = ToNodeList("

textnode"); + var sut = CreateHtmlDiffer( + nodeMatcher: OneToOneNodeListMatcher, + nodeFilter: NoneNodeFilter, + nodeComparer: DiffResultCustomNodeComparer); + + var results = sut.Compare(nodes, nodes).ToList(); + + results.Count.ShouldBe(3); + results[0].ShouldBeOfType().ShouldSatisfyAllConditions( + diff => diff.Control.Node.NodeName.ShouldBe("P"), + diff => diff.Result.ShouldBe(DiffResult.Different), + diff => diff.Target.ShouldBe(DiffTarget.Element) + ); + results[1].ShouldBeOfType().ShouldSatisfyAllConditions( + diff => diff.Control.Node.NodeName.ShouldBe("#comment"), + diff => diff.Result.ShouldBe(DiffResult.Different), + diff => diff.Target.ShouldBe(DiffTarget.Comment) + ); + results[2].ShouldBeOfType().ShouldSatisfyAllConditions( diff => diff.Control.Node.NodeName.ShouldBe("#text"), diff => diff.Result.ShouldBe(DiffResult.Different), diff => diff.Target.ShouldBe(DiffTarget.Text) @@ -237,6 +266,30 @@ public void WhenMatchedAttrsAreDiffAttrDiffIsReturned() ); } + [Fact(DisplayName = "When matched control/test attributes are different, a diff is returned with custom diff")] + public void WhenMatchedAttrsAreDiffAttrDiffIsReturnedWithCustomDiff() + { + var nodes = ToNodeList(@"

"); + + var sut = CreateHtmlDiffer( + nodeMatcher: OneToOneNodeListMatcher, + nodeFilter: NoneNodeFilter, + nodeComparer: SameResultNodeComparer, + attrMatcher: AttributeNameMatcher, + attrFilter: NoneAttrFilter, + attrComparer: DiffResultCustomAttrComparer); + + var results = sut.Compare(nodes, nodes).ToList(); + + results.Count.ShouldBe(1); + results[0].ShouldBeOfType().ShouldSatisfyAllConditions( + diff => diff.Control.Attribute.Name.ShouldBe("id"), + diff => diff.Test.Attribute.Name.ShouldBe("id"), + diff => diff.Result.ShouldBe(DiffResult.Different), + diff => diff.Target.ShouldBe(DiffTarget.Attribute) + ); + } + [Fact(DisplayName = "When matched control/test attributes are the same, no diffs are returned")] public void WhenMatchedAttrsAreSameNoDiffIsReturned() { @@ -268,11 +321,11 @@ public void WhenBothTestAndControlHaveChildNodesTheseAreCompared() var results = sut.Compare(nodes, nodes).ToList(); results.Count.ShouldBe(5); - results[0].ShouldBeOfType().Control.Node.NodeName.ShouldBe("MAIN"); - results[1].ShouldBeOfType().Control.Node.NodeName.ShouldBe("H1"); - results[2].ShouldBeOfType().Control.Node.NodeValue.ShouldBe("foobar"); - results[3].ShouldBeOfType().Control.Node.NodeName.ShouldBe("P"); - results[4].ShouldBeOfType().Control.Node.NodeName.ShouldBe("#text"); + results[0].ShouldBeAssignableTo().Control.Node.NodeName.ShouldBe("MAIN"); + results[1].ShouldBeAssignableTo().Control.Node.NodeName.ShouldBe("H1"); + results[2].ShouldBeAssignableTo().Control.Node.NodeValue.ShouldBe("foobar"); + results[3].ShouldBeAssignableTo().Control.Node.NodeName.ShouldBe("P"); + results[4].ShouldBeAssignableTo().Control.Node.NodeName.ShouldBe("#text"); } [Theory(DisplayName = "When only one of the control or test node in a comparison has child nodes, a missing/unexpected diff is returned")] @@ -288,7 +341,7 @@ public void OnlyOnePartHasChildNodes(string control, string test, Type expectedD var results = sut.Compare(ToNodeList(control), ToNodeList(test)).ToList(); results.Count.ShouldBe(2); - results[0].ShouldBeOfType(); + results[0].ShouldBeAssignableTo(); results[1].ShouldBeOfType(expectedDiffType); } @@ -309,8 +362,8 @@ public void ComparisonSourcesHaveCorrectType() results.Count.ShouldBe(2); - results[0].ShouldBeOfType().Control.SourceType.ShouldBe(ComparisonSourceType.Control); - results[0].ShouldBeOfType().Test.SourceType.ShouldBe(ComparisonSourceType.Test); + results[0].ShouldBeAssignableTo().Control.SourceType.ShouldBe(ComparisonSourceType.Control); + results[0].ShouldBeAssignableTo().Test.SourceType.ShouldBe(ComparisonSourceType.Test); results[1].ShouldBeOfType().Control.SourceType.ShouldBe(ComparisonSourceType.Control); results[1].ShouldBeOfType().Test.SourceType.ShouldBe(ComparisonSourceType.Test); } @@ -349,14 +402,14 @@ public void Test2() } [Theory(DisplayName = "When comparer returns SkipChildren flag from an element comparison, child nodes are not compared")] - [InlineData(CompareResult.Same | CompareResult.SkipChildren)] - [InlineData(CompareResult.Skip | CompareResult.SkipChildren)] - public void Test3(CompareResult compareResult) + [InlineData(CompareDecision.Same | CompareDecision.SkipChildren)] + [InlineData(CompareDecision.Skip | CompareDecision.SkipChildren)] + public void Test3(CompareDecision decision) { var sut = CreateHtmlDiffer( nodeMatcher: OneToOneNodeListMatcher, nodeFilter: NoneNodeFilter, - nodeComparer: c => c.Control.Node.NodeName == "P" ? compareResult : throw new Exception("NODE COMPARER SHOULD NOT BE CALLED ON CHILD NODES"), + nodeComparer: c => c.Control.Node.NodeName == "P" ? new CompareResult(decision) : throw new Exception("NODE COMPARER SHOULD NOT BE CALLED ON CHILD NODES"), attrMatcher: AttributeNameMatcher, attrFilter: NoneAttrFilter, attrComparer: SameResultAttrComparer @@ -368,14 +421,14 @@ public void Test3(CompareResult compareResult) } [Theory(DisplayName = "When comparer returns SkipAttributes flag from an element comparison, attributes are not compared")] - [InlineData(CompareResult.Same | CompareResult.SkipAttributes)] - [InlineData(CompareResult.Skip | CompareResult.SkipAttributes)] - public void Test4(CompareResult compareResult) + [InlineData(CompareDecision.Same | CompareDecision.SkipAttributes)] + [InlineData(CompareDecision.Skip | CompareDecision.SkipAttributes)] + public void Test4(CompareDecision decision) { var sut = CreateHtmlDiffer( nodeMatcher: OneToOneNodeListMatcher, nodeFilter: NoneNodeFilter, - nodeComparer: c => compareResult, + nodeComparer: c => new CompareResult(decision), attrMatcher: AttributeNameMatcher, attrFilter: NoneAttrFilter, attrComparer: SameResultAttrComparer @@ -411,6 +464,7 @@ private static IEnumerable OneToOneNodeListMatcher( #region NodeComparers private static CompareResult SameResultNodeComparer(Comparison comparison) => CompareResult.Same; private static CompareResult DiffResultNodeComparer(Comparison comparison) => CompareResult.Different; + private static CompareResult DiffResultCustomNodeComparer(Comparison comparison) => CompareResult.FromDiff(new CustomNodeDiff(comparison)); #endregion #region AttributeMatchers @@ -452,5 +506,22 @@ private static Func SpecificAttrFilte #region AttributeComparers public static CompareResult SameResultAttrComparer(AttributeComparison comparison) => CompareResult.Same; public static CompareResult DiffResultAttrComparer(AttributeComparison comparison) => CompareResult.Different; + public static CompareResult DiffResultCustomAttrComparer(AttributeComparison comparison) => CompareResult.FromDiff(new CustomAttrDiff(comparison)); + #endregion + + #region CustomDiff + public record CustomNodeDiff : NodeDiff + { + public CustomNodeDiff(in Comparison comparison) : base(comparison) + { + } + } + + public record CustomAttrDiff : AttrDiff + { + public CustomAttrDiff(in AttributeComparison comparison) : base(comparison, AttrDiffKind.Unspecified) + { + } + } #endregion } \ No newline at end of file diff --git a/src/AngleSharp.Diffing.Tests/DiffingTestBase.cs b/src/AngleSharp.Diffing.Tests/DiffingTestBase.cs index e463258..af96fe0 100644 --- a/src/AngleSharp.Diffing.Tests/DiffingTestBase.cs +++ b/src/AngleSharp.Diffing.Tests/DiffingTestBase.cs @@ -68,4 +68,10 @@ protected SourceMap ToSourceMap(string html, ComparisonSourceType sourceType = C var source = ToComparisonSource(html, sourceType); return new SourceMap(source); } + + public static TheoryData SameAndSkipCompareResult = new TheoryData + { + CompareResult.Same, + CompareResult.Skip, + }; } \ No newline at end of file diff --git a/src/AngleSharp.Diffing.Tests/Strategies/AttributeStrategies/AttributeComparerTest.cs b/src/AngleSharp.Diffing.Tests/Strategies/AttributeStrategies/AttributeComparerTest.cs index e281fb8..d3fcafd 100644 --- a/src/AngleSharp.Diffing.Tests/Strategies/AttributeStrategies/AttributeComparerTest.cs +++ b/src/AngleSharp.Diffing.Tests/Strategies/AttributeStrategies/AttributeComparerTest.cs @@ -1,20 +1,21 @@ namespace AngleSharp.Diffing.Strategies.AttributeStrategies; - public class AttributeComparerTest : DiffingTestBase { public AttributeComparerTest(DiffingTestFixture fixture) : base(fixture) { } - [Fact(DisplayName = "When compare is called with a current decision of Same or Skip, the current decision is returned")] - public void Test001() + [Theory(DisplayName = "When current result is same or skip, the current decision is returned")] + [MemberData(nameof(SameAndSkipCompareResult))] + public void Test001(CompareResult currentResult) { var comparison = ToAttributeComparison(@"", "foo", - "", "bar"); + "", "bar"); - AttributeComparer.Compare(comparison, CompareResult.Same).ShouldBe(CompareResult.Same); - AttributeComparer.Compare(comparison, CompareResult.Skip).ShouldBe(CompareResult.Skip); + new BooleanAttributeComparer(BooleanAttributeComparision.Strict) + .Compare(comparison, currentResult) + .ShouldBe(currentResult); } [Fact(DisplayName = "When two attributes has the same name and no value, the compare result is Same")] @@ -23,7 +24,9 @@ public void Test002() var comparison = ToAttributeComparison(@"", "foo", "", "foo"); - AttributeComparer.Compare(comparison, CompareResult.Unknown).ShouldBe(CompareResult.Same); + AttributeComparer + .Compare(comparison, CompareResult.Unknown) + .ShouldBe(CompareResult.Same); } [Fact(DisplayName = "When two attributes does not have the same name, the compare result is Different")] @@ -32,7 +35,9 @@ public void Test003() var comparison = ToAttributeComparison(@"", "foo", "", "bar"); - AttributeComparer.Compare(comparison, CompareResult.Unknown).ShouldBe(CompareResult.Different); + AttributeComparer + .Compare(comparison, CompareResult.Unknown) + .ShouldBe(CompareResult.FromDiff(new AttrDiff(comparison, AttrDiffKind.Name))); } [Fact(DisplayName = "When two attribute values are the same, the compare result is Same")] @@ -41,7 +46,9 @@ public void Test004() var comparison = ToAttributeComparison(@"", "foo", @"", "foo"); - AttributeComparer.Compare(comparison, CompareResult.Unknown).ShouldBe(CompareResult.Same); + AttributeComparer + .Compare(comparison, CompareResult.Unknown) + .ShouldBe(CompareResult.Same); } [Fact(DisplayName = "When two attribute values are different, the compare result is Different")] @@ -50,7 +57,9 @@ public void Test005() var comparison = ToAttributeComparison(@"", "foo", @"", "foo"); - AttributeComparer.Compare(comparison, CompareResult.Unknown).ShouldBe(CompareResult.Different); + AttributeComparer + .Compare(comparison, CompareResult.Unknown) + .ShouldBe(CompareResult.FromDiff(new AttrDiff(comparison, AttrDiffKind.Value))); } [Fact(DisplayName = "When the control attribute is postfixed with :ignoreCase, " + diff --git a/src/AngleSharp.Diffing.Tests/Strategies/AttributeStrategies/BooleanAttributeComparerTest.cs b/src/AngleSharp.Diffing.Tests/Strategies/AttributeStrategies/BooleanAttributeComparerTest.cs index 05d3d98..db9dcbe 100644 --- a/src/AngleSharp.Diffing.Tests/Strategies/AttributeStrategies/BooleanAttributeComparerTest.cs +++ b/src/AngleSharp.Diffing.Tests/Strategies/AttributeStrategies/BooleanAttributeComparerTest.cs @@ -8,13 +8,25 @@ public BooleanAttributeComparerTest(DiffingTestFixture fixture) : base(fixture) { } + [Theory(DisplayName = "When current result is same or skip, the current decision is returned")] + [MemberData(nameof(SameAndSkipCompareResult))] + public void Test000(CompareResult currentResult) + { + var comparison = ToAttributeComparison(@"", "allowfullscreen", @"", "allowfullscreen"); + + new BooleanAttributeComparer(BooleanAttributeComparision.Strict) + .Compare(comparison, currentResult) + .ShouldBe(currentResult); + } + [Fact(DisplayName = "When attribute names are not the same comparer returns different")] public void Test001() { var sut = new BooleanAttributeComparer(BooleanAttributeComparision.Strict); var comparison = ToAttributeComparison("", "foo", "", "bar"); - sut.Compare(comparison, CompareResult.Unknown).ShouldBe(CompareResult.Different); + sut.Compare(comparison, CompareResult.Unknown) + .ShouldBe(CompareResult.FromDiff(new AttrDiff(comparison, AttrDiffKind.Name))); } [Fact(DisplayName = "When attribute name is not an boolean attribute, its current result is returned")] diff --git a/src/AngleSharp.Diffing.Tests/Strategies/AttributeStrategies/ClassAttributeComparerTest.cs b/src/AngleSharp.Diffing.Tests/Strategies/AttributeStrategies/ClassAttributeComparerTest.cs index a9eb44f..5987846 100644 --- a/src/AngleSharp.Diffing.Tests/Strategies/AttributeStrategies/ClassAttributeComparerTest.cs +++ b/src/AngleSharp.Diffing.Tests/Strategies/AttributeStrategies/ClassAttributeComparerTest.cs @@ -6,6 +6,18 @@ public ClassAttributeComparerTest(DiffingTestFixture fixture) : base(fixture) { } + [Theory(DisplayName = "When current result is same or skip, the current decision is returned")] + [MemberData(nameof(SameAndSkipCompareResult))] + public void Test000(CompareResult currentResult) + { + var comparison = ToAttributeComparison($@"

", "class", + $@"

", "class"); + + ClassAttributeComparer + .Compare(comparison, currentResult) + .ShouldBe(currentResult); + } + [Theory(DisplayName = "When a class attribute is compared, the order of individual " + "classes and multiple whitespace is ignored")] [InlineData("", "")] @@ -18,7 +30,9 @@ public void Test009(string controlClasses, string testClasses) var comparison = ToAttributeComparison($@"

", "class", $@"

", "class"); - ClassAttributeComparer.Compare(comparison, CompareResult.Unknown).ShouldBe(CompareResult.Same); + ClassAttributeComparer + .Compare(comparison, CompareResult.Unknown) + .ShouldBe(CompareResult.Same); } [Fact(DisplayName = "When a class attribute is matched up with another attribute, the result is different")] @@ -27,7 +41,9 @@ public void Test010() var comparison = ToAttributeComparison(@"

", "class", @"

", "bar"); - ClassAttributeComparer.Compare(comparison, CompareResult.Unknown).ShouldBe(CompareResult.Different); + ClassAttributeComparer + .Compare(comparison, CompareResult.Unknown) + .ShouldBe(CompareResult.FromDiff(new AttrDiff(comparison, AttrDiffKind.Name))); } [Theory(DisplayName = "When there are different number of classes in the class attributes the result is different")] @@ -38,7 +54,9 @@ public void Test011(string controlClasses, string testClasses) var comparison = ToAttributeComparison($@"

", "class", $@"

", "class"); - ClassAttributeComparer.Compare(comparison, CompareResult.Unknown).ShouldBe(CompareResult.Different); + ClassAttributeComparer + .Compare(comparison, CompareResult.Unknown) + .ShouldBe(CompareResult.FromDiff(new AttrDiff(comparison, AttrDiffKind.Value))); } [Theory(DisplayName = "When the classes in the class attributes are different the result is different")] @@ -51,6 +69,8 @@ public void Test012(string controlClasses, string testClasses) var comparison = ToAttributeComparison($@"

", "class", $@"

", "class"); - ClassAttributeComparer.Compare(comparison, CompareResult.Unknown).ShouldBe(CompareResult.Different); + ClassAttributeComparer + .Compare(comparison, CompareResult.Unknown) + .ShouldBe(CompareResult.FromDiff(new AttrDiff(comparison, AttrDiffKind.Value))); } } diff --git a/src/AngleSharp.Diffing.Tests/Strategies/AttributeStrategies/IgnoreAttributeComparerTest.cs b/src/AngleSharp.Diffing.Tests/Strategies/AttributeStrategies/IgnoreAttributeComparerTest.cs index 06cd19d..a59efc3 100644 --- a/src/AngleSharp.Diffing.Tests/Strategies/AttributeStrategies/IgnoreAttributeComparerTest.cs +++ b/src/AngleSharp.Diffing.Tests/Strategies/AttributeStrategies/IgnoreAttributeComparerTest.cs @@ -8,6 +8,20 @@ public IgnoreAttributeComparerTest(DiffingTestFixture fixture) : base(fixture) { } + [Theory(DisplayName = "When current result is same or skip, the current decision is returned")] + [MemberData(nameof(SameAndSkipCompareResult))] + public void Test000(CompareResult currentResult) + { + var comparison = ToAttributeComparison( + @"

", "foo", + @"

", "foo" + ); + + IgnoreAttributeComparer + .Compare(comparison, currentResult) + .ShouldBe(currentResult); + } + [Fact(DisplayName = "When a attribute does not contain have the ':ignore' postfix, the current decision is returned")] public void Test003() { diff --git a/src/AngleSharp.Diffing.Tests/Strategies/AttributeStrategies/OrderingStyleAttributeComparerTest.cs b/src/AngleSharp.Diffing.Tests/Strategies/AttributeStrategies/OrderingStyleAttributeComparerTest.cs index 2b06a0f..6e54c4b 100644 --- a/src/AngleSharp.Diffing.Tests/Strategies/AttributeStrategies/OrderingStyleAttributeComparerTest.cs +++ b/src/AngleSharp.Diffing.Tests/Strategies/AttributeStrategies/OrderingStyleAttributeComparerTest.cs @@ -6,13 +6,23 @@ public OrderingStyleAttributeComparerTest(DiffingTestFixture fixture) : base(fix { } + [Theory(DisplayName = "When current result is same or skip, the current decision is returned")] + [MemberData(nameof(SameAndSkipCompareResult))] + public void Test000(CompareResult currentResult) + { + var comparison = ToAttributeComparison(@"

", "style", @"

", "style"); + OrderingStyleAttributeComparer + .Compare(comparison, currentResult) + .ShouldBe(currentResult); + } + [Fact(DisplayName = "When attribute is not style the current decision is used")] public void Test001() { var comparison = ToAttributeComparison(@"

", "foo", @"

", "foo"); - StyleAttributeComparer.Compare(comparison, CompareResult.Different).ShouldBe(CompareResult.Different); - StyleAttributeComparer.Compare(comparison, CompareResult.Same).ShouldBe(CompareResult.Same); - StyleAttributeComparer.Compare(comparison, CompareResult.Skip).ShouldBe(CompareResult.Skip); + OrderingStyleAttributeComparer.Compare(comparison, CompareResult.Different).ShouldBe(CompareResult.Different); + OrderingStyleAttributeComparer.Compare(comparison, CompareResult.Same).ShouldBe(CompareResult.Same); + OrderingStyleAttributeComparer.Compare(comparison, CompareResult.Skip).ShouldBe(CompareResult.Skip); } [Theory(DisplayName = "When style attributes has different values then Different is returned")] @@ -23,7 +33,10 @@ public void Test001() public void Test002(string control, string test) { var comparison = ToAttributeComparison(control, "style", test, "style"); - OrderingStyleAttributeComparer.Compare(comparison, CompareResult.Unknown).ShouldBe(CompareResult.Different); + + OrderingStyleAttributeComparer + .Compare(comparison, CompareResult.Unknown) + .ShouldBe(CompareResult.FromDiff(new AttrDiff(comparison, AttrDiffKind.Value))); } [Fact(DisplayName = "Comparer should correctly ignore insignificant whitespace")] diff --git a/src/AngleSharp.Diffing.Tests/Strategies/AttributeStrategies/StyleAttributeComparerTest.cs b/src/AngleSharp.Diffing.Tests/Strategies/AttributeStrategies/StyleAttributeComparerTest.cs index b2e617c..1595920 100644 --- a/src/AngleSharp.Diffing.Tests/Strategies/AttributeStrategies/StyleAttributeComparerTest.cs +++ b/src/AngleSharp.Diffing.Tests/Strategies/AttributeStrategies/StyleAttributeComparerTest.cs @@ -6,6 +6,16 @@ public StyleAttributeComparerTest(DiffingTestFixture fixture) : base(fixture) { } + [Theory(DisplayName = "When current result is same or skip, the current decision is returned")] + [MemberData(nameof(SameAndSkipCompareResult))] + public void Test000(CompareResult currentResult) + { + var comparison = ToAttributeComparison(@"

", "style", @"

", "style"); + StyleAttributeComparer + .Compare(comparison, currentResult) + .ShouldBe(currentResult); + } + [Fact(DisplayName = "When attribute is not style the current decision is used")] public void Test001() { @@ -15,6 +25,7 @@ public void Test001() StyleAttributeComparer.Compare(comparison, CompareResult.Skip).ShouldBe(CompareResult.Skip); } + [Theory(DisplayName = "When style attributes has different values then Different is returned")] [InlineData(@"

", @"

")] [InlineData(@"

", @"

")] @@ -23,7 +34,10 @@ public void Test001() public void Test002(string control, string test) { var comparison = ToAttributeComparison(control, "style", test, "style"); - StyleAttributeComparer.Compare(comparison, CompareResult.Unknown).ShouldBe(CompareResult.Different); + + StyleAttributeComparer + .Compare(comparison, CompareResult.Unknown) + .ShouldBe(CompareResult.FromDiff(new AttrDiff(comparison, AttrDiffKind.Value))); } [Fact(DisplayName = "Comparer should correctly ignore insignificant whitespace")] diff --git a/src/AngleSharp.Diffing.Tests/Strategies/CommentStrategies/CommentComparerTest.cs b/src/AngleSharp.Diffing.Tests/Strategies/CommentStrategies/CommentComparerTest.cs new file mode 100644 index 0000000..b525104 --- /dev/null +++ b/src/AngleSharp.Diffing.Tests/Strategies/CommentStrategies/CommentComparerTest.cs @@ -0,0 +1,53 @@ +namespace AngleSharp.Diffing.Strategies.CommentStrategies; + +public class CommentComparerTest : DiffingTestBase +{ + public CommentComparerTest(DiffingTestFixture fixture) : base(fixture) + { + } + + [Theory(DisplayName = "When current result is same or skip, current result is returned")] + [MemberData(nameof(SameAndSkipCompareResult))] + public void Test000(CompareResult currentResult) + { + var comparison = ToComparison("", ""); + CommentComparer + .Compare(comparison, currentResult) + .ShouldBe(currentResult); + } + + [Theory(DisplayName = "When control and test are comment with equal content, the result is Same")] + [InlineData("", "")] + [InlineData("", "")] + public void Test001(string controlHtml, string testHtml) + { + var comparison = ToComparison(controlHtml, testHtml); + CommentComparer + .Compare(comparison, CompareResult.Unknown) + .ShouldBe(CompareResult.Same); + } + + [Theory(DisplayName = "When control and test are comment with unequal content, the result is Different")] + [InlineData("", "")] + [InlineData("", "")] + [InlineData("", "")] + public void Test002(string controlHtml, string testHtml) + { + var comparison = ToComparison(controlHtml, testHtml); + CommentComparer + .Compare(comparison, CompareResult.Unknown) + .ShouldBe(CompareResult.FromDiff(new CommentDiff(comparison))); + } + + [Theory(DisplayName = "When input node is not a IComment node, comparer does not run nor change the current decision")] + [InlineData("foo", "bar")] + [InlineData("

", "

")] + public void Test003(string controlHtml, string testHtml) + { + var comparison = ToComparison(controlHtml, testHtml); + + CommentComparer.Compare(comparison, CompareResult.Different).ShouldBe(CompareResult.Different); + CommentComparer.Compare(comparison, CompareResult.Same).ShouldBe(CompareResult.Same); + CommentComparer.Compare(comparison, CompareResult.Skip).ShouldBe(CompareResult.Skip); + } +} diff --git a/src/AngleSharp.Diffing.Tests/Strategies/DiffingStrategyPipelineTest.cs b/src/AngleSharp.Diffing.Tests/Strategies/DiffingStrategyPipelineTest.cs index 00e1e40..c071b28 100644 --- a/src/AngleSharp.Diffing.Tests/Strategies/DiffingStrategyPipelineTest.cs +++ b/src/AngleSharp.Diffing.Tests/Strategies/DiffingStrategyPipelineTest.cs @@ -71,8 +71,6 @@ public void Test003(FilterDecision expected) sut.Filter(new AttributeComparisonSource()).ShouldBe(expected); } -#pragma warning disable CS8625 // Cannot convert null literal to non-nullable reference type. -#pragma warning disable CS8600 // Converting null literal or possible null value to non-nullable type. [Fact(DisplayName = "When no matcher strategy have been added, no comparisons are returned")] public void Test2() { @@ -81,8 +79,6 @@ public void Test2() sut.Match(null, null, (SourceCollection)null).ShouldBeEmpty(); sut.Match(null, null, (SourceMap)null).ShouldBeEmpty(); } -#pragma warning restore CS8600 // Converting null literal or possible null value to non-nullable type. -#pragma warning restore CS8625 // Cannot convert null literal to non-nullable reference type. [Fact(DisplayName = "Specialized node matchers are executed in the reverse order they are added in")] public void Test61() @@ -179,51 +175,51 @@ public void Test3() } [Theory(DisplayName = "Specialized comparers are executed in the order they are added in")] - [InlineData(CompareResult.Different, CompareResult.Same)] - [InlineData(CompareResult.Same, CompareResult.Different)] - public void Test8(CompareResult first, CompareResult final) + [InlineData(CompareDecision.Different, CompareDecision.Same)] + [InlineData(CompareDecision.Same, CompareDecision.Different)] + public void Test8(CompareDecision first, CompareDecision final) { var sut = new DiffingStrategyPipeline(); - sut.AddComparer((in Comparison c, CompareResult current) => first, StrategyType.Specialized); - sut.AddComparer((in Comparison c, CompareResult current) => final, StrategyType.Specialized); - sut.AddComparer((in AttributeComparison c, CompareResult current) => first, StrategyType.Specialized); - sut.AddComparer((in AttributeComparison c, CompareResult current) => final, StrategyType.Specialized); + sut.AddComparer((in Comparison c, CompareResult current) => new CompareResult(first), StrategyType.Specialized); + sut.AddComparer((in Comparison c, CompareResult current) => new CompareResult(final), StrategyType.Specialized); + sut.AddComparer((in AttributeComparison c, CompareResult current) => new CompareResult(first), StrategyType.Specialized); + sut.AddComparer((in AttributeComparison c, CompareResult current) => new CompareResult(final), StrategyType.Specialized); - sut.Compare(new Comparison()).ShouldBe(final); - sut.Compare(new AttributeComparison()).ShouldBe(final); + sut.Compare(new Comparison()).ShouldBe(new CompareResult(final)); + sut.Compare(new AttributeComparison()).ShouldBe(new CompareResult(final)); } [Theory(DisplayName = "Generalized comparers are executed in the reverse order they are added in")] - [InlineData(CompareResult.Different, CompareResult.Same)] - [InlineData(CompareResult.Same, CompareResult.Different)] - public void Test12321(CompareResult first, CompareResult final) + [InlineData(CompareDecision.Different, CompareDecision.Same)] + [InlineData(CompareDecision.Same, CompareDecision.Different)] + public void Test12321(CompareDecision first, CompareDecision final) { var sut = new DiffingStrategyPipeline(); - sut.AddComparer((in Comparison c, CompareResult current) => final, StrategyType.Generalized); - sut.AddComparer((in Comparison c, CompareResult current) => first, StrategyType.Generalized); - sut.AddComparer((in AttributeComparison c, CompareResult current) => final, StrategyType.Generalized); - sut.AddComparer((in AttributeComparison c, CompareResult current) => first, StrategyType.Generalized); + sut.AddComparer((in Comparison c, CompareResult current) => new CompareResult(final), StrategyType.Generalized); + sut.AddComparer((in Comparison c, CompareResult current) => new CompareResult(first), StrategyType.Generalized); + sut.AddComparer((in AttributeComparison c, CompareResult current) => new CompareResult(final), StrategyType.Generalized); + sut.AddComparer((in AttributeComparison c, CompareResult current) => new CompareResult(first), StrategyType.Generalized); - sut.Compare(new Comparison()).ShouldBe(final); - sut.Compare(new AttributeComparison()).ShouldBe(final); + sut.Compare(new Comparison()).ShouldBe(new CompareResult(final)); + sut.Compare(new AttributeComparison()).ShouldBe(new CompareResult(final)); } [Theory(DisplayName = "Generalized comparers are always executed before specialized comparers")] - [InlineData(CompareResult.Different, CompareResult.Same)] - [InlineData(CompareResult.Same, CompareResult.Different)] - public void Test8314(CompareResult first, CompareResult final) + [InlineData(CompareDecision.Different, CompareDecision.Same)] + [InlineData(CompareDecision.Same, CompareDecision.Different)] + public void Test8314(CompareDecision first, CompareDecision final) { var sut = new DiffingStrategyPipeline(); - sut.AddComparer((in Comparison c, CompareResult current) => first, StrategyType.Generalized); - sut.AddComparer((in Comparison c, CompareResult current) => final, StrategyType.Specialized); - sut.AddComparer((in AttributeComparison c, CompareResult current) => first, StrategyType.Generalized); - sut.AddComparer((in AttributeComparison c, CompareResult current) => final, StrategyType.Specialized); + sut.AddComparer((in Comparison c, CompareResult current) => new CompareResult(first), StrategyType.Generalized); + sut.AddComparer((in Comparison c, CompareResult current) => new CompareResult(final), StrategyType.Specialized); + sut.AddComparer((in AttributeComparison c, CompareResult current) => new CompareResult(first), StrategyType.Generalized); + sut.AddComparer((in AttributeComparison c, CompareResult current) => new CompareResult(final), StrategyType.Specialized); - sut.Compare(new Comparison()).ShouldBe(final); - sut.Compare(new AttributeComparison()).ShouldBe(final); + sut.Compare(new Comparison()).ShouldBe(new CompareResult(final)); + sut.Compare(new AttributeComparison()).ShouldBe(new CompareResult(final)); } [Fact(DisplayName = "After two nodes has been matched, they are marked as matched in the source collection")] diff --git a/src/AngleSharp.Diffing.Tests/Strategies/ElementStrategies/ElementComparerTest.cs b/src/AngleSharp.Diffing.Tests/Strategies/ElementStrategies/ElementComparerTest.cs index f0877ce..1061a98 100644 --- a/src/AngleSharp.Diffing.Tests/Strategies/ElementStrategies/ElementComparerTest.cs +++ b/src/AngleSharp.Diffing.Tests/Strategies/ElementStrategies/ElementComparerTest.cs @@ -1,4 +1,6 @@ -namespace AngleSharp.Diffing.Strategies.ElementStrategies; +using AngleSharp.Diffing.Core.Diffs; + +namespace AngleSharp.Diffing.Strategies.ElementStrategies; public class ElementComparerTest : DiffingTestBase { @@ -6,6 +8,17 @@ public ElementComparerTest(DiffingTestFixture fixture) : base(fixture) { } + [Theory(DisplayName = "When current result is same or skip, the current decision is returned")] + [MemberData(nameof(SameAndSkipCompareResult))] + public void Test000(CompareResult currentResult) + { + var comparison = ToComparison("

", "

"); + + new ElementComparer(enforceTagClosing: false) + .Compare(comparison, currentResult) + .ShouldBe(currentResult); + } + [Theory(DisplayName = "When control and test nodes have the same type and name and enforceTagClosing is false, the result is Same")] [InlineData("

", "

")] [InlineData("
", "
")] @@ -19,23 +32,28 @@ public void Test001(string controlHtml, string testHtml) .ShouldBe(CompareResult.Same); } - [Theory(DisplayName = "When control and test nodes have the a different type and name, the result is Different")] + [Theory(DisplayName = "When control and test nodes have the a name, the result is Different")] [InlineData("

", "

", false)] - [InlineData("

", "textnode", false)] - [InlineData("
", "", false)] - [InlineData("", "textnode", false)] - [InlineData("
", "
", true)] - [InlineData("", "", true)] [InlineData("
", "

", true)] - [InlineData("

", "textnode", true)] - [InlineData("
", "", true)] - [InlineData("", "textnode", true)] public void Test002(string controlHtml, string testHtml, bool enforceTagClosing) { var comparison = ToComparison(controlHtml, testHtml); + + new ElementComparer(enforceTagClosing) + .Compare(comparison, CompareResult.Unknown) + .ShouldBe(CompareResult.FromDiff(new ElementDiff(comparison, ElementDiffKind.Name))); + } + + [Theory(DisplayName = "When control and test nodes have the a different closing style, the result is Different")] + [InlineData("
", "
", true)] + [InlineData("", "", true)] + public void Test003(string controlHtml, string testHtml, bool enforceTagClosing) + { + var comparison = ToComparison(controlHtml, testHtml); + new ElementComparer(enforceTagClosing) .Compare(comparison, CompareResult.Unknown) - .ShouldBe(CompareResult.Different); + .ShouldBe(CompareResult.FromDiff(new ElementDiff(comparison, ElementDiffKind.ClosingStyle))); } [Theory(DisplayName = "When unknown node is used in comparison, but node name is equal, the result is Same")] diff --git a/src/AngleSharp.Diffing.Tests/Strategies/TextNodeStrategies/StyleSheetTextNodeComparerTest.cs b/src/AngleSharp.Diffing.Tests/Strategies/TextNodeStrategies/StyleSheetTextNodeComparerTest.cs index e37e669..a7db2a8 100644 --- a/src/AngleSharp.Diffing.Tests/Strategies/TextNodeStrategies/StyleSheetTextNodeComparerTest.cs +++ b/src/AngleSharp.Diffing.Tests/Strategies/TextNodeStrategies/StyleSheetTextNodeComparerTest.cs @@ -32,7 +32,10 @@ public void Test001() { var comparison = ToStyleComparison(@"h1{background:#000;}", @"h1{color:#000;}"); - StyleSheetTextNodeComparer.Compare(comparison, CompareResult.Unknown).ShouldBe(CompareResult.Different); + var result = StyleSheetTextNodeComparer.Compare(comparison, CompareResult.Unknown); + + result.Decision.ShouldBe(CompareDecision.Different); + result.Diff.ShouldBeEquivalentTo(new StylesheetDiff(comparison)); } [Theory(DisplayName = "The comparer ignores insignificant whitespace")] diff --git a/src/AngleSharp.Diffing.Tests/Strategies/TextNodeStrategies/TextNodeComparerTest.cs b/src/AngleSharp.Diffing.Tests/Strategies/TextNodeStrategies/TextNodeComparerTest.cs index 85d6e2b..7d18c0e 100644 --- a/src/AngleSharp.Diffing.Tests/Strategies/TextNodeStrategies/TextNodeComparerTest.cs +++ b/src/AngleSharp.Diffing.Tests/Strategies/TextNodeStrategies/TextNodeComparerTest.cs @@ -6,24 +6,22 @@ public TextNodeComparerTest(DiffingTestFixture fixture) : base(fixture) { } - [Fact(DisplayName = "When input node is not a IText node, comparer does not run nor change the current decision")] - public void Test2() + [Theory(DisplayName = "When current result is same or skip, the current decision is returned")] + [MemberData(nameof(SameAndSkipCompareResult))] + public void Test000(CompareResult currentResult) { - var comparison = ToComparison("

", "

"); - var sut = new TextNodeComparer(); + var comparison = ToComparison("hello world", " hello world "); - sut.Compare(comparison, CompareResult.Different).ShouldBe(CompareResult.Different); - sut.Compare(comparison, CompareResult.Same).ShouldBe(CompareResult.Same); - sut.Compare(comparison, CompareResult.Skip).ShouldBe(CompareResult.Skip); + new TextNodeComparer(WhitespaceOption.Preserve) + .Compare(comparison, currentResult) + .ShouldBe(currentResult); } - [Theory(DisplayName = "When option is Preserve or RemoveWhitespaceNodes, comparer does not run nor change the current decision")] - [InlineData(WhitespaceOption.Preserve)] - [InlineData(WhitespaceOption.RemoveWhitespaceNodes)] - public void Test5(WhitespaceOption whitespaceOption) + [Fact(DisplayName = "When input node is not a IText node, comparer does not run nor change the current decision")] + public void Test2() { - var comparison = ToComparison("hello world", " hello world "); - var sut = new TextNodeComparer(whitespaceOption); + var comparison = ToComparison("

", "

"); + var sut = new TextNodeComparer(); sut.Compare(comparison, CompareResult.Different).ShouldBe(CompareResult.Different); sut.Compare(comparison, CompareResult.Same).ShouldBe(CompareResult.Same); @@ -122,7 +120,8 @@ public void Test005(string tag) var testSource = ToComparisonSource("foo bar", ComparisonSourceType.Test); var comparison = new Comparison(controlSource, testSource); - sut.Compare(comparison, CompareResult.Unknown).ShouldBe(CompareResult.Different); + sut.Compare(comparison, CompareResult.Unknown) + .ShouldBe(CompareResult.FromDiff(new TextDiff(comparison))); } [Theory(DisplayName = "When the parent element is
 and the whitespace option is set " +
@@ -187,7 +186,8 @@ public void Test009(string controlHtml)
         var testSource = ToComparisonSource("hello world", ComparisonSourceType.Test);
         var comparison = new Comparison(controlSource, testSource);
 
-        sut.Compare(comparison, CompareResult.Unknown).ShouldBe(CompareResult.Different);
+        sut.Compare(comparison, CompareResult.Unknown)
+            .ShouldBe(CompareResult.FromDiff(new TextDiff(comparison)));
     }
 
     [Theory(DisplayName = "When diff:regex attribute is found on the immediate parent element, the control text is expected to a regex and that used when comparing to the test text node.")]
@@ -228,6 +228,4 @@ public void Test012(string controlHtml)
 
         sut.Compare(comparison, CompareResult.Unknown).ShouldBe(CompareResult.Same);
     }
-}
-
-
+}
\ No newline at end of file
diff --git a/src/AngleSharp.Diffing/Core/AttributeComparison.cs b/src/AngleSharp.Diffing/Core/AttributeComparison.cs
index 9d9d5c9..18a0da7 100644
--- a/src/AngleSharp.Diffing/Core/AttributeComparison.cs
+++ b/src/AngleSharp.Diffing/Core/AttributeComparison.cs
@@ -3,45 +3,13 @@ namespace AngleSharp.Diffing.Core;
 /// 
 /// A match between two attributes that should be compared.
 /// 
-public readonly struct AttributeComparison : IEquatable
+/// Gets the control attribute which the  attribute is supposed to match.
+/// Gets the test attribute which should be compared to the  attribute.
+public readonly record struct AttributeComparison(in AttributeComparisonSource Control, in AttributeComparisonSource Test)
 {
-    /// 
-    /// Gets the control attribute which the  attribute is supposed to match.
-    /// 
-    public AttributeComparisonSource Control { get; }
-
-    /// 
-    /// Gets the test attribute which should be compared to the  attribute.
-    /// 
-    public AttributeComparisonSource Test { get; }
-
-    /// 
-    /// Create a attribute comparison match.
-    /// 
-    /// The attribute control source
-    /// The attribute test source
-    public AttributeComparison(in AttributeComparisonSource control, in AttributeComparisonSource test)
-    {
-        Control = control;
-        Test = test;
-    }
-
     /// 
     /// Returns the control and test elements which the control and test attributes belongs to.
     /// 
     public (IElement ControlElement, IElement TestElement) GetAttributeElements()
         => ((IElement)Control.ElementSource.Node, (IElement)Test.ElementSource.Node);
-
-    #region Equals and HashCode
-    /// 
-    public bool Equals(AttributeComparison other) => Control.Equals(other.Control) && Test.Equals(other.Test);
-    /// 
-    public override bool Equals(object? obj) => obj is AttributeComparison other && Equals(other);
-    /// 
-    public override int GetHashCode() => (Control, Test).GetHashCode();
-    /// 
-    public static bool operator ==(AttributeComparison left, AttributeComparison right) => left.Equals(right);
-    /// 
-    public static bool operator !=(AttributeComparison left, AttributeComparison right) => !left.Equals(right);
-    #endregion
 }
diff --git a/src/AngleSharp.Diffing/Core/AttributeComparisonSource.cs b/src/AngleSharp.Diffing/Core/AttributeComparisonSource.cs
index 0c35d15..4f9939f 100644
--- a/src/AngleSharp.Diffing/Core/AttributeComparisonSource.cs
+++ b/src/AngleSharp.Diffing/Core/AttributeComparisonSource.cs
@@ -30,7 +30,6 @@ namespace AngleSharp.Diffing.Core;
     /// 
     /// Name of the attribute.
     /// The source of the element the attribute belongs to.
-    [SuppressMessage("Globalization", "CA1308:Normalize strings to uppercase", Justification = "Path should be in lower case")]
     public AttributeComparisonSource(string attributeName, in ComparisonSource elementSource)
     {
         if (string.IsNullOrEmpty(attributeName))
@@ -44,16 +43,24 @@ public AttributeComparisonSource(string attributeName, in ComparisonSource eleme
         Path = $"{elementSource.Path}[{attribute.Name.ToLowerInvariant()}]";
     }
 
-    #region Equals and HashCode
     /// 
-    public bool Equals(AttributeComparisonSource other) => Object.ReferenceEquals(Attribute, other.Attribute) && Path.Equals(other.Path, StringComparison.Ordinal) && ElementSource.Equals(other.ElementSource);
+    public override string ToString() => $"{{ Type = Attribute, Path = {Path} }}";
+
+    /// 
+    public bool Equals(AttributeComparisonSource other)
+        => ReferenceEquals(Attribute, other.Attribute) // AngleSharp overrides Equals and == for it's types, so we're using ReferenceEquals to check if the instances are the same.
+        && Path.Equals(other.Path, StringComparison.Ordinal)
+        && ElementSource.Equals(other.ElementSource);
+
     /// 
     public override int GetHashCode() => (Attribute, ElementSource).GetHashCode();
+
     /// 
     public override bool Equals(object? obj) => obj is AttributeComparisonSource other && Equals(other);
+
     /// 
     public static bool operator ==(AttributeComparisonSource left, AttributeComparisonSource right) => left.Equals(right);
+
     /// 
     public static bool operator !=(AttributeComparisonSource left, AttributeComparisonSource right) => !left.Equals(right);
-    #endregion
 }
diff --git a/src/AngleSharp.Diffing/Core/CompareDecision.cs b/src/AngleSharp.Diffing/Core/CompareDecision.cs
new file mode 100644
index 0000000..cc0942e
--- /dev/null
+++ b/src/AngleSharp.Diffing/Core/CompareDecision.cs
@@ -0,0 +1,34 @@
+namespace AngleSharp.Diffing.Core;
+
+/// 
+/// Represents the decision of a comparison.
+/// 
+[Flags]
+public enum CompareDecision
+{
+    /// 
+    /// Use when the compare result is unknown.
+    /// 
+    Unknown = 0,
+    /// 
+    /// Use when the two compared nodes or attributes are the same.
+    /// 
+    Same = 1,
+    /// 
+    /// Use when the two compared nodes or attributes are the different.
+    /// 
+    Different = 2,
+    /// 
+    /// Use when the comparison should be skipped and any child-nodes or attributes skipped as well.
+    /// 
+    Skip = 4,
+    /// 
+    /// Use when the comparison should skip any child-nodes.
+    /// 
+    SkipChildren = 8,
+    /// 
+    /// Use when the comparison should skip any attributes.
+    /// 
+    SkipAttributes = 16,
+}
+
diff --git a/src/AngleSharp.Diffing/Core/CompareResult.cs b/src/AngleSharp.Diffing/Core/CompareResult.cs
index 6ca7564..0d4ea28 100644
--- a/src/AngleSharp.Diffing/Core/CompareResult.cs
+++ b/src/AngleSharp.Diffing/Core/CompareResult.cs
@@ -3,44 +3,49 @@
 /// 
 /// Represents a result of a comparison.
 /// 
-[Flags]
-public enum CompareResult
+/// Gets the latest  of the comparison.
+/// Gets the optional  related to the current .
+public readonly record struct CompareResult(CompareDecision Decision, IDiff? Diff = null)
 {
     /// 
     /// Use when the compare result is unknown.
     /// 
-    Unknown = 0,
+    public static readonly CompareResult Unknown = default;
+
     /// 
     /// Use when the two compared nodes or attributes are the same.
     /// 
-    Same = 1,
-    /// 
-    /// Use when the two compared nodes or attributes are the different.
-    /// 
-    Different = 2,
+    public static readonly CompareResult Same = new CompareResult(CompareDecision.Same);
+
     /// 
     /// Use when the comparison should be skipped and any child-nodes or attributes skipped as well.
     /// 
-    Skip = 4,
+    public static readonly CompareResult Skip = new CompareResult(CompareDecision.Skip);
+
     /// 
     /// Use when the comparison should skip any child-nodes.
     /// 
-    SkipChildren = 8,
+    public static readonly CompareResult SkipChildren = new CompareResult(CompareDecision.SkipChildren);
+
     /// 
     /// Use when the comparison should skip any attributes.
     /// 
-    SkipAttributes = 16,
-}
+    public static readonly CompareResult SkipAttributes = new CompareResult(CompareDecision.SkipAttributes);
 
-/// 
-/// Helper methods for 
-/// 
-public static class CompareResultExtensions
-{
     /// 
-    /// Checks if a  is either a  or .
+    /// Use when the two compared nodes or attributes are the different.
     /// 
-    /// The compare result
-    public static bool IsSameOrSkip(this CompareResult compareResult) => compareResult == CompareResult.Same || compareResult == CompareResult.Skip;
-}
+    public static CompareResult Different => new CompareResult(CompareDecision.Different);
 
+    /// 
+    /// Use when the two compared nodes or attributes are the different.
+    /// 
+    /// The associated  describing the difference.
+    /// Returns a  with  set to .
+    public static CompareResult FromDiff(IDiff diff) => new CompareResult(CompareDecision.Different, diff);
+
+    /// 
+    /// Checks if a  is either a  or .
+    /// 
+    public bool IsSameOrSkip => this == Same || this == Skip;
+}
\ No newline at end of file
diff --git a/src/AngleSharp.Diffing/Core/Comparison.cs b/src/AngleSharp.Diffing/Core/Comparison.cs
index 3050f18..cdab391 100644
--- a/src/AngleSharp.Diffing/Core/Comparison.cs
+++ b/src/AngleSharp.Diffing/Core/Comparison.cs
@@ -3,19 +3,10 @@ namespace AngleSharp.Diffing.Core;
 /// 
 /// Represent a comparison between two nodes.
 /// 
-[DebuggerDisplay("Control: {Control.Path} | Test: {Test.Path}")]
-public readonly struct Comparison : IEquatable
+/// Gets the control source in the comparison.
+/// Gets the test source in the comparison.
+public readonly record struct Comparison(in ComparisonSource Control, in ComparisonSource Test)
 {
-    /// 
-    /// Gets the control source in the comparison.
-    /// 
-    public ComparisonSource Control { get; }
-
-    /// 
-    /// Gets the test source in the comparison
-    /// 
-    public ComparisonSource Test { get; }
-
     /// 
     /// Gets whether the control and test nodes are of the same type and has the same name.
     /// 
@@ -23,17 +14,6 @@ public bool AreNodeTypesEqual
         => Control.Node.NodeType == Test.Node.NodeType
         && Control.Node.NodeName.Equals(Test.Node.NodeName, StringComparison.OrdinalIgnoreCase);
 
-    /// 
-    /// Creates a new comparison.
-    /// 
-    /// The control source in the comparison.
-    /// The tes tsource in the comparison.
-    public Comparison(in ComparisonSource control, in ComparisonSource test)
-    {
-        Control = control;
-        Test = test;
-    }
-
     /// 
     /// Try to get the control and test node as the  type.
     /// Returns true if both test and control node is of type , false otherwise.
@@ -55,17 +35,4 @@ public bool TryGetNodesAsType([NotNullWhen(true)]out TNode? controlNode,
             return false;
         }
     }
-
-    #region Equals and HashCode
-    /// 
-    public bool Equals(Comparison other) => Control.Equals(other.Control) && Test.Equals(other.Test);
-    /// 
-    public override bool Equals(object? obj) => obj is Comparison other && Equals(other);
-    /// 
-    public override int GetHashCode() => (Control, Test).GetHashCode();
-    /// 
-    public static bool operator ==(Comparison left, Comparison right) => left.Equals(right);
-    /// 
-    public static bool operator !=(Comparison left, Comparison right) => !left.Equals(right);
-    #endregion
 }
diff --git a/src/AngleSharp.Diffing/Core/ComparisonSource.cs b/src/AngleSharp.Diffing/Core/ComparisonSource.cs
index ddf45ba..13025e2 100644
--- a/src/AngleSharp.Diffing/Core/ComparisonSource.cs
+++ b/src/AngleSharp.Diffing/Core/ComparisonSource.cs
@@ -3,7 +3,6 @@ namespace AngleSharp.Diffing.Core;
 /// 
 /// Represents a node source in a comparison.
 ///     
-[DebuggerDisplay("{Index} : {Path}")]
 public readonly struct ComparisonSource : IEquatable, IComparisonSource
 {
     private readonly int _hashCode;
@@ -65,7 +64,6 @@ public ComparisonSource(INode node, int index, string parentsPath, ComparisonSou
     /// 
     /// 
     /// 
-    [SuppressMessage("Globalization", "CA1308:Normalize strings to uppercase", Justification = "Path should be in lower case")]
     public static string GetNodePathSegment(INode node)
     {
         var index = GetPathIndex(node);
@@ -123,16 +121,21 @@ private static int GetPathIndex(INode node)
         throw new InvalidOperationException("Unexpected node tree state. The node was not found in its parents child nodes collection.");
     }
 
-    #region Equals and HashCode
+    /// 
+    public override string ToString() => $"{{ Type = {Node.NodeType}, Index = {Index}, Path = {Path} }}";
+
     /// 
     public bool Equals(ComparisonSource other) => Object.ReferenceEquals(Node, other.Node) && Index == other.Index && Path.Equals(other.Path, StringComparison.Ordinal) && SourceType == other.SourceType;
+
     /// 
     public override int GetHashCode() => _hashCode;
+
     /// 
     public override bool Equals(object? obj) => obj is ComparisonSource other && Equals(other);
+
     /// 
     public static bool operator ==(ComparisonSource left, ComparisonSource right) => left.Equals(right);
+
     /// 
     public static bool operator !=(ComparisonSource left, ComparisonSource right) => !left.Equals(right);
-    #endregion
 }
diff --git a/src/AngleSharp.Diffing/Core/Diffs/AttrDiff.cs b/src/AngleSharp.Diffing/Core/Diffs/AttrDiff.cs
index 9924b9c..e667a0d 100644
--- a/src/AngleSharp.Diffing/Core/Diffs/AttrDiff.cs
+++ b/src/AngleSharp.Diffing/Core/Diffs/AttrDiff.cs
@@ -1,15 +1,20 @@
 namespace AngleSharp.Diffing.Core;
 
 /// 
-/// Represents an attribute difference
+/// Represents an attribute difference.
 /// 
-[DebuggerDisplay("Attribute diff: Control = {Control.Path}, Test = {Test.Path}")]
-public class AttrDiff : DiffBase
+public record class AttrDiff : DiffBase
 {
+    /// 
+    /// Gets the kind of the diff.
+    /// 
+    public AttrDiffKind Kind { get; }
+
     /// 
     /// Creates an .
     /// 
-    public AttrDiff(in AttributeComparison comparison) : base(comparison.Control, comparison.Test, DiffTarget.Attribute)
+    public AttrDiff(in AttributeComparison comparison, AttrDiffKind kind) : base(comparison.Control, comparison.Test, DiffTarget.Attribute)
     {
+        Kind = kind;
     }
 }
diff --git a/src/AngleSharp.Diffing/Core/Diffs/AttrDiffKind.cs b/src/AngleSharp.Diffing/Core/Diffs/AttrDiffKind.cs
new file mode 100644
index 0000000..18f105a
--- /dev/null
+++ b/src/AngleSharp.Diffing/Core/Diffs/AttrDiffKind.cs
@@ -0,0 +1,20 @@
+namespace AngleSharp.Diffing.Core;
+
+/// 
+/// Defines the reason for two attributes to be the different.
+/// 
+public enum AttrDiffKind
+{
+    /// 
+    /// The attribute difference is unspecified.
+    /// 
+    Unspecified = 0,
+    /// 
+    /// The name of the attribute is different.
+    /// 
+    Name,
+    /// 
+    /// The value of the attribute is different.
+    /// 
+    Value,
+}
diff --git a/src/AngleSharp.Diffing/Core/Diffs/CommentDiff.cs b/src/AngleSharp.Diffing/Core/Diffs/CommentDiff.cs
new file mode 100644
index 0000000..e450eb2
--- /dev/null
+++ b/src/AngleSharp.Diffing/Core/Diffs/CommentDiff.cs
@@ -0,0 +1,14 @@
+namespace AngleSharp.Diffing.Core;
+
+/// 
+/// Represents a difference between two nodes.
+/// 
+public record class CommentDiff : NodeDiff
+{
+    /// 
+    /// Creates a .
+    /// 
+    public CommentDiff(in Comparison comparison) : base(comparison)
+    {
+    }
+}
diff --git a/src/AngleSharp.Diffing/Core/Diffs/DiffBase.cs b/src/AngleSharp.Diffing/Core/Diffs/DiffBase.cs
index 40273ce..2657337 100644
--- a/src/AngleSharp.Diffing/Core/Diffs/DiffBase.cs
+++ b/src/AngleSharp.Diffing/Core/Diffs/DiffBase.cs
@@ -4,32 +4,11 @@ namespace AngleSharp.Diffing.Core;
 /// Represents a difference found during comparison.
 /// 
 /// 
-public abstract class DiffBase : IDiff where T : struct
+/// Gets the control source in the comparison.
+/// Gets the test source in the comparison.
+///  Gets the target type in that failed the comparison.
+public abstract record class DiffBase(T Control, T Test, DiffTarget Target) : IDiff where T : struct
 {
-    /// 
-    /// Gets the control source in the comparison.
-    /// 
-    public T Control { get; }
-
-    /// 
-    /// Gets the test source in the comparison.
-    /// 
-    public T Test { get; }
-
     /// 
-    public DiffResult Result { get; }
-
-    /// 
-    public DiffTarget Target { get; }
-
-    /// 
-    /// Instantiate the 
-    /// 
-    protected DiffBase(in T control, in T test, DiffTarget target)
-    {
-        Control = control;
-        Test = test;
-        Result = DiffResult.Different;
-        Target = target;
-    }
+    public DiffResult Result => DiffResult.Different;
 }
diff --git a/src/AngleSharp.Diffing/Core/Diffs/ElementDiff.cs b/src/AngleSharp.Diffing/Core/Diffs/ElementDiff.cs
new file mode 100644
index 0000000..bebd620
--- /dev/null
+++ b/src/AngleSharp.Diffing/Core/Diffs/ElementDiff.cs
@@ -0,0 +1,21 @@
+namespace AngleSharp.Diffing.Core.Diffs;
+
+/// 
+/// Represents an element difference.
+/// 
+public record class ElementDiff : NodeDiff
+{
+    /// 
+    /// Gets the kind of the diff.
+    /// 
+    public ElementDiffKind Kind { get; }
+
+    /// 
+    /// Creates an .
+    /// 
+    public ElementDiff(in Comparison comparison, ElementDiffKind kind) : base(comparison)
+    {
+        Kind = kind;
+    }
+
+}
diff --git a/src/AngleSharp.Diffing/Core/Diffs/ElementDiffKind.cs b/src/AngleSharp.Diffing/Core/Diffs/ElementDiffKind.cs
new file mode 100644
index 0000000..4404e03
--- /dev/null
+++ b/src/AngleSharp.Diffing/Core/Diffs/ElementDiffKind.cs
@@ -0,0 +1,20 @@
+namespace AngleSharp.Diffing.Core.Diffs;
+
+/// 
+/// Defines the reason of two elements to be different.
+/// 
+public enum ElementDiffKind
+{
+    /// 
+    /// The attribute difference is unspecified.
+    /// 
+    Unspecified = 0,
+    /// 
+    /// The type/name of the elements are different.
+    /// 
+    Name,
+    /// 
+    /// The two elements have difference tag closing styles.
+    /// 
+    ClosingStyle,
+}
diff --git a/src/AngleSharp.Diffing/Core/Diffs/MissingAttrDiff.cs b/src/AngleSharp.Diffing/Core/Diffs/MissingAttrDiff.cs
index 2df6334..17d7c54 100644
--- a/src/AngleSharp.Diffing/Core/Diffs/MissingAttrDiff.cs
+++ b/src/AngleSharp.Diffing/Core/Diffs/MissingAttrDiff.cs
@@ -3,8 +3,7 @@ namespace AngleSharp.Diffing.Core;
 /// 
 /// Represents a missing attribute in a test node.
 /// 
-[DebuggerDisplay("Missing Attribute: Control = {Control.Path}")]
-public class MissingAttrDiff : MissingDiffBase
+public record class MissingAttrDiff : MissingDiffBase
 {
     /// 
     /// Creates a .
diff --git a/src/AngleSharp.Diffing/Core/Diffs/MissingDiffBase.cs b/src/AngleSharp.Diffing/Core/Diffs/MissingDiffBase.cs
index d172c55..ae076c6 100644
--- a/src/AngleSharp.Diffing/Core/Diffs/MissingDiffBase.cs
+++ b/src/AngleSharp.Diffing/Core/Diffs/MissingDiffBase.cs
@@ -3,27 +3,10 @@
 /// 
 /// Represents a missing node or attribute in the test DOM tree.
 /// 
-/// 
-public abstract class MissingDiffBase : IDiff where T : struct
+/// Gets the control source that has the missing item.
+/// Gets the target type in that failed the comparison.
+public abstract record class MissingDiffBase(T Control, DiffTarget Target) : IDiff where T : struct
 {
-    /// 
-    /// Gets the control source that has the missing item.
-    /// 
-    public T Control { get; }
-
-    /// 
-    public DiffResult Result { get; }
-
     /// 
-    public DiffTarget Target { get; }
-
-    /// 
-    /// Create a 
-    /// 
-    protected MissingDiffBase(in T control, DiffTarget target)
-    {
-        Control = control;
-        Result = DiffResult.Missing;
-        Target = target;
-    }
+    public DiffResult Result => DiffResult.Missing;
 }
diff --git a/src/AngleSharp.Diffing/Core/Diffs/MissingNodeDiff.cs b/src/AngleSharp.Diffing/Core/Diffs/MissingNodeDiff.cs
index e85dae1..6334838 100644
--- a/src/AngleSharp.Diffing/Core/Diffs/MissingNodeDiff.cs
+++ b/src/AngleSharp.Diffing/Core/Diffs/MissingNodeDiff.cs
@@ -3,8 +3,7 @@ namespace AngleSharp.Diffing.Core;
 /// 
 /// Represents a missing node in the test DOM tree.
 /// 
-[DebuggerDisplay("Missing {Target}: Control = {Control.Path}")]
-public class MissingNodeDiff : MissingDiffBase
+public record class MissingNodeDiff : MissingDiffBase
 {
     /// 
     /// Creates a .
diff --git a/src/AngleSharp.Diffing/Core/Diffs/NodeDiff.cs b/src/AngleSharp.Diffing/Core/Diffs/NodeDiff.cs
index 0afd036..6fa5cec 100644
--- a/src/AngleSharp.Diffing/Core/Diffs/NodeDiff.cs
+++ b/src/AngleSharp.Diffing/Core/Diffs/NodeDiff.cs
@@ -3,8 +3,7 @@ namespace AngleSharp.Diffing.Core;
 /// 
 /// Represents a difference between two nodes.
 /// 
-[DebuggerDisplay("{Target} diff: Control = {Control.Path}, Test = {Test.Path}")]
-public class NodeDiff : DiffBase
+public record class NodeDiff : DiffBase
 {
     /// 
     /// Creates a .
@@ -12,4 +11,4 @@ public class NodeDiff : DiffBase
     public NodeDiff(in Comparison comparison) : base(comparison.Control, comparison.Test, comparison.Control.Node.NodeType.ToDiffTarget())
     {
     }
-}
+}
\ No newline at end of file
diff --git a/src/AngleSharp.Diffing/Core/Diffs/StylesheetDiff.cs b/src/AngleSharp.Diffing/Core/Diffs/StylesheetDiff.cs
new file mode 100644
index 0000000..99a97fa
--- /dev/null
+++ b/src/AngleSharp.Diffing/Core/Diffs/StylesheetDiff.cs
@@ -0,0 +1,14 @@
+namespace AngleSharp.Diffing.Core;
+
+/// 
+/// Represents a difference between the applied style of two nodes
+/// 
+public record class StylesheetDiff : NodeDiff
+{
+    /// 
+    /// Creates a .
+    /// 
+    public StylesheetDiff(in Comparison comparison) : base(comparison)
+    {
+    }
+}
diff --git a/src/AngleSharp.Diffing/Core/Diffs/TextDiff.cs b/src/AngleSharp.Diffing/Core/Diffs/TextDiff.cs
new file mode 100644
index 0000000..6598593
--- /dev/null
+++ b/src/AngleSharp.Diffing/Core/Diffs/TextDiff.cs
@@ -0,0 +1,14 @@
+namespace AngleSharp.Diffing.Core;
+
+/// 
+/// Represents a difference between two texts.
+/// 
+public record class TextDiff : NodeDiff
+{
+    /// 
+    /// Creates a .
+    /// 
+    public TextDiff(in Comparison comparison) : base(comparison)
+    {
+    }
+}
diff --git a/src/AngleSharp.Diffing/Core/Diffs/UnexpectedAttrDiff.cs b/src/AngleSharp.Diffing/Core/Diffs/UnexpectedAttrDiff.cs
index c85d58c..7a7e364 100644
--- a/src/AngleSharp.Diffing/Core/Diffs/UnexpectedAttrDiff.cs
+++ b/src/AngleSharp.Diffing/Core/Diffs/UnexpectedAttrDiff.cs
@@ -3,8 +3,7 @@ namespace AngleSharp.Diffing.Core;
 /// 
 /// Represents an unexpected attribute found in the test DOM tree.
 /// 
-[DebuggerDisplay("Unexpected Attribute: Test = {Test.Path}")]
-public class UnexpectedAttrDiff : UnexpectedDiffBase
+public record class UnexpectedAttrDiff : UnexpectedDiffBase
 {
     /// 
     /// Creates a .
diff --git a/src/AngleSharp.Diffing/Core/Diffs/UnexpectedDiffBase.cs b/src/AngleSharp.Diffing/Core/Diffs/UnexpectedDiffBase.cs
index c675ae5..04c62be 100644
--- a/src/AngleSharp.Diffing/Core/Diffs/UnexpectedDiffBase.cs
+++ b/src/AngleSharp.Diffing/Core/Diffs/UnexpectedDiffBase.cs
@@ -3,26 +3,10 @@ namespace AngleSharp.Diffing.Core;
 /// 
 /// Represents an unexpected node or attribute in the test DOM tree.
 /// 
-public abstract class UnexpectedDiffBase : IDiff where T : struct
+/// The source of the unexpected item in the test DOM tree.
+///  Gets the target type in that failed the comparison.
+public abstract record class UnexpectedDiffBase(T Test, DiffTarget Target) : IDiff where T : struct
 {
-    /// 
-    /// The source of the unexpected item in the test DOM tree.
-    /// 
-    public T Test { get; }
-
-    /// 
-    public DiffResult Result { get; }
-
     /// 
-    public DiffTarget Target { get; }
-
-    /// 
-    /// Creates a .
-    /// 
-    protected UnexpectedDiffBase(in T test, DiffTarget target)
-    {
-        Test = test;
-        Result = DiffResult.Unexpected;
-        Target = target;
-    }
+    public DiffResult Result => DiffResult.Unexpected;
 }
diff --git a/src/AngleSharp.Diffing/Core/Diffs/UnexpectedNodeDiff.cs b/src/AngleSharp.Diffing/Core/Diffs/UnexpectedNodeDiff.cs
index 43b5d02..a67eb50 100644
--- a/src/AngleSharp.Diffing/Core/Diffs/UnexpectedNodeDiff.cs
+++ b/src/AngleSharp.Diffing/Core/Diffs/UnexpectedNodeDiff.cs
@@ -3,8 +3,7 @@ namespace AngleSharp.Diffing.Core;
 /// 
 /// Represents an unexpected node in the test DOM tree.
 /// 
-[DebuggerDisplay("Unexpected {Target}: Test = {Test.Path}")]
-public class UnexpectedNodeDiff : UnexpectedDiffBase
+public record class UnexpectedNodeDiff : UnexpectedDiffBase
 {
     /// 
     /// Creates a .
diff --git a/src/AngleSharp.Diffing/Core/HtmlDifferenceEngine.cs b/src/AngleSharp.Diffing/Core/HtmlDifferenceEngine.cs
index f0f9aa8..8065dac 100644
--- a/src/AngleSharp.Diffing/Core/HtmlDifferenceEngine.cs
+++ b/src/AngleSharp.Diffing/Core/HtmlDifferenceEngine.cs
@@ -1,3 +1,5 @@
+using AngleSharp.Diffing.Core.Diffs;
+
 namespace AngleSharp.Diffing.Core;
 
 /// 
@@ -85,9 +87,9 @@ private IEnumerable CompareNode(in Comparison comparison)
         }
 
         var compareRes = _diffingStrategy.Compare(comparison);
-        if (compareRes.HasFlag(CompareResult.Different))
+        if (compareRes.Decision.HasFlag(CompareDecision.Different))
         {
-            IDiff diff = new NodeDiff(comparison);
+            IDiff diff = compareRes.Diff ?? new NodeDiff(comparison);
             return new[] { diff };
         }
 
@@ -99,16 +101,16 @@ private IEnumerable CompareElement(in Comparison comparison)
         var result = new List();
 
         var compareRes = _diffingStrategy.Compare(comparison);
-        if (compareRes.HasFlag(CompareResult.Different))
+        if (compareRes.Decision.HasFlag(CompareDecision.Different))
         {
-            result.Add(new NodeDiff(comparison));
+            result.Add(compareRes.Diff ?? new ElementDiff(comparison, ElementDiffKind.Unspecified));
         }
 
-        if (!compareRes.HasFlag(CompareResult.Skip))
+        if (!compareRes.Decision.HasFlag(CompareDecision.Skip))
         {
-            if (!compareRes.HasFlag(CompareResult.SkipAttributes))
+            if (!compareRes.Decision.HasFlag(CompareDecision.SkipAttributes))
                 result.AddRange(CompareElementAttributes(comparison));
-            if (!compareRes.HasFlag(CompareResult.SkipChildren))
+            if (!compareRes.Decision.HasFlag(CompareDecision.SkipChildren))
                 result.AddRange(CompareChildNodes(comparison));
         }
 
@@ -184,8 +186,10 @@ private IEnumerable CompareAttributes(IEnumerable co
         foreach (var comparison in comparisons)
         {
             var compareRes = _diffingStrategy.Compare(comparison);
-            if (compareRes == CompareResult.Different)
-                yield return new AttrDiff(comparison);
+            if (compareRes.Decision == CompareDecision.Different)
+            {
+                yield return compareRes.Diff ?? new AttrDiff(comparison, AttrDiffKind.Unspecified);
+            }
         }
     }
 }
diff --git a/src/AngleSharp.Diffing/Extensions/UnexpectedDOMTreeStructureException.cs b/src/AngleSharp.Diffing/Extensions/UnexpectedDOMTreeStructureException.cs
index 014cf85..cd77df0 100644
--- a/src/AngleSharp.Diffing/Extensions/UnexpectedDOMTreeStructureException.cs
+++ b/src/AngleSharp.Diffing/Extensions/UnexpectedDOMTreeStructureException.cs
@@ -1,6 +1,4 @@
-using System.Runtime.Serialization;
-
-namespace AngleSharp.Diffing.Extensions;
+namespace AngleSharp.Diffing.Extensions;
 
 /// 
 /// Represents an exception that is thrown when a part of the DOM tree is not as expected.
diff --git a/src/AngleSharp.Diffing/Strategies/AttributeStrategies/AttributeComparer.cs b/src/AngleSharp.Diffing/Strategies/AttributeStrategies/AttributeComparer.cs
index 6c935c2..782773d 100644
--- a/src/AngleSharp.Diffing/Strategies/AttributeStrategies/AttributeComparer.cs
+++ b/src/AngleSharp.Diffing/Strategies/AttributeStrategies/AttributeComparer.cs
@@ -17,7 +17,7 @@ public static class AttributeComparer
     /// 
     public static CompareResult Compare(in AttributeComparison comparison, CompareResult currentDecision)
     {
-        if (currentDecision.IsSameOrSkip())
+        if (currentDecision.IsSameOrSkip)
             return currentDecision;
 
         var (ignoreCase, isRegexValueCompare) = GetComparisonModifiers(comparison);
@@ -25,7 +25,7 @@ public static CompareResult Compare(in AttributeComparison comparison, CompareRe
         var hasSameName = CompareAttributeNames(comparison, ignoreCase, isRegexValueCompare);
 
         if (!hasSameName)
-            return CompareResult.Different;
+            return CompareResult.FromDiff(new AttrDiff(comparison, AttrDiffKind.Name));
 
         var hasSameValue = isRegexValueCompare
             ? CompareAttributeValuesByRegex(comparison, ignoreCase)
@@ -33,7 +33,7 @@ public static CompareResult Compare(in AttributeComparison comparison, CompareRe
 
         return hasSameValue
             ? CompareResult.Same
-            : CompareResult.Different;
+            : CompareResult.FromDiff(new AttrDiff(comparison, AttrDiffKind.Value));
     }
 
     private static (bool ignoreCase, bool isRegexCompare) GetComparisonModifiers(in AttributeComparison comparison)
diff --git a/src/AngleSharp.Diffing/Strategies/AttributeStrategies/BooleanAttributeComparer.cs b/src/AngleSharp.Diffing/Strategies/AttributeStrategies/BooleanAttributeComparer.cs
index 2658b63..889badc 100644
--- a/src/AngleSharp.Diffing/Strategies/AttributeStrategies/BooleanAttributeComparer.cs
+++ b/src/AngleSharp.Diffing/Strategies/AttributeStrategies/BooleanAttributeComparer.cs
@@ -55,10 +55,10 @@ public BooleanAttributeComparer(BooleanAttributeComparision mode)
     /// 
     public CompareResult Compare(in AttributeComparison comparison, CompareResult currentDecision)
     {
-        if (currentDecision.IsSameOrSkip())
+        if (currentDecision.IsSameOrSkip)
             return currentDecision;
         if (!IsAttributeNamesEqual(comparison))
-            return CompareResult.Different;
+            return CompareResult.FromDiff(new AttrDiff(comparison, AttrDiffKind.Name));
         if (!BooleanAttributesSet.Contains(comparison.Control.Attribute.Name))
             return currentDecision;
 
@@ -66,7 +66,9 @@ public CompareResult Compare(in AttributeComparison comparison, CompareResult cu
             ? CompareStrict(comparison)
             : true;
 
-        return hasSameValue ? CompareResult.Same : CompareResult.Different;
+        return hasSameValue ?
+            CompareResult.Same :
+            CompareResult.FromDiff(new AttrDiff(comparison, AttrDiffKind.Value));
     }
 
     private static bool IsAttributeNamesEqual(in AttributeComparison comparison)
diff --git a/src/AngleSharp.Diffing/Strategies/AttributeStrategies/ClassAttributeComparer.cs b/src/AngleSharp.Diffing/Strategies/AttributeStrategies/ClassAttributeComparer.cs
index 8f0d56e..cfe136b 100644
--- a/src/AngleSharp.Diffing/Strategies/AttributeStrategies/ClassAttributeComparer.cs
+++ b/src/AngleSharp.Diffing/Strategies/AttributeStrategies/ClassAttributeComparer.cs
@@ -12,19 +12,19 @@ public static class ClassAttributeComparer
     /// 
     public static CompareResult Compare(in AttributeComparison comparison, CompareResult currentDecision)
     {
-        if (currentDecision.IsSameOrSkip())
+        if (currentDecision.IsSameOrSkip)
             return currentDecision;
 
         if (!IsBothClassAttributes(comparison))
-            return CompareResult.Different;
+            return CompareResult.FromDiff(new AttrDiff(comparison, AttrDiffKind.Name));
 
         var (ctrlElm, testElm) = comparison.GetAttributeElements();
         if (ctrlElm.ClassList.Length != testElm.ClassList.Length)
-            return CompareResult.Different;
+            return CompareResult.FromDiff(new AttrDiff(comparison, AttrDiffKind.Value));
 
         return ctrlElm.ClassList.All(x => testElm.ClassList.Contains(x))
             ? CompareResult.Same
-            : CompareResult.Different;
+            : CompareResult.FromDiff(new AttrDiff(comparison, AttrDiffKind.Value));
     }
 
     private static bool IsBothClassAttributes(in AttributeComparison comparison)
diff --git a/src/AngleSharp.Diffing/Strategies/AttributeStrategies/IgnoreAttributeComparer.cs b/src/AngleSharp.Diffing/Strategies/AttributeStrategies/IgnoreAttributeComparer.cs
index bafb648..c74f619 100644
--- a/src/AngleSharp.Diffing/Strategies/AttributeStrategies/IgnoreAttributeComparer.cs
+++ b/src/AngleSharp.Diffing/Strategies/AttributeStrategies/IgnoreAttributeComparer.cs
@@ -12,7 +12,7 @@ public static class IgnoreAttributeComparer
     /// 
     public static CompareResult Compare(in AttributeComparison comparison, CompareResult currentDecision)
     {
-        if (currentDecision.IsSameOrSkip())
+        if (currentDecision.IsSameOrSkip)
             return currentDecision;
 
         return comparison.Control.Attribute.Name.EndsWith(DIFF_IGNORE_POSTFIX, StringComparison.OrdinalIgnoreCase)
diff --git a/src/AngleSharp.Diffing/Strategies/AttributeStrategies/OrderingStyleAttributeComparer.cs b/src/AngleSharp.Diffing/Strategies/AttributeStrategies/OrderingStyleAttributeComparer.cs
index 5e3e73d..4e3d830 100644
--- a/src/AngleSharp.Diffing/Strategies/AttributeStrategies/OrderingStyleAttributeComparer.cs
+++ b/src/AngleSharp.Diffing/Strategies/AttributeStrategies/OrderingStyleAttributeComparer.cs
@@ -10,7 +10,7 @@ public static class OrderingStyleAttributeComparer
     /// 
     public static CompareResult Compare(in AttributeComparison comparison, CompareResult currentDecision)
     {
-        if (currentDecision.IsSameOrSkip())
+        if (currentDecision.IsSameOrSkip)
             return currentDecision;
 
         return IsStyleAttributeComparison(comparison)
@@ -18,6 +18,12 @@ public static CompareResult Compare(in AttributeComparison comparison, CompareRe
             : currentDecision;
     }
 
+    private static bool IsStyleAttributeComparison(in AttributeComparison comparison)
+    {
+        return comparison.Control.Attribute.Name.Equals(AttributeNames.Style, StringComparison.Ordinal) &&
+            comparison.Test.Attribute.Name.Equals(AttributeNames.Style, StringComparison.Ordinal);
+    }
+
     private static CompareResult CompareElementStyle(in AttributeComparison comparison)
     {
         var (ctrlElm, testElm) = comparison.GetAttributeElements();
@@ -25,13 +31,7 @@ private static CompareResult CompareElementStyle(in AttributeComparison comparis
         var testStyle = testElm.GetStyle();
         return CompareCssStyleDeclarations(ctrlStyle, testStyle)
             ? CompareResult.Same
-            : CompareResult.Different;
-    }
-
-    private static bool IsStyleAttributeComparison(in AttributeComparison comparison)
-    {
-        return comparison.Control.Attribute.Name.Equals(AttributeNames.Style, StringComparison.Ordinal) &&
-            comparison.Test.Attribute.Name.Equals(AttributeNames.Style, StringComparison.Ordinal);
+            : CompareResult.FromDiff(new AttrDiff(comparison, AttrDiffKind.Value));
     }
 
     private static bool CompareCssStyleDeclarations(ICssStyleDeclaration control, ICssStyleDeclaration test)
diff --git a/src/AngleSharp.Diffing/Strategies/AttributeStrategies/StyleAttributeComparer.cs b/src/AngleSharp.Diffing/Strategies/AttributeStrategies/StyleAttributeComparer.cs
index 76ee635..00dfaee 100644
--- a/src/AngleSharp.Diffing/Strategies/AttributeStrategies/StyleAttributeComparer.cs
+++ b/src/AngleSharp.Diffing/Strategies/AttributeStrategies/StyleAttributeComparer.cs
@@ -10,7 +10,7 @@ public static class StyleAttributeComparer
     /// 
     public static CompareResult Compare(in AttributeComparison comparison, CompareResult currentDecision)
     {
-        if (currentDecision.IsSameOrSkip())
+        if (currentDecision.IsSameOrSkip)
             return currentDecision;
 
         return IsStyleAttributeComparison(comparison)
@@ -18,6 +18,12 @@ public static CompareResult Compare(in AttributeComparison comparison, CompareRe
             : currentDecision;
     }
 
+    private static bool IsStyleAttributeComparison(in AttributeComparison comparison)
+    {
+        return comparison.Control.Attribute.Name.Equals(AttributeNames.Style, StringComparison.Ordinal) &&
+            comparison.Test.Attribute.Name.Equals(AttributeNames.Style, StringComparison.Ordinal);
+    }
+
     private static CompareResult CompareElementStyle(in AttributeComparison comparison)
     {
         var (ctrlElm, testElm) = comparison.GetAttributeElements();
@@ -25,13 +31,7 @@ private static CompareResult CompareElementStyle(in AttributeComparison comparis
         var testStyle = testElm.GetStyle();
         return CompareCssStyleDeclarations(ctrlStyle, testStyle)
             ? CompareResult.Same
-            : CompareResult.Different;
-    }
-
-    private static bool IsStyleAttributeComparison(in AttributeComparison comparison)
-    {
-        return comparison.Control.Attribute.Name.Equals(AttributeNames.Style, StringComparison.Ordinal) &&
-            comparison.Test.Attribute.Name.Equals(AttributeNames.Style, StringComparison.Ordinal);
+            : CompareResult.FromDiff(new AttrDiff(comparison, AttrDiffKind.Value));
     }
 
     private static bool CompareCssStyleDeclarations(ICssStyleDeclaration control, ICssStyleDeclaration test)
diff --git a/src/AngleSharp.Diffing/Strategies/CommentStrategies/CommentComparer.cs b/src/AngleSharp.Diffing/Strategies/CommentStrategies/CommentComparer.cs
index d06359e..7ea0bc5 100644
--- a/src/AngleSharp.Diffing/Strategies/CommentStrategies/CommentComparer.cs
+++ b/src/AngleSharp.Diffing/Strategies/CommentStrategies/CommentComparer.cs
@@ -10,10 +10,14 @@ public static class CommentComparer
     /// 
     public static CompareResult Compare(in Comparison comparison, CompareResult currentDecision)
     {
-        if (currentDecision.IsSameOrSkip())
+        if (currentDecision.IsSameOrSkip)
+            return currentDecision;
+
+        if (comparison.TryGetNodesAsType(out var controlComment, out var testComment))
+            return controlComment.Data.Equals(testComment.Data, StringComparison.Ordinal)
+                ? CompareResult.Same
+                : CompareResult.FromDiff(new CommentDiff(comparison));
+        else
             return currentDecision;
-        return comparison.Control.Node.NodeType == NodeType.Comment && comparison.AreNodeTypesEqual
-            ? CompareResult.Same
-            : CompareResult.Different;
     }
 }
diff --git a/src/AngleSharp.Diffing/Strategies/ElementStrategies/ElementComparer.cs b/src/AngleSharp.Diffing/Strategies/ElementStrategies/ElementComparer.cs
index ef85c93..4c5d12a 100644
--- a/src/AngleSharp.Diffing/Strategies/ElementStrategies/ElementComparer.cs
+++ b/src/AngleSharp.Diffing/Strategies/ElementStrategies/ElementComparer.cs
@@ -1,3 +1,4 @@
+using AngleSharp.Diffing.Core.Diffs;
 using AngleSharp.Html.Parser.Tokens;
 
 namespace AngleSharp.Diffing.Strategies.ElementStrategies;
@@ -30,24 +31,27 @@ public ElementComparer(bool enforceTagClosing)
     /// 
     public CompareResult Compare(in Comparison comparison, CompareResult currentDecision)
     {
-        if (currentDecision.IsSameOrSkip())
+        if (currentDecision.IsSameOrSkip)
             return currentDecision;
 
-        var result = comparison.Control.Node.NodeType == NodeType.Element && comparison.AreNodeTypesEqual
+        if (!comparison.TryGetNodesAsType(out var controlElement, out var testElement))
+            return currentDecision;
+
+        var result = comparison.AreNodeTypesEqual
             ? CompareResult.Same
-            : CompareResult.Different;
+            : CompareResult.FromDiff(new ElementDiff(comparison, ElementDiffKind.Name));
 
         if (EnforceTagClosing && result == CompareResult.Same)
         {
-            if (comparison.Test.Node is not IElement testElement || testElement.SourceReference is not HtmlTagToken testTag)
+            if (testElement.SourceReference is not HtmlTagToken testTag)
                 throw new InvalidOperationException("No source reference attached to test element, cannot determine element tag closing style.");
 
-            if (comparison.Control.Node is not IElement controlElement || controlElement.SourceReference is not HtmlTagToken controlTag)
+            if (controlElement.SourceReference is not HtmlTagToken controlTag)
                 throw new InvalidOperationException("No source reference attached to test element, cannot determine element tag closing style.");
 
             return testTag.IsSelfClosing == controlTag.IsSelfClosing
                 ? result
-                : CompareResult.Different;
+                : CompareResult.FromDiff(new ElementDiff(comparison, ElementDiffKind.ClosingStyle));
         }
 
         return result;
diff --git a/src/AngleSharp.Diffing/Strategies/TextNodeStrategies/StyleSheetTextNodeComparer.cs b/src/AngleSharp.Diffing/Strategies/TextNodeStrategies/StyleSheetTextNodeComparer.cs
index f8784e1..8284a2a 100644
--- a/src/AngleSharp.Diffing/Strategies/TextNodeStrategies/StyleSheetTextNodeComparer.cs
+++ b/src/AngleSharp.Diffing/Strategies/TextNodeStrategies/StyleSheetTextNodeComparer.cs
@@ -10,10 +10,10 @@ public static class StyleSheetTextNodeComparer
     /// 
     public static CompareResult Compare(in Comparison comparison, CompareResult currentDecision)
     {
-        if (currentDecision.IsSameOrSkip())
+        if (currentDecision.IsSameOrSkip)
             return currentDecision;
         if (TryGetStyleDeclaretions(comparison, out var controlStyles, out var testStyles))
-            return Compare(controlStyles, testStyles);
+            return Compare(comparison, controlStyles, testStyles);
         else
             return currentDecision;
     }
@@ -36,13 +36,13 @@ private static bool TryGetStyleDeclaretions(in Comparison comparison, [NotNullWh
             return false;
     }
 
-    private static CompareResult Compare(IStyleSheet controlStyles, IStyleSheet testStyles)
+    private static CompareResult Compare(in Comparison comparison, IStyleSheet controlStyles, IStyleSheet testStyles)
     {
         var control = controlStyles.ToCss();
         var test = testStyles.ToCss();
 
         return control.Equals(test, StringComparison.Ordinal)
             ? CompareResult.Same
-            : CompareResult.Different;
+            : CompareResult.FromDiff(new StylesheetDiff(comparison));
     }
 }
diff --git a/src/AngleSharp.Diffing/Strategies/TextNodeStrategies/TextNodeComparer.cs b/src/AngleSharp.Diffing/Strategies/TextNodeStrategies/TextNodeComparer.cs
index af458a4..bfe64f0 100644
--- a/src/AngleSharp.Diffing/Strategies/TextNodeStrategies/TextNodeComparer.cs
+++ b/src/AngleSharp.Diffing/Strategies/TextNodeStrategies/TextNodeComparer.cs
@@ -37,16 +37,16 @@ public TextNodeComparer(WhitespaceOption option = WhitespaceOption.Preserve, boo
     /// 
     public CompareResult Compare(in Comparison comparison, CompareResult currentDecision)
     {
-        if (currentDecision.IsSameOrSkip())
+        if (currentDecision.IsSameOrSkip)
             return currentDecision;
 
         if (comparison.TryGetNodesAsType(out var controlTextNode, out var testTextNode))
-            return Compare(controlTextNode, testTextNode);
+            return Compare(comparison, controlTextNode, testTextNode);
         else
             return currentDecision;
     }
 
-    private CompareResult Compare(IText controlTextNode, IText testTextNode)
+    private CompareResult Compare(in Comparison comparison, IText controlTextNode, IText testTextNode)
     {
         var option = GetWhitespaceOption(controlTextNode);
         var compareMethod = GetCompareMethod(controlTextNode);
@@ -68,11 +68,11 @@ private CompareResult Compare(IText controlTextNode, IText testTextNode)
         var isRegexCompare = GetIsRegexComparison(controlTextNode);
 
         return isRegexCompare
-            ? PerformRegexCompare(compareMethod, controlText, testText)
-            : PerformStringCompare(compareMethod, controlText, testText);
+            ? PerformRegexCompare(comparison, compareMethod, controlText, testText)
+            : PerformStringCompare(comparison, compareMethod, controlText, testText);
     }
 
-    private static CompareResult PerformRegexCompare(StringComparison compareMethod, string controlText, string testText)
+    private static CompareResult PerformRegexCompare(in Comparison comparison, StringComparison compareMethod, string controlText, string testText)
     {
         var regexOptions = compareMethod == StringComparison.OrdinalIgnoreCase
             ? RegexOptions.IgnoreCase
@@ -80,14 +80,14 @@ private static CompareResult PerformRegexCompare(StringComparison compareMethod,
 
         return Regex.IsMatch(testText, controlText, regexOptions, TimeSpan.FromSeconds(5))
             ? CompareResult.Same
-            : CompareResult.Different;
+            : CompareResult.FromDiff(new TextDiff(comparison));
     }
 
-    private static CompareResult PerformStringCompare(StringComparison compareMethod, string controlText, string testText)
+    private static CompareResult PerformStringCompare(in Comparison comparison, StringComparison compareMethod, string controlText, string testText)
     {
         return controlText.Equals(testText, compareMethod)
             ? CompareResult.Same
-            : CompareResult.Different;
+            : CompareResult.FromDiff(new TextDiff(comparison));
     }
 
     private static bool GetIsRegexComparison(IText controlTextNode)
diff --git a/src/AngleSharp.Diffing/System.Runtime.CompilerServices/IsExternalInit.cs b/src/AngleSharp.Diffing/System.Runtime.CompilerServices/IsExternalInit.cs
new file mode 100644
index 0000000..6492a5e
--- /dev/null
+++ b/src/AngleSharp.Diffing/System.Runtime.CompilerServices/IsExternalInit.cs
@@ -0,0 +1,66 @@
+// 
+//   This code file has automatically been added by the "IsExternalInit" NuGet package (https://www.nuget.org/packages/IsExternalInit).
+//   Please see https://github.com/manuelroemer/IsExternalInit for more information.
+//
+//   IMPORTANT:
+//   DO NOT DELETE THIS FILE if you are using a "packages.config" file to manage your NuGet references.
+//   Consider migrating to PackageReferences instead:
+//   https://docs.microsoft.com/en-us/nuget/consume-packages/migrate-packages-config-to-package-reference
+//   Migrating brings the following benefits:
+//   * The "IsExternalInit" folder and the "IsExternalInit.cs" file don't appear in your project.
+//   * The added file is immutable and can therefore not be modified by coincidence.
+//   * Updating/Uninstalling the package will work flawlessly.
+// 
+
+#region License
+// MIT License
+// 
+// Copyright (c) Manuel Römer
+// 
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to deal
+// in the Software without restriction, including without limitation the rights
+// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+// copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+// 
+// The above copyright notice and this permission notice shall be included in all
+// copies or substantial portions of the Software.
+// 
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+// SOFTWARE.
+#endregion
+
+#if !ISEXTERNALINIT_DISABLE
+#nullable enable
+#pragma warning disable
+
+namespace System.Runtime.CompilerServices
+{
+    using global::System.Diagnostics;
+    using global::System.Diagnostics.CodeAnalysis;
+
+    /// 
+    ///     Reserved to be used by the compiler for tracking metadata.
+    ///     This class should not be used by developers in source code.
+    /// 
+    /// 
+    ///     This definition is provided by the IsExternalInit NuGet package (https://www.nuget.org/packages/IsExternalInit).
+    ///     Please see https://github.com/manuelroemer/IsExternalInit for more information.
+    /// 
+#if !ISEXTERNALINIT_INCLUDE_IN_CODE_COVERAGE
+    [ExcludeFromCodeCoverage, DebuggerNonUserCode]
+#endif
+    internal static class IsExternalInit
+    {
+    }
+}
+
+#pragma warning restore
+#nullable restore
+#endif // ISEXTERNALINIT_DISABLE
\ No newline at end of file
diff --git a/src/Directory.Build.props b/src/Directory.Build.props
index ad2c443..3a59f95 100644
--- a/src/Directory.Build.props
+++ b/src/Directory.Build.props
@@ -1,6 +1,6 @@
 
   
-    0.18.1
+    0.18.2
   
 
   

From e6c272238a087e3daee92b586e2187d7a3472923 Mon Sep 17 00:00:00 2001
From: Egil Hansen 
Date: Sun, 13 Oct 2024 17:25:57 +0000
Subject: [PATCH 2/6] fix: allow diff:ignoreAttributes and diff:ignoreChildren
 to be used on an element at the same time

---
 CHANGELOG.md                                     |  4 ++++
 .../AngleSharp.DiffingTests.csproj               | 14 +++++++-------
 .../IgnoreAttributesElementComparerTest.cs       | 15 +++++++++++++++
 .../IgnoreChildrenElementComparerTest.cs         | 15 +++++++++++++++
 src/AngleSharp.Diffing/AngleSharp.Diffing.csproj |  3 +--
 src/AngleSharp.Diffing/Core/CompareResult.cs     |  5 +++++
 .../IgnoreAttributesElementComparer.cs           | 16 ++++++++++++----
 .../IgnoreChildrenElementComparer.cs             | 16 ++++++++++++----
 8 files changed, 71 insertions(+), 17 deletions(-)

diff --git a/CHANGELOG.md b/CHANGELOG.md
index c352737..b806b09 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,3 +1,7 @@
+# 0.18.3
+
+- Enabled using `diff:ignoreAttributes` and `diff:ignoreChildren` together on the same element.
+
 # 0.18.2
 
 - Changed `CompareStrategy` such that it now can control the `IDiff` type that should be returned in case a difference is found in a comparison. This allows a comparer to embed additional context in the `IDiff` object. By [@SebastianStehle](https://github.com/SebastianStehle).
diff --git a/src/AngleSharp.Diffing.Tests/AngleSharp.DiffingTests.csproj b/src/AngleSharp.Diffing.Tests/AngleSharp.DiffingTests.csproj
index c3605ce..ec15b72 100644
--- a/src/AngleSharp.Diffing.Tests/AngleSharp.DiffingTests.csproj
+++ b/src/AngleSharp.Diffing.Tests/AngleSharp.DiffingTests.csproj
@@ -1,7 +1,7 @@
 
 
   
-    net6.0
+    net8.0
     false
     AngleSharp.Diffing.Tests
     AngleSharp.Diffing
@@ -10,18 +10,18 @@
   
 
   
-    
-    
+    
+    
       all
       runtime; build; native; contentfiles; analyzers; buildtransitive
     
-    
-    
-    
+    
+    
+    
       all
       runtime; build; native; contentfiles; analyzers; buildtransitive
     
-    
+    
       all
       runtime; build; native; contentfiles; analyzers; buildtransitive
     
diff --git a/src/AngleSharp.Diffing.Tests/Strategies/ElementStrategies/IgnoreAttributesElementComparerTest.cs b/src/AngleSharp.Diffing.Tests/Strategies/ElementStrategies/IgnoreAttributesElementComparerTest.cs
index 67bcd96..261a655 100644
--- a/src/AngleSharp.Diffing.Tests/Strategies/ElementStrategies/IgnoreAttributesElementComparerTest.cs
+++ b/src/AngleSharp.Diffing.Tests/Strategies/ElementStrategies/IgnoreAttributesElementComparerTest.cs
@@ -32,4 +32,19 @@ public void Test002(string controlHtml)
         IgnoreAttributesElementComparer.Compare(comparison, CompareResult.Unknown).ShouldBe(CompareResult.SkipAttributes);
         IgnoreAttributesElementComparer.Compare(comparison, CompareResult.Unknown).ShouldBe(CompareResult.SkipAttributes);
     }
+
+    [Theory(DisplayName = "When a control element has both 'diff:ignoreChildren' and a 'diff:ignoreAttributes'")]
+    [InlineData("", @"")]
+    [InlineData("", @"")]
+    public void Test003(string controlHtml, string testHtml)
+    {
+        var comparison = ToComparison(controlHtml, testHtml);
+
+        IgnoreAttributesElementComparer.Compare(comparison, CompareResult.Same).ShouldBe(CompareResult.SkipAttributes);
+        IgnoreAttributesElementComparer.Compare(comparison, CompareResult.Unknown).ShouldBe(CompareResult.SkipAttributes);
+        IgnoreAttributesElementComparer.Compare(comparison, CompareResult.Skip).ShouldBe(CompareResult.Skip);
+        IgnoreAttributesElementComparer.Compare(comparison, CompareResult.SkipChildrenAndAttributes).ShouldBe(CompareResult.SkipChildrenAndAttributes);
+        IgnoreAttributesElementComparer.Compare(comparison, CompareResult.SkipChildren).ShouldBe(CompareResult.SkipChildrenAndAttributes);
+        IgnoreAttributesElementComparer.Compare(comparison, CompareResult.SkipAttributes).ShouldBe(CompareResult.SkipAttributes);
+    }
 }
diff --git a/src/AngleSharp.Diffing.Tests/Strategies/ElementStrategies/IgnoreChildrenElementComparerTest.cs b/src/AngleSharp.Diffing.Tests/Strategies/ElementStrategies/IgnoreChildrenElementComparerTest.cs
index 0fa89b9..96b064a 100644
--- a/src/AngleSharp.Diffing.Tests/Strategies/ElementStrategies/IgnoreChildrenElementComparerTest.cs
+++ b/src/AngleSharp.Diffing.Tests/Strategies/ElementStrategies/IgnoreChildrenElementComparerTest.cs
@@ -32,4 +32,19 @@ public void Test002(string controlHtml)
         IgnoreChildrenElementComparer.Compare(comparison, CompareResult.Unknown).ShouldBe(CompareResult.SkipChildren);
         IgnoreChildrenElementComparer.Compare(comparison, CompareResult.Unknown).ShouldBe(CompareResult.SkipChildren);
     }
+
+    [Theory(DisplayName = "When a control element has both 'diff:ignoreChildren' and a 'diff:ignoreAttributes'")]
+    [InlineData("", @"")]
+    [InlineData("", @"")]
+    public void Test003(string controlHtml, string testHtml)
+    {
+        var comparison = ToComparison(controlHtml, testHtml);
+
+        IgnoreChildrenElementComparer.Compare(comparison, CompareResult.Same).ShouldBe(CompareResult.SkipChildren);
+        IgnoreChildrenElementComparer.Compare(comparison, CompareResult.Unknown).ShouldBe(CompareResult.SkipChildren);
+        IgnoreChildrenElementComparer.Compare(comparison, CompareResult.Skip).ShouldBe(CompareResult.Skip);
+        IgnoreChildrenElementComparer.Compare(comparison, CompareResult.SkipChildrenAndAttributes).ShouldBe(CompareResult.SkipChildrenAndAttributes);
+        IgnoreChildrenElementComparer.Compare(comparison, CompareResult.SkipAttributes).ShouldBe(CompareResult.SkipChildrenAndAttributes);
+        IgnoreChildrenElementComparer.Compare(comparison, CompareResult.SkipChildren).ShouldBe(CompareResult.SkipChildren);
+    }
 }
diff --git a/src/AngleSharp.Diffing/AngleSharp.Diffing.csproj b/src/AngleSharp.Diffing/AngleSharp.Diffing.csproj
index d115210..0b06153 100644
--- a/src/AngleSharp.Diffing/AngleSharp.Diffing.csproj
+++ b/src/AngleSharp.Diffing/AngleSharp.Diffing.csproj
@@ -24,7 +24,7 @@
   
 
   
-    
+    
   
 
   
@@ -34,7 +34,6 @@
 
   
     
-    
   
 
 
\ No newline at end of file
diff --git a/src/AngleSharp.Diffing/Core/CompareResult.cs b/src/AngleSharp.Diffing/Core/CompareResult.cs
index 0d4ea28..973ded4 100644
--- a/src/AngleSharp.Diffing/Core/CompareResult.cs
+++ b/src/AngleSharp.Diffing/Core/CompareResult.cs
@@ -32,6 +32,11 @@ public readonly record struct CompareResult(CompareDecision Decision, IDiff? Dif
     /// 
     public static readonly CompareResult SkipAttributes = new CompareResult(CompareDecision.SkipAttributes);
 
+    /// 
+    /// Use when the comparison should skip any attributes.
+    /// 
+    public static readonly CompareResult SkipChildrenAndAttributes = new CompareResult(CompareDecision.SkipChildren | CompareDecision.SkipAttributes);
+
     /// 
     /// Use when the two compared nodes or attributes are the different.
     /// 
diff --git a/src/AngleSharp.Diffing/Strategies/ElementStrategies/IgnoreAttributesElementComparer.cs b/src/AngleSharp.Diffing/Strategies/ElementStrategies/IgnoreAttributesElementComparer.cs
index 7bc25dd..c54e002 100644
--- a/src/AngleSharp.Diffing/Strategies/ElementStrategies/IgnoreAttributesElementComparer.cs
+++ b/src/AngleSharp.Diffing/Strategies/ElementStrategies/IgnoreAttributesElementComparer.cs
@@ -12,12 +12,20 @@ public static class IgnoreAttributesElementComparer
     /// 
     public static CompareResult Compare(in Comparison comparison, CompareResult currentDecision)
     {
-        if (currentDecision == CompareResult.Skip)
+        if (currentDecision == CompareResult.Skip || currentDecision == CompareResult.SkipAttributes || currentDecision == CompareResult.SkipChildrenAndAttributes)
             return currentDecision;
 
-        return ControlHasTruthyIgnoreAttributesAttribute(comparison)
-            ? CompareResult.SkipAttributes
-            : currentDecision;
+        if (!ControlHasTruthyIgnoreAttributesAttribute(comparison))
+            return currentDecision;
+
+        return currentDecision.Decision switch
+        {
+            CompareDecision.Unknown => CompareResult.SkipAttributes,
+            CompareDecision.Same => CompareResult.SkipAttributes,
+            CompareDecision.Different => CompareResult.SkipAttributes,
+            CompareDecision.SkipChildren => CompareResult.SkipChildrenAndAttributes,
+            _ => currentDecision,
+        };
     }
 
     private static bool ControlHasTruthyIgnoreAttributesAttribute(in Comparison comparison)
diff --git a/src/AngleSharp.Diffing/Strategies/ElementStrategies/IgnoreChildrenElementComparer.cs b/src/AngleSharp.Diffing/Strategies/ElementStrategies/IgnoreChildrenElementComparer.cs
index cc19f1f..2df3804 100644
--- a/src/AngleSharp.Diffing/Strategies/ElementStrategies/IgnoreChildrenElementComparer.cs
+++ b/src/AngleSharp.Diffing/Strategies/ElementStrategies/IgnoreChildrenElementComparer.cs
@@ -12,12 +12,20 @@ public static class IgnoreChildrenElementComparer
     /// 
     public static CompareResult Compare(in Comparison comparison, CompareResult currentDecision)
     {
-        if (currentDecision == CompareResult.Skip)
+        if (currentDecision == CompareResult.Skip || currentDecision == CompareResult.SkipChildren || currentDecision == CompareResult.SkipChildrenAndAttributes)
             return currentDecision;
 
-        return ControlHasTruthyIgnoreChildrenAttribute(comparison)
-            ? CompareResult.SkipChildren
-            : currentDecision;
+        if (!ControlHasTruthyIgnoreChildrenAttribute(comparison))
+            return currentDecision;
+
+        return currentDecision.Decision switch
+        {
+            CompareDecision.Unknown => CompareResult.SkipChildren,
+            CompareDecision.Same => CompareResult.SkipChildren,
+            CompareDecision.Different => CompareResult.SkipChildren,
+            CompareDecision.SkipAttributes => CompareResult.SkipChildrenAndAttributes,
+            _ => currentDecision,
+        };
     }
 
     private static bool ControlHasTruthyIgnoreChildrenAttribute(in Comparison comparison)

From 32218fd7278b5d6f437d313910396f75912aa517 Mon Sep 17 00:00:00 2001
From: Egil Hansen 
Date: Sun, 13 Oct 2024 17:48:33 +0000
Subject: [PATCH 3/6] chore: enable all modern analyzers

---
 .../Core/AttributeComparisonTest.cs           |  4 +-
 .../Core/DiffingEngineTestBase.cs             |  4 +-
 .../Core/HtmlDifferenceEngineTest.cs          |  4 +-
 .../DiffingTestBase.cs                        | 15 ++++----
 .../GlobalSuppressions.cs                     |  9 +++++
 .../Strategies/DiffingStrategyPipelineTest.cs |  2 +-
 .../TextNodeStrategies/TextNodeTestBase.cs    |  2 +-
 .../Core/AttributeComparison.cs               |  2 +-
 .../Core/AttributeComparisonSource.cs         |  1 +
 .../Core/CompareDecision.cs                   |  2 +-
 src/AngleSharp.Diffing/Core/CompareResult.cs  |  2 +-
 .../Core/ComparisonSource.cs                  |  1 +
 .../Core/HtmlDifferenceEngine.cs              |  4 +-
 .../Extensions/EmptyHtmlCollection.cs         |  2 +
 .../UnexpectedDOMTreeStructureException.cs    | 15 +++++++-
 .../ClassAttributeComparer.cs                 |  2 +-
 .../OrderingStyleAttributeComparer.cs         |  2 +-
 .../StyleAttributeComparer.cs                 |  2 +-
 .../Strategies/DiffingStrategyPipeline.cs     |  4 +-
 ...SelectorReturnedTooManyResultsException.cs |  7 ----
 .../IgnoreAttributesElementComparer.cs        |  2 +-
 .../IgnoreChildrenElementComparer.cs          |  2 +-
 .../Strategies/IDiffingStrategyCollection.cs  |  1 +
 src/Directory.Build.props                     | 37 +++++++++++--------
 24 files changed, 77 insertions(+), 51 deletions(-)
 create mode 100644 src/AngleSharp.Diffing.Tests/GlobalSuppressions.cs

diff --git a/src/AngleSharp.Diffing.Tests/Core/AttributeComparisonTest.cs b/src/AngleSharp.Diffing.Tests/Core/AttributeComparisonTest.cs
index d7ec516..d8cc0ba 100644
--- a/src/AngleSharp.Diffing.Tests/Core/AttributeComparisonTest.cs
+++ b/src/AngleSharp.Diffing.Tests/Core/AttributeComparisonTest.cs
@@ -65,7 +65,7 @@ public void Test005()
         var test = ToAttributeComparisonSource(@"
", "foo"); var comparison = new AttributeComparison(control, test); - var (actualCtrlElm, actualTestElm) = comparison.GetAttributeElements(); + var (actualCtrlElm, actualTestElm) = comparison.AttributeElements; actualCtrlElm.ShouldBe(control.ElementSource.Node); actualTestElm.ShouldBe(test.ElementSource.Node); @@ -135,7 +135,7 @@ public void Test005() var test = ToAttributeComparisonSource(@"
", "foo"); var comparison = new AttributeComparison(control, test); - var (actualCtrlElm, actualTestElm) = comparison.GetAttributeElements(); + var (actualCtrlElm, actualTestElm) = comparison.AttributeElements; actualCtrlElm.ShouldBe(control.ElementSource.Node); actualTestElm.ShouldBe(test.ElementSource.Node); diff --git a/src/AngleSharp.Diffing.Tests/Core/DiffingEngineTestBase.cs b/src/AngleSharp.Diffing.Tests/Core/DiffingEngineTestBase.cs index 901b004..e5609b8 100644 --- a/src/AngleSharp.Diffing.Tests/Core/DiffingEngineTestBase.cs +++ b/src/AngleSharp.Diffing.Tests/Core/DiffingEngineTestBase.cs @@ -2,7 +2,7 @@ namespace AngleSharp.Diffing.Core; public abstract class DiffingEngineTestBase : DiffingTestBase { - public DiffingEngineTestBase(DiffingTestFixture fixture) : base(fixture) + protected DiffingEngineTestBase(DiffingTestFixture fixture) : base(fixture) { } @@ -24,7 +24,7 @@ protected static HtmlDiffer CreateHtmlDiffer( ); } - private class MockDiffingStrategy : IDiffingStrategy + private sealed class MockDiffingStrategy : IDiffingStrategy { private readonly Func? _nodeFilter; private readonly Func? _attrFilter; diff --git a/src/AngleSharp.Diffing.Tests/Core/HtmlDifferenceEngineTest.cs b/src/AngleSharp.Diffing.Tests/Core/HtmlDifferenceEngineTest.cs index 7f8ae45..32ba4c4 100644 --- a/src/AngleSharp.Diffing.Tests/Core/HtmlDifferenceEngineTest.cs +++ b/src/AngleSharp.Diffing.Tests/Core/HtmlDifferenceEngineTest.cs @@ -510,14 +510,14 @@ private static Func SpecificAttrFilte #endregion #region CustomDiff - public record CustomNodeDiff : NodeDiff + public sealed record CustomNodeDiff : NodeDiff { public CustomNodeDiff(in Comparison comparison) : base(comparison) { } } - public record CustomAttrDiff : AttrDiff + public sealed record CustomAttrDiff : AttrDiff { public CustomAttrDiff(in AttributeComparison comparison) : base(comparison, AttrDiffKind.Unspecified) { diff --git a/src/AngleSharp.Diffing.Tests/DiffingTestBase.cs b/src/AngleSharp.Diffing.Tests/DiffingTestBase.cs index af96fe0..08ab929 100644 --- a/src/AngleSharp.Diffing.Tests/DiffingTestBase.cs +++ b/src/AngleSharp.Diffing.Tests/DiffingTestBase.cs @@ -4,11 +4,18 @@ public abstract class DiffingTestBase : IClassFixture { private readonly DiffingTestFixture _testFixture; + + public static readonly TheoryData SameAndSkipCompareResult = new TheoryData + { + CompareResult.Same, + CompareResult.Skip, + }; + protected IDiffContext DummyContext { get; } = new DiffContext(default(IElement), default(IElement)); protected INodeList EmptyNodeList => ToNodeList(""); - public DiffingTestBase(DiffingTestFixture fixture) + protected DiffingTestBase(DiffingTestFixture fixture) { _testFixture = fixture; } @@ -68,10 +75,4 @@ protected SourceMap ToSourceMap(string html, ComparisonSourceType sourceType = C var source = ToComparisonSource(html, sourceType); return new SourceMap(source); } - - public static TheoryData SameAndSkipCompareResult = new TheoryData - { - CompareResult.Same, - CompareResult.Skip, - }; } \ No newline at end of file diff --git a/src/AngleSharp.Diffing.Tests/GlobalSuppressions.cs b/src/AngleSharp.Diffing.Tests/GlobalSuppressions.cs new file mode 100644 index 0000000..0501c6d --- /dev/null +++ b/src/AngleSharp.Diffing.Tests/GlobalSuppressions.cs @@ -0,0 +1,9 @@ +// This file is used by Code Analysis to maintain SuppressMessage +// attributes that are applied to this project. +// Project-level suppressions either have no target or are given +// a specific target and scoped to a namespace, type, member, etc. + +using System.Diagnostics.CodeAnalysis; + +[assembly: SuppressMessage("Usage", "CA2201:Do not raise reserved exception types", Justification = "Not relevant in tests.")] +[assembly: SuppressMessage("Design", "CA1034:Nested types should not be visible", Justification = "Not relevant in tests")] diff --git a/src/AngleSharp.Diffing.Tests/Strategies/DiffingStrategyPipelineTest.cs b/src/AngleSharp.Diffing.Tests/Strategies/DiffingStrategyPipelineTest.cs index c071b28..9f985a7 100644 --- a/src/AngleSharp.Diffing.Tests/Strategies/DiffingStrategyPipelineTest.cs +++ b/src/AngleSharp.Diffing.Tests/Strategies/DiffingStrategyPipelineTest.cs @@ -6,7 +6,7 @@ public DiffingStrategyPipelineTest(DiffingTestFixture fixture) : base(fixture) { } - private FilterDecision NegateDecision(FilterDecision decision) => decision switch + private static FilterDecision NegateDecision(FilterDecision decision) => decision switch { FilterDecision.Keep => FilterDecision.Exclude, FilterDecision.Exclude => FilterDecision.Keep, diff --git a/src/AngleSharp.Diffing.Tests/Strategies/TextNodeStrategies/TextNodeTestBase.cs b/src/AngleSharp.Diffing.Tests/Strategies/TextNodeStrategies/TextNodeTestBase.cs index 1b8fc0a..3df2e00 100644 --- a/src/AngleSharp.Diffing.Tests/Strategies/TextNodeStrategies/TextNodeTestBase.cs +++ b/src/AngleSharp.Diffing.Tests/Strategies/TextNodeStrategies/TextNodeTestBase.cs @@ -18,7 +18,7 @@ public abstract class TextNodeTestBase : DiffingTestBase public static readonly IEnumerable WhitespaceCharStrings = AllWhitespaceCharacters.Select(c => new string[] { c.ToString(CultureInfo.InvariantCulture) }).ToArray(); - public TextNodeTestBase(DiffingTestFixture fixture) : base(fixture) + protected TextNodeTestBase(DiffingTestFixture fixture) : base(fixture) { } } diff --git a/src/AngleSharp.Diffing/Core/AttributeComparison.cs b/src/AngleSharp.Diffing/Core/AttributeComparison.cs index 18a0da7..b61fb39 100644 --- a/src/AngleSharp.Diffing/Core/AttributeComparison.cs +++ b/src/AngleSharp.Diffing/Core/AttributeComparison.cs @@ -10,6 +10,6 @@ public readonly record struct AttributeComparison(in AttributeComparisonSource C /// /// Returns the control and test elements which the control and test attributes belongs to. /// - public (IElement ControlElement, IElement TestElement) GetAttributeElements() + public readonly (IElement ControlElement, IElement TestElement) AttributeElements => ((IElement)Control.ElementSource.Node, (IElement)Test.ElementSource.Node); } diff --git a/src/AngleSharp.Diffing/Core/AttributeComparisonSource.cs b/src/AngleSharp.Diffing/Core/AttributeComparisonSource.cs index 4f9939f..a5a1e68 100644 --- a/src/AngleSharp.Diffing/Core/AttributeComparisonSource.cs +++ b/src/AngleSharp.Diffing/Core/AttributeComparisonSource.cs @@ -30,6 +30,7 @@ namespace AngleSharp.Diffing.Core; /// /// Name of the attribute. /// The source of the element the attribute belongs to. + [SuppressMessage("Globalization", "CA1308:Normalize strings to uppercase", Justification = "Attribute names should be safe to lowercase.")] public AttributeComparisonSource(string attributeName, in ComparisonSource elementSource) { if (string.IsNullOrEmpty(attributeName)) diff --git a/src/AngleSharp.Diffing/Core/CompareDecision.cs b/src/AngleSharp.Diffing/Core/CompareDecision.cs index cc0942e..73d626d 100644 --- a/src/AngleSharp.Diffing/Core/CompareDecision.cs +++ b/src/AngleSharp.Diffing/Core/CompareDecision.cs @@ -9,7 +9,7 @@ public enum CompareDecision /// /// Use when the compare result is unknown. /// - Unknown = 0, + None = 0, /// /// Use when the two compared nodes or attributes are the same. /// diff --git a/src/AngleSharp.Diffing/Core/CompareResult.cs b/src/AngleSharp.Diffing/Core/CompareResult.cs index 973ded4..33b536a 100644 --- a/src/AngleSharp.Diffing/Core/CompareResult.cs +++ b/src/AngleSharp.Diffing/Core/CompareResult.cs @@ -10,7 +10,7 @@ public readonly record struct CompareResult(CompareDecision Decision, IDiff? Dif /// /// Use when the compare result is unknown. /// - public static readonly CompareResult Unknown = default; + public static readonly CompareResult Unknown; /// /// Use when the two compared nodes or attributes are the same. diff --git a/src/AngleSharp.Diffing/Core/ComparisonSource.cs b/src/AngleSharp.Diffing/Core/ComparisonSource.cs index 13025e2..f900d64 100644 --- a/src/AngleSharp.Diffing/Core/ComparisonSource.cs +++ b/src/AngleSharp.Diffing/Core/ComparisonSource.cs @@ -64,6 +64,7 @@ public ComparisonSource(INode node, int index, string parentsPath, ComparisonSou /// /// /// + [SuppressMessage("Globalization", "CA1308:Normalize strings to uppercase", Justification = "Nodenames are safe to lower case.")] public static string GetNodePathSegment(INode node) { var index = GetPathIndex(node); diff --git a/src/AngleSharp.Diffing/Core/HtmlDifferenceEngine.cs b/src/AngleSharp.Diffing/Core/HtmlDifferenceEngine.cs index 8065dac..575ca96 100644 --- a/src/AngleSharp.Diffing/Core/HtmlDifferenceEngine.cs +++ b/src/AngleSharp.Diffing/Core/HtmlDifferenceEngine.cs @@ -90,13 +90,13 @@ private IEnumerable CompareNode(in Comparison comparison) if (compareRes.Decision.HasFlag(CompareDecision.Different)) { IDiff diff = compareRes.Diff ?? new NodeDiff(comparison); - return new[] { diff }; + return [diff]; } return Array.Empty(); } - private IEnumerable CompareElement(in Comparison comparison) + private List CompareElement(in Comparison comparison) { var result = new List(); diff --git a/src/AngleSharp.Diffing/Extensions/EmptyHtmlCollection.cs b/src/AngleSharp.Diffing/Extensions/EmptyHtmlCollection.cs index be9c1ac..2cb0435 100644 --- a/src/AngleSharp.Diffing/Extensions/EmptyHtmlCollection.cs +++ b/src/AngleSharp.Diffing/Extensions/EmptyHtmlCollection.cs @@ -8,10 +8,12 @@ public class EmptyHtmlCollection : IHtmlCollection where T : IElement { /// [SuppressMessage("Design", "CA1065:Do not raise exceptions in unexpected locations")] + [SuppressMessage("Usage", "CA2201:Do not raise reserved exception types")] public T this[int index] => throw new IndexOutOfRangeException(); /// [SuppressMessage("Design", "CA1065:Do not raise exceptions in unexpected locations")] + [SuppressMessage("Usage", "CA2201:Do not raise reserved exception types")] public T this[string id] => throw new IndexOutOfRangeException(); /// diff --git a/src/AngleSharp.Diffing/Extensions/UnexpectedDOMTreeStructureException.cs b/src/AngleSharp.Diffing/Extensions/UnexpectedDOMTreeStructureException.cs index cd77df0..74e27d7 100644 --- a/src/AngleSharp.Diffing/Extensions/UnexpectedDOMTreeStructureException.cs +++ b/src/AngleSharp.Diffing/Extensions/UnexpectedDOMTreeStructureException.cs @@ -13,6 +13,17 @@ public sealed class UnexpectedDOMTreeStructureException : Exception public UnexpectedDOMTreeStructureException() : base("The DOM tree structure was not as expected by AngleSharp.Diffing.") { } - private UnexpectedDOMTreeStructureException(SerializationInfo info, StreamingContext context) - : base(info, context) { } + /// + /// Creates an instance of the . + /// + public UnexpectedDOMTreeStructureException(string message) : base(message) + { + } + + /// + /// Creates an instance of the . + /// + public UnexpectedDOMTreeStructureException(string message, Exception innerException) : base(message, innerException) + { + } } diff --git a/src/AngleSharp.Diffing/Strategies/AttributeStrategies/ClassAttributeComparer.cs b/src/AngleSharp.Diffing/Strategies/AttributeStrategies/ClassAttributeComparer.cs index cfe136b..448c22b 100644 --- a/src/AngleSharp.Diffing/Strategies/AttributeStrategies/ClassAttributeComparer.cs +++ b/src/AngleSharp.Diffing/Strategies/AttributeStrategies/ClassAttributeComparer.cs @@ -18,7 +18,7 @@ public static CompareResult Compare(in AttributeComparison comparison, CompareRe if (!IsBothClassAttributes(comparison)) return CompareResult.FromDiff(new AttrDiff(comparison, AttrDiffKind.Name)); - var (ctrlElm, testElm) = comparison.GetAttributeElements(); + var (ctrlElm, testElm) = comparison.AttributeElements; if (ctrlElm.ClassList.Length != testElm.ClassList.Length) return CompareResult.FromDiff(new AttrDiff(comparison, AttrDiffKind.Value)); diff --git a/src/AngleSharp.Diffing/Strategies/AttributeStrategies/OrderingStyleAttributeComparer.cs b/src/AngleSharp.Diffing/Strategies/AttributeStrategies/OrderingStyleAttributeComparer.cs index 4e3d830..72fcba7 100644 --- a/src/AngleSharp.Diffing/Strategies/AttributeStrategies/OrderingStyleAttributeComparer.cs +++ b/src/AngleSharp.Diffing/Strategies/AttributeStrategies/OrderingStyleAttributeComparer.cs @@ -26,7 +26,7 @@ private static bool IsStyleAttributeComparison(in AttributeComparison comparison private static CompareResult CompareElementStyle(in AttributeComparison comparison) { - var (ctrlElm, testElm) = comparison.GetAttributeElements(); + var (ctrlElm, testElm) = comparison.AttributeElements; var ctrlStyle = ctrlElm.GetStyle(); var testStyle = testElm.GetStyle(); return CompareCssStyleDeclarations(ctrlStyle, testStyle) diff --git a/src/AngleSharp.Diffing/Strategies/AttributeStrategies/StyleAttributeComparer.cs b/src/AngleSharp.Diffing/Strategies/AttributeStrategies/StyleAttributeComparer.cs index 00dfaee..8b41cdf 100644 --- a/src/AngleSharp.Diffing/Strategies/AttributeStrategies/StyleAttributeComparer.cs +++ b/src/AngleSharp.Diffing/Strategies/AttributeStrategies/StyleAttributeComparer.cs @@ -26,7 +26,7 @@ private static bool IsStyleAttributeComparison(in AttributeComparison comparison private static CompareResult CompareElementStyle(in AttributeComparison comparison) { - var (ctrlElm, testElm) = comparison.GetAttributeElements(); + var (ctrlElm, testElm) = comparison.AttributeElements; var ctrlStyle = ctrlElm.GetStyle(); var testStyle = testElm.GetStyle(); return CompareCssStyleDeclarations(ctrlStyle, testStyle) diff --git a/src/AngleSharp.Diffing/Strategies/DiffingStrategyPipeline.cs b/src/AngleSharp.Diffing/Strategies/DiffingStrategyPipeline.cs index 4856f0f..e4febee 100644 --- a/src/AngleSharp.Diffing/Strategies/DiffingStrategyPipeline.cs +++ b/src/AngleSharp.Diffing/Strategies/DiffingStrategyPipeline.cs @@ -123,7 +123,7 @@ public IDiffingStrategyCollection AddComparer(CompareStrategy(in T source, List> filterStrategies) + private static FilterDecision Filter(in T source, List> filterStrategies) { var result = FilterDecision.Keep; for (int i = 0; i < filterStrategies.Count; i++) @@ -133,7 +133,7 @@ private FilterDecision Filter(in T source, List> filterStra return result; } - private CompareResult Compare(in TComparison comparison, List> compareStrategies, CompareResult initialResult) + private static CompareResult Compare(in TComparison comparison, List> compareStrategies, CompareResult initialResult) { var result = initialResult; foreach (var comparer in compareStrategies) diff --git a/src/AngleSharp.Diffing/Strategies/ElementStrategies/DiffMatchSelectorReturnedTooManyResultsException.cs b/src/AngleSharp.Diffing/Strategies/ElementStrategies/DiffMatchSelectorReturnedTooManyResultsException.cs index aaac7a6..390ccbc 100644 --- a/src/AngleSharp.Diffing/Strategies/ElementStrategies/DiffMatchSelectorReturnedTooManyResultsException.cs +++ b/src/AngleSharp.Diffing/Strategies/ElementStrategies/DiffMatchSelectorReturnedTooManyResultsException.cs @@ -27,11 +27,4 @@ public DiffMatchSelectorReturnedTooManyResultsException(string message) : base(m public DiffMatchSelectorReturnedTooManyResultsException(string message, Exception innerException) : base(message, innerException) { } - - /// - /// Creates a . - /// - protected DiffMatchSelectorReturnedTooManyResultsException(SerializationInfo info, StreamingContext context) : base(info, context) - { - } } \ No newline at end of file diff --git a/src/AngleSharp.Diffing/Strategies/ElementStrategies/IgnoreAttributesElementComparer.cs b/src/AngleSharp.Diffing/Strategies/ElementStrategies/IgnoreAttributesElementComparer.cs index c54e002..a5cd052 100644 --- a/src/AngleSharp.Diffing/Strategies/ElementStrategies/IgnoreAttributesElementComparer.cs +++ b/src/AngleSharp.Diffing/Strategies/ElementStrategies/IgnoreAttributesElementComparer.cs @@ -20,7 +20,7 @@ public static CompareResult Compare(in Comparison comparison, CompareResult curr return currentDecision.Decision switch { - CompareDecision.Unknown => CompareResult.SkipAttributes, + CompareDecision.None => CompareResult.SkipAttributes, CompareDecision.Same => CompareResult.SkipAttributes, CompareDecision.Different => CompareResult.SkipAttributes, CompareDecision.SkipChildren => CompareResult.SkipChildrenAndAttributes, diff --git a/src/AngleSharp.Diffing/Strategies/ElementStrategies/IgnoreChildrenElementComparer.cs b/src/AngleSharp.Diffing/Strategies/ElementStrategies/IgnoreChildrenElementComparer.cs index 2df3804..966fe7b 100644 --- a/src/AngleSharp.Diffing/Strategies/ElementStrategies/IgnoreChildrenElementComparer.cs +++ b/src/AngleSharp.Diffing/Strategies/ElementStrategies/IgnoreChildrenElementComparer.cs @@ -20,7 +20,7 @@ public static CompareResult Compare(in Comparison comparison, CompareResult curr return currentDecision.Decision switch { - CompareDecision.Unknown => CompareResult.SkipChildren, + CompareDecision.None => CompareResult.SkipChildren, CompareDecision.Same => CompareResult.SkipChildren, CompareDecision.Different => CompareResult.SkipChildren, CompareDecision.SkipAttributes => CompareResult.SkipChildrenAndAttributes, diff --git a/src/AngleSharp.Diffing/Strategies/IDiffingStrategyCollection.cs b/src/AngleSharp.Diffing/Strategies/IDiffingStrategyCollection.cs index 6a6768f..6da2532 100644 --- a/src/AngleSharp.Diffing/Strategies/IDiffingStrategyCollection.cs +++ b/src/AngleSharp.Diffing/Strategies/IDiffingStrategyCollection.cs @@ -3,6 +3,7 @@ namespace AngleSharp.Diffing.Strategies; /// /// Represents a collection of diffing strategies. /// +[SuppressMessage("Naming", "CA1711:Identifiers should not have incorrect suffix", Justification = "Ignored.")] public interface IDiffingStrategyCollection { /// diff --git a/src/Directory.Build.props b/src/Directory.Build.props index 3a59f95..2004708 100644 --- a/src/Directory.Build.props +++ b/src/Directory.Build.props @@ -1,19 +1,26 @@ - - 0.18.2 - + + 0.18.3 + - - enable - 11.0 - enable - CS8600;CS8602;CS8603;CS8625 - true - ../Key.snk - + + enable + latest + enable + CS8600;CS8602;CS8603;CS8625 + true + ../Key.snk + - - - - + + AllEnabledByDefault + true + latest + true + + + + + + \ No newline at end of file From 0d16d87e29e40dac454311caea248024c5d06f1a Mon Sep 17 00:00:00 2001 From: Egil Hansen Date: Sun, 13 Oct 2024 18:13:36 +0000 Subject: [PATCH 4/6] fix: updated list of boolean attributes --- CHANGELOG.md | 1 + .../AttributeStrategies/BooleanAttributeComparer.cs | 8 ++++++-- 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index b806b09..53aff6a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,7 @@ # 0.18.3 - Enabled using `diff:ignoreAttributes` and `diff:ignoreChildren` together on the same element. +- Corrected list of attributes that is considered as boolean attributes. Removed `hidden`, added `inert`, `playsinline`, `shadowrootclonable`, `shadowrootdelegatesfocus`, and `shadowrootserializable`. # 0.18.2 diff --git a/src/AngleSharp.Diffing/Strategies/AttributeStrategies/BooleanAttributeComparer.cs b/src/AngleSharp.Diffing/Strategies/AttributeStrategies/BooleanAttributeComparer.cs index 889badc..feb77b2 100644 --- a/src/AngleSharp.Diffing/Strategies/AttributeStrategies/BooleanAttributeComparer.cs +++ b/src/AngleSharp.Diffing/Strategies/AttributeStrategies/BooleanAttributeComparer.cs @@ -5,7 +5,7 @@ /// public class BooleanAttributeComparer { - private static readonly HashSet BooleanAttributesSet = new HashSet() + private static readonly HashSet BooleanAttributesSet = new(StringComparer.OrdinalIgnoreCase) { "allowfullscreen", "allowpaymentrequest", @@ -18,7 +18,7 @@ public class BooleanAttributeComparer "defer", "disabled", "formnovalidate", - "hidden", + "inert", "ismap", "itemscope", "loop", @@ -27,10 +27,14 @@ public class BooleanAttributeComparer "nomodule", "novalidate", "open", + "playsinline", "readonly", "required", "reversed", "selected", + "shadowrootclonable", + "shadowrootdelegatesfocus", + "shadowrootserializable", "typemustmatch" }; From ff580f800769abaa3ddb7785edefb257fa2d71be Mon Sep 17 00:00:00 2001 From: Egil Hansen Date: Sun, 13 Oct 2024 18:16:22 +0000 Subject: [PATCH 5/6] fix: code clean up, optimizing string comparison --- src/.editorconfig | 3 + .../Core/HtmlDifferenceEngineTest.cs | 4 +- .../DiffBuilderTest.cs | 4 +- .../DiffingTestBase.cs | 2 +- src/AngleSharp.Diffing.sln | 6 +- .../AngleSharp.Diffing.csproj | 66 ++++++++++--------- .../Core/AttributeComparison.cs | 3 + src/AngleSharp.Diffing/Core/CompareResult.cs | 14 ++-- src/AngleSharp.Diffing/Core/Comparison.cs | 3 + ...{SourceType.cs => ComparisonSourceType.cs} | 0 src/AngleSharp.Diffing/Core/DiffTarget.cs | 21 ------ src/AngleSharp.Diffing/Core/FilterDecision.cs | 16 ----- .../Core/FilterDecisionExtensions.cs | 17 +++++ .../Core/NodeTypeExtensions.cs | 22 +++++++ src/AngleSharp.Diffing/Core/SourceMap.cs | 4 +- src/AngleSharp.Diffing/DiffBuilder.cs | 4 +- ...tensions.cs => AngleSharpDomExtensions.cs} | 2 +- .../ClassAttributeComparer.cs | 2 +- .../OrderingStyleAttributeComparer.cs | 4 +- .../Strategies/DiffingStrategyPipeline.cs | 24 +++---- .../TextNodeStrategies/TextNodeComparer.cs | 2 +- 21 files changed, 119 insertions(+), 104 deletions(-) rename src/AngleSharp.Diffing/Core/{SourceType.cs => ComparisonSourceType.cs} (100%) create mode 100644 src/AngleSharp.Diffing/Core/FilterDecisionExtensions.cs create mode 100644 src/AngleSharp.Diffing/Core/NodeTypeExtensions.cs rename src/AngleSharp.Diffing/Extensions/{NodeListExtensions.cs => AngleSharpDomExtensions.cs} (95%) diff --git a/src/.editorconfig b/src/.editorconfig index 8dea088..daaaa18 100644 --- a/src/.editorconfig +++ b/src/.editorconfig @@ -225,3 +225,6 @@ dotnet_naming_style.fields_begin_with__.required_prefix = _ dotnet_naming_style.fields_begin_with__.required_suffix = dotnet_naming_style.fields_begin_with__.word_separator = dotnet_naming_style.fields_begin_with__.capitalization = camel_case + +# MA0012: Do not raise reserved exception type +dotnet_diagnostic.MA0012.severity = none \ No newline at end of file diff --git a/src/AngleSharp.Diffing.Tests/Core/HtmlDifferenceEngineTest.cs b/src/AngleSharp.Diffing.Tests/Core/HtmlDifferenceEngineTest.cs index 32ba4c4..1383304 100644 --- a/src/AngleSharp.Diffing.Tests/Core/HtmlDifferenceEngineTest.cs +++ b/src/AngleSharp.Diffing.Tests/Core/HtmlDifferenceEngineTest.cs @@ -451,7 +451,7 @@ private static IEnumerable NoneNodeMatcher(IDiffContext ctx, SourceC private static Func> SpecificIndexNodeMatcher(int index) => (ctx, controlNodes, testNodes) => { - return new List { new Comparison(controlNodes[index], testNodes[index]) }; + return new List { new(controlNodes[index], testNodes[index]) }; }; private static IEnumerable OneToOneNodeListMatcher( @@ -477,7 +477,7 @@ private static Func new List { - new AttributeComparison(ctrlAttrs[matchAttrName], testAttrs[matchAttrName] ) + new(ctrlAttrs[matchAttrName], testAttrs[matchAttrName] ) }; } diff --git a/src/AngleSharp.Diffing.Tests/DiffBuilderTest.cs b/src/AngleSharp.Diffing.Tests/DiffBuilderTest.cs index 9287f03..9769990 100644 --- a/src/AngleSharp.Diffing.Tests/DiffBuilderTest.cs +++ b/src/AngleSharp.Diffing.Tests/DiffBuilderTest.cs @@ -20,8 +20,8 @@ public void Test001() [Fact(DisplayName = "Builder throws if null is passed to control and test")] public void Test002() { - Should.Throw(() => DiffBuilder.Compare(null!)).ParamName.ShouldBe(nameof(DiffBuilder.Control)); - Should.Throw(() => DiffBuilder.Compare("").WithTest(null!)).ParamName.ShouldBe(nameof(DiffBuilder.Test)); + Should.Throw(() => DiffBuilder.Compare(null!)).ParamName.ShouldBe("value"); + Should.Throw(() => DiffBuilder.Compare("").WithTest(null!)).ParamName.ShouldBe("value"); } [Fact(DisplayName = "Calling Build() with DefaultOptions() returns expected diffs")] diff --git a/src/AngleSharp.Diffing.Tests/DiffingTestBase.cs b/src/AngleSharp.Diffing.Tests/DiffingTestBase.cs index 08ab929..41d41ce 100644 --- a/src/AngleSharp.Diffing.Tests/DiffingTestBase.cs +++ b/src/AngleSharp.Diffing.Tests/DiffingTestBase.cs @@ -5,7 +5,7 @@ public abstract class DiffingTestBase : IClassFixture private readonly DiffingTestFixture _testFixture; - public static readonly TheoryData SameAndSkipCompareResult = new TheoryData + public static readonly TheoryData SameAndSkipCompareResult = new() { CompareResult.Same, CompareResult.Skip, diff --git a/src/AngleSharp.Diffing.sln b/src/AngleSharp.Diffing.sln index c93f6cd..83fb1ab 100644 --- a/src/AngleSharp.Diffing.sln +++ b/src/AngleSharp.Diffing.sln @@ -1,7 +1,7 @@  Microsoft Visual Studio Solution File, Format Version 12.00 -# Visual Studio Version 16 -VisualStudioVersion = 16.0.29230.61 +# Visual Studio Version 17 +VisualStudioVersion = 17.11.35312.102 MinimumVisualStudioVersion = 10.0.40219.1 Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "AngleSharp.Diffing", "AngleSharp.Diffing\AngleSharp.Diffing.csproj", "{2BFFA992-22C2-4A65-94D8-CA06E81D2364}" EndProject @@ -9,7 +9,7 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "AngleSharp.DiffingTests", " EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{E8E3C8B4-92C3-4DB7-B920-D28651E24A57}" ProjectSection(SolutionItems) = preProject - ..\.editorconfig = ..\.editorconfig + .editorconfig = .editorconfig ..\tools\anglesharp.cake = ..\tools\anglesharp.cake ..\build.cake = ..\build.cake ..\build.ps1 = ..\build.ps1 diff --git a/src/AngleSharp.Diffing/AngleSharp.Diffing.csproj b/src/AngleSharp.Diffing/AngleSharp.Diffing.csproj index 0b06153..0594b2d 100644 --- a/src/AngleSharp.Diffing/AngleSharp.Diffing.csproj +++ b/src/AngleSharp.Diffing/AngleSharp.Diffing.csproj @@ -1,39 +1,43 @@ - - netstandard2.0 - true - + + netstandard2.0 + true + - - Provides a complete diffing model of HTML. - AngleSharp.Diffing - AngleSharp - AngleSharp.Diffing - MIT - https://anglesharp.github.io - logo.png - https://raw.github.com/AngleSharp/AngleSharp.Diffing/master/logo.png - html html5 css css3 dom library diffing anglesharp diff difference compare comparison testing - Egil Hansen - https://github.com/AngleSharp/AngleSharp.Diffing - git - true - true - snupkg - + + Provides a complete diffing model of HTML. + AngleSharp.Diffing + AngleSharp + AngleSharp.Diffing + MIT + https://anglesharp.github.io + logo.png + https://raw.github.com/AngleSharp/AngleSharp.Diffing/master/logo.png + html html5 css css3 dom library diffing anglesharp diff difference compare comparison testing + Egil Hansen + https://github.com/AngleSharp/AngleSharp.Diffing + git + true + true + snupkg + - - - + + + - - - - + + + all + runtime; build; native; contentfiles; analyzers + + + + - - - + + + \ No newline at end of file diff --git a/src/AngleSharp.Diffing/Core/AttributeComparison.cs b/src/AngleSharp.Diffing/Core/AttributeComparison.cs index b61fb39..5f69f71 100644 --- a/src/AngleSharp.Diffing/Core/AttributeComparison.cs +++ b/src/AngleSharp.Diffing/Core/AttributeComparison.cs @@ -1,3 +1,5 @@ +using System.Runtime.InteropServices; + namespace AngleSharp.Diffing.Core; /// @@ -5,6 +7,7 @@ namespace AngleSharp.Diffing.Core; /// /// Gets the control attribute which the attribute is supposed to match. /// Gets the test attribute which should be compared to the attribute. +[StructLayout(LayoutKind.Auto)] public readonly record struct AttributeComparison(in AttributeComparisonSource Control, in AttributeComparisonSource Test) { /// diff --git a/src/AngleSharp.Diffing/Core/CompareResult.cs b/src/AngleSharp.Diffing/Core/CompareResult.cs index 33b536a..cd3de65 100644 --- a/src/AngleSharp.Diffing/Core/CompareResult.cs +++ b/src/AngleSharp.Diffing/Core/CompareResult.cs @@ -15,39 +15,39 @@ public readonly record struct CompareResult(CompareDecision Decision, IDiff? Dif /// /// Use when the two compared nodes or attributes are the same. /// - public static readonly CompareResult Same = new CompareResult(CompareDecision.Same); + public static readonly CompareResult Same = new(CompareDecision.Same); /// /// Use when the comparison should be skipped and any child-nodes or attributes skipped as well. /// - public static readonly CompareResult Skip = new CompareResult(CompareDecision.Skip); + public static readonly CompareResult Skip = new(CompareDecision.Skip); /// /// Use when the comparison should skip any child-nodes. /// - public static readonly CompareResult SkipChildren = new CompareResult(CompareDecision.SkipChildren); + public static readonly CompareResult SkipChildren = new(CompareDecision.SkipChildren); /// /// Use when the comparison should skip any attributes. /// - public static readonly CompareResult SkipAttributes = new CompareResult(CompareDecision.SkipAttributes); + public static readonly CompareResult SkipAttributes = new(CompareDecision.SkipAttributes); /// /// Use when the comparison should skip any attributes. /// - public static readonly CompareResult SkipChildrenAndAttributes = new CompareResult(CompareDecision.SkipChildren | CompareDecision.SkipAttributes); + public static readonly CompareResult SkipChildrenAndAttributes = new(CompareDecision.SkipChildren | CompareDecision.SkipAttributes); /// /// Use when the two compared nodes or attributes are the different. /// - public static CompareResult Different => new CompareResult(CompareDecision.Different); + public static CompareResult Different => new(CompareDecision.Different); /// /// Use when the two compared nodes or attributes are the different. /// /// The associated describing the difference. /// Returns a with set to . - public static CompareResult FromDiff(IDiff diff) => new CompareResult(CompareDecision.Different, diff); + public static CompareResult FromDiff(IDiff diff) => new(CompareDecision.Different, diff); /// /// Checks if a is either a or . diff --git a/src/AngleSharp.Diffing/Core/Comparison.cs b/src/AngleSharp.Diffing/Core/Comparison.cs index cdab391..41742e8 100644 --- a/src/AngleSharp.Diffing/Core/Comparison.cs +++ b/src/AngleSharp.Diffing/Core/Comparison.cs @@ -1,3 +1,5 @@ +using System.Runtime.InteropServices; + namespace AngleSharp.Diffing.Core; /// @@ -5,6 +7,7 @@ namespace AngleSharp.Diffing.Core; /// /// Gets the control source in the comparison. /// Gets the test source in the comparison. +[StructLayout(LayoutKind.Auto)] public readonly record struct Comparison(in ComparisonSource Control, in ComparisonSource Test) { /// diff --git a/src/AngleSharp.Diffing/Core/SourceType.cs b/src/AngleSharp.Diffing/Core/ComparisonSourceType.cs similarity index 100% rename from src/AngleSharp.Diffing/Core/SourceType.cs rename to src/AngleSharp.Diffing/Core/ComparisonSourceType.cs diff --git a/src/AngleSharp.Diffing/Core/DiffTarget.cs b/src/AngleSharp.Diffing/Core/DiffTarget.cs index af6579b..a4f400b 100644 --- a/src/AngleSharp.Diffing/Core/DiffTarget.cs +++ b/src/AngleSharp.Diffing/Core/DiffTarget.cs @@ -30,24 +30,3 @@ public enum DiffTarget /// Text } - -/// -/// Helper methods for working with . -/// -public static class NodeTypeExtensions -{ - /// - /// Gets the diff target based on the node type. - /// - /// Mode type to get the diff target off. - public static DiffTarget ToDiffTarget(this NodeType nodeType) - { - return nodeType switch - { - NodeType.Element => DiffTarget.Element, - NodeType.Comment => DiffTarget.Comment, - NodeType.Text => DiffTarget.Text, - _ => DiffTarget.Node - }; - } -} diff --git a/src/AngleSharp.Diffing/Core/FilterDecision.cs b/src/AngleSharp.Diffing/Core/FilterDecision.cs index 1274efb..ad80d89 100644 --- a/src/AngleSharp.Diffing/Core/FilterDecision.cs +++ b/src/AngleSharp.Diffing/Core/FilterDecision.cs @@ -14,19 +14,3 @@ public enum FilterDecision /// Exclude } - -/// -/// Helper methods for . -/// -public static class FilterDecisionExtensions -{ - /// - /// Gets whether the is . - /// - public static bool IsExclude(this FilterDecision decision) => decision == FilterDecision.Exclude; - - /// - /// Gets whether the is . - /// - public static bool IsKeep(this FilterDecision decision) => decision == FilterDecision.Keep; -} diff --git a/src/AngleSharp.Diffing/Core/FilterDecisionExtensions.cs b/src/AngleSharp.Diffing/Core/FilterDecisionExtensions.cs new file mode 100644 index 0000000..ba7d609 --- /dev/null +++ b/src/AngleSharp.Diffing/Core/FilterDecisionExtensions.cs @@ -0,0 +1,17 @@ +namespace AngleSharp.Diffing.Core; + +/// +/// Helper methods for . +/// +public static class FilterDecisionExtensions +{ + /// + /// Gets whether the is . + /// + public static bool IsExclude(this FilterDecision decision) => decision == FilterDecision.Exclude; + + /// + /// Gets whether the is . + /// + public static bool IsKeep(this FilterDecision decision) => decision == FilterDecision.Keep; +} diff --git a/src/AngleSharp.Diffing/Core/NodeTypeExtensions.cs b/src/AngleSharp.Diffing/Core/NodeTypeExtensions.cs new file mode 100644 index 0000000..4467e48 --- /dev/null +++ b/src/AngleSharp.Diffing/Core/NodeTypeExtensions.cs @@ -0,0 +1,22 @@ +namespace AngleSharp.Diffing.Core; + +/// +/// Helper methods for working with . +/// +public static class NodeTypeExtensions +{ + /// + /// Gets the diff target based on the node type. + /// + /// Mode type to get the diff target off. + public static DiffTarget ToDiffTarget(this NodeType nodeType) + { + return nodeType switch + { + NodeType.Element => DiffTarget.Element, + NodeType.Comment => DiffTarget.Comment, + NodeType.Text => DiffTarget.Text, + _ => DiffTarget.Node + }; + } +} diff --git a/src/AngleSharp.Diffing/Core/SourceMap.cs b/src/AngleSharp.Diffing/Core/SourceMap.cs index 47e504d..4f99c8d 100644 --- a/src/AngleSharp.Diffing/Core/SourceMap.cs +++ b/src/AngleSharp.Diffing/Core/SourceMap.cs @@ -6,8 +6,8 @@ namespace AngleSharp.Diffing.Core; [SuppressMessage("Naming", "CA1710:Identifiers should have correct suffix")] public class SourceMap : IEnumerable { - private readonly HashSet _matched = new HashSet(); - private readonly Dictionary _sources = new Dictionary(); + private readonly HashSet _matched = new(StringComparer.OrdinalIgnoreCase); + private readonly Dictionary _sources = new(StringComparer.OrdinalIgnoreCase); /// /// Gets the type of the sources in the collection. diff --git a/src/AngleSharp.Diffing/DiffBuilder.cs b/src/AngleSharp.Diffing/DiffBuilder.cs index ee40b9e..bf94b6c 100644 --- a/src/AngleSharp.Diffing/DiffBuilder.cs +++ b/src/AngleSharp.Diffing/DiffBuilder.cs @@ -17,12 +17,12 @@ public class DiffBuilder /// /// Gets or sets the control markup string. /// - public string Control { get => _control; set => _control = value ?? throw new ArgumentNullException(nameof(Control)); } + public string Control { get => _control; set => _control = value ?? throw new ArgumentNullException(nameof(value)); } /// /// Gets or sets the test markup string. /// - public string Test { get => _test; set => _test = value ?? throw new ArgumentNullException(nameof(Test)); } + public string Test { get => _test; set => _test = value ?? throw new ArgumentNullException(nameof(value)); } private DiffBuilder(string control) { diff --git a/src/AngleSharp.Diffing/Extensions/NodeListExtensions.cs b/src/AngleSharp.Diffing/Extensions/AngleSharpDomExtensions.cs similarity index 95% rename from src/AngleSharp.Diffing/Extensions/NodeListExtensions.cs rename to src/AngleSharp.Diffing/Extensions/AngleSharpDomExtensions.cs index 14ca9d5..2049656 100644 --- a/src/AngleSharp.Diffing/Extensions/NodeListExtensions.cs +++ b/src/AngleSharp.Diffing/Extensions/AngleSharpDomExtensions.cs @@ -34,5 +34,5 @@ public static IEnumerable ToComparisonSourceList(this IEnumera /// Creates a comparison source from a node. /// public static ComparisonSource ToComparisonSource(this INode node, int index, ComparisonSourceType sourceType, string path = "") - => new ComparisonSource(node, index, path, sourceType); + => new(node, index, path, sourceType); } diff --git a/src/AngleSharp.Diffing/Strategies/AttributeStrategies/ClassAttributeComparer.cs b/src/AngleSharp.Diffing/Strategies/AttributeStrategies/ClassAttributeComparer.cs index 448c22b..5cd59c5 100644 --- a/src/AngleSharp.Diffing/Strategies/AttributeStrategies/ClassAttributeComparer.cs +++ b/src/AngleSharp.Diffing/Strategies/AttributeStrategies/ClassAttributeComparer.cs @@ -22,7 +22,7 @@ public static CompareResult Compare(in AttributeComparison comparison, CompareRe if (ctrlElm.ClassList.Length != testElm.ClassList.Length) return CompareResult.FromDiff(new AttrDiff(comparison, AttrDiffKind.Value)); - return ctrlElm.ClassList.All(x => testElm.ClassList.Contains(x)) + return ctrlElm.ClassList.All(x => testElm.ClassList.Contains(x, StringComparer.Ordinal)) ? CompareResult.Same : CompareResult.FromDiff(new AttrDiff(comparison, AttrDiffKind.Value)); } diff --git a/src/AngleSharp.Diffing/Strategies/AttributeStrategies/OrderingStyleAttributeComparer.cs b/src/AngleSharp.Diffing/Strategies/AttributeStrategies/OrderingStyleAttributeComparer.cs index 72fcba7..19fcb5e 100644 --- a/src/AngleSharp.Diffing/Strategies/AttributeStrategies/OrderingStyleAttributeComparer.cs +++ b/src/AngleSharp.Diffing/Strategies/AttributeStrategies/OrderingStyleAttributeComparer.cs @@ -39,8 +39,8 @@ private static bool CompareCssStyleDeclarations(ICssStyleDeclaration control, IC if (control.Length != test.Length) return false; - var orderedControl = control.CssText.Split(';').Select(x => x.Trim()).OrderBy(x => x); - var orderedTest = test.CssText.Split(';').Select(x => x.Trim()).OrderBy(x => x); + var orderedControl = control.CssText.Split(';').Select(x => x.Trim()).OrderBy(x => x, StringComparer.Ordinal); + var orderedTest = test.CssText.Split(';').Select(x => x.Trim()).OrderBy(x => x, StringComparer.Ordinal); return orderedControl.SequenceEqual(orderedTest, StringComparer.Ordinal); } diff --git a/src/AngleSharp.Diffing/Strategies/DiffingStrategyPipeline.cs b/src/AngleSharp.Diffing/Strategies/DiffingStrategyPipeline.cs index e4febee..b4348c0 100644 --- a/src/AngleSharp.Diffing/Strategies/DiffingStrategyPipeline.cs +++ b/src/AngleSharp.Diffing/Strategies/DiffingStrategyPipeline.cs @@ -6,12 +6,12 @@ namespace AngleSharp.Diffing.Strategies; /// public class DiffingStrategyPipeline : IDiffingStrategy, IDiffingStrategyCollection { - private readonly List> _nodeFilters = new List>(); - private readonly List> _attrsFilters = new List>(); - private readonly List> _nodeMatchers = new List>(); - private readonly List> _attrsMatchers = new List>(); - private readonly List> _nodeComparers = new List>(); - private readonly List> _attrComparers = new List>(); + private readonly List> _nodeFilters = new(); + private readonly List> _attrsFilters = new(); + private readonly List> _nodeMatchers = new(); + private readonly List> _attrsMatchers = new(); + private readonly List> _nodeComparers = new(); + private readonly List> _attrComparers = new(); /// /// Gets whether the pipeline have any matchers registered. @@ -64,7 +64,7 @@ public IEnumerable Match(IDiffContext context, SourceMap co public CompareResult Compare(in AttributeComparison comparison) => Compare(comparison, _attrComparers, CompareResult.Different); /// - public IDiffingStrategyCollection AddFilter(FilterStrategy filterStrategy, StrategyType strategyType) + public IDiffingStrategyCollection AddFilter(FilterStrategy filterStrategy, StrategyType strategyType = StrategyType.Specialized) { if (strategyType == StrategyType.Specialized) _nodeFilters.Add(filterStrategy); @@ -74,7 +74,7 @@ public IDiffingStrategyCollection AddFilter(FilterStrategy fil } /// - public IDiffingStrategyCollection AddFilter(FilterStrategy filterStrategy, StrategyType strategyType) + public IDiffingStrategyCollection AddFilter(FilterStrategy filterStrategy, StrategyType strategyType = StrategyType.Specialized) { if (strategyType == StrategyType.Specialized) _attrsFilters.Add(filterStrategy); @@ -84,7 +84,7 @@ public IDiffingStrategyCollection AddFilter(FilterStrategy - public IDiffingStrategyCollection AddMatcher(MatchStrategy matchStrategy, StrategyType strategyType) + public IDiffingStrategyCollection AddMatcher(MatchStrategy matchStrategy, StrategyType strategyType = StrategyType.Specialized) { if (strategyType == StrategyType.Specialized) _nodeMatchers.Insert(0, matchStrategy); @@ -94,7 +94,7 @@ public IDiffingStrategyCollection AddMatcher(MatchStrategy - public IDiffingStrategyCollection AddMatcher(MatchStrategy matchStrategy, StrategyType strategyType) + public IDiffingStrategyCollection AddMatcher(MatchStrategy matchStrategy, StrategyType strategyType = StrategyType.Specialized) { if (strategyType == StrategyType.Specialized) _attrsMatchers.Insert(0, matchStrategy); @@ -104,7 +104,7 @@ public IDiffingStrategyCollection AddMatcher(MatchStrategy - public IDiffingStrategyCollection AddComparer(CompareStrategy compareStrategy, StrategyType strategyType) + public IDiffingStrategyCollection AddComparer(CompareStrategy compareStrategy, StrategyType strategyType = StrategyType.Specialized) { if (strategyType == StrategyType.Specialized) _nodeComparers.Add(compareStrategy); @@ -114,7 +114,7 @@ public IDiffingStrategyCollection AddComparer(CompareStrategy compar } /// - public IDiffingStrategyCollection AddComparer(CompareStrategy compareStrategy, StrategyType strategyType) + public IDiffingStrategyCollection AddComparer(CompareStrategy compareStrategy, StrategyType strategyType = StrategyType.Specialized) { if (strategyType == StrategyType.Specialized) _attrComparers.Add(compareStrategy); diff --git a/src/AngleSharp.Diffing/Strategies/TextNodeStrategies/TextNodeComparer.cs b/src/AngleSharp.Diffing/Strategies/TextNodeStrategies/TextNodeComparer.cs index bfe64f0..9f6f5fe 100644 --- a/src/AngleSharp.Diffing/Strategies/TextNodeStrategies/TextNodeComparer.cs +++ b/src/AngleSharp.Diffing/Strategies/TextNodeStrategies/TextNodeComparer.cs @@ -11,7 +11,7 @@ public class TextNodeComparer private const string WHITESPACE_ATTR_NAME = "diff:whitespace"; private const string IGNORECASE_ATTR_NAME = "diff:ignorecase"; private const string REGEX_ATTR_NAME = "diff:regex"; - private static readonly Regex WhitespaceReplace = new Regex(@"\s+", RegexOptions.Compiled | RegexOptions.CultureInvariant, TimeSpan.FromSeconds(5)); + private static readonly Regex WhitespaceReplace = new(@"\s+", RegexOptions.Compiled | RegexOptions.CultureInvariant, TimeSpan.FromSeconds(5)); /// /// Gets the whitespace option of the comparer instance. From 1118b70cd796073e640048d5b032b9b7b687a05f Mon Sep 17 00:00:00 2001 From: Egil Hansen Date: Sun, 13 Oct 2024 23:58:44 +0000 Subject: [PATCH 6/6] ci: upgrade to anglesharp 1.1.2 and anglesharp.css.1.0.0-beta.144 --- CHANGELOG.md | 3 ++- src/AngleSharp.Diffing/AngleSharp.Diffing.csproj | 1 + src/Directory.Build.props | 6 +++--- 3 files changed, 6 insertions(+), 4 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 53aff6a..72e8386 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,7 +1,8 @@ -# 0.18.3 +# 1.0.0 - Enabled using `diff:ignoreAttributes` and `diff:ignoreChildren` together on the same element. - Corrected list of attributes that is considered as boolean attributes. Removed `hidden`, added `inert`, `playsinline`, `shadowrootclonable`, `shadowrootdelegatesfocus`, and `shadowrootserializable`. +- Upgrade to v1.x of AngleSharp. # 0.18.2 diff --git a/src/AngleSharp.Diffing/AngleSharp.Diffing.csproj b/src/AngleSharp.Diffing/AngleSharp.Diffing.csproj index 0594b2d..1c574ff 100644 --- a/src/AngleSharp.Diffing/AngleSharp.Diffing.csproj +++ b/src/AngleSharp.Diffing/AngleSharp.Diffing.csproj @@ -21,6 +21,7 @@ true true snupkg + $(NoWarn);NU5104 diff --git a/src/Directory.Build.props b/src/Directory.Build.props index 2004708..bbbcb7b 100644 --- a/src/Directory.Build.props +++ b/src/Directory.Build.props @@ -1,6 +1,6 @@ - 0.18.3 + 1.0.0 @@ -20,7 +20,7 @@ - - + + \ No newline at end of file