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.
Files changed (149) hide show
  1. checksums.yaml +7 -0
  2. data/CHANGELOG.md +13 -0
  3. data/LICENSE +7 -0
  4. data/README.md +375 -0
  5. data/lib/archunit/common/assertion/empty_test_violation.rb +50 -0
  6. data/lib/archunit/common/assertion/violation.rb +14 -0
  7. data/lib/archunit/common/extraction/edge.rb +46 -0
  8. data/lib/archunit/common/extraction/graph.rb +73 -0
  9. data/lib/archunit/common/extraction/import_kind.rb +21 -0
  10. data/lib/archunit/common/filter.rb +46 -0
  11. data/lib/archunit/common/fluentapi/check_options.rb +48 -0
  12. data/lib/archunit/common/fluentapi/checkable.rb +70 -0
  13. data/lib/archunit/common/logging/check_logger.rb +124 -0
  14. data/lib/archunit/common/logging/logging_options.rb +57 -0
  15. data/lib/archunit/common/pattern.rb +99 -0
  16. data/lib/archunit/common/pattern_matching.rb +76 -0
  17. data/lib/archunit/common/projection/cycles/johnson_cycles.rb +103 -0
  18. data/lib/archunit/common/projection/cycles/tarjan_scc.rb +82 -0
  19. data/lib/archunit/common/projection/edge_projections.rb +50 -0
  20. data/lib/archunit/common/projection/mapped_edge.rb +26 -0
  21. data/lib/archunit/common/projection/project_cycles.rb +92 -0
  22. data/lib/archunit/common/projection/project_edges.rb +69 -0
  23. data/lib/archunit/common/projection/project_to_nodes.rb +59 -0
  24. data/lib/archunit/common/projection/projected_edge.rb +35 -0
  25. data/lib/archunit/common/projection/projected_node.rb +36 -0
  26. data/lib/archunit/common/regex_factory.rb +96 -0
  27. data/lib/archunit/error/technical_error.rb +6 -0
  28. data/lib/archunit/error/user_error.rb +6 -0
  29. data/lib/archunit/extraction/enumerate_source_files.rb +131 -0
  30. data/lib/archunit/extraction/extract_dependencies.rb +61 -0
  31. data/lib/archunit/extraction/extract_graph.rb +89 -0
  32. data/lib/archunit/extraction/extract_imports.rb +197 -0
  33. data/lib/archunit/extraction/locate_project.rb +80 -0
  34. data/lib/archunit/extraction/resolve_import.rb +76 -0
  35. data/lib/archunit/extraction/resolved_import.rb +46 -0
  36. data/lib/archunit/files/assertion/custom_file_condition.rb +89 -0
  37. data/lib/archunit/files/assertion/cycle_free.rb +58 -0
  38. data/lib/archunit/files/assertion/depend_on_external_modules.rb +116 -0
  39. data/lib/archunit/files/assertion/depend_on_files.rb +110 -0
  40. data/lib/archunit/files/assertion/matching_files.rb +87 -0
  41. data/lib/archunit/files/extraction/extract_file_info.rb +84 -0
  42. data/lib/archunit/files/extraction/file_info.rb +46 -0
  43. data/lib/archunit/files/fluentapi/custom_file_condition.rb +73 -0
  44. data/lib/archunit/files/fluentapi/cycle_free_file_condition.rb +55 -0
  45. data/lib/archunit/files/fluentapi/depend_on_external_module_condition.rb +69 -0
  46. data/lib/archunit/files/fluentapi/depend_on_external_module_condition_builder.rb +36 -0
  47. data/lib/archunit/files/fluentapi/depend_on_file_condition.rb +80 -0
  48. data/lib/archunit/files/fluentapi/depend_on_file_condition_builder.rb +49 -0
  49. data/lib/archunit/files/fluentapi/file_condition_builder.rb +71 -0
  50. data/lib/archunit/files/fluentapi/file_rule_support.rb +24 -0
  51. data/lib/archunit/files/fluentapi/files.rb +31 -0
  52. data/lib/archunit/files/fluentapi/match_pattern_file_condition.rb +59 -0
  53. data/lib/archunit/files/fluentapi/match_pattern_file_condition_builder.rb +66 -0
  54. data/lib/archunit/files/fluentapi/negated_match_pattern_file_condition_builder.rb +16 -0
  55. data/lib/archunit/files/fluentapi/positive_match_pattern_file_condition_builder.rb +21 -0
  56. data/lib/archunit/graph/fluentapi/graph.rb +29 -0
  57. data/lib/archunit/graph/fluentapi/project_graph_builder.rb +106 -0
  58. data/lib/archunit/graph/projection/aggregate_edges.rb +48 -0
  59. data/lib/archunit/graph/projection/collapse_node.rb +33 -0
  60. data/lib/archunit/graph/projection/create_snapshot.rb +84 -0
  61. data/lib/archunit/graph/projection/folder_depth_collapse.rb +18 -0
  62. data/lib/archunit/graph/projection/graph_query_options.rb +81 -0
  63. data/lib/archunit/graph/projection/graph_report_edge.rb +50 -0
  64. data/lib/archunit/graph/projection/graph_report_node.rb +22 -0
  65. data/lib/archunit/graph/projection/graph_report_snapshot.rb +43 -0
  66. data/lib/archunit/graph/projection/graph_report_summary.rb +20 -0
  67. data/lib/archunit/graph/projection/node_selection.rb +111 -0
  68. data/lib/archunit/graph/projection/pattern_collapse.rb +24 -0
  69. data/lib/archunit/graph/rendering/csv_renderer.rb +30 -0
  70. data/lib/archunit/graph/rendering/d2_renderer.rb +30 -0
  71. data/lib/archunit/graph/rendering/dot_renderer.rb +51 -0
  72. data/lib/archunit/graph/rendering/escaping.rb +31 -0
  73. data/lib/archunit/graph/rendering/export_report.rb +29 -0
  74. data/lib/archunit/graph/rendering/graph_renderer.rb +54 -0
  75. data/lib/archunit/graph/rendering/html_document.rb +46 -0
  76. data/lib/archunit/graph/rendering/html_renderer.rb +102 -0
  77. data/lib/archunit/graph/rendering/json_renderer.rb +38 -0
  78. data/lib/archunit/graph/rendering/mermaid_renderer.rb +33 -0
  79. data/lib/archunit/graph/rendering/rendering_support.rb +24 -0
  80. data/lib/archunit/layers/assertion/layer_definition.rb +56 -0
  81. data/lib/archunit/layers/assertion/layer_dependencies.rb +91 -0
  82. data/lib/archunit/layers/assertion/layer_dependency_violation.rb +59 -0
  83. data/lib/archunit/layers/fluentapi/layer_definition_builder.rb +46 -0
  84. data/lib/archunit/layers/fluentapi/layer_dependency_rule_builder.rb +44 -0
  85. data/lib/archunit/layers/fluentapi/layered_architecture.rb +133 -0
  86. data/lib/archunit/layers/fluentapi/layered_architecture_values.rb +51 -0
  87. data/lib/archunit/layers/fluentapi/layers.rb +29 -0
  88. data/lib/archunit/metrics/assertion/custom_metric.rb +73 -0
  89. data/lib/archunit/metrics/assertion/metric_predicate.rb +66 -0
  90. data/lib/archunit/metrics/assertion/metric_threshold.rb +99 -0
  91. data/lib/archunit/metrics/assertion/metric_zone.rb +57 -0
  92. data/lib/archunit/metrics/calculation/count.rb +45 -0
  93. data/lib/archunit/metrics/calculation/distance.rb +84 -0
  94. data/lib/archunit/metrics/calculation/lcom.rb +103 -0
  95. data/lib/archunit/metrics/calculation/metric.rb +52 -0
  96. data/lib/archunit/metrics/calculation/numeric_value.rb +25 -0
  97. data/lib/archunit/metrics/extraction/distance_info.rb +60 -0
  98. data/lib/archunit/metrics/extraction/extract_distance_info.rb +50 -0
  99. data/lib/archunit/metrics/extraction/extract_project_info.rb +90 -0
  100. data/lib/archunit/metrics/extraction/metric_info.rb +159 -0
  101. data/lib/archunit/metrics/extraction/source_metrics_visitor.rb +278 -0
  102. data/lib/archunit/metrics/fluentapi/count_metrics_builder.rb +49 -0
  103. data/lib/archunit/metrics/fluentapi/custom_metric_builder.rb +30 -0
  104. data/lib/archunit/metrics/fluentapi/custom_metric_condition.rb +40 -0
  105. data/lib/archunit/metrics/fluentapi/distance_metrics_builder.rb +48 -0
  106. data/lib/archunit/metrics/fluentapi/lcom_metrics_builder.rb +39 -0
  107. data/lib/archunit/metrics/fluentapi/metric_measurement.rb +36 -0
  108. data/lib/archunit/metrics/fluentapi/metric_predicate_condition.rb +42 -0
  109. data/lib/archunit/metrics/fluentapi/metric_report_builder.rb +34 -0
  110. data/lib/archunit/metrics/fluentapi/metric_selection.rb +54 -0
  111. data/lib/archunit/metrics/fluentapi/metric_threshold_condition.rb +47 -0
  112. data/lib/archunit/metrics/fluentapi/metrics.rb +21 -0
  113. data/lib/archunit/metrics/fluentapi/metrics_builder.rb +136 -0
  114. data/lib/archunit/metrics/fluentapi/zone_condition.rb +40 -0
  115. data/lib/archunit/metrics/reporting/metrics_export_options.rb +57 -0
  116. data/lib/archunit/metrics/reporting/metrics_exporter.rb +130 -0
  117. data/lib/archunit/slices/assertion/adhere_to_diagram.rb +57 -0
  118. data/lib/archunit/slices/assertion/contain_dependency.rb +42 -0
  119. data/lib/archunit/slices/assertion/diagram_adherence_options.rb +37 -0
  120. data/lib/archunit/slices/assertion/slice_dependency_violation.rb +70 -0
  121. data/lib/archunit/slices/fluentapi/diagram_slice_condition.rb +65 -0
  122. data/lib/archunit/slices/fluentapi/diagram_source.rb +36 -0
  123. data/lib/archunit/slices/fluentapi/forbidden_slice_dependency_condition.rb +57 -0
  124. data/lib/archunit/slices/fluentapi/negative_slice_condition_builder.rb +27 -0
  125. data/lib/archunit/slices/fluentapi/positive_slice_condition_builder.rb +51 -0
  126. data/lib/archunit/slices/fluentapi/slice_scope_builder.rb +83 -0
  127. data/lib/archunit/slices/fluentapi/slices.rb +29 -0
  128. data/lib/archunit/slices/projection/slice_projection.rb +76 -0
  129. data/lib/archunit/slices/projection/slicing_projections.rb +98 -0
  130. data/lib/archunit/slices/uml/export_diagram.rb +70 -0
  131. data/lib/archunit/slices/uml/parse_diagram.rb +53 -0
  132. data/lib/archunit/slices/uml/plant_uml_dependency.rb +25 -0
  133. data/lib/archunit/slices/uml/plant_uml_diagram.rb +44 -0
  134. data/lib/archunit/testing/assert_passes.rb +24 -0
  135. data/lib/archunit/testing/assertion_failure.rb +22 -0
  136. data/lib/archunit/testing/color_utils.rb +66 -0
  137. data/lib/archunit/testing/layer_violation_formatter.rb +26 -0
  138. data/lib/archunit/testing/metric_violation_formatter.rb +58 -0
  139. data/lib/archunit/testing/minitest_adapter.rb +33 -0
  140. data/lib/archunit/testing/result_factory.rb +93 -0
  141. data/lib/archunit/testing/rspec_adapter.rb +74 -0
  142. data/lib/archunit/testing/slice_violation_formatter.rb +25 -0
  143. data/lib/archunit/testing/test_result.rb +26 -0
  144. data/lib/archunit/testing/test_violation.rb +23 -0
  145. data/lib/archunit/testing/violation_factory.rb +138 -0
  146. data/lib/archunit/testing.rb +39 -0
  147. data/lib/archunit/version.rb +5 -0
  148. data/lib/archunit.rb +110 -0
  149. 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'
@@ -0,0 +1,5 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ArchUnit
4
+ VERSION = '0.0.1'
5
+ end
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