lutaml 0.10.4 → 0.10.6
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/.gitignore +8 -0
- data/.rubocop.yml +10 -0
- data/.rubocop_todo.yml +218 -94
- data/TODO.cleanups/01-resolve-production-todos.md +65 -0
- data/TODO.cleanups/02-reduce-metrics-offenses.md +37 -0
- data/TODO.cleanups/03-reduce-rspec-multiple-expectations.md +54 -0
- data/TODO.cleanups/04-reduce-rspec-example-length.md +45 -0
- data/TODO.cleanups/05-replace-marshal-load.md +37 -0
- data/TODO.cleanups/06-replace-eval-in-tests.md +41 -0
- data/TODO.cleanups/07-fix-lint-offenses.md +74 -0
- data/TODO.cleanups/08-reduce-memoized-helpers-and-nesting.md +43 -0
- data/TODO.cleanups/09-reduce-verified-doubles-and-rspec-style.md +57 -0
- data/TODO.cleanups/10-split-large-files.md +47 -0
- data/bin/console +0 -1
- data/exe/lutaml +1 -0
- data/lib/lutaml/cli/element_identifier.rb +3 -6
- data/lib/lutaml/cli/interactive_shell/bookmark_commands.rb +88 -0
- data/lib/lutaml/cli/interactive_shell/command_base.rb +32 -0
- data/lib/lutaml/cli/interactive_shell/export_handler.rb +67 -0
- data/lib/lutaml/cli/interactive_shell/help_display.rb +114 -0
- data/lib/lutaml/cli/interactive_shell/navigation_commands.rb +135 -0
- data/lib/lutaml/cli/interactive_shell/query_commands.rb +185 -0
- data/lib/lutaml/cli/interactive_shell.rb +116 -802
- data/lib/lutaml/cli/uml/build_command.rb +5 -5
- data/lib/lutaml/cli/uml/verify_command.rb +0 -1
- data/lib/lutaml/converter/xmi_to_uml.rb +3 -153
- data/lib/lutaml/converter/xmi_to_uml_generalization.rb +193 -0
- data/lib/lutaml/formatter/graphviz.rb +1 -2
- data/lib/lutaml/qea/database.rb +1 -47
- data/lib/lutaml/qea/factory/association_builder.rb +188 -0
- data/lib/lutaml/qea/factory/base_transformer.rb +0 -1
- data/lib/lutaml/qea/factory/class_transformer.rb +40 -590
- data/lib/lutaml/qea/factory/diagram_transformer.rb +0 -3
- data/lib/lutaml/qea/factory/generalization_builder.rb +211 -0
- data/lib/lutaml/qea/factory/package_transformer.rb +1 -2
- data/lib/lutaml/qea/factory/stereotype_loader.rb +34 -0
- data/lib/lutaml/qea/lookup_indexes.rb +54 -0
- data/lib/lutaml/qea/models/ea_datatype.rb +0 -2
- data/lib/lutaml/qea/validation/validation_engine.rb +0 -2
- data/lib/lutaml/uml/has_members.rb +0 -1
- data/lib/lutaml/uml/inheritance_walker.rb +92 -0
- data/lib/lutaml/uml/model_helpers.rb +129 -0
- data/lib/lutaml/uml/node/attribute.rb +3 -1
- data/lib/lutaml/uml/node/class_node.rb +3 -3
- data/lib/lutaml/uml/operation.rb +2 -0
- data/lib/lutaml/uml_repository/class_lookup_index.rb +40 -0
- data/lib/lutaml/uml_repository/exporters/markdown/class_page_builder.rb +179 -0
- data/lib/lutaml/uml_repository/exporters/markdown/formatting.rb +36 -0
- data/lib/lutaml/uml_repository/exporters/markdown/index_page_builder.rb +73 -0
- data/lib/lutaml/uml_repository/exporters/markdown/link_resolver.rb +40 -0
- data/lib/lutaml/uml_repository/exporters/markdown/package_page_builder.rb +107 -0
- data/lib/lutaml/uml_repository/exporters/markdown_exporter.rb +26 -538
- data/lib/lutaml/uml_repository/index_builder.rb +3 -271
- data/lib/lutaml/uml_repository/index_builders/association_index.rb +141 -0
- data/lib/lutaml/uml_repository/index_builders/class_index.rb +94 -0
- data/lib/lutaml/uml_repository/index_builders/package_index.rb +57 -0
- data/lib/lutaml/uml_repository/package_exporter.rb +10 -20
- data/lib/lutaml/uml_repository/package_loader.rb +37 -17
- data/lib/lutaml/uml_repository/repository/deprecated.rb +39 -0
- data/lib/lutaml/uml_repository/repository/loader.rb +112 -0
- data/lib/lutaml/uml_repository/repository.rb +7 -57
- data/lib/lutaml/uml_repository/static_site/association_serialization.rb +142 -0
- data/lib/lutaml/uml_repository/static_site/configuration.rb +0 -2
- data/lib/lutaml/uml_repository/static_site/data_transformer.rb +52 -873
- data/lib/lutaml/uml_repository/static_site/generator.rb +29 -8
- data/lib/lutaml/uml_repository/static_site/search_index_builder.rb +1 -4
- data/lib/lutaml/uml_repository/static_site/serializers/attribute_serializer.rb +78 -0
- data/lib/lutaml/uml_repository/static_site/serializers/class_serializer.rb +124 -0
- data/lib/lutaml/uml_repository/static_site/serializers/diagram_serializer.rb +60 -0
- data/lib/lutaml/uml_repository/static_site/serializers/inheritance_resolver.rb +258 -0
- data/lib/lutaml/uml_repository/static_site/serializers/metadata_builder.rb +48 -0
- data/lib/lutaml/uml_repository/static_site/serializers/operation_serializer.rb +57 -0
- data/lib/lutaml/uml_repository/static_site/serializers/package_serializer.rb +94 -0
- data/lib/lutaml/uml_repository/static_site/serializers/package_tree_builder.rb +93 -0
- data/lib/lutaml/version.rb +1 -1
- data/lib/lutaml/xmi/liquid_drops/association_drop.rb +13 -35
- data/lib/lutaml/xmi/liquid_drops/attribute_drop.rb +12 -18
- data/lib/lutaml/xmi/liquid_drops/cardinality_drop.rb +14 -6
- data/lib/lutaml/xmi/liquid_drops/connector_drop.rb +0 -3
- data/lib/lutaml/xmi/liquid_drops/constraint_drop.rb +1 -3
- data/lib/lutaml/xmi/liquid_drops/data_type_drop.rb +13 -70
- data/lib/lutaml/xmi/liquid_drops/dependency_drop.rb +2 -5
- data/lib/lutaml/xmi/liquid_drops/diagram_drop.rb +5 -11
- data/lib/lutaml/xmi/liquid_drops/enum_drop.rb +8 -16
- data/lib/lutaml/xmi/liquid_drops/enum_owned_literal_drop.rb +3 -9
- data/lib/lutaml/xmi/liquid_drops/generalization_attribute_drop.rb +11 -13
- data/lib/lutaml/xmi/liquid_drops/generalization_drop.rb +27 -85
- data/lib/lutaml/xmi/liquid_drops/klass_drop.rb +39 -91
- data/lib/lutaml/xmi/liquid_drops/operation_drop.rb +3 -9
- data/lib/lutaml/xmi/liquid_drops/package_drop.rb +16 -44
- data/lib/lutaml/xmi/liquid_drops/root_drop.rb +3 -11
- data/lib/lutaml/xmi/liquid_drops/source_target_drop.rb +2 -5
- data/lib/lutaml/xmi/parsers/xmi_base.rb +2 -749
- data/lib/lutaml/xmi/parsers/xmi_class_members.rb +45 -0
- data/lib/lutaml/xmi/parsers/xmi_connector.rb +251 -0
- data/lib/lutaml/xmi/parsers/xml.rb +7 -120
- data/lib/lutaml/xmi/xmi_lookup_service.rb +42 -0
- data/lib/lutaml.rb +0 -1
- metadata +48 -21
- data/lib/lutaml/cli/commands/base_command.rb +0 -118
- data/lib/lutaml/command_line.rb +0 -272
- data/lib/lutaml/sysml/allocate.rb +0 -9
- data/lib/lutaml/sysml/allocated.rb +0 -9
- data/lib/lutaml/sysml/binding_connector.rb +0 -9
- data/lib/lutaml/sysml/block.rb +0 -32
- data/lib/lutaml/sysml/constraint_block.rb +0 -14
- data/lib/lutaml/sysml/copy.rb +0 -8
- data/lib/lutaml/sysml/derive_requirement.rb +0 -9
- data/lib/lutaml/sysml/nested_connector_end.rb +0 -13
- data/lib/lutaml/sysml/refine.rb +0 -9
- data/lib/lutaml/sysml/requirement.rb +0 -44
- data/lib/lutaml/sysml/requirement_related.rb +0 -9
- data/lib/lutaml/sysml/satisfy.rb +0 -9
- data/lib/lutaml/sysml/test_case.rb +0 -25
- data/lib/lutaml/sysml/trace.rb +0 -9
- data/lib/lutaml/sysml/verify.rb +0 -8
- data/lib/lutaml/sysml/xmi_file.rb +0 -486
- data/lib/lutaml/sysml.rb +0 -11
|
@@ -0,0 +1,54 @@
|
|
|
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)
|
|
@@ -0,0 +1,45 @@
|
|
|
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
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
# 05 — Replace Marshal.load with safe deserialization (4 offenses)
|
|
2
|
+
|
|
3
|
+
## Problem
|
|
4
|
+
|
|
5
|
+
`Marshal.load` is flagged by `Security/MarshalLoad` because deserializing untrusted data can lead to remote code execution. The gem uses it in the `.lur` package format (a ZIP file containing serialized Ruby objects).
|
|
6
|
+
|
|
7
|
+
## Affected files
|
|
8
|
+
|
|
9
|
+
```ruby
|
|
10
|
+
# lib/lutaml/uml_repository/package_loader.rb:194
|
|
11
|
+
Marshal.load(entry.get_input_stream.read)
|
|
12
|
+
|
|
13
|
+
# lib/lutaml/uml_repository/package_loader.rb:210
|
|
14
|
+
Marshal.load(entry.get_input_stream.read)
|
|
15
|
+
|
|
16
|
+
# spec/lutaml/uml_repository/package_exporter_spec.rb:141
|
|
17
|
+
expect { Marshal.load(serialized) }.not_to raise_error
|
|
18
|
+
|
|
19
|
+
# spec/lutaml/uml_repository/package_exporter_spec.rb:171
|
|
20
|
+
expect { Marshal.load(serialized) }.not_to raise_error
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
## Approach
|
|
24
|
+
|
|
25
|
+
**Option A: Switch to JSON serialization** — Replace `Marshal.dump`/`Marshal.load` with `JSON.generate`/`JSON.parse` in `PackageExporter` and `PackageLoader`. This is safe but requires models to be JSON-serializable.
|
|
26
|
+
|
|
27
|
+
**Option B: Use YAML with permitted classes** — `YAML.safe_load` with an explicit allowlist of classes. More flexible but still requires maintaining the class list.
|
|
28
|
+
|
|
29
|
+
**Option C: Keep Marshal but document the trust boundary** — If `.lur` files are only ever created by the gem itself (trusted), the risk is mitigated. Add a clear comment at the load site and suppress the cop with justification.
|
|
30
|
+
|
|
31
|
+
**Recommended**: Option A if feasible (cleanest), Option C as a minimal fix.
|
|
32
|
+
|
|
33
|
+
## Verification
|
|
34
|
+
|
|
35
|
+
- `bundle exec rubocop --only Security/MarshalLoad` shows 0 offenses
|
|
36
|
+
- `.lur` package round-trip (export → load) works correctly
|
|
37
|
+
- `bundle exec rspec` passes
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
# 06 — Replace eval in tests with StringIO redirection (3 Security/Eval + 2 Style/EvalWithLocation + 2 Style/DocumentDynamicEvalDefinition)
|
|
2
|
+
|
|
3
|
+
## Problem
|
|
4
|
+
|
|
5
|
+
`spec/lutaml/cli/uml/search_command_spec.rb` uses `eval` to redirect `$stdout`/`$stderr` for capturing output. This triggers 7 rubocop offenses across Security and Style cops.
|
|
6
|
+
|
|
7
|
+
## Affected code
|
|
8
|
+
|
|
9
|
+
```ruby
|
|
10
|
+
# spec/lutaml/cli/uml/search_command_spec.rb:61-65
|
|
11
|
+
old_stream = eval(stream_var.to_s)
|
|
12
|
+
eval("#{stream_var} = StringIO.new")
|
|
13
|
+
# ...
|
|
14
|
+
eval("#{stream_var} = old_stream") if defined?(old_stream) && old_stream
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
## Fix
|
|
18
|
+
|
|
19
|
+
Replace with a standard output capture pattern:
|
|
20
|
+
|
|
21
|
+
```ruby
|
|
22
|
+
# Use RSpec's built-in output matcher:
|
|
23
|
+
expect { command.run }.to output(/expected text/).to_stdout
|
|
24
|
+
|
|
25
|
+
# Or use a capture helper:
|
|
26
|
+
def capture_output
|
|
27
|
+
old_stdout = $stdout
|
|
28
|
+
$stdout = StringIO.new
|
|
29
|
+
yield
|
|
30
|
+
$stdout.string
|
|
31
|
+
ensure
|
|
32
|
+
$stdout = old_stdout
|
|
33
|
+
end
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
No `eval` needed — just use `$stdout` directly instead of dynamically resolving the variable name.
|
|
37
|
+
|
|
38
|
+
## Verification
|
|
39
|
+
|
|
40
|
+
- `bundle exec rubocop --only Security/Eval,Style/EvalWithLocation,Style/DocumentDynamicEvalDefinition` shows 0 offenses
|
|
41
|
+
- `bundle exec rspec spec/lutaml/cli/uml/search_command_spec.rb` passes
|
|
@@ -0,0 +1,74 @@
|
|
|
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
|
|
@@ -0,0 +1,43 @@
|
|
|
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
|
|
@@ -0,0 +1,57 @@
|
|
|
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
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
# 10 — Split large files into focused modules (Layout/LineLength + file organization)
|
|
2
|
+
|
|
3
|
+
## Problem
|
|
4
|
+
|
|
5
|
+
Several files have grown too large and do too much. The top offenders:
|
|
6
|
+
|
|
7
|
+
| File | Lines | Role |
|
|
8
|
+
|------|-------|------|
|
|
9
|
+
| `lib/lutaml/xmi/parsers/xmi_base.rb` | 1047 | XMI parsing (13 Metrics offenses) |
|
|
10
|
+
| `lib/lutaml/uml_repository/index_builder.rb` | 480 | Index building (8 Metrics offenses) |
|
|
11
|
+
| `lib/lutaml/qea/database.rb` | 477 | QEA database (8 Metrics offenses) |
|
|
12
|
+
| `lib/lutaml/converter/xmi_to_uml.rb` | 474 | XMI→UML conversion (8 Metrics offenses) |
|
|
13
|
+
| `lib/lutaml/command_line.rb` | 272 | CLI option parsing |
|
|
14
|
+
| `lib/lutaml/uml_repository/static_site/data_transformer.rb` | ~950 | Static site generation |
|
|
15
|
+
|
|
16
|
+
Additionally, `Layout/LineLength` is globally disabled (80 offenses).
|
|
17
|
+
|
|
18
|
+
## Approach
|
|
19
|
+
|
|
20
|
+
### xmi_base.rb (1047 lines)
|
|
21
|
+
Split into:
|
|
22
|
+
- `xmi_base.rb` — shared module with common methods
|
|
23
|
+
- `connector_parser.rb` — connector-related parsing
|
|
24
|
+
- `diagram_parser.rb` — diagram-related parsing
|
|
25
|
+
- `element_parser.rb` — element-related parsing
|
|
26
|
+
|
|
27
|
+
### converter/xmi_to_uml.rb (474 lines)
|
|
28
|
+
Split into:
|
|
29
|
+
- `xmi_to_uml.rb` — main converter class
|
|
30
|
+
- Per-element mapping methods extracted into separate modules or classes
|
|
31
|
+
|
|
32
|
+
### database.rb (477 lines)
|
|
33
|
+
Already well-organized with lazy accessors. Split:
|
|
34
|
+
- `database.rb` — core + public API
|
|
35
|
+
- `lookup_indexes.rb` — the `build_lookup_indexes` method and index logic
|
|
36
|
+
|
|
37
|
+
### Layout/LineLength (80 offenses)
|
|
38
|
+
Re-enable the cop with a reasonable Max (120) and fix the worst offenders. Long lines often indicate:
|
|
39
|
+
- Deeply nested code → extract to methods
|
|
40
|
+
- Complex string interpolation → use `format` or heredocs
|
|
41
|
+
- Long method chains → use `tap` or break meaningfully
|
|
42
|
+
|
|
43
|
+
## Verification
|
|
44
|
+
|
|
45
|
+
- No file over ~300 lines in `lib/`
|
|
46
|
+
- `bundle exec rubocop --only Layout/LineLength` with `Max: 120` shows 0 offenses
|
|
47
|
+
- `bundle exec rspec` passes
|
data/bin/console
CHANGED
|
@@ -6,7 +6,6 @@ require "lutaml"
|
|
|
6
6
|
require "lutaml/express"
|
|
7
7
|
require "lutaml/uml"
|
|
8
8
|
require "lutaml/xmi"
|
|
9
|
-
require "lutaml/sysml"
|
|
10
9
|
|
|
11
10
|
# You can add fixtures and/or initialization code here to make experimenting
|
|
12
11
|
# with your gem easier. You can also use a different console, if you like.
|
data/exe/lutaml
CHANGED
|
@@ -112,12 +112,9 @@ module Lutaml
|
|
|
112
112
|
# Lowercase start - could be attribute or diagram name
|
|
113
113
|
:diagram
|
|
114
114
|
end
|
|
115
|
-
when 1
|
|
116
|
-
# One
|
|
117
|
-
|
|
118
|
-
when 2..Float::INFINITY
|
|
119
|
-
# Multiple separators - likely fully qualified class name
|
|
120
|
-
# e.g., ModelRoot::Package::Class
|
|
115
|
+
when 1..Float::INFINITY
|
|
116
|
+
# One or more separators - likely qualified class name
|
|
117
|
+
# e.g., Package::Class or ModelRoot::Package::Class
|
|
121
118
|
:class
|
|
122
119
|
else
|
|
123
120
|
# Default
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "command_base"
|
|
4
|
+
require_relative "../enhanced_formatter"
|
|
5
|
+
|
|
6
|
+
module Lutaml
|
|
7
|
+
module Cli
|
|
8
|
+
class InteractiveShell
|
|
9
|
+
class BookmarkCommands < CommandBase
|
|
10
|
+
def cmd_bookmark(args)
|
|
11
|
+
return bookmark_list if args.empty?
|
|
12
|
+
|
|
13
|
+
subcommand = args[0].downcase
|
|
14
|
+
|
|
15
|
+
case subcommand
|
|
16
|
+
when "add"
|
|
17
|
+
bookmark_add(args[1])
|
|
18
|
+
when "list"
|
|
19
|
+
bookmark_list
|
|
20
|
+
when "go"
|
|
21
|
+
bookmark_go(args[1])
|
|
22
|
+
when "rm", "remove"
|
|
23
|
+
bookmark_remove(args[1])
|
|
24
|
+
else
|
|
25
|
+
bookmark_go(subcommand)
|
|
26
|
+
end
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
def bookmark_add(name)
|
|
30
|
+
if name.nil? || name.empty?
|
|
31
|
+
puts OutputFormatter.warning("Usage: bookmark add NAME")
|
|
32
|
+
return
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
target = last_results&.first || current_path
|
|
36
|
+
bookmarks[name] = target
|
|
37
|
+
puts OutputFormatter.success("Bookmark '#{name}' added: #{target}")
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
def bookmark_list
|
|
41
|
+
if bookmarks.empty?
|
|
42
|
+
puts "No bookmarks"
|
|
43
|
+
else
|
|
44
|
+
puts OutputFormatter.colorize("Bookmarks:", :cyan)
|
|
45
|
+
bookmarks.each do |name, target|
|
|
46
|
+
icon = config[:icons] ? "#{EnhancedFormatter::ICONS[:favorite]} " : ""
|
|
47
|
+
puts " #{icon}#{name} → #{target}"
|
|
48
|
+
end
|
|
49
|
+
end
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
def bookmark_go(name)
|
|
53
|
+
unless bookmarks.key?(name)
|
|
54
|
+
puts OutputFormatter.error("Bookmark not found: #{name}")
|
|
55
|
+
return
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
target = bookmarks[name]
|
|
59
|
+
if repository.find_package(target)
|
|
60
|
+
push_path_history
|
|
61
|
+
self.current_path = target
|
|
62
|
+
puts "Changed to: #{target}"
|
|
63
|
+
else
|
|
64
|
+
puts OutputFormatter.warning(
|
|
65
|
+
"Bookmark target no longer exists: #{target}",
|
|
66
|
+
)
|
|
67
|
+
end
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
def bookmark_remove(name)
|
|
71
|
+
if bookmarks.delete(name)
|
|
72
|
+
puts OutputFormatter.success("Bookmark '#{name}' removed")
|
|
73
|
+
else
|
|
74
|
+
puts OutputFormatter.error("Bookmark not found: #{name}")
|
|
75
|
+
end
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
private
|
|
79
|
+
|
|
80
|
+
def push_path_history
|
|
81
|
+
return if path_history.last == current_path
|
|
82
|
+
|
|
83
|
+
path_history << current_path
|
|
84
|
+
end
|
|
85
|
+
end
|
|
86
|
+
end
|
|
87
|
+
end
|
|
88
|
+
end
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Lutaml
|
|
4
|
+
module Cli
|
|
5
|
+
class InteractiveShell
|
|
6
|
+
class CommandBase
|
|
7
|
+
attr_reader :shell
|
|
8
|
+
|
|
9
|
+
def initialize(shell)
|
|
10
|
+
@shell = shell
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
def repository = shell.repository
|
|
14
|
+
def current_path = shell.current_path
|
|
15
|
+
|
|
16
|
+
def current_path=(path)
|
|
17
|
+
shell.instance_variable_set(:@current_path, path)
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
def config = shell.config
|
|
21
|
+
def bookmarks = shell.bookmarks
|
|
22
|
+
def last_results = shell.last_results
|
|
23
|
+
|
|
24
|
+
def last_results=(results)
|
|
25
|
+
shell.instance_variable_set(:@last_results, results)
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
def path_history = shell.path_history
|
|
29
|
+
end
|
|
30
|
+
end
|
|
31
|
+
end
|
|
32
|
+
end
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "command_base"
|
|
4
|
+
|
|
5
|
+
module Lutaml
|
|
6
|
+
module Cli
|
|
7
|
+
class InteractiveShell
|
|
8
|
+
class ExportHandler < CommandBase
|
|
9
|
+
def cmd_export(args)
|
|
10
|
+
if last_results.nil? || last_results.empty?
|
|
11
|
+
puts OutputFormatter.warning("No results to export")
|
|
12
|
+
return
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
if args.size < 3 || args[0] != "last"
|
|
16
|
+
puts OutputFormatter.warning("Usage: export last FORMAT FILE")
|
|
17
|
+
return
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
format = args[1].downcase
|
|
21
|
+
file_path = args[2]
|
|
22
|
+
|
|
23
|
+
case format
|
|
24
|
+
when "csv"
|
|
25
|
+
export_csv(file_path)
|
|
26
|
+
when "json"
|
|
27
|
+
export_json(file_path)
|
|
28
|
+
when "yaml"
|
|
29
|
+
export_yaml(file_path)
|
|
30
|
+
else
|
|
31
|
+
puts OutputFormatter.error("Unsupported format: #{format}")
|
|
32
|
+
end
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
def export_csv(file_path)
|
|
36
|
+
require "csv"
|
|
37
|
+
|
|
38
|
+
CSV.open(file_path, "w") do |csv|
|
|
39
|
+
csv << ["Qualified Name"]
|
|
40
|
+
last_results.each do |qname|
|
|
41
|
+
csv << [qname]
|
|
42
|
+
end
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
puts OutputFormatter.success("Exported #{last_results.size} " \
|
|
46
|
+
"results to #{file_path}")
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
def export_json(file_path)
|
|
50
|
+
require "json"
|
|
51
|
+
|
|
52
|
+
File.write(file_path, JSON.pretty_generate(last_results))
|
|
53
|
+
puts OutputFormatter.success("Exported #{last_results.size} " \
|
|
54
|
+
"results to #{file_path}")
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
def export_yaml(file_path)
|
|
58
|
+
require "yaml"
|
|
59
|
+
|
|
60
|
+
File.write(file_path, last_results.to_yaml)
|
|
61
|
+
puts OutputFormatter.success("Exported #{last_results.size} " \
|
|
62
|
+
"results to #{file_path}")
|
|
63
|
+
end
|
|
64
|
+
end
|
|
65
|
+
end
|
|
66
|
+
end
|
|
67
|
+
end
|