lutaml 0.10.13 → 0.10.15

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 (46) hide show
  1. checksums.yaml +4 -4
  2. data/.rubocop_todo.yml +29 -33
  3. data/lib/lutaml/cli/interactive_shell/export_handler.rb +25 -17
  4. data/lib/lutaml/cli/interactive_shell/help_display.rb +39 -45
  5. data/lib/lutaml/cli/interactive_shell/navigation_commands.rb +45 -26
  6. data/lib/lutaml/cli/interactive_shell/query_commands.rb +73 -47
  7. data/lib/lutaml/cli/interactive_shell.rb +53 -27
  8. data/lib/lutaml/cli/tree_view_formatter.rb +11 -3
  9. data/lib/lutaml/converter/xmi_to_uml.rb +11 -6
  10. data/lib/lutaml/formatter/graphviz.rb +65 -35
  11. data/lib/lutaml/model_transformations/parsers/base_parser.rb +27 -29
  12. data/lib/lutaml/qea/factory/association_builder.rb +144 -127
  13. data/lib/lutaml/qea/factory/class_transformer.rb +91 -53
  14. data/lib/lutaml/qea/factory/ea_to_uml_factory.rb +11 -22
  15. data/lib/lutaml/qea/factory/enum_transformer.rb +41 -31
  16. data/lib/lutaml/qea/factory/generalization_builder.rb +155 -125
  17. data/lib/lutaml/qea/factory/stereotype_loader.rb +13 -7
  18. data/lib/lutaml/qea/lookup_indexes.rb +31 -13
  19. data/lib/lutaml/uml/inheritance_walker.rb +11 -7
  20. data/lib/lutaml/uml_repository/exporters/markdown/class_page_builder.rb +33 -25
  21. data/lib/lutaml/uml_repository/exporters/markdown/index_page_builder.rb +17 -9
  22. data/lib/lutaml/uml_repository/exporters/markdown_exporter.rb +27 -20
  23. data/lib/lutaml/uml_repository/index_builders/association_index.rb +60 -48
  24. data/lib/lutaml/uml_repository/index_builders/class_index.rb +35 -24
  25. data/lib/lutaml/uml_repository/queries/class_query.rb +79 -48
  26. data/lib/lutaml/uml_repository/queries/inheritance_query.rb +42 -32
  27. data/lib/lutaml/uml_repository/queries/search_query.rb +93 -85
  28. data/lib/lutaml/uml_repository/query_dsl/conditions/package_condition.rb +9 -2
  29. data/lib/lutaml/uml_repository/repository/loader.rb +14 -7
  30. data/lib/lutaml/uml_repository/static_site/data_transformer.rb +64 -35
  31. data/lib/lutaml/uml_repository/static_site/search_index_builder.rb +32 -19
  32. data/lib/lutaml/uml_repository/static_site/serializers/class_serializer.rb +36 -20
  33. data/lib/lutaml/uml_repository/static_site/serializers/inheritance_resolver.rb +131 -105
  34. data/lib/lutaml/uml_repository/static_site/serializers/package_serializer.rb +15 -9
  35. data/lib/lutaml/uml_repository/static_site/serializers/package_tree_builder.rb +38 -24
  36. data/lib/lutaml/version.rb +1 -1
  37. data/lib/lutaml/xmi/liquid_drops/klass_drop.rb +34 -18
  38. data/lib/lutaml/xmi/parsers/xmi_connector.rb +35 -23
  39. metadata +2 -9
  40. data/TODO.cleanups/01-resolve-production-todos.md +0 -65
  41. data/TODO.cleanups/02-reduce-metrics-offenses.md +0 -37
  42. data/TODO.cleanups/03-reduce-rspec-multiple-expectations.md +0 -54
  43. data/TODO.cleanups/04-reduce-rspec-example-length.md +0 -45
  44. data/TODO.cleanups/07-fix-lint-offenses.md +0 -74
  45. data/TODO.cleanups/08-reduce-memoized-helpers-and-nesting.md +0 -43
  46. data/TODO.cleanups/09-reduce-verified-doubles-and-rspec-style.md +0 -57
@@ -25,30 +25,26 @@ module Lutaml
25
25
  if root_packages.size == 1
26
26
  build_tree_node(root_packages.first)
27
27
  else
28
- Models::SpaPackageTreeNode.new(
29
- id: "root",
30
- name: "Model",
31
- path: "",
32
- class_count: 0,
33
- children: root_packages.map { |pkg| build_tree_node(pkg) },
34
- )
28
+ build_virtual_root(root_packages)
35
29
  end
36
30
  end
37
31
 
38
32
  private
39
33
 
34
+ def build_virtual_root(root_packages)
35
+ Models::SpaPackageTreeNode.new(
36
+ id: "root",
37
+ name: "Model",
38
+ path: "",
39
+ class_count: 0,
40
+ children: root_packages.map { |pkg| build_tree_node(pkg) },
41
+ )
42
+ end
43
+
40
44
  def build_tree_node(package)
41
45
  pkg_id = @id_generator.package_id(package)
42
-
43
- sorted_children = (package.packages || []).sort_by do |p|
44
- p.name || ""
45
- end
46
- sorted_classes = (package.classes || [])
47
- .reject { |c| c.name.nil? || c.name.empty? }
48
- .sort_by(&:name)
49
-
50
- child_nodes = sorted_children.map { |child| build_tree_node(child) }
51
-
46
+ child_nodes = build_child_nodes(package)
47
+ sorted_classes = filter_valid_classes(package.classes || [])
52
48
  total_class_count = sorted_classes.size + child_nodes.sum(&:class_count)
53
49
 
54
50
  Models::SpaPackageTreeNode.new(
@@ -57,17 +53,35 @@ module Lutaml
57
53
  path: package_path_for(package),
58
54
  stereotypes: normalize_stereotypes(package.stereotype),
59
55
  class_count: total_class_count,
60
- classes: sorted_classes.map do |c|
61
- Models::SpaTreeClassRef.new(
62
- id: @id_generator.class_id(c),
63
- name: c.name,
64
- stereotypes: normalize_stereotypes(c.stereotype),
65
- )
66
- end,
56
+ classes: build_class_refs(sorted_classes),
67
57
  children: child_nodes,
68
58
  )
69
59
  end
70
60
 
61
+ def build_child_nodes(package)
62
+ sort_by_name(package.packages || []).map do |child|
63
+ build_tree_node(child)
64
+ end
65
+ end
66
+
67
+ def sort_by_name(items)
68
+ items.sort_by { |p| p.name || "" }
69
+ end
70
+
71
+ def filter_valid_classes(classes)
72
+ classes.reject { |c| c.name.nil? || c.name.empty? }.sort_by(&:name)
73
+ end
74
+
75
+ def build_class_refs(classes)
76
+ classes.map do |c|
77
+ Models::SpaTreeClassRef.new(
78
+ id: @id_generator.class_id(c),
79
+ name: c.name,
80
+ stereotypes: normalize_stereotypes(c.stereotype),
81
+ )
82
+ end
83
+ end
84
+
71
85
  def package_path_for(package)
72
86
  return package.name unless package.namespace
73
87
  return package.name unless package.namespace.is_a?(Lutaml::Uml::Package)
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Lutaml
4
- VERSION = "0.10.13"
4
+ VERSION = "0.10.15"
5
5
  end
@@ -4,7 +4,7 @@ module Lutaml
4
4
  module Xmi
5
5
  module LiquidDrops
6
6
  class KlassDrop < Liquid::Drop
7
- def initialize(model, guidance = nil, options = {}) # rubocop:disable Lint/MissingSuper,Metrics/AbcSize,Metrics/CyclomaticComplexity,Metrics/MethodLength,Metrics/PerceivedComplexity
7
+ def initialize(model, guidance = nil, options = {}) # rubocop:disable Lint/MissingSuper
8
8
  @model = model
9
9
  @guidance = guidance
10
10
  @options = options
@@ -12,23 +12,8 @@ module Lutaml
12
12
  @xmi_root_model = options[:xmi_root_model]
13
13
  @id_name_mapping = options[:id_name_mapping]
14
14
 
15
- if @xmi_root_model
16
- @clients_dependencies = @lookup.select_dependencies_by_supplier(@model.xmi_id)
17
- @suppliers_dependencies = @lookup.select_dependencies_by_client(@model.xmi_id)
18
-
19
- matched_element = @lookup.find_matched_element(@model.xmi_id)
20
- @inheritance_ids = matched_element&.links&.map do |link|
21
- link.generalization.select do |gen|
22
- gen.end == @model.xmi_id
23
- end.map(&:id)
24
- end&.flatten&.compact || []
25
- end
26
-
27
- if guidance
28
- @klass_guidance = guidance["classes"].find do |klass|
29
- klass["name"] == name || klass["name"] == absolute_path
30
- end
31
- end
15
+ init_xmi_dependencies if @xmi_root_model
16
+ init_guidance(guidance) if guidance
32
17
  end
33
18
 
34
19
  def xmi_id
@@ -53,6 +38,37 @@ module Lutaml
53
38
  absolute_path_arr.reverse.join("::")
54
39
  end
55
40
 
41
+ private
42
+
43
+ def init_xmi_dependencies
44
+ @clients_dependencies = @lookup.select_dependencies_by_supplier(@model.xmi_id)
45
+ @suppliers_dependencies = @lookup.select_dependencies_by_client(@model.xmi_id)
46
+
47
+ matched_element = @lookup.find_matched_element(@model.xmi_id)
48
+ @inheritance_ids = extract_inheritance_ids(matched_element)
49
+ end
50
+
51
+ def init_guidance(guidance)
52
+ @klass_guidance = guidance["classes"].find do |klass|
53
+ klass["name"] == name || klass["name"] == absolute_path
54
+ end
55
+ end
56
+
57
+ def extract_inheritance_ids(matched_element)
58
+ return [] unless matched_element
59
+
60
+ links = matched_element.links
61
+ return [] unless links
62
+
63
+ links.flat_map do |link|
64
+ link.generalization
65
+ .select { |gen| gen.end == @model.xmi_id }
66
+ .map(&:id)
67
+ end.compact
68
+ end
69
+
70
+ public
71
+
56
72
  def package
57
73
  nested_pkg = @lookup.find_packaged_element_by_id(@model.xmi_id)
58
74
  return unless nested_pkg
@@ -131,26 +131,32 @@ module Lutaml
131
131
  # @param connector_type [String]
132
132
  # @return [Array<Hash, String>]
133
133
  # @note xpath %(//connector[@xmi:idref="#{link_id}"]/#{connector_type})
134
- def fetch_assoc_connector(link_id, connector_type) # rubocop:disable Metrics/AbcSize,Metrics/MethodLength,Metrics/CyclomaticComplexity,Metrics/PerceivedComplexity
134
+ def fetch_assoc_connector(link_id, connector_type)
135
135
  connector = fetch_connector(link_id)
136
136
  return [nil, nil] unless connector
137
137
 
138
138
  assoc_connector = connector.public_send(connector_type.to_sym)
139
+ return [nil, nil] unless assoc_connector
139
140
 
140
- if assoc_connector
141
- assoc_connector_type = assoc_connector.type
142
- if assoc_connector_type&.multiplicity
143
- cardinality = assoc_connector_type.multiplicity.split("..")
144
- cardinality.unshift("1") if cardinality.length == 1
145
- min, max = cardinality
146
- end
147
-
148
- assoc_connector_role = assoc_connector.role
149
- attribute_name = assoc_connector.model.name if assoc_connector_role
150
- cardinality = cardinality_min_max_value(min, max)
141
+ cardinality = extract_cardinality(assoc_connector)
142
+ attribute_name = extract_attribute_name(assoc_connector)
143
+ [cardinality, attribute_name]
144
+ end
145
+
146
+ def extract_cardinality(assoc_connector)
147
+ assoc_connector_type = assoc_connector.type
148
+ min = nil
149
+ max = nil
150
+ if assoc_connector_type&.multiplicity
151
+ cardinality = assoc_connector_type.multiplicity.split("..")
152
+ cardinality.unshift("1") if cardinality.length == 1
153
+ min, max = cardinality
151
154
  end
155
+ cardinality_min_max_value(min, max)
156
+ end
152
157
 
153
- [cardinality, attribute_name]
158
+ def extract_attribute_name(assoc_connector)
159
+ assoc_connector.role ? assoc_connector.model.name : nil
154
160
  end
155
161
 
156
162
  # @param owner_xmi_id [String]
@@ -202,16 +208,22 @@ module Lutaml
202
208
  # Lazy-built hash index for O(1) connector lookups
203
209
  # @return [Hash] Mapping of [direction, idref] => connector
204
210
  def connector_lookup
205
- @connector_lookup ||= begin
206
- lookup = {}
207
- connectors = @xmi_root_model.extension&.connectors&.connector || []
208
- connectors.each do |con|
209
- %i[source target].each do |dir|
210
- idref = con.public_send(dir)&.idref
211
- lookup[[dir, idref]] = con if idref
212
- end
213
- end
214
- lookup
211
+ @connector_lookup ||= build_connector_lookup
212
+ end
213
+
214
+ def build_connector_lookup
215
+ lookup = {}
216
+ connectors = @xmi_root_model.extension&.connectors&.connector || []
217
+ connectors.each do |con|
218
+ index_connector_directions(con, lookup)
219
+ end
220
+ lookup
221
+ end
222
+
223
+ def index_connector_directions(con, lookup)
224
+ %i[source target].each do |dir|
225
+ idref = con.public_send(dir)&.idref
226
+ lookup[[dir, idref]] = con if idref
215
227
  end
216
228
  end
217
229
 
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: lutaml
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.10.13
4
+ version: 0.10.15
5
5
  platform: ruby
6
6
  authors:
7
7
  - Ribose Inc.
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2026-05-12 00:00:00.000000000 Z
11
+ date: 2026-05-13 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: expressir
@@ -321,15 +321,8 @@ files:
321
321
  - Makefile
322
322
  - README.adoc
323
323
  - Rakefile
324
- - TODO.cleanups/01-resolve-production-todos.md
325
- - TODO.cleanups/02-reduce-metrics-offenses.md
326
- - TODO.cleanups/03-reduce-rspec-multiple-expectations.md
327
- - TODO.cleanups/04-reduce-rspec-example-length.md
328
324
  - TODO.cleanups/05-replace-marshal-load.md
329
325
  - TODO.cleanups/06-replace-eval-in-tests.md
330
- - TODO.cleanups/07-fix-lint-offenses.md
331
- - TODO.cleanups/08-reduce-memoized-helpers-and-nesting.md
332
- - TODO.cleanups/09-reduce-verified-doubles-and-rspec-style.md
333
326
  - TODO.cleanups/10-split-large-files.md
334
327
  - bin/console
335
328
  - bin/folder_yaml2lutaml.sh
@@ -1,65 +0,0 @@
1
- # 01 — Resolve 7 production TODOs
2
-
3
- ## Problem
4
-
5
- Seven `TODO`/`FIXME` comments in `lib/` represent incomplete features, missing validation, or known tech debt. They risk silent data loss or incorrect behavior.
6
-
7
- ## Items
8
-
9
- ### 1. `lib/lutaml/qea/factory/diagram_transformer.rb:28`
10
- ```ruby
11
- # TODO: Fix diagram_type assignment -
12
- # lutaml-model compatibility issue
13
- # diagram.diagram_type = ea_diagram.diagram_type
14
- ```
15
- **Impact**: Diagram type is silently dropped during QEA→UML transformation. The `diagram_type` attribute exists on `Lutaml::Uml::Diagram` but is never populated from QEA.
16
- **Fix**: Investigate lutaml-model compatibility (likely a type mismatch or missing attribute). Either fix the assignment or document why it's intentionally omitted.
17
-
18
- ### 2. `lib/lutaml/qea/factory/package_transformer.rb:32`
19
- ```ruby
20
- # TODO: Fix tagged_values assignment - temporarily commented out
21
- # pkg.tagged_values = load_tagged_values(ea_package.ea_guid)
22
- ```
23
- **Impact**: Tagged values (metadata/annotations) are silently dropped from packages during QEA→UML transformation. This causes XMI/QEA equivalence mismatches.
24
- **Fix**: Check if `load_tagged_values` returns correct type. Likely needs the same serialization pattern used in class_transformer.
25
-
26
- ### 3. `lib/lutaml/formatter/graphviz.rb:56`
27
- ```ruby
28
- # TODO: set rankdir
29
- # @graph['rankdir'] = 'BT'
30
- ```
31
- **Impact**: Graph direction can't be configured. The value is commented out.
32
- **Fix**: Either expose as configurable option or remove dead code.
33
-
34
- ### 4. `lib/lutaml/uml/node/class_node.rb:19`
35
- ```ruby
36
- @modifier = value.to_s # TODO: Validate?
37
- ```
38
- **Impact**: No validation on modifier values — invalid strings accepted silently.
39
- **Fix**: Add enum validation for known modifiers (`public`, `private`, `protected`, etc.) or remove the TODO if validation isn't needed.
40
-
41
- ### 5. `lib/lutaml/uml/node/attribute.rb:27`
42
- ```ruby
43
- @access = value.to_s # TODO: Validate?
44
- ```
45
- **Impact**: Same as #4 but for access modifiers on attributes.
46
- **Fix**: Same as #4 — validate or remove TODO.
47
-
48
- ### 6. `lib/lutaml/uml/node/class_node.rb:24`
49
- ```ruby
50
- type = member.to_a[0][0] # TODO: This is dumb
51
- ```
52
- **Impact**: Fragile parsing of member data structure. The `TODO: This is dumb` indicates the developer knew it was wrong but shipped it.
53
- **Fix**: Replace with structured member parsing using named access.
54
-
55
- ### 7. `lib/lutaml/uml/has_members.rb:8`
56
- ```ruby
57
- # TODO: move to Parslet::Transform
58
- ```
59
- **Impact**: Member type logic is in the model instead of the parser layer.
60
- **Fix**: Evaluate whether moving to Parslet::Transform is still desirable. If not, remove the TODO.
61
-
62
- ## Verification
63
-
64
- - `grep -rn "TODO\|FIXME" lib/ --include="*.rb"` should return 0 results (or only intentional ones)
65
- - Full test suite passes: `bundle exec rspec`
@@ -1,37 +0,0 @@
1
- # 02 — Reduce Metrics/AbcSize and method complexity (212 + 130 + 11 + 11 + 8 offenses)
2
-
3
- ## Problem
4
-
5
- 5 Metrics cops account for **372 offenses** across production code. The worst offenders are large methods that do too much, making them hard to test, debug, and maintain.
6
-
7
- ## Top targets by file
8
-
9
- | File | AbcSize | MethodLength | Cyclomatic | Perceived | Lines |
10
- |------|---------|-------------|------------|-----------|-------|
11
- | `lib/lutaml/xmi/parsers/xmi_base.rb` | Yes | — | Yes | Yes | 1047 |
12
- | `lib/lutaml/converter/xmi_to_uml.rb` | Yes | — | Yes | Yes | 474 |
13
- | `lib/lutaml/uml_repository/index_builder.rb` | Yes | — | Yes | Yes | 480 |
14
- | `lib/lutaml/qea/database.rb` | Yes | — | Yes | Yes | 477 |
15
- | `lib/lutaml/uml_repository/queries/class_query.rb` | Yes | — | Yes | Yes | 151 |
16
- | `lib/lutaml/uml_repository/queries/inheritance_query.rb` | Yes | — | Yes | Yes | — |
17
- | `lib/lutaml/model_transformations/parsers/base_parser.rb` | Yes | — | — | — | — |
18
- | `lib/lutaml/qea/factory/enum_transformer.rb` | — | — | Yes | Yes | — |
19
- | `lib/lutaml/uml_repository/queries/search_query.rb` | — | — | Yes | — | — |
20
- | `lib/lutaml/cli/tree_view_formatter.rb` | — | — | Yes | — | — |
21
-
22
- ## Approach
23
-
24
- Extract helper methods from god methods. Common patterns:
25
-
26
- 1. **xmi_base.rb** — The 1047-line file has massive parsing methods. Extract per-element-type parsing into separate methods (already partially done).
27
- 2. **converter/xmi_to_uml.rb** — The `build_*` methods handle too many concerns. Extract mapping logic per element type.
28
- 3. **index_builder.rb** — The single-pass `build_all` is better than before but individual index builders can be further split.
29
- 4. **database.rb** — The `build_lookup_indexes` method builds all indexes at once; split into per-index methods.
30
-
31
- **Do NOT**: Increase Max thresholds in `.rubocop_todo.yml`. Instead, refactor to reduce actual complexity.
32
-
33
- ## Verification
34
-
35
- - `bundle exec rubocop --only Metrics` shows reduced offense counts
36
- - `bundle exec rspec` passes
37
- - No functional changes — pure refactoring
@@ -1,54 +0,0 @@
1
- # 03 — Reduce RSpec/MultipleExpectations (946 offenses)
2
-
3
- ## Problem
4
-
5
- 946 test examples have more than 1 expectation. This is the single largest rubocop offense. It makes tests brittle (failures stop at the first failed expectation, masking other issues) and harder to understand what's being verified.
6
-
7
- ## Approach
8
-
9
- Split multi-expectation examples into focused single-expectation examples:
10
-
11
- ```ruby
12
- # BEFORE:
13
- it "validates the widget" do
14
- expect(widget.name).to eq("Test")
15
- expect(widget.size).to eq(10)
16
- expect(widget.active).to be true
17
- end
18
-
19
- # AFTER:
20
- it { expect(widget.name).to eq("Test") }
21
- it { expect(widget.size).to eq(10) }
22
- it { expect(widget.active).to be true }
23
- ```
24
-
25
- For cases where multiple expectations are genuinely related (e.g., checking a hash structure), use compound matchers:
26
-
27
- ```ruby
28
- expect(result).to include("name" => "Test", "size" => 10, "active" => true)
29
- ```
30
-
31
- Or aggregate failures with `aggregate_failures` when the expectations are about the same concern:
32
-
33
- ```ruby
34
- it "has correct address fields" do
35
- aggregate_failures do
36
- expect(address.street).to eq("Main St")
37
- expect(address.city).to eq("London")
38
- end
39
- end
40
- ```
41
-
42
- ## Priority files (highest offense density)
43
-
44
- Run `bundle exec rubocop --only RSpec/MultipleExpectations` to see the full list. Focus on:
45
- 1. `spec/lutaml/qea/verification/comprehensive_equivalence_spec.rb`
46
- 2. `spec/lutaml/qea/verification/equivalence_integration_spec.rb`
47
- 3. `spec/lutaml/cli/` command specs
48
- 4. `spec/lutaml/uml_repository/` specs
49
-
50
- ## Verification
51
-
52
- - `bundle exec rubocop --only RSpec/MultipleExpectations` shows reduced count
53
- - `bundle exec rspec` passes
54
- - Reduce Max from 26 toward 5 (default)
@@ -1,45 +0,0 @@
1
- # 04 — Reduce RSpec/ExampleLength (657 offenses)
2
-
3
- ## Problem
4
-
5
- 657 test examples exceed the default 5-line limit (current Max: 108). Long examples are hard to read, hide intent, and often combine setup + action + multiple assertions.
6
-
7
- ## Approach
8
-
9
- Common patterns to fix:
10
-
11
- 1. **Extract test data into `let`/`let!`** — Move setup out of `it` blocks:
12
- ```ruby
13
- # BEFORE:
14
- it "parses correctly" do
15
- result = described_class.parse(fixture_content)
16
- expect(result.name).to eq("Test")
17
- end
18
-
19
- # AFTER:
20
- let(:result) { described_class.parse(fixture_content) }
21
- it { expect(result.name).to eq("Test") }
22
- ```
23
-
24
- 2. **Extract shared setup into `before` blocks** — If multiple examples repeat the same setup.
25
-
26
- 3. **Use subject with one-liner syntax** — Where the subject is the tested value:
27
- ```ruby
28
- subject { described_class.new(attrs) }
29
- it { is_expected.to be_valid }
30
- ```
31
-
32
- 4. **Extract helper methods** — Complex assertions that span many lines can become custom matchers or helper methods in `spec/support/`.
33
-
34
- ## Priority
35
-
36
- Focus on the longest examples first — run `bundle exec rubocop --only RSpec/ExampleLength` and sort by severity. Files with the worst examples:
37
- - `spec/lutaml/qea/` verification and factory specs
38
- - `spec/lutaml/uml_repository/` static site specs
39
- - `spec/lutaml/cli/` command specs
40
-
41
- ## Verification
42
-
43
- - `bundle exec rubocop --only RSpec/ExampleLength` shows reduced count
44
- - Reduce Max from 108 toward 10
45
- - `bundle exec rspec` passes
@@ -1,74 +0,0 @@
1
- # 07 — Fix Lint offenses (15 + 8 + 4 + 3 + 4 + 1 + 1 = 36 offenses)
2
-
3
- ## Problem
4
-
5
- Lint cops catch real bugs and code quality issues. 36 offenses across 7 Lint cops.
6
-
7
- ## Items
8
-
9
- ### 1. Lint/DuplicateBranch (8 offenses)
10
- Identical code in different `case`/`if` branches — likely copy-paste bugs or missing differentiation.
11
- ```
12
- lib/lutaml/cli/element_identifier.rb
13
- lib/lutaml/cli/uml/verify_command.rb
14
- lib/lutaml/qea/factory/base_transformer.rb
15
- lib/lutaml/qea/models/ea_datatype.rb
16
- lib/lutaml/qea/validation/validation_engine.rb
17
- lib/lutaml/uml_repository/queries/package_query.rb
18
- lib/lutaml/uml_repository/static_site/configuration.rb
19
- lib/lutaml/uml_repository/static_site/search_index_builder.rb
20
- ```
21
- **Fix**: Merge duplicate branches or differentiate them if they should do different things.
22
-
23
- ### 2. Lint/IneffectiveAccessModifier (4 offenses)
24
- `private`/`protected` used inside a `class << self` block — doesn't actually restrict access.
25
- ```
26
- lib/lutaml/uml_repository/repository_enhanced.rb
27
- ```
28
- **Fix**: Move methods outside the singleton class or use `private_class_method`.
29
-
30
- ### 3. Lint/ConstantDefinitionInBlock (15 offenses)
31
- Constants defined inside `it`/`before` blocks leak and can cause test pollution.
32
- ```
33
- spec/lutaml/model_transformations/format_registry_spec.rb
34
- spec/lutaml/model_transformations/parsers/base_parser_spec.rb
35
- spec/lutaml/model_transformations/transformation_engine_spec.rb
36
- spec/lutaml/model_transformations_spec.rb
37
- spec/lutaml/uml_repository/presenters/presenter_factory_spec.rb
38
- ```
39
- **Fix**: Extract to top-level constants, use `stub_const`, or define in a shared context.
40
-
41
- ### 4. Lint/EmptyConditionalBody (3 offenses)
42
- `if`/`unless` with empty body — likely incomplete logic.
43
- ```
44
- spec/integration/qea_xmi_equivalency_spec.rb
45
- spec/lutaml/qea/verification/equivalence_integration_spec.rb
46
- ```
47
- **Fix**: Add the missing body or refactor to use a guard clause.
48
-
49
- ### 5. Lint/EmptyBlock (4 offenses)
50
- Empty blocks passed to methods — likely stub placeholders.
51
- ```
52
- spec/lutaml/qea/integration/tagged_values_integration_spec.rb
53
- spec/lutaml/qea/services/database_loader_spec.rb
54
- spec/lutaml/uml_repository/package_exporter_spec.rb
55
- spec/lutaml/uml_repository/repository_spec.rb
56
- ```
57
- **Fix**: Remove empty blocks or add `{ }` style comment if intentional.
58
-
59
- ### 6. Lint/BinaryOperatorWithIdenticalOperands (1 offense)
60
- ```ruby
61
- # spec/lutaml/qea/lookup_tables_spec.rb
62
- ```
63
- **Fix**: Likely a test typo — check if both operands should be the same.
64
-
65
- ### 7. Lint/MissingSuper (1 offense)
66
- ```ruby
67
- # lib/lutaml/uml_repository/lazy_repository.rb
68
- ```
69
- **Fix**: Add `super` call or document why it's intentionally omitted.
70
-
71
- ## Verification
72
-
73
- - `bundle exec rubocop --only Lint` shows 0 offenses
74
- - `bundle exec rspec` passes
@@ -1,43 +0,0 @@
1
- # 08 — Reduce RSpec/MultipleMemoizedHelpers (130) and RSpec/NestedGroups (29)
2
-
3
- ## Problem
4
-
5
- 130 examples exceed memoized helper limits (Max: 11) and 29 examples have excessive nesting (Max: 5). Both indicate test organization issues.
6
-
7
- ## RSpec/MultipleMemoizedHelpers (130)
8
-
9
- Too many `let`, `let!`, `subject` declarations make test setup hard to follow. Each helper adds cognitive load.
10
-
11
- ### Approach
12
- 1. **Group-related helpers into factory methods** — Instead of 10 separate `let` declarations, use one factory:
13
- ```ruby
14
- # Instead of:
15
- let(:name) { "Test" }
16
- let(:age) { 30 }
17
- let(:address) { Address.new(city: "London") }
18
- let(:person) { Person.new(name: name, age: age, address: address) }
19
-
20
- # Use:
21
- let(:person) { build(:person) } # via FactoryBot or test factory
22
- ```
23
-
24
- 2. **Use `subject!` instead of separate `let` + action** — Combine setup and action.
25
-
26
- 3. **Extract shared contexts** — Move common setup into `shared_context` blocks.
27
-
28
- ### Priority files
29
- These are the worst offenders — check with `bundle exec rubocop --only RSpec/MultipleMemoizedHelpers`.
30
-
31
- ## RSpec/NestedGroups (29)
32
-
33
- Deeply nested `describe`/`context` blocks (5+ levels) make tests hard to scan.
34
-
35
- ### Approach
36
- 1. **Flatten by using more descriptive top-level descriptions** — Instead of 3 levels of nesting, use a single `describe` with a descriptive name.
37
- 2. **Extract nested contexts into separate `describe` blocks** — Move `context "when X"` / `context "and Y"` into `context "when X and Y"`.
38
- 3. **Use shared examples** — Repeated nested structures can become shared examples.
39
-
40
- ## Verification
41
-
42
- - `bundle exec rubocop --only RSpec/MultipleMemoizedHelpers,RSpec/NestedGroups` shows reduced counts
43
- - `bundle exec rspec` passes
@@ -1,57 +0,0 @@
1
- # 09 — Reduce RSpec/VerifiedDoubles (212) and other RSpec style offenses
2
-
3
- ## Problem
4
-
5
- 212 test doubles don't verify their methods exist against real classes. Other RSpec style issues total ~130 additional offenses.
6
-
7
- ## RSpec/VerifiedDoubles (212 offenses)
8
-
9
- Currently disabled entirely (`Enabled: false`). Every mock/stub in the test suite uses unverified doubles:
10
-
11
- ```ruby
12
- # CURRENT (unverified):
13
- let(:database) { instance_double("Database") }
14
- allow(database).to receive(:find_object).with(1).and_return(obj)
15
-
16
- # SHOULD BE (verified):
17
- let(:database) { instance_double(Lutaml::Qea::Database) }
18
- # Now rubocop verifies `find_object` is a real method
19
- ```
20
-
21
- ### Approach
22
- 1. Re-enable the cop in `.rubocop_todo.yml` but with a high initial Max
23
- 2. Fix in batches per directory:
24
- - `spec/lutaml/qea/` (highest density)
25
- - `spec/lutaml/uml_repository/`
26
- - `spec/lutaml/cli/`
27
- - rest
28
- 3. Replace `double(...)` with `instance_double(RealClass, ...)` or `class_double(RealClass, ...)`
29
- 4. Remove any stubbed methods that don't exist on the real class (they were wrong)
30
-
31
- ### Risk
32
- Fixing verified doubles may expose test/implementation drift where tests mock methods that no longer exist. This is valuable — it means the tests were providing false confidence.
33
-
34
- ## Other RSpec style offenses to fix in same pass
35
-
36
- | Cop | Count | Fix |
37
- |-----|-------|-----|
38
- | RSpec/ContextWording | 25 | Prefix with "when"/"with"/"without" |
39
- | RSpec/ExpectOutput | 90 | Use `expect { }.to output().to_stdout` |
40
- | RSpec/InstanceVariable | 48 | Replace `@var` with `let` (except `before(:all)` blocks) |
41
- | RSpec/RepeatedExample | 16 | Remove or differentiate duplicate test cases |
42
- | RSpec/LeakyConstantDeclaration | 15 | Use `stub_const` instead of defining constants in tests |
43
- | RSpec/MessageSpies | 11 | Use `have_received` instead of `receive` with `allow` |
44
- | RSpec/UnspecifiedException | 6 | Specify exception class in `expect { }.to raise_error(SomeError)` |
45
- | RSpec/SpecFilePathFormat | 6 | Rename spec files to match their described class |
46
- | RSpec/IndexedLet | 5 | Rename `thing1`, `thing2` to descriptive names |
47
- | RSpec/RepeatedExampleGroupDescription | 2 | Rename duplicate describe/context descriptions |
48
- | RSpec/ExpectActual | 7 | Move expectations out of `let`/`subject` |
49
- | RSpec/NoExpectationExample | 2 | Add expectations or use `pending` |
50
- | RSpec/ExpectInLet | 1 | Move expectation out of `let` |
51
- | RSpec/LeakyLocalVariable | 1 | Use `let` instead of local assignment |
52
- | RSpec/BeforeAfterAll | 1 | Evaluate if `before(:all)` can be `before(:each)` |
53
-
54
- ## Verification
55
-
56
- - `bundle exec rubocop --only RSpec` shows significantly reduced count
57
- - `bundle exec rspec` passes