canon 0.2.11 → 0.2.12

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (148) hide show
  1. checksums.yaml +4 -4
  2. data/.rubocop_todo.yml +12 -22
  3. data/Rakefile +5 -2
  4. data/lib/canon/cache.rb +3 -1
  5. data/lib/canon/cli.rb +0 -3
  6. data/lib/canon/commands/diff_command.rb +0 -6
  7. data/lib/canon/commands/format_command.rb +0 -4
  8. data/lib/canon/commands.rb +9 -0
  9. data/lib/canon/comparison/child_realignment.rb +0 -2
  10. data/lib/canon/comparison/compare_profile.rb +30 -36
  11. data/lib/canon/comparison/comparison_result.rb +0 -2
  12. data/lib/canon/comparison/diff_node_builder.rb +353 -0
  13. data/lib/canon/comparison/dimensions/dimension.rb +51 -0
  14. data/lib/canon/comparison/dimensions/dimension_set.rb +49 -0
  15. data/lib/canon/comparison/dimensions/registry.rb +101 -60
  16. data/lib/canon/comparison/dimensions.rb +15 -46
  17. data/lib/canon/comparison/html_comparator.rb +18 -141
  18. data/lib/canon/comparison/html_compare_profile.rb +15 -18
  19. data/lib/canon/comparison/json_comparator.rb +4 -165
  20. data/lib/canon/comparison/json_parser.rb +0 -2
  21. data/lib/canon/comparison/markup_comparator.rb +14 -210
  22. data/lib/canon/comparison/match_options/base_resolver.rb +18 -29
  23. data/lib/canon/comparison/match_options/json_resolver.rb +4 -28
  24. data/lib/canon/comparison/match_options/xml_resolver.rb +4 -45
  25. data/lib/canon/comparison/match_options/yaml_resolver.rb +4 -30
  26. data/lib/canon/comparison/match_options.rb +13 -88
  27. data/lib/canon/comparison/pipeline.rb +269 -0
  28. data/lib/canon/comparison/profile_definition.rb +0 -2
  29. data/lib/canon/comparison/ruby_object_comparator.rb +1 -1
  30. data/lib/canon/comparison/strategies/match_strategy_factory.rb +9 -58
  31. data/lib/canon/comparison/strategies/semantic_tree_match_strategy.rb +4 -11
  32. data/lib/canon/comparison/strategies.rb +16 -0
  33. data/lib/canon/comparison/xml_comparator/attribute_comparator.rb +0 -3
  34. data/lib/canon/comparison/xml_comparator/attribute_filter.rb +0 -3
  35. data/lib/canon/comparison/xml_comparator/child_comparison.rb +0 -6
  36. data/lib/canon/comparison/xml_comparator/namespace_comparator.rb +1 -6
  37. data/lib/canon/comparison/xml_comparator/node_parser.rb +0 -4
  38. data/lib/canon/comparison/xml_comparator.rb +4 -492
  39. data/lib/canon/comparison/xml_comparator_helpers.rb +21 -0
  40. data/lib/canon/comparison/xml_node_comparison.rb +4 -119
  41. data/lib/canon/comparison/yaml_comparator.rb +0 -3
  42. data/lib/canon/comparison.rb +143 -266
  43. data/lib/canon/config/config_dsl.rb +159 -0
  44. data/lib/canon/config/env_provider.rb +0 -3
  45. data/lib/canon/config/env_schema.rb +48 -58
  46. data/lib/canon/config/profile_loader.rb +0 -1
  47. data/lib/canon/config.rb +116 -468
  48. data/lib/canon/diff/diff_block_builder.rb +0 -2
  49. data/lib/canon/diff/diff_classifier.rb +0 -5
  50. data/lib/canon/diff/diff_context.rb +0 -2
  51. data/lib/canon/diff/diff_context_builder.rb +0 -2
  52. data/lib/canon/diff/diff_line_builder.rb +0 -3
  53. data/lib/canon/diff/diff_node_enricher.rb +0 -4
  54. data/lib/canon/diff/diff_node_mapper.rb +0 -4
  55. data/lib/canon/diff/diff_report_builder.rb +0 -4
  56. data/lib/canon/diff/formatting_detector.rb +0 -1
  57. data/lib/canon/diff/node_serializer.rb +0 -7
  58. data/lib/canon/diff.rb +39 -0
  59. data/lib/canon/diff_formatter/by_line/base_formatter.rb +4 -17
  60. data/lib/canon/diff_formatter/by_line/html_formatter.rb +7 -19
  61. data/lib/canon/diff_formatter/by_line/json_formatter.rb +0 -3
  62. data/lib/canon/diff_formatter/by_line/simple_formatter.rb +0 -3
  63. data/lib/canon/diff_formatter/by_line/xml_formatter.rb +7 -26
  64. data/lib/canon/diff_formatter/by_line/yaml_formatter.rb +0 -3
  65. data/lib/canon/diff_formatter/by_object/base_formatter.rb +8 -15
  66. data/lib/canon/diff_formatter/by_object/json_formatter.rb +0 -2
  67. data/lib/canon/diff_formatter/by_object/xml_formatter.rb +0 -2
  68. data/lib/canon/diff_formatter/by_object/yaml_formatter.rb +0 -2
  69. data/lib/canon/diff_formatter/debug_output.rb +0 -2
  70. data/lib/canon/diff_formatter/diff_detail_formatter/dimension_formatter.rb +24 -58
  71. data/lib/canon/diff_formatter/diff_detail_formatter/location_extractor.rb +0 -2
  72. data/lib/canon/diff_formatter/diff_detail_formatter/node_utils.rb +1 -2
  73. data/lib/canon/diff_formatter/diff_detail_formatter/text_utils.rb +1 -7
  74. data/lib/canon/diff_formatter/diff_detail_formatter.rb +0 -7
  75. data/lib/canon/diff_formatter/diff_detail_formatter_helpers.rb +23 -0
  76. data/lib/canon/diff_formatter.rb +11 -9
  77. data/lib/canon/formatters/html4_formatter.rb +0 -2
  78. data/lib/canon/formatters/html5_formatter.rb +0 -2
  79. data/lib/canon/formatters/html_formatter.rb +0 -3
  80. data/lib/canon/formatters/json_formatter.rb +0 -1
  81. data/lib/canon/formatters/xml_formatter.rb +0 -4
  82. data/lib/canon/formatters/yaml_formatter.rb +0 -1
  83. data/lib/canon/formatters.rb +16 -0
  84. data/lib/canon/html/data_model.rb +0 -10
  85. data/lib/canon/html.rb +4 -3
  86. data/lib/canon/options/cli_generator.rb +0 -2
  87. data/lib/canon/options/registry.rb +0 -2
  88. data/lib/canon/options.rb +9 -0
  89. data/lib/canon/pretty_printer/html.rb +0 -1
  90. data/lib/canon/pretty_printer/xml_normalized.rb +0 -2
  91. data/lib/canon/pretty_printer.rb +12 -0
  92. data/lib/canon/tree_diff/adapters/html_adapter.rb +1 -1
  93. data/lib/canon/tree_diff/adapters.rb +14 -0
  94. data/lib/canon/tree_diff/core/attribute_comparator.rb +0 -6
  95. data/lib/canon/tree_diff/core/node_signature.rb +1 -1
  96. data/lib/canon/tree_diff/core/tree_node.rb +12 -5
  97. data/lib/canon/tree_diff/core.rb +17 -0
  98. data/lib/canon/tree_diff/matchers/hash_matcher.rb +0 -7
  99. data/lib/canon/tree_diff/matchers/similarity_matcher.rb +1 -5
  100. data/lib/canon/tree_diff/matchers/structural_propagator.rb +1 -5
  101. data/lib/canon/tree_diff/matchers.rb +15 -0
  102. data/lib/canon/tree_diff/operation_converter.rb +0 -8
  103. data/lib/canon/tree_diff/operation_converter_helpers/metadata_enricher.rb +2 -12
  104. data/lib/canon/tree_diff/operation_converter_helpers/post_processor.rb +13 -7
  105. data/lib/canon/tree_diff/operation_converter_helpers/reason_builder.rb +2 -2
  106. data/lib/canon/tree_diff/operation_converter_helpers/update_change_handler.rb +4 -6
  107. data/lib/canon/tree_diff/operation_converter_helpers.rb +18 -0
  108. data/lib/canon/tree_diff/operations/operation_detector.rb +2 -5
  109. data/lib/canon/tree_diff/operations.rb +13 -0
  110. data/lib/canon/tree_diff.rb +26 -27
  111. data/lib/canon/validators/base_validator.rb +0 -2
  112. data/lib/canon/validators/html_validator.rb +0 -1
  113. data/lib/canon/validators/json_validator.rb +0 -1
  114. data/lib/canon/validators/xml_validator.rb +0 -1
  115. data/lib/canon/validators/yaml_validator.rb +0 -1
  116. data/lib/canon/validators.rb +12 -0
  117. data/lib/canon/version.rb +1 -1
  118. data/lib/canon/xml/c14n.rb +0 -4
  119. data/lib/canon/xml/data_model.rb +0 -10
  120. data/lib/canon/xml/line_range_mapper.rb +0 -2
  121. data/lib/canon/xml/nodes/attribute_node.rb +0 -2
  122. data/lib/canon/xml/nodes/comment_node.rb +0 -2
  123. data/lib/canon/xml/nodes/element_node.rb +0 -2
  124. data/lib/canon/xml/nodes/namespace_node.rb +0 -2
  125. data/lib/canon/xml/nodes/processing_instruction_node.rb +0 -2
  126. data/lib/canon/xml/nodes/root_node.rb +0 -2
  127. data/lib/canon/xml/nodes/text_node.rb +0 -2
  128. data/lib/canon/xml/nodes.rb +19 -0
  129. data/lib/canon/xml/processor.rb +0 -5
  130. data/lib/canon/xml/sax_builder.rb +0 -7
  131. data/lib/canon/xml.rb +33 -0
  132. data/lib/canon/xml_backend.rb +50 -14
  133. data/lib/canon/xml_parsing.rb +4 -2
  134. data/lib/canon.rb +25 -15
  135. data/lib/tasks/performance.rake +0 -58
  136. data/lib/tasks/performance_comparator.rb +132 -65
  137. data/lib/tasks/performance_helpers.rb +4 -249
  138. data/lib/tasks/performance_report.rb +309 -0
  139. metadata +24 -11
  140. data/lib/canon/comparison/dimensions/attribute_order_dimension.rb +0 -64
  141. data/lib/canon/comparison/dimensions/attribute_presence_dimension.rb +0 -64
  142. data/lib/canon/comparison/dimensions/attribute_values_dimension.rb +0 -167
  143. data/lib/canon/comparison/dimensions/base_dimension.rb +0 -107
  144. data/lib/canon/comparison/dimensions/comments_dimension.rb +0 -117
  145. data/lib/canon/comparison/dimensions/element_position_dimension.rb +0 -86
  146. data/lib/canon/comparison/dimensions/structural_whitespace_dimension.rb +0 -115
  147. data/lib/canon/comparison/dimensions/text_content_dimension.rb +0 -102
  148. data/lib/canon/comparison/xml_comparator/diff_node_builder.rb +0 -300
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 8fcacddf719d0cb69472bc55b2a23d724c938fdf76f995eb77be962aeda42182
4
- data.tar.gz: 3a1d14c811209e8353c7539d12c2f7a4d23d44c1e6742b6f48a8d94f65282bd5
3
+ metadata.gz: 48895efa0afcaaa525d8dcb9b04c1eb5969aeacd96c2e9b1f575383109229ce8
4
+ data.tar.gz: 12baa3b412f857b7b6038149f8344f2aa295d8b29e96ed2a0401e64010df5478
5
5
  SHA512:
6
- metadata.gz: f4685b05d13d6cedad780751a8011a722511a9a5623a3a3fa14ce548b95d036c8fd36cabb10d8793ede0a97cf5a1e72d8f53729022c90c8b316c510dc133983e
7
- data.tar.gz: 5201625d93a1ed9c2efb929905c1d14f4a292e914604149f8e5902a61d1ea6059b987c8875b073ec71340adb1833a33bbce450f58006f29736a37d810f326d6d
6
+ metadata.gz: 9d9e2d1bfce446d850e1c991e161991a5112bdccdcb3d57d28170ba9d3180bcd0897aba7c78c7c2e6fe3ec3f5d5ba0e295c9c80627fd0d3c9e2a8bfef1434125
7
+ data.tar.gz: ec91e939f8d39f21e9c93fcfea178241eb601c6e5f66e9956468bfef0bf480ba05d587b705d108f39de661e184d7aaae3e0b79cf4c907a2d0c98b062d4564ec5
data/.rubocop_todo.yml CHANGED
@@ -1,6 +1,6 @@
1
1
  # This configuration was generated by
2
- # `rubocop --auto-gen-config --no-auto-gen-timestamp`
3
- # using RuboCop version 1.86.0.
2
+ # `rubocop --auto-gen-config`
3
+ # on 2026-06-15 09:49:31 UTC using RuboCop version 1.87.0.
4
4
  # The point is for the user to remove these configuration records
5
5
  # one by one as the offenses are removed from the code base.
6
6
  # Note that changes in the inspected code, or installation of new
@@ -11,14 +11,14 @@ Gemspec/RequiredRubyVersion:
11
11
  Exclude:
12
12
  - 'canon.gemspec'
13
13
 
14
- # Offense count: 1357
14
+ # Offense count: 1272
15
15
  # This cop supports safe autocorrection (--autocorrect).
16
16
  # Configuration parameters: Max, AllowHeredoc, AllowURI, AllowQualifiedName, URISchemes, AllowRBSInlineAnnotation, AllowCopDirectives, AllowedPatterns, SplitStrings.
17
17
  # URISchemes: http, https
18
18
  Layout/LineLength:
19
19
  Enabled: false
20
20
 
21
- # Offense count: 58
21
+ # Offense count: 57
22
22
  # Configuration parameters: IgnoreLiteralBranches, IgnoreConstantBranches, IgnoreDuplicateElseBranch.
23
23
  Lint/DuplicateBranch:
24
24
  Enabled: false
@@ -62,7 +62,7 @@ Lint/UselessConstantScoping:
62
62
  Exclude:
63
63
  - 'lib/canon/diff_formatter/theme.rb'
64
64
 
65
- # Offense count: 316
65
+ # Offense count: 298
66
66
  # Configuration parameters: AllowedMethods, AllowedPatterns, CountRepeatedAttributes, Max.
67
67
  Metrics/AbcSize:
68
68
  Enabled: false
@@ -78,12 +78,12 @@ Metrics/BlockLength:
78
78
  Metrics/BlockNesting:
79
79
  Max: 4
80
80
 
81
- # Offense count: 276
81
+ # Offense count: 261
82
82
  # Configuration parameters: AllowedMethods, AllowedPatterns, Max.
83
83
  Metrics/CyclomaticComplexity:
84
84
  Enabled: false
85
85
 
86
- # Offense count: 527
86
+ # Offense count: 501
87
87
  # Configuration parameters: CountComments, CountAsOne, AllowedMethods, AllowedPatterns.
88
88
  Metrics/MethodLength:
89
89
  Max: 146
@@ -93,12 +93,12 @@ Metrics/MethodLength:
93
93
  Metrics/ParameterLists:
94
94
  Max: 10
95
95
 
96
- # Offense count: 214
96
+ # Offense count: 207
97
97
  # Configuration parameters: AllowedMethods, AllowedPatterns, Max.
98
98
  Metrics/PerceivedComplexity:
99
99
  Enabled: false
100
100
 
101
- # Offense count: 30
101
+ # Offense count: 28
102
102
  # Configuration parameters: MinNameLength, AllowNamesEndingInNumbers, AllowedNames, ForbiddenNames.
103
103
  # AllowedNames: as, at, by, cc, db, id, if, in, io, ip, of, on, os, pp, to
104
104
  Naming/MethodParameterName:
@@ -115,16 +115,6 @@ Naming/PredicateMethod:
115
115
  Exclude:
116
116
  - 'spec/canon/comparison/xml_attribute_diff_spec.rb'
117
117
 
118
- # Offense count: 6
119
- # Configuration parameters: EnforcedStyle, CheckMethodNames, CheckSymbols, AllowedIdentifiers, AllowedPatterns.
120
- # SupportedStyles: snake_case, normalcase, non_integer
121
- # AllowedIdentifiers: TLS1_1, TLS1_2, capture3, iso8601, rfc1123_date, rfc822, rfc2822, rfc3339, x86_64
122
- Naming/VariableNumber:
123
- Exclude:
124
- - 'lib/canon/comparison/json_comparator.rb'
125
- - 'lib/canon/comparison/markup_comparator.rb'
126
- - 'lib/canon/comparison/xml_comparator/diff_node_builder.rb'
127
-
128
118
  # Offense count: 4
129
119
  # Configuration parameters: MinSize.
130
120
  Performance/CollectionLiteralInLoop:
@@ -134,7 +124,7 @@ Performance/CollectionLiteralInLoop:
134
124
  - 'lib/canon/xml/xml_base_handler.rb'
135
125
  - 'spec/canon/diff/diff_node_mapper_comments_spec.rb'
136
126
 
137
- # Offense count: 107
127
+ # Offense count: 111
138
128
  # Configuration parameters: Prefixes, AllowedPatterns.
139
129
  # Prefixes: when, with, without
140
130
  RSpec/ContextWording:
@@ -151,7 +141,7 @@ RSpec/DescribeMethod:
151
141
  - 'spec/canon/comparison/multiple_differences_spec.rb'
152
142
  - 'spec/canon/diff_formatter/character_map_customization_spec.rb'
153
143
 
154
- # Offense count: 893
144
+ # Offense count: 916
155
145
  # Configuration parameters: CountAsOne.
156
146
  RSpec/ExampleLength:
157
147
  Max: 44
@@ -203,7 +193,7 @@ RSpec/MultipleDescribes:
203
193
  Exclude:
204
194
  - 'spec/canon/comparison/match_options_spec.rb'
205
195
 
206
- # Offense count: 749
196
+ # Offense count: 783
207
197
  RSpec/MultipleExpectations:
208
198
  Max: 15
209
199
 
data/Rakefile CHANGED
@@ -24,7 +24,9 @@ begin
24
24
  end
25
25
 
26
26
  # REXML: bundled gem since Ruby 3.4
27
- rexml_lib = $LOAD_PATH.find { |p| File.exist?(File.join(p, "rexml", "document.rb")) }
27
+ rexml_lib = $LOAD_PATH.find do |p|
28
+ File.exist?(File.join(p, "rexml", "document.rb"))
29
+ end
28
30
  Opal.append_path rexml_lib if rexml_lib
29
31
  end
30
32
  rescue LoadError
@@ -42,7 +44,8 @@ namespace :spec do
42
44
  desc "Run Opal (JavaScript) tests"
43
45
  Opal::RSpec::RakeTask.new(:opal) do |_server, runner|
44
46
  runner.default_path = "spec"
45
- runner.requires = %w[rexml_compat rexml/document rexml/xpath moxml/adapter/rexml spec_helper]
47
+ runner.requires = %w[rexml_compat rexml/document rexml/xpath
48
+ moxml moxml/adapter/rexml spec_helper]
46
49
  runner.pattern = "spec/canon/opal_xml_smoke_spec.rb"
47
50
  end
48
51
  end
data/lib/canon/cache.rb CHANGED
@@ -101,7 +101,9 @@ module Canon
101
101
  Digest::SHA256.hexdigest(content)[0..16]
102
102
  else
103
103
  # Opal fallback: simple string hash
104
- h = content.each_char.reduce(0) { |acc, c| ((acc * 31) + c.ord) & 0xFFFFFFFF }
104
+ h = content.each_char.reduce(0) do |acc, c|
105
+ ((acc * 31) + c.ord) & 0xFFFFFFFF
106
+ end
105
107
  h.to_s(16).rjust(8, "0")
106
108
  end
107
109
  end
data/lib/canon/cli.rb CHANGED
@@ -1,9 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require "thor" unless RUBY_ENGINE == "opal"
4
- require_relative "commands/format_command"
5
- require_relative "commands/diff_command"
6
- require_relative "options/registry"
7
4
 
8
5
  module Canon
9
6
  # Command-line interface for Canon
@@ -1,8 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require_relative "../comparison"
4
- require_relative "../diff_formatter"
5
- require_relative "../color_detector"
6
3
  require "json"
7
4
  require "yaml"
8
5
 
@@ -66,7 +63,6 @@ module Canon
66
63
 
67
64
  # Show configuration in verbose mode using shared DebugOutput
68
65
  if @options[:verbose]
69
- require_relative "../diff_formatter/debug_output"
70
66
  config_output = Canon::DiffFormatter::DebugOutput.verbose_tables_only(
71
67
  result,
72
68
  {
@@ -219,12 +215,10 @@ module Canon
219
215
 
220
216
  case format
221
217
  when :xml
222
- require_relative "../pretty_printer/xml"
223
218
  formatted1 = Canon::PrettyPrinter::Xml.new(indent: 2).format(content1)
224
219
  formatted2 = Canon::PrettyPrinter::Xml.new(indent: 2).format(content2)
225
220
  [formatted1, formatted2]
226
221
  when :html
227
- require_relative "../pretty_printer/html"
228
222
  formatted1 = Canon::PrettyPrinter::Html.new(indent: 2).format(content1)
229
223
  formatted2 = Canon::PrettyPrinter::Html.new(indent: 2).format(content2)
230
224
  [formatted1, formatted2]
@@ -1,9 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require_relative "../pretty_printer/xml"
4
- require_relative "../pretty_printer/json"
5
- require_relative "../pretty_printer/html"
6
-
7
3
  module Canon
8
4
  module Commands
9
5
  # Command for canonicalizing files
@@ -0,0 +1,9 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Canon
4
+ # Thor command implementations invoked by {CLI}. Children are autoloaded.
5
+ module Commands
6
+ autoload :DiffCommand, "canon/commands/diff_command"
7
+ autoload :FormatCommand, "canon/commands/format_command"
8
+ end
9
+ end
@@ -1,7 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require_relative "node_inspector"
4
-
5
3
  module Canon
6
4
  module Comparison
7
5
  # Shared two-cursor walk over child arrays with noise-aware realignment.
@@ -3,7 +3,7 @@
3
3
  module Canon
4
4
  module Comparison
5
5
  # CompareProfile encapsulates the policy decisions about how differences
6
- # in various dimensions should be handled during comparison
6
+ # in various dimensions should be handled during comparison.
7
7
  #
8
8
  # This class provides separation of concerns:
9
9
  # - CompareProfile: Policy decisions (what to track, what affects equivalence)
@@ -17,69 +17,46 @@ module Canon
17
17
  @match_options = match_options
18
18
  end
19
19
 
20
- # Should DiffNodes be created for differences in this dimension?
21
- #
22
- # In verbose mode, we want to track ALL differences for reporting.
23
- # In non-verbose mode, we only need to track normative differences.
24
- #
25
- # @param dimension [Symbol] The match dimension to check
26
- # @return [Boolean] true if differences should be tracked
27
20
  def track_dimension?(_dimension)
28
- # Always track dimensions that affect equivalence
29
- # In verbose mode, also track informative dimensions
30
21
  true
31
22
  end
32
23
 
33
24
  # Should differences in this dimension affect equivalence?
34
25
  #
35
- # This determines the return value of the comparison:
36
- # - true: differences make documents non-equivalent
37
- # - false: differences are informative only
38
- #
39
26
  # @param dimension [Symbol] The match dimension to check
40
27
  # @return [Boolean] true if differences affect equivalence
41
28
  def affects_equivalence?(dimension)
42
29
  behavior = behavior_for(dimension)
43
-
44
- # :strict → affects equivalence
45
- # :normalize → might affect (if normalization fails)
46
- # :ignore → does NOT affect equivalence
47
30
  behavior != :ignore
48
31
  end
49
32
 
50
33
  # Is a difference in this dimension normative (affects equivalence)?
51
34
  #
52
- # This is used by DiffClassifier to determine the normative flag.
53
- #
54
- # Normative rules by dimension:
55
- # - structural_whitespace: only :strict is normative (:normalize and :ignore are informative)
56
- # - all other dimensions: normative unless behavior is :ignore
35
+ # Delegates to the Dimension object's normative? rule. Falls back to
36
+ # the default rule (normative unless :ignore) for dimensions not in the
37
+ # format's dimension set (e.g., derived dimensions like :element_structure).
57
38
  #
58
39
  # @param dimension [Symbol] The match dimension to check
59
40
  # @return [Boolean] true if normative, false if informative
60
41
  def normative_dimension?(dimension)
61
- # Structural whitespace with :normalize or :ignore behavior is INFORMATIVE
62
- # Only :strict mode makes whitespace normative
63
- if dimension == :structural_whitespace
64
- behavior = behavior_for(dimension)
65
- return behavior == :strict
42
+ dim = dimension_for(dimension)
43
+ if dim
44
+ dim.normative?(behavior_for(dimension))
45
+ else
46
+ behavior_for(dimension) != :ignore
66
47
  end
67
-
68
- # For all other dimensions, normative if behavior affects equivalence
69
- affects_equivalence?(dimension)
70
48
  end
71
49
 
72
50
  # Can a difference in this dimension be formatting-only?
73
51
  #
74
- # This determines whether FormattingDetector should be applied.
75
- # Only text/content dimensions can have formatting-only differences.
52
+ # Delegates to the Dimension object's supports_formatting_detection?
53
+ # flag. Falls back to false for unknown dimensions.
76
54
  #
77
55
  # @param dimension [Symbol] The match dimension to check
78
56
  # @return [Boolean] true if formatting detection should apply
79
57
  def supports_formatting_detection?(dimension)
80
- # Only text_content and structural_whitespace can have formatting-only diffs
81
- # Comments are policy-based (strict/ignore), not formatting-based
82
- %i[text_content structural_whitespace].include?(dimension)
58
+ dim = dimension_for(dimension)
59
+ dim ? dim.supports_formatting_detection? : false
83
60
  end
84
61
 
85
62
  # Get the behavior setting for a dimension
@@ -94,6 +71,23 @@ module Canon
94
71
  :strict
95
72
  end
96
73
  end
74
+
75
+ private
76
+
77
+ def dimension_for(name)
78
+ set = Dimensions::Registry.for(extract_format)
79
+ set[name]
80
+ end
81
+
82
+ def extract_format
83
+ if match_options.is_a?(ResolvedMatchOptions)
84
+ match_options.format
85
+ elsif match_options.is_a?(Hash)
86
+ match_options[:format]
87
+ else
88
+ :xml
89
+ end
90
+ end
97
91
  end
98
92
  end
99
93
  end
@@ -133,8 +133,6 @@ parse_errors_expected: nil, parse_errors_received: nil)
133
133
  # @return [String] Formatted diff output
134
134
  def diff(use_color: true, context_lines: 3, diff_grouping_lines: nil,
135
135
  show_diffs: :all, diff_mode: :separate, legacy_terminal: false)
136
- require_relative "../diff_formatter"
137
-
138
136
  formatter = Canon::DiffFormatter.new(
139
137
  use_color: use_color,
140
138
  mode: :by_line,