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.
- checksums.yaml +4 -4
- data/.rubocop_todo.yml +29 -33
- data/lib/lutaml/cli/interactive_shell/export_handler.rb +25 -17
- data/lib/lutaml/cli/interactive_shell/help_display.rb +39 -45
- data/lib/lutaml/cli/interactive_shell/navigation_commands.rb +45 -26
- data/lib/lutaml/cli/interactive_shell/query_commands.rb +73 -47
- data/lib/lutaml/cli/interactive_shell.rb +53 -27
- data/lib/lutaml/cli/tree_view_formatter.rb +11 -3
- data/lib/lutaml/converter/xmi_to_uml.rb +11 -6
- data/lib/lutaml/formatter/graphviz.rb +65 -35
- data/lib/lutaml/model_transformations/parsers/base_parser.rb +27 -29
- data/lib/lutaml/qea/factory/association_builder.rb +144 -127
- data/lib/lutaml/qea/factory/class_transformer.rb +91 -53
- data/lib/lutaml/qea/factory/ea_to_uml_factory.rb +11 -22
- data/lib/lutaml/qea/factory/enum_transformer.rb +41 -31
- data/lib/lutaml/qea/factory/generalization_builder.rb +155 -125
- data/lib/lutaml/qea/factory/stereotype_loader.rb +13 -7
- data/lib/lutaml/qea/lookup_indexes.rb +31 -13
- data/lib/lutaml/uml/inheritance_walker.rb +11 -7
- data/lib/lutaml/uml_repository/exporters/markdown/class_page_builder.rb +33 -25
- data/lib/lutaml/uml_repository/exporters/markdown/index_page_builder.rb +17 -9
- data/lib/lutaml/uml_repository/exporters/markdown_exporter.rb +27 -20
- data/lib/lutaml/uml_repository/index_builders/association_index.rb +60 -48
- data/lib/lutaml/uml_repository/index_builders/class_index.rb +35 -24
- data/lib/lutaml/uml_repository/queries/class_query.rb +79 -48
- data/lib/lutaml/uml_repository/queries/inheritance_query.rb +42 -32
- data/lib/lutaml/uml_repository/queries/search_query.rb +93 -85
- data/lib/lutaml/uml_repository/query_dsl/conditions/package_condition.rb +9 -2
- data/lib/lutaml/uml_repository/repository/loader.rb +14 -7
- data/lib/lutaml/uml_repository/static_site/data_transformer.rb +64 -35
- data/lib/lutaml/uml_repository/static_site/search_index_builder.rb +32 -19
- data/lib/lutaml/uml_repository/static_site/serializers/class_serializer.rb +36 -20
- data/lib/lutaml/uml_repository/static_site/serializers/inheritance_resolver.rb +131 -105
- data/lib/lutaml/uml_repository/static_site/serializers/package_serializer.rb +15 -9
- data/lib/lutaml/uml_repository/static_site/serializers/package_tree_builder.rb +38 -24
- data/lib/lutaml/version.rb +1 -1
- data/lib/lutaml/xmi/liquid_drops/klass_drop.rb +34 -18
- data/lib/lutaml/xmi/parsers/xmi_connector.rb +35 -23
- metadata +2 -9
- data/TODO.cleanups/01-resolve-production-todos.md +0 -65
- data/TODO.cleanups/02-reduce-metrics-offenses.md +0 -37
- data/TODO.cleanups/03-reduce-rspec-multiple-expectations.md +0 -54
- data/TODO.cleanups/04-reduce-rspec-example-length.md +0 -45
- data/TODO.cleanups/07-fix-lint-offenses.md +0 -74
- data/TODO.cleanups/08-reduce-memoized-helpers-and-nesting.md +0 -43
- 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
|
-
|
|
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
|
-
|
|
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
|
|
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)
|
data/lib/lutaml/version.rb
CHANGED
|
@@ -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
|
|
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
|
-
|
|
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)
|
|
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
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
cardinality =
|
|
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
|
-
|
|
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 ||=
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
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.
|
|
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-
|
|
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
|