archunit 0.0.1
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 +7 -0
- data/CHANGELOG.md +13 -0
- data/LICENSE +7 -0
- data/README.md +375 -0
- data/lib/archunit/common/assertion/empty_test_violation.rb +50 -0
- data/lib/archunit/common/assertion/violation.rb +14 -0
- data/lib/archunit/common/extraction/edge.rb +46 -0
- data/lib/archunit/common/extraction/graph.rb +73 -0
- data/lib/archunit/common/extraction/import_kind.rb +21 -0
- data/lib/archunit/common/filter.rb +46 -0
- data/lib/archunit/common/fluentapi/check_options.rb +48 -0
- data/lib/archunit/common/fluentapi/checkable.rb +70 -0
- data/lib/archunit/common/logging/check_logger.rb +124 -0
- data/lib/archunit/common/logging/logging_options.rb +57 -0
- data/lib/archunit/common/pattern.rb +99 -0
- data/lib/archunit/common/pattern_matching.rb +76 -0
- data/lib/archunit/common/projection/cycles/johnson_cycles.rb +103 -0
- data/lib/archunit/common/projection/cycles/tarjan_scc.rb +82 -0
- data/lib/archunit/common/projection/edge_projections.rb +50 -0
- data/lib/archunit/common/projection/mapped_edge.rb +26 -0
- data/lib/archunit/common/projection/project_cycles.rb +92 -0
- data/lib/archunit/common/projection/project_edges.rb +69 -0
- data/lib/archunit/common/projection/project_to_nodes.rb +59 -0
- data/lib/archunit/common/projection/projected_edge.rb +35 -0
- data/lib/archunit/common/projection/projected_node.rb +36 -0
- data/lib/archunit/common/regex_factory.rb +96 -0
- data/lib/archunit/error/technical_error.rb +6 -0
- data/lib/archunit/error/user_error.rb +6 -0
- data/lib/archunit/extraction/enumerate_source_files.rb +131 -0
- data/lib/archunit/extraction/extract_dependencies.rb +61 -0
- data/lib/archunit/extraction/extract_graph.rb +89 -0
- data/lib/archunit/extraction/extract_imports.rb +197 -0
- data/lib/archunit/extraction/locate_project.rb +80 -0
- data/lib/archunit/extraction/resolve_import.rb +76 -0
- data/lib/archunit/extraction/resolved_import.rb +46 -0
- data/lib/archunit/files/assertion/custom_file_condition.rb +89 -0
- data/lib/archunit/files/assertion/cycle_free.rb +58 -0
- data/lib/archunit/files/assertion/depend_on_external_modules.rb +116 -0
- data/lib/archunit/files/assertion/depend_on_files.rb +110 -0
- data/lib/archunit/files/assertion/matching_files.rb +87 -0
- data/lib/archunit/files/extraction/extract_file_info.rb +84 -0
- data/lib/archunit/files/extraction/file_info.rb +46 -0
- data/lib/archunit/files/fluentapi/custom_file_condition.rb +73 -0
- data/lib/archunit/files/fluentapi/cycle_free_file_condition.rb +55 -0
- data/lib/archunit/files/fluentapi/depend_on_external_module_condition.rb +69 -0
- data/lib/archunit/files/fluentapi/depend_on_external_module_condition_builder.rb +36 -0
- data/lib/archunit/files/fluentapi/depend_on_file_condition.rb +80 -0
- data/lib/archunit/files/fluentapi/depend_on_file_condition_builder.rb +49 -0
- data/lib/archunit/files/fluentapi/file_condition_builder.rb +71 -0
- data/lib/archunit/files/fluentapi/file_rule_support.rb +24 -0
- data/lib/archunit/files/fluentapi/files.rb +31 -0
- data/lib/archunit/files/fluentapi/match_pattern_file_condition.rb +59 -0
- data/lib/archunit/files/fluentapi/match_pattern_file_condition_builder.rb +66 -0
- data/lib/archunit/files/fluentapi/negated_match_pattern_file_condition_builder.rb +16 -0
- data/lib/archunit/files/fluentapi/positive_match_pattern_file_condition_builder.rb +21 -0
- data/lib/archunit/graph/fluentapi/graph.rb +29 -0
- data/lib/archunit/graph/fluentapi/project_graph_builder.rb +106 -0
- data/lib/archunit/graph/projection/aggregate_edges.rb +48 -0
- data/lib/archunit/graph/projection/collapse_node.rb +33 -0
- data/lib/archunit/graph/projection/create_snapshot.rb +84 -0
- data/lib/archunit/graph/projection/folder_depth_collapse.rb +18 -0
- data/lib/archunit/graph/projection/graph_query_options.rb +81 -0
- data/lib/archunit/graph/projection/graph_report_edge.rb +50 -0
- data/lib/archunit/graph/projection/graph_report_node.rb +22 -0
- data/lib/archunit/graph/projection/graph_report_snapshot.rb +43 -0
- data/lib/archunit/graph/projection/graph_report_summary.rb +20 -0
- data/lib/archunit/graph/projection/node_selection.rb +111 -0
- data/lib/archunit/graph/projection/pattern_collapse.rb +24 -0
- data/lib/archunit/graph/rendering/csv_renderer.rb +30 -0
- data/lib/archunit/graph/rendering/d2_renderer.rb +30 -0
- data/lib/archunit/graph/rendering/dot_renderer.rb +51 -0
- data/lib/archunit/graph/rendering/escaping.rb +31 -0
- data/lib/archunit/graph/rendering/export_report.rb +29 -0
- data/lib/archunit/graph/rendering/graph_renderer.rb +54 -0
- data/lib/archunit/graph/rendering/html_document.rb +46 -0
- data/lib/archunit/graph/rendering/html_renderer.rb +102 -0
- data/lib/archunit/graph/rendering/json_renderer.rb +38 -0
- data/lib/archunit/graph/rendering/mermaid_renderer.rb +33 -0
- data/lib/archunit/graph/rendering/rendering_support.rb +24 -0
- data/lib/archunit/layers/assertion/layer_definition.rb +56 -0
- data/lib/archunit/layers/assertion/layer_dependencies.rb +91 -0
- data/lib/archunit/layers/assertion/layer_dependency_violation.rb +59 -0
- data/lib/archunit/layers/fluentapi/layer_definition_builder.rb +46 -0
- data/lib/archunit/layers/fluentapi/layer_dependency_rule_builder.rb +44 -0
- data/lib/archunit/layers/fluentapi/layered_architecture.rb +133 -0
- data/lib/archunit/layers/fluentapi/layered_architecture_values.rb +51 -0
- data/lib/archunit/layers/fluentapi/layers.rb +29 -0
- data/lib/archunit/metrics/assertion/custom_metric.rb +73 -0
- data/lib/archunit/metrics/assertion/metric_predicate.rb +66 -0
- data/lib/archunit/metrics/assertion/metric_threshold.rb +99 -0
- data/lib/archunit/metrics/assertion/metric_zone.rb +57 -0
- data/lib/archunit/metrics/calculation/count.rb +45 -0
- data/lib/archunit/metrics/calculation/distance.rb +84 -0
- data/lib/archunit/metrics/calculation/lcom.rb +103 -0
- data/lib/archunit/metrics/calculation/metric.rb +52 -0
- data/lib/archunit/metrics/calculation/numeric_value.rb +25 -0
- data/lib/archunit/metrics/extraction/distance_info.rb +60 -0
- data/lib/archunit/metrics/extraction/extract_distance_info.rb +50 -0
- data/lib/archunit/metrics/extraction/extract_project_info.rb +90 -0
- data/lib/archunit/metrics/extraction/metric_info.rb +159 -0
- data/lib/archunit/metrics/extraction/source_metrics_visitor.rb +278 -0
- data/lib/archunit/metrics/fluentapi/count_metrics_builder.rb +49 -0
- data/lib/archunit/metrics/fluentapi/custom_metric_builder.rb +30 -0
- data/lib/archunit/metrics/fluentapi/custom_metric_condition.rb +40 -0
- data/lib/archunit/metrics/fluentapi/distance_metrics_builder.rb +48 -0
- data/lib/archunit/metrics/fluentapi/lcom_metrics_builder.rb +39 -0
- data/lib/archunit/metrics/fluentapi/metric_measurement.rb +36 -0
- data/lib/archunit/metrics/fluentapi/metric_predicate_condition.rb +42 -0
- data/lib/archunit/metrics/fluentapi/metric_report_builder.rb +34 -0
- data/lib/archunit/metrics/fluentapi/metric_selection.rb +54 -0
- data/lib/archunit/metrics/fluentapi/metric_threshold_condition.rb +47 -0
- data/lib/archunit/metrics/fluentapi/metrics.rb +21 -0
- data/lib/archunit/metrics/fluentapi/metrics_builder.rb +136 -0
- data/lib/archunit/metrics/fluentapi/zone_condition.rb +40 -0
- data/lib/archunit/metrics/reporting/metrics_export_options.rb +57 -0
- data/lib/archunit/metrics/reporting/metrics_exporter.rb +130 -0
- data/lib/archunit/slices/assertion/adhere_to_diagram.rb +57 -0
- data/lib/archunit/slices/assertion/contain_dependency.rb +42 -0
- data/lib/archunit/slices/assertion/diagram_adherence_options.rb +37 -0
- data/lib/archunit/slices/assertion/slice_dependency_violation.rb +70 -0
- data/lib/archunit/slices/fluentapi/diagram_slice_condition.rb +65 -0
- data/lib/archunit/slices/fluentapi/diagram_source.rb +36 -0
- data/lib/archunit/slices/fluentapi/forbidden_slice_dependency_condition.rb +57 -0
- data/lib/archunit/slices/fluentapi/negative_slice_condition_builder.rb +27 -0
- data/lib/archunit/slices/fluentapi/positive_slice_condition_builder.rb +51 -0
- data/lib/archunit/slices/fluentapi/slice_scope_builder.rb +83 -0
- data/lib/archunit/slices/fluentapi/slices.rb +29 -0
- data/lib/archunit/slices/projection/slice_projection.rb +76 -0
- data/lib/archunit/slices/projection/slicing_projections.rb +98 -0
- data/lib/archunit/slices/uml/export_diagram.rb +70 -0
- data/lib/archunit/slices/uml/parse_diagram.rb +53 -0
- data/lib/archunit/slices/uml/plant_uml_dependency.rb +25 -0
- data/lib/archunit/slices/uml/plant_uml_diagram.rb +44 -0
- data/lib/archunit/testing/assert_passes.rb +24 -0
- data/lib/archunit/testing/assertion_failure.rb +22 -0
- data/lib/archunit/testing/color_utils.rb +66 -0
- data/lib/archunit/testing/layer_violation_formatter.rb +26 -0
- data/lib/archunit/testing/metric_violation_formatter.rb +58 -0
- data/lib/archunit/testing/minitest_adapter.rb +33 -0
- data/lib/archunit/testing/result_factory.rb +93 -0
- data/lib/archunit/testing/rspec_adapter.rb +74 -0
- data/lib/archunit/testing/slice_violation_formatter.rb +25 -0
- data/lib/archunit/testing/test_result.rb +26 -0
- data/lib/archunit/testing/test_violation.rb +23 -0
- data/lib/archunit/testing/violation_factory.rb +138 -0
- data/lib/archunit/testing.rb +39 -0
- data/lib/archunit/version.rb +5 -0
- data/lib/archunit.rb +110 -0
- metadata +251 -0
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative '../common/assertion/violation'
|
|
4
|
+
require_relative 'color_utils'
|
|
5
|
+
require_relative 'test_result'
|
|
6
|
+
require_relative 'violation_factory'
|
|
7
|
+
|
|
8
|
+
module ArchUnit
|
|
9
|
+
# Violation presentation and test-framework integration.
|
|
10
|
+
module Testing
|
|
11
|
+
# Shapes a violation collection into one framework-neutral test result.
|
|
12
|
+
class ResultFactory
|
|
13
|
+
class << self
|
|
14
|
+
def from_violations(violations, color: nil, expected_to_pass: true)
|
|
15
|
+
validate_violations(violations)
|
|
16
|
+
validate_expected_to_pass(expected_to_pass)
|
|
17
|
+
color = resolve_color(color)
|
|
18
|
+
return empty_result(color, expected_to_pass) if violations.empty?
|
|
19
|
+
|
|
20
|
+
failing_result(violations, color, expected_to_pass)
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
private
|
|
24
|
+
|
|
25
|
+
def passing_result(color)
|
|
26
|
+
message = ColorUtils.green('No architecture violations found.', enabled: color)
|
|
27
|
+
TestResult.new(passed: true, message:)
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
def empty_result(color, expected_to_pass)
|
|
31
|
+
return passing_result(color) if expected_to_pass
|
|
32
|
+
|
|
33
|
+
message = 'Expected architecture violations, but none were found.'
|
|
34
|
+
message = ColorUtils.bold(ColorUtils.red(message, enabled: color), enabled: color)
|
|
35
|
+
TestResult.new(passed: false, message:)
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
def failing_result(violations, color, expected_to_pass)
|
|
39
|
+
test_violations = violations.map do |violation|
|
|
40
|
+
ViolationFactory.from_violation(violation)
|
|
41
|
+
end
|
|
42
|
+
TestResult.new(
|
|
43
|
+
passed: !expected_to_pass,
|
|
44
|
+
message: failure_message(test_violations, color)
|
|
45
|
+
)
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
def failure_message(test_violations, color)
|
|
49
|
+
lines = [failure_title(test_violations.length, color), '']
|
|
50
|
+
|
|
51
|
+
test_violations.each_with_index do |violation, index|
|
|
52
|
+
append_violation(lines, violation, index, color)
|
|
53
|
+
end
|
|
54
|
+
lines.join("\n").rstrip
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
def failure_title(count, color)
|
|
58
|
+
noun = count == 1 ? 'violation' : 'violations'
|
|
59
|
+
title = "Found #{count} architecture #{noun}:"
|
|
60
|
+
ColorUtils.bold(ColorUtils.red(title, enabled: color), enabled: color)
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
def append_violation(lines, violation, index, color)
|
|
64
|
+
heading = " #{index + 1}. #{violation.message}"
|
|
65
|
+
lines << ColorUtils.yellow(heading, enabled: color)
|
|
66
|
+
lines << " #{violation.details}"
|
|
67
|
+
lines << ''
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
def validate_violations(values)
|
|
71
|
+
return if values.is_a?(Array) && values.all?(Common::Assertion::Violation)
|
|
72
|
+
|
|
73
|
+
raise ArgumentError, 'violations must be an Array of Violation values'
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
def validate_expected_to_pass(value)
|
|
77
|
+
return if [true, false].include?(value)
|
|
78
|
+
|
|
79
|
+
raise ArgumentError, 'expected_to_pass must be true or false'
|
|
80
|
+
end
|
|
81
|
+
|
|
82
|
+
def resolve_color(value)
|
|
83
|
+
return ColorUtils.supported? if value.nil?
|
|
84
|
+
return value if [true, false].include?(value)
|
|
85
|
+
|
|
86
|
+
raise ArgumentError, 'color must be true, false, or nil'
|
|
87
|
+
end
|
|
88
|
+
end
|
|
89
|
+
|
|
90
|
+
private_class_method :new
|
|
91
|
+
end
|
|
92
|
+
end
|
|
93
|
+
end
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module ArchUnit
|
|
4
|
+
module Testing
|
|
5
|
+
# RSpec's matcher protocol, delegating all evaluation and prose to shared testing code.
|
|
6
|
+
class RSpecPassMatcher
|
|
7
|
+
def initialize(options = nil)
|
|
8
|
+
@options = options
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
def matches?(rule)
|
|
12
|
+
matches_expectation?(rule, expected_to_pass: true)
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
# RSpec's matcher protocol fixes this method name.
|
|
16
|
+
# rubocop:disable Naming/PredicatePrefix
|
|
17
|
+
def does_not_match?(rule)
|
|
18
|
+
matches_expectation?(rule, expected_to_pass: false)
|
|
19
|
+
end
|
|
20
|
+
# rubocop:enable Naming/PredicatePrefix
|
|
21
|
+
|
|
22
|
+
def failure_message
|
|
23
|
+
result.message
|
|
24
|
+
end
|
|
25
|
+
alias failure_message_when_negated failure_message
|
|
26
|
+
|
|
27
|
+
private
|
|
28
|
+
|
|
29
|
+
attr_reader :options, :result
|
|
30
|
+
|
|
31
|
+
def matches_expectation?(rule, expected_to_pass:)
|
|
32
|
+
@result = ArchUnit::Testing.result_for(rule, options, expected_to_pass:)
|
|
33
|
+
result.passed?
|
|
34
|
+
end
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
# Methods mixed into RSpec's matcher namespace when the framework is present.
|
|
38
|
+
module RSpecMatchers
|
|
39
|
+
def pass(options = nil)
|
|
40
|
+
RSpecPassMatcher.new(options)
|
|
41
|
+
end
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
# Installs the native `expect(rule).to pass` matcher when RSpec is already loaded.
|
|
45
|
+
module RSpecAdapter
|
|
46
|
+
module_function
|
|
47
|
+
|
|
48
|
+
def install!
|
|
49
|
+
return unless rspec_available?
|
|
50
|
+
return if installed?
|
|
51
|
+
|
|
52
|
+
::RSpec::Matchers.include(RSpecMatchers)
|
|
53
|
+
@installed = true
|
|
54
|
+
nil
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
def installed?
|
|
58
|
+
@installed == true
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
def rspec_available?
|
|
62
|
+
return false unless defined?(::RSpec)
|
|
63
|
+
|
|
64
|
+
require 'rspec/matchers' unless defined?(::RSpec::Matchers)
|
|
65
|
+
defined?(::RSpec::Matchers)
|
|
66
|
+
rescue LoadError
|
|
67
|
+
false
|
|
68
|
+
end
|
|
69
|
+
private_class_method :rspec_available?
|
|
70
|
+
end
|
|
71
|
+
end
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
ArchUnit::Testing::RSpecAdapter.install!
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative 'test_violation'
|
|
4
|
+
|
|
5
|
+
module ArchUnit
|
|
6
|
+
module Testing
|
|
7
|
+
# Human-readable presentation for slice architecture violations.
|
|
8
|
+
module SliceViolationFormatter
|
|
9
|
+
private
|
|
10
|
+
|
|
11
|
+
def slice_dependency(violation)
|
|
12
|
+
relationship = if violation.rule == :contain_dependency
|
|
13
|
+
'depends on forbidden slice'
|
|
14
|
+
else
|
|
15
|
+
'has a dependency not allowed by the diagram on'
|
|
16
|
+
end
|
|
17
|
+
TestViolation.new(
|
|
18
|
+
message: 'Slice dependency violation',
|
|
19
|
+
details: "Slice '#{violation.source_slice}' #{relationship} " \
|
|
20
|
+
"'#{violation.target_slice}'."
|
|
21
|
+
)
|
|
22
|
+
end
|
|
23
|
+
end
|
|
24
|
+
end
|
|
25
|
+
end
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module ArchUnit
|
|
4
|
+
# Violation presentation and test-framework integration.
|
|
5
|
+
module Testing
|
|
6
|
+
# Immutable framework-neutral pass flag and formatted message.
|
|
7
|
+
TestResult = Data.define(:passed, :message) do
|
|
8
|
+
def initialize(passed:, message:)
|
|
9
|
+
raise ArgumentError, 'passed must be true or false' unless [true, false].include?(passed)
|
|
10
|
+
unless message.is_a?(String) && !message.empty?
|
|
11
|
+
raise ArgumentError, 'message must be a non-empty String'
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
super(passed:, message: message.dup.freeze)
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
def passed?
|
|
18
|
+
passed
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
def failed?
|
|
22
|
+
!passed
|
|
23
|
+
end
|
|
24
|
+
end
|
|
25
|
+
end
|
|
26
|
+
end
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module ArchUnit
|
|
4
|
+
# Violation presentation and test-framework integration.
|
|
5
|
+
module Testing
|
|
6
|
+
# Immutable human-readable representation of one structured violation.
|
|
7
|
+
TestViolation = Data.define(:message, :details) do
|
|
8
|
+
def initialize(message:, details:)
|
|
9
|
+
message = non_empty_string(message, :message)
|
|
10
|
+
details = non_empty_string(details, :details)
|
|
11
|
+
super
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
private
|
|
15
|
+
|
|
16
|
+
def non_empty_string(value, attribute)
|
|
17
|
+
return value.dup.freeze if value.is_a?(String) && !value.strip.empty?
|
|
18
|
+
|
|
19
|
+
raise ArgumentError, "#{attribute} must be a non-empty String"
|
|
20
|
+
end
|
|
21
|
+
end
|
|
22
|
+
end
|
|
23
|
+
end
|
|
@@ -0,0 +1,138 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative '../common/assertion/empty_test_violation'
|
|
4
|
+
require_relative '../common/assertion/violation'
|
|
5
|
+
require_relative '../files/assertion/custom_file_condition'
|
|
6
|
+
require_relative '../files/assertion/cycle_free'
|
|
7
|
+
require_relative '../files/assertion/depend_on_external_modules'
|
|
8
|
+
require_relative '../files/assertion/depend_on_files'
|
|
9
|
+
require_relative '../files/assertion/matching_files'
|
|
10
|
+
require_relative '../layers/assertion/layer_dependency_violation'
|
|
11
|
+
require_relative '../metrics/assertion/custom_metric'
|
|
12
|
+
require_relative '../metrics/assertion/metric_predicate'
|
|
13
|
+
require_relative '../metrics/assertion/metric_threshold'
|
|
14
|
+
require_relative '../metrics/assertion/metric_zone'
|
|
15
|
+
require_relative '../slices/assertion/slice_dependency_violation'
|
|
16
|
+
require_relative 'layer_violation_formatter'
|
|
17
|
+
require_relative 'metric_violation_formatter'
|
|
18
|
+
require_relative 'slice_violation_formatter'
|
|
19
|
+
require_relative 'test_violation'
|
|
20
|
+
|
|
21
|
+
module ArchUnit
|
|
22
|
+
# Violation presentation and test-framework integration.
|
|
23
|
+
module Testing
|
|
24
|
+
# The sole mapping from structured violation data to human-readable prose.
|
|
25
|
+
class ViolationFactory
|
|
26
|
+
extend LayerViolationFormatter
|
|
27
|
+
extend MetricViolationFormatter
|
|
28
|
+
extend SliceViolationFormatter
|
|
29
|
+
|
|
30
|
+
FORMATTERS = {
|
|
31
|
+
Common::Assertion::EmptyTestViolation => :empty_test,
|
|
32
|
+
Files::Assertion::FilePatternViolation => :file_pattern,
|
|
33
|
+
Files::Assertion::FileDependencyViolation => :file_dependency,
|
|
34
|
+
Files::Assertion::ExternalModuleDependencyViolation => :external_module_dependency,
|
|
35
|
+
Files::Assertion::CycleViolation => :cycle,
|
|
36
|
+
Files::Assertion::CustomFileViolation => :custom_file,
|
|
37
|
+
Layers::Assertion::LayerDependencyViolation => :layer_dependency,
|
|
38
|
+
Slices::Assertion::SliceDependencyViolation => :slice_dependency
|
|
39
|
+
}.merge(MetricViolationFormatter::FORMATTERS).freeze
|
|
40
|
+
|
|
41
|
+
class << self
|
|
42
|
+
def from_violation(violation)
|
|
43
|
+
unless violation.is_a?(Common::Assertion::Violation)
|
|
44
|
+
raise ArgumentError, 'violation must be a Violation'
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
formatter = FORMATTERS.find { |type, _method| violation.is_a?(type) }&.last
|
|
48
|
+
formatter ? send(formatter, violation) : generic(violation)
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
private
|
|
52
|
+
|
|
53
|
+
def empty_test(violation)
|
|
54
|
+
selectors = violation.filters.map { |filter| describe_filter(filter) }
|
|
55
|
+
details = if selectors.empty?
|
|
56
|
+
'The unfiltered rule scope contained no files.'
|
|
57
|
+
else
|
|
58
|
+
"No files matched: #{selectors.join(' AND ')}."
|
|
59
|
+
end
|
|
60
|
+
TestViolation.new(message: 'No files matched the rule scope', details:)
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
def file_pattern(violation)
|
|
64
|
+
relationship = violation.negated? ? 'matches forbidden' : 'does not match required'
|
|
65
|
+
TestViolation.new(
|
|
66
|
+
message: 'File pattern violation',
|
|
67
|
+
details: "File '#{violation.projected_node.label}' #{relationship} " \
|
|
68
|
+
"#{describe_filter(violation.check_filter)}."
|
|
69
|
+
)
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
def file_dependency(violation)
|
|
73
|
+
edge = violation.dependency
|
|
74
|
+
relationship = if violation.negated?
|
|
75
|
+
'depends on forbidden file'
|
|
76
|
+
else
|
|
77
|
+
'depends on file outside the allowed target set'
|
|
78
|
+
end
|
|
79
|
+
TestViolation.new(
|
|
80
|
+
message: 'File dependency violation',
|
|
81
|
+
details: "File '#{edge.source_label}' #{relationship} '#{edge.target_label}'."
|
|
82
|
+
)
|
|
83
|
+
end
|
|
84
|
+
|
|
85
|
+
def external_module_dependency(violation)
|
|
86
|
+
edge = violation.dependency
|
|
87
|
+
relationship = if violation.negated?
|
|
88
|
+
'depends on forbidden external module'
|
|
89
|
+
else
|
|
90
|
+
'depends on external module outside the allowlist'
|
|
91
|
+
end
|
|
92
|
+
TestViolation.new(
|
|
93
|
+
message: 'External module dependency violation',
|
|
94
|
+
details: "File '#{edge.source_label}' #{relationship} '#{edge.target_label}'."
|
|
95
|
+
)
|
|
96
|
+
end
|
|
97
|
+
|
|
98
|
+
def cycle(violation)
|
|
99
|
+
TestViolation.new(
|
|
100
|
+
message: 'Circular dependency detected',
|
|
101
|
+
details: "Cycle: #{violation.path.join(' -> ')}."
|
|
102
|
+
)
|
|
103
|
+
end
|
|
104
|
+
|
|
105
|
+
def custom_file(violation)
|
|
106
|
+
relationship = if violation.negated?
|
|
107
|
+
'matched the forbidden predicate'
|
|
108
|
+
else
|
|
109
|
+
'failed the predicate'
|
|
110
|
+
end
|
|
111
|
+
TestViolation.new(
|
|
112
|
+
message: violation.message,
|
|
113
|
+
details: "File '#{violation.file_info.path}' #{relationship}."
|
|
114
|
+
)
|
|
115
|
+
end
|
|
116
|
+
|
|
117
|
+
def generic(violation)
|
|
118
|
+
TestViolation.new(
|
|
119
|
+
message: 'Architecture violation',
|
|
120
|
+
details: "Unformatted violation type: #{violation.class.name}."
|
|
121
|
+
)
|
|
122
|
+
end
|
|
123
|
+
|
|
124
|
+
def describe_filter(filter)
|
|
125
|
+
target = {
|
|
126
|
+
filename: 'filename',
|
|
127
|
+
path_without_filename: 'folder',
|
|
128
|
+
path: 'path',
|
|
129
|
+
classname: 'class name'
|
|
130
|
+
}.fetch(filter.target)
|
|
131
|
+
"#{target} pattern /#{filter.regexp.source}/ (#{filter.matching})"
|
|
132
|
+
end
|
|
133
|
+
end
|
|
134
|
+
|
|
135
|
+
private_class_method :new
|
|
136
|
+
end
|
|
137
|
+
end
|
|
138
|
+
end
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative 'testing/color_utils'
|
|
4
|
+
require_relative 'testing/test_violation'
|
|
5
|
+
require_relative 'testing/test_result'
|
|
6
|
+
require_relative 'testing/violation_factory'
|
|
7
|
+
require_relative 'testing/result_factory'
|
|
8
|
+
require_relative 'testing/assertion_failure'
|
|
9
|
+
require_relative 'testing/assert_passes'
|
|
10
|
+
|
|
11
|
+
# Public violation formatting entry points.
|
|
12
|
+
module ArchUnit
|
|
13
|
+
# Violation presentation and test-framework integration.
|
|
14
|
+
module Testing
|
|
15
|
+
module_function
|
|
16
|
+
|
|
17
|
+
def format_violations(violations, color: nil)
|
|
18
|
+
ResultFactory.from_violations(violations, color:).message
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
def result_for(rule, options = nil, expected_to_pass: true)
|
|
22
|
+
unless rule.is_a?(Common::FluentApi::Checkable)
|
|
23
|
+
raise ArgumentError, 'rule must implement Checkable'
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
violations = rule.check(options)
|
|
27
|
+
return ResultFactory.from_violations(violations) if expected_to_pass
|
|
28
|
+
|
|
29
|
+
ResultFactory.from_violations(violations, expected_to_pass: false)
|
|
30
|
+
end
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
def self.format_violations(violations, color: nil)
|
|
34
|
+
Testing.format_violations(violations, color:)
|
|
35
|
+
end
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
require_relative 'testing/rspec_adapter'
|
|
39
|
+
require_relative 'testing/minitest_adapter'
|
data/lib/archunit.rb
ADDED
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative 'archunit/version'
|
|
4
|
+
require_relative 'archunit/error/technical_error'
|
|
5
|
+
require_relative 'archunit/error/user_error'
|
|
6
|
+
require_relative 'archunit/common/pattern'
|
|
7
|
+
require_relative 'archunit/common/filter'
|
|
8
|
+
require_relative 'archunit/common/pattern_matching'
|
|
9
|
+
require_relative 'archunit/common/regex_factory'
|
|
10
|
+
require_relative 'archunit/common/logging/logging_options'
|
|
11
|
+
require_relative 'archunit/common/logging/check_logger'
|
|
12
|
+
require_relative 'archunit/common/assertion/violation'
|
|
13
|
+
require_relative 'archunit/common/assertion/empty_test_violation'
|
|
14
|
+
require_relative 'archunit/common/fluentapi/check_options'
|
|
15
|
+
require_relative 'archunit/common/fluentapi/checkable'
|
|
16
|
+
require_relative 'archunit/common/extraction/import_kind'
|
|
17
|
+
require_relative 'archunit/common/extraction/edge'
|
|
18
|
+
require_relative 'archunit/common/extraction/graph'
|
|
19
|
+
require_relative 'archunit/common/projection/mapped_edge'
|
|
20
|
+
require_relative 'archunit/common/projection/projected_edge'
|
|
21
|
+
require_relative 'archunit/common/projection/projected_node'
|
|
22
|
+
require_relative 'archunit/common/projection/edge_projections'
|
|
23
|
+
require_relative 'archunit/common/projection/project_edges'
|
|
24
|
+
require_relative 'archunit/common/projection/project_to_nodes'
|
|
25
|
+
require_relative 'archunit/common/projection/project_cycles'
|
|
26
|
+
require_relative 'archunit/extraction/locate_project'
|
|
27
|
+
require_relative 'archunit/extraction/enumerate_source_files'
|
|
28
|
+
require_relative 'archunit/extraction/resolved_import'
|
|
29
|
+
require_relative 'archunit/extraction/resolve_import'
|
|
30
|
+
require_relative 'archunit/extraction/extract_imports'
|
|
31
|
+
require_relative 'archunit/extraction/extract_dependencies'
|
|
32
|
+
require_relative 'archunit/extraction/extract_graph'
|
|
33
|
+
require_relative 'archunit/files/fluentapi/files'
|
|
34
|
+
require_relative 'archunit/layers/fluentapi/layers'
|
|
35
|
+
require_relative 'archunit/slices/fluentapi/slices'
|
|
36
|
+
require_relative 'archunit/graph/fluentapi/graph'
|
|
37
|
+
require_relative 'archunit/metrics/fluentapi/metrics'
|
|
38
|
+
require_relative 'archunit/testing'
|
|
39
|
+
|
|
40
|
+
# Public ArchUnitRuby API and shared data types.
|
|
41
|
+
module ArchUnit
|
|
42
|
+
Filter = Common::Filter
|
|
43
|
+
PatternMatching = Common::PatternMatching
|
|
44
|
+
RegexFactory = Common::RegexFactory
|
|
45
|
+
LoggingOptions = Common::Logging::LoggingOptions
|
|
46
|
+
CheckLogger = Common::Logging::CheckLogger
|
|
47
|
+
Violation = Common::Assertion::Violation
|
|
48
|
+
EmptyTestViolation = Common::Assertion::EmptyTestViolation
|
|
49
|
+
CheckOptions = Common::FluentApi::CheckOptions
|
|
50
|
+
Checkable = Common::FluentApi::Checkable
|
|
51
|
+
ImportKind = Common::Extraction::ImportKind
|
|
52
|
+
Edge = Common::Extraction::Edge
|
|
53
|
+
Graph = Common::Extraction::Graph
|
|
54
|
+
MappedEdge = Common::Projection::MappedEdge
|
|
55
|
+
ProjectedEdge = Common::Projection::ProjectedEdge
|
|
56
|
+
ProjectedNode = Common::Projection::ProjectedNode
|
|
57
|
+
CycleViolation = Files::Assertion::CycleViolation
|
|
58
|
+
FilePatternViolation = Files::Assertion::FilePatternViolation
|
|
59
|
+
FileDependencyViolation = Files::Assertion::FileDependencyViolation
|
|
60
|
+
ExternalModuleDependencyViolation = Files::Assertion::ExternalModuleDependencyViolation
|
|
61
|
+
FileInfo = Files::Extraction::FileInfo
|
|
62
|
+
CustomFileViolation = Files::Assertion::CustomFileViolation
|
|
63
|
+
LayerDefinition = Layers::Assertion::LayerDefinition
|
|
64
|
+
LayerDependencyViolation = Layers::Assertion::LayerDependencyViolation
|
|
65
|
+
SliceProjection = Slices::Projection::SliceProjection
|
|
66
|
+
SliceProjections = Slices::Projection
|
|
67
|
+
SliceDependencyViolation = Slices::Assertion::SliceDependencyViolation
|
|
68
|
+
DiagramAdherenceOptions = Slices::Assertion::DiagramAdherenceOptions
|
|
69
|
+
PlantUmlDependency = Slices::Uml::PlantUmlDependency
|
|
70
|
+
PlantUmlDiagram = Slices::Uml::PlantUmlDiagram
|
|
71
|
+
PlantUmlParser = Slices::Uml::PlantUmlParser
|
|
72
|
+
PlantUmlRenderer = Slices::Uml::PlantUmlRenderer
|
|
73
|
+
FolderDepthCollapse = GraphReporting::Projection::FolderDepthCollapse
|
|
74
|
+
PatternCollapse = GraphReporting::Projection::PatternCollapse
|
|
75
|
+
GraphQueryOptions = GraphReporting::Projection::GraphQueryOptions
|
|
76
|
+
GraphReportNode = GraphReporting::Projection::GraphReportNode
|
|
77
|
+
GraphReportEdge = GraphReporting::Projection::GraphReportEdge
|
|
78
|
+
GraphReportSummary = GraphReporting::Projection::GraphReportSummary
|
|
79
|
+
GraphReportSnapshot = GraphReporting::Projection::GraphReportSnapshot
|
|
80
|
+
GraphSnapshotFactory = GraphReporting::Projection::SnapshotFactory
|
|
81
|
+
GraphRenderer = GraphReporting::Rendering::GraphRenderer
|
|
82
|
+
MetricMethodInfo = Metrics::Extraction::MethodInfo
|
|
83
|
+
MetricFieldInfo = Metrics::Extraction::FieldInfo
|
|
84
|
+
ClassInfo = Metrics::Extraction::ClassInfo
|
|
85
|
+
MetricFileInfo = Metrics::Extraction::FileInfo
|
|
86
|
+
MetricProjectInfo = Metrics::Extraction::ProjectInfo
|
|
87
|
+
DistanceInfo = Metrics::Extraction::DistanceInfo
|
|
88
|
+
Metric = Metrics::Calculation::Metric
|
|
89
|
+
CountMetrics = Metrics::Calculation::Count
|
|
90
|
+
LCOMMetrics = Metrics::Calculation::LCOM
|
|
91
|
+
DistanceMetrics = Metrics::Calculation::Distance
|
|
92
|
+
MetricMeasurement = Metrics::FluentApi::MetricMeasurement
|
|
93
|
+
MetricsExportOptions = Metrics::Reporting::MetricsExportOptions
|
|
94
|
+
MetricsExporter = Metrics::Reporting::MetricsExporter
|
|
95
|
+
MetricExtraction = Metrics::Extraction
|
|
96
|
+
MetricZoneViolation = Metrics::Assertion::MetricZoneViolation
|
|
97
|
+
MetricThresholdViolation = Metrics::Assertion::MetricThresholdViolation
|
|
98
|
+
MetricPredicateViolation = Metrics::Assertion::MetricPredicateViolation
|
|
99
|
+
CustomMetricViolation = Metrics::Assertion::CustomMetricViolation
|
|
100
|
+
TestViolation = Testing::TestViolation
|
|
101
|
+
TestResult = Testing::TestResult
|
|
102
|
+
ColorUtils = Testing::ColorUtils
|
|
103
|
+
ViolationFactory = Testing::ViolationFactory
|
|
104
|
+
ResultFactory = Testing::ResultFactory
|
|
105
|
+
AssertionFailure = Testing::AssertionFailure
|
|
106
|
+
|
|
107
|
+
def self.clear_graph_cache
|
|
108
|
+
Extraction.clear_graph_cache
|
|
109
|
+
end
|
|
110
|
+
end
|