rubyzen-lint 0.1.0 → 0.3.0
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/CHANGELOG.md +48 -0
- data/README.md +57 -9
- data/lib/rubyzen/assertions/assert_zen_empty.rb +51 -0
- data/lib/rubyzen/assertions/assert_zen_false.rb +49 -0
- data/lib/rubyzen/assertions/assert_zen_true.rb +49 -0
- data/lib/rubyzen/assertions/zen_assertions.rb +29 -0
- data/lib/rubyzen/cache/parse_cache.rb +1 -0
- data/lib/rubyzen/collections/arguments_collection.rb +16 -0
- data/lib/rubyzen/collections/assignments_collection.rb +11 -0
- data/lib/rubyzen/collections/base_collection.rb +1 -0
- data/lib/rubyzen/collections/blocks_collection.rb +21 -0
- data/lib/rubyzen/collections/expressions_collection.rb +27 -0
- data/lib/rubyzen/collections/methods_collection.rb +29 -0
- data/lib/rubyzen/collections/returns_collection.rb +20 -0
- data/lib/rubyzen/core.rb +114 -0
- data/lib/rubyzen/declarations/assignment_declaration.rb +57 -0
- data/lib/rubyzen/declarations/block_declaration.rb +2 -0
- data/lib/rubyzen/declarations/call_site_declaration.rb +14 -0
- data/lib/rubyzen/declarations/expression_declaration.rb +99 -0
- data/lib/rubyzen/declarations/file_declaration.rb +1 -0
- data/lib/rubyzen/declarations/method_declaration.rb +2 -0
- data/lib/rubyzen/declarations/return_declaration.rb +51 -0
- data/lib/rubyzen/expectation_helpers.rb +184 -0
- data/lib/rubyzen/matchers/zen_empty_matcher.rb +22 -11
- data/lib/rubyzen/matchers/zen_false_matcher.rb +22 -14
- data/lib/rubyzen/matchers/zen_true_matcher.rb +19 -8
- data/lib/rubyzen/minitest.rb +33 -0
- data/lib/rubyzen/parsers/a_s_t_parser.rb +1 -0
- data/lib/rubyzen/providers/arguments_provider.rb +15 -0
- data/lib/rubyzen/providers/assignments_provider.rb +15 -0
- data/lib/rubyzen/providers/blocks_provider.rb +2 -1
- data/lib/rubyzen/providers/enclosing_blocks_provider.rb +16 -0
- data/lib/rubyzen/providers/returns_provider.rb +49 -0
- data/lib/rubyzen/rspec.rb +29 -0
- data/lib/rubyzen/version.rb +2 -1
- data/lib/rubyzen.rb +8 -95
- data/rubyzen-lint.gemspec +9 -4
- metadata +65 -10
- data/lib/rubyzen/matchers/matcher_helpers.rb +0 -176
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
module Rubyzen
|
|
2
|
+
module Providers
|
|
3
|
+
# Provides the points at which a method or block yields a value: the implicit final
|
|
4
|
+
# expression of its body, plus every explicit +return+. Each is wrapped in a
|
|
5
|
+
# {Rubyzen::Declarations::ReturnDeclaration}, which knows how to extract its own value.
|
|
6
|
+
module ReturnsProvider
|
|
7
|
+
# @return [Rubyzen::Collections::ReturnsCollection]
|
|
8
|
+
def returns
|
|
9
|
+
Collections::ReturnsCollection.new(
|
|
10
|
+
return_nodes.map do |return_node|
|
|
11
|
+
Declarations::ReturnDeclaration.new(return_node, self)
|
|
12
|
+
end
|
|
13
|
+
)
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
# The value-expression(s) this method or block evaluates to, as a flat collection.
|
|
17
|
+
# A shortcut for +returns.expressions+.
|
|
18
|
+
#
|
|
19
|
+
# @return [Rubyzen::Collections::ExpressionsCollection]
|
|
20
|
+
def return_expressions
|
|
21
|
+
returns.expressions
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
private
|
|
25
|
+
|
|
26
|
+
# The return points to wrap: the implicit final expression of the body (unless it is
|
|
27
|
+
# itself an explicit +return+, which is collected separately to avoid double-counting),
|
|
28
|
+
# followed by every explicit +return+.
|
|
29
|
+
def return_nodes
|
|
30
|
+
nodes = []
|
|
31
|
+
final = implicit_final_node
|
|
32
|
+
nodes << final unless final.nil?
|
|
33
|
+
node.each_descendant(:return) { |return_node| nodes << return_node }
|
|
34
|
+
nodes
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
# The node a body implicitly evaluates to: the last statement of a multi-statement
|
|
38
|
+
# body (+begin+) or an explicit +begin..end+ (+kwbegin+), otherwise the body itself.
|
|
39
|
+
# Returns +nil+ when that node is an explicit +return+ (collected separately).
|
|
40
|
+
def implicit_final_node
|
|
41
|
+
body = node.body
|
|
42
|
+
return nil if body.nil?
|
|
43
|
+
|
|
44
|
+
final = body.begin_type? || body.kwbegin_type? ? body.children.last : body
|
|
45
|
+
final unless final.return_type?
|
|
46
|
+
end
|
|
47
|
+
end
|
|
48
|
+
end
|
|
49
|
+
end
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
# RSpec entry point for Rubyzen.
|
|
2
|
+
#
|
|
3
|
+
# Loads the framework-agnostic core plus the RSpec matchers.
|
|
4
|
+
# Require it from your spec/spec_helper.rb:
|
|
5
|
+
#
|
|
6
|
+
# # Gemfile
|
|
7
|
+
# group :test do
|
|
8
|
+
# gem 'rubyzen-lint'
|
|
9
|
+
# gem 'rspec' # or rspec-rails
|
|
10
|
+
# end
|
|
11
|
+
#
|
|
12
|
+
# # spec/spec_helper.rb
|
|
13
|
+
# require 'rubyzen/rspec'
|
|
14
|
+
#
|
|
15
|
+
# Every RSpec project should already have the `rspec` gem.
|
|
16
|
+
# If it is missing we raise an error.
|
|
17
|
+
require_relative 'core'
|
|
18
|
+
|
|
19
|
+
begin
|
|
20
|
+
require 'rspec'
|
|
21
|
+
rescue LoadError
|
|
22
|
+
raise LoadError, "Rubyzen's RSpec matchers require the 'rspec' gem. " \
|
|
23
|
+
"Add `gem 'rspec'` to your Gemfile, or use the Minitest assertions via `require 'rubyzen/minitest'`."
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
require_relative 'expectation_helpers'
|
|
27
|
+
require_relative 'matchers/zen_empty_matcher'
|
|
28
|
+
require_relative 'matchers/zen_true_matcher'
|
|
29
|
+
require_relative 'matchers/zen_false_matcher'
|
data/lib/rubyzen/version.rb
CHANGED
data/lib/rubyzen.rb
CHANGED
|
@@ -1,98 +1,11 @@
|
|
|
1
|
-
|
|
2
|
-
require 'rspec'
|
|
3
|
-
require 'zeitwerk'
|
|
4
|
-
|
|
5
|
-
loader = Zeitwerk::Loader.for_gem
|
|
6
|
-
loader.ignore("#{__dir__}/rubyzen/matchers")
|
|
7
|
-
loader.ignore("#{__dir__}/rubyzen/rspec")
|
|
8
|
-
loader.ignore("#{__dir__}/rubyzen/lint.rb")
|
|
9
|
-
loader.ignore("#{__dir__}/rubyzen-lint.rb")
|
|
10
|
-
loader.setup
|
|
11
|
-
|
|
12
|
-
require_relative 'rubyzen/matchers/matcher_helpers'
|
|
13
|
-
require_relative 'rubyzen/matchers/zen_empty_matcher'
|
|
14
|
-
require_relative 'rubyzen/matchers/zen_true_matcher'
|
|
15
|
-
require_relative 'rubyzen/matchers/zen_false_matcher'
|
|
16
|
-
|
|
17
|
-
# Rubyzen is a Ruby architectural linter that lets you write lint rules as RSpec tests.
|
|
18
|
-
# It wraps RuboCop AST to provide a high-level, easy-to-use API for enforcing architectural
|
|
19
|
-
# rules across a codebase.
|
|
1
|
+
# Rubyzen entry point — loads the framework-agnostic core only.
|
|
20
2
|
#
|
|
21
|
-
#
|
|
22
|
-
#
|
|
23
|
-
#
|
|
3
|
+
# `require 'rubyzen'` gives you the parsing/analysis API (Rubyzen::Project,
|
|
4
|
+
# declarations, collections) without any test framework attached. To write lint
|
|
5
|
+
# rules, require the adapter for your test framework instead:
|
|
24
6
|
#
|
|
25
|
-
# #
|
|
26
|
-
#
|
|
7
|
+
# require 'rubyzen/rspec' # RSpec matchers: zen_empty / zen_true / zen_false
|
|
8
|
+
# require 'rubyzen/minitest' # Minitest assertions: assert_zen_empty / _true / _false
|
|
27
9
|
#
|
|
28
|
-
#
|
|
29
|
-
|
|
30
|
-
#
|
|
31
|
-
module Rubyzen
|
|
32
|
-
# Base error class for all Rubyzen errors.
|
|
33
|
-
class Error < StandardError; end
|
|
34
|
-
|
|
35
|
-
# Raised when a Ruby file cannot be parsed.
|
|
36
|
-
class ParseError < Error; end
|
|
37
|
-
|
|
38
|
-
# Yields the global configuration for customization.
|
|
39
|
-
#
|
|
40
|
-
# @example
|
|
41
|
-
# Rubyzen.configure do |config|
|
|
42
|
-
# config.paths = ['app', 'lib']
|
|
43
|
-
# end
|
|
44
|
-
def self.configure
|
|
45
|
-
yield(configuration)
|
|
46
|
-
end
|
|
47
|
-
|
|
48
|
-
# Returns the global configuration instance.
|
|
49
|
-
#
|
|
50
|
-
# @return [Configuration]
|
|
51
|
-
def self.configuration
|
|
52
|
-
@configuration ||= Configuration.new
|
|
53
|
-
end
|
|
54
|
-
|
|
55
|
-
# Holds project path configuration with auto-discovery support.
|
|
56
|
-
#
|
|
57
|
-
# Resolution order:
|
|
58
|
-
# 1. Explicit paths via {#paths=} (set via +Rubyzen.configure+)
|
|
59
|
-
# 2. Auto-discovery of +app/+, +lib/+, +src/+, +spec/+ from +Dir.pwd+
|
|
60
|
-
#
|
|
61
|
-
# @example
|
|
62
|
-
# Rubyzen.configure { |c| c.paths = ['app/models', 'app/controllers'] }
|
|
63
|
-
# Rubyzen.configuration.project_paths #=> ["/full/path/app/models", "/full/path/app/controllers"]
|
|
64
|
-
#
|
|
65
|
-
class Configuration
|
|
66
|
-
# Sets explicit paths to scan.
|
|
67
|
-
# Relative paths are resolved against +Dir.pwd+.
|
|
68
|
-
#
|
|
69
|
-
# @param value [Array<String>] directories to analyze
|
|
70
|
-
attr_writer :paths
|
|
71
|
-
|
|
72
|
-
# Returns the resolved project paths.
|
|
73
|
-
#
|
|
74
|
-
# @return [Array<String>] absolute paths to directories to analyze
|
|
75
|
-
def project_paths
|
|
76
|
-
resolve_paths(@paths) || auto_discover_paths
|
|
77
|
-
end
|
|
78
|
-
|
|
79
|
-
private
|
|
80
|
-
|
|
81
|
-
def resolve_paths(paths)
|
|
82
|
-
return nil unless paths&.any?
|
|
83
|
-
|
|
84
|
-
root = Dir.pwd
|
|
85
|
-
paths.map do |path|
|
|
86
|
-
File.expand_path(path, root)
|
|
87
|
-
end
|
|
88
|
-
end
|
|
89
|
-
|
|
90
|
-
def auto_discover_paths
|
|
91
|
-
root = Dir.pwd
|
|
92
|
-
candidates = %w[app lib src spec].map { |d| File.join(root, d) }
|
|
93
|
-
paths = candidates.select { |d| Dir.exist?(d) }
|
|
94
|
-
paths = [root] if paths.empty?
|
|
95
|
-
paths
|
|
96
|
-
end
|
|
97
|
-
end
|
|
98
|
-
end
|
|
10
|
+
# Each adapter loads this core automatically
|
|
11
|
+
require_relative 'rubyzen/core'
|
data/rubyzen-lint.gemspec
CHANGED
|
@@ -4,7 +4,7 @@ Gem::Specification.new do |spec|
|
|
|
4
4
|
spec.name = 'rubyzen-lint'
|
|
5
5
|
spec.version = Rubyzen::VERSION
|
|
6
6
|
spec.authors = ['Perry Street Software']
|
|
7
|
-
spec.summary = 'Architectural linter for Ruby — write lint rules as
|
|
7
|
+
spec.summary = 'Architectural linter for Ruby — write lint rules as unit tests'
|
|
8
8
|
spec.description = 'Rubyzen is a modern linter for Ruby that allows you to write architectural ' \
|
|
9
9
|
'lint rules as unit tests. In the era of AI-generated code, it provides your ' \
|
|
10
10
|
'team with deterministic guardrails to keep your codebase clean, maintainable, ' \
|
|
@@ -14,15 +14,20 @@ Gem::Specification.new do |spec|
|
|
|
14
14
|
|
|
15
15
|
spec.required_ruby_version = '>= 3.1'
|
|
16
16
|
|
|
17
|
-
spec.files = Dir.glob(%w[lib/**/*.rb rubyzen-lint.gemspec LICENSE README.md])
|
|
17
|
+
spec.files = Dir.glob(%w[lib/**/*.rb rubyzen-lint.gemspec LICENSE README.md CHANGELOG.md])
|
|
18
18
|
spec.require_paths = ['lib']
|
|
19
19
|
|
|
20
20
|
spec.add_dependency 'rubocop-ast', '~> 1.26'
|
|
21
21
|
spec.add_dependency 'zeitwerk', '~> 2.6'
|
|
22
|
-
|
|
22
|
+
|
|
23
|
+
spec.add_development_dependency 'rspec', '~> 3.12'
|
|
24
|
+
spec.add_development_dependency 'minitest', '>= 5.0', '< 7.0'
|
|
25
|
+
spec.add_development_dependency 'rake', '~> 13.0'
|
|
23
26
|
|
|
24
27
|
spec.metadata = {
|
|
25
28
|
'source_code_uri' => 'https://github.com/perrystreetsoftware/Rubyzen',
|
|
26
|
-
'bug_tracker_uri' => 'https://github.com/perrystreetsoftware/Rubyzen/issues'
|
|
29
|
+
'bug_tracker_uri' => 'https://github.com/perrystreetsoftware/Rubyzen/issues',
|
|
30
|
+
'documentation_uri' => 'https://perrystreetsoftware.github.io/Rubyzen',
|
|
31
|
+
'changelog_uri' => 'https://github.com/perrystreetsoftware/Rubyzen/blob/main/CHANGELOG.md'
|
|
27
32
|
}
|
|
28
33
|
end
|
metadata
CHANGED
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: rubyzen-lint
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.
|
|
4
|
+
version: 0.3.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Perry Street Software
|
|
8
|
-
autorequire:
|
|
8
|
+
autorequire:
|
|
9
9
|
bindir: bin
|
|
10
10
|
cert_chain: []
|
|
11
|
-
date: 2026-
|
|
11
|
+
date: 2026-06-24 00:00:00.000000000 Z
|
|
12
12
|
dependencies:
|
|
13
13
|
- !ruby/object:Gem::Dependency
|
|
14
14
|
name: rubocop-ast
|
|
@@ -45,27 +45,68 @@ dependencies:
|
|
|
45
45
|
- - "~>"
|
|
46
46
|
- !ruby/object:Gem::Version
|
|
47
47
|
version: '3.12'
|
|
48
|
-
type: :
|
|
48
|
+
type: :development
|
|
49
49
|
prerelease: false
|
|
50
50
|
version_requirements: !ruby/object:Gem::Requirement
|
|
51
51
|
requirements:
|
|
52
52
|
- - "~>"
|
|
53
53
|
- !ruby/object:Gem::Version
|
|
54
54
|
version: '3.12'
|
|
55
|
+
- !ruby/object:Gem::Dependency
|
|
56
|
+
name: minitest
|
|
57
|
+
requirement: !ruby/object:Gem::Requirement
|
|
58
|
+
requirements:
|
|
59
|
+
- - ">="
|
|
60
|
+
- !ruby/object:Gem::Version
|
|
61
|
+
version: '5.0'
|
|
62
|
+
- - "<"
|
|
63
|
+
- !ruby/object:Gem::Version
|
|
64
|
+
version: '7.0'
|
|
65
|
+
type: :development
|
|
66
|
+
prerelease: false
|
|
67
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
68
|
+
requirements:
|
|
69
|
+
- - ">="
|
|
70
|
+
- !ruby/object:Gem::Version
|
|
71
|
+
version: '5.0'
|
|
72
|
+
- - "<"
|
|
73
|
+
- !ruby/object:Gem::Version
|
|
74
|
+
version: '7.0'
|
|
75
|
+
- !ruby/object:Gem::Dependency
|
|
76
|
+
name: rake
|
|
77
|
+
requirement: !ruby/object:Gem::Requirement
|
|
78
|
+
requirements:
|
|
79
|
+
- - "~>"
|
|
80
|
+
- !ruby/object:Gem::Version
|
|
81
|
+
version: '13.0'
|
|
82
|
+
type: :development
|
|
83
|
+
prerelease: false
|
|
84
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
85
|
+
requirements:
|
|
86
|
+
- - "~>"
|
|
87
|
+
- !ruby/object:Gem::Version
|
|
88
|
+
version: '13.0'
|
|
55
89
|
description: Rubyzen is a modern linter for Ruby that allows you to write architectural
|
|
56
90
|
lint rules as unit tests. In the era of AI-generated code, it provides your team
|
|
57
91
|
with deterministic guardrails to keep your codebase clean, maintainable, and consistent
|
|
58
92
|
as it grows.
|
|
59
|
-
email:
|
|
93
|
+
email:
|
|
60
94
|
executables: []
|
|
61
95
|
extensions: []
|
|
62
96
|
extra_rdoc_files: []
|
|
63
97
|
files:
|
|
98
|
+
- CHANGELOG.md
|
|
64
99
|
- LICENSE
|
|
65
100
|
- README.md
|
|
66
101
|
- lib/rubyzen-lint.rb
|
|
67
102
|
- lib/rubyzen.rb
|
|
103
|
+
- lib/rubyzen/assertions/assert_zen_empty.rb
|
|
104
|
+
- lib/rubyzen/assertions/assert_zen_false.rb
|
|
105
|
+
- lib/rubyzen/assertions/assert_zen_true.rb
|
|
106
|
+
- lib/rubyzen/assertions/zen_assertions.rb
|
|
68
107
|
- lib/rubyzen/cache/parse_cache.rb
|
|
108
|
+
- lib/rubyzen/collections/arguments_collection.rb
|
|
109
|
+
- lib/rubyzen/collections/assignments_collection.rb
|
|
69
110
|
- lib/rubyzen/collections/attributes_collection.rb
|
|
70
111
|
- lib/rubyzen/collections/base_collection.rb
|
|
71
112
|
- lib/rubyzen/collections/blocks_collection.rb
|
|
@@ -73,6 +114,7 @@ files:
|
|
|
73
114
|
- lib/rubyzen/collections/classes_collection.rb
|
|
74
115
|
- lib/rubyzen/collections/constants_collection.rb
|
|
75
116
|
- lib/rubyzen/collections/declaration_collection.rb
|
|
117
|
+
- lib/rubyzen/collections/expressions_collection.rb
|
|
76
118
|
- lib/rubyzen/collections/file_collection.rb
|
|
77
119
|
- lib/rubyzen/collections/macros_collection.rb
|
|
78
120
|
- lib/rubyzen/collections/methods_collection.rb
|
|
@@ -81,11 +123,15 @@ files:
|
|
|
81
123
|
- lib/rubyzen/collections/raises_collection.rb
|
|
82
124
|
- lib/rubyzen/collections/requires_collection.rb
|
|
83
125
|
- lib/rubyzen/collections/rescues_collection.rb
|
|
126
|
+
- lib/rubyzen/collections/returns_collection.rb
|
|
127
|
+
- lib/rubyzen/core.rb
|
|
128
|
+
- lib/rubyzen/declarations/assignment_declaration.rb
|
|
84
129
|
- lib/rubyzen/declarations/attribute_declaration.rb
|
|
85
130
|
- lib/rubyzen/declarations/block_declaration.rb
|
|
86
131
|
- lib/rubyzen/declarations/call_site_declaration.rb
|
|
87
132
|
- lib/rubyzen/declarations/class_declaration.rb
|
|
88
133
|
- lib/rubyzen/declarations/constant_declaration.rb
|
|
134
|
+
- lib/rubyzen/declarations/expression_declaration.rb
|
|
89
135
|
- lib/rubyzen/declarations/file_declaration.rb
|
|
90
136
|
- lib/rubyzen/declarations/if_statement_declaration.rb
|
|
91
137
|
- lib/rubyzen/declarations/macro_declaration.rb
|
|
@@ -95,19 +141,24 @@ files:
|
|
|
95
141
|
- lib/rubyzen/declarations/raise_declaration.rb
|
|
96
142
|
- lib/rubyzen/declarations/require_declaration.rb
|
|
97
143
|
- lib/rubyzen/declarations/rescue_declaration.rb
|
|
144
|
+
- lib/rubyzen/declarations/return_declaration.rb
|
|
145
|
+
- lib/rubyzen/expectation_helpers.rb
|
|
98
146
|
- lib/rubyzen/lint.rb
|
|
99
|
-
- lib/rubyzen/matchers/matcher_helpers.rb
|
|
100
147
|
- lib/rubyzen/matchers/zen_empty_matcher.rb
|
|
101
148
|
- lib/rubyzen/matchers/zen_false_matcher.rb
|
|
102
149
|
- lib/rubyzen/matchers/zen_true_matcher.rb
|
|
150
|
+
- lib/rubyzen/minitest.rb
|
|
103
151
|
- lib/rubyzen/parsers/a_s_t_parser.rb
|
|
104
152
|
- lib/rubyzen/project.rb
|
|
153
|
+
- lib/rubyzen/providers/arguments_provider.rb
|
|
154
|
+
- lib/rubyzen/providers/assignments_provider.rb
|
|
105
155
|
- lib/rubyzen/providers/attributes_provider.rb
|
|
106
156
|
- lib/rubyzen/providers/blocks_provider.rb
|
|
107
157
|
- lib/rubyzen/providers/call_site_provider.rb
|
|
108
158
|
- lib/rubyzen/providers/class_name_provider.rb
|
|
109
159
|
- lib/rubyzen/providers/collection_filter_provider.rb
|
|
110
160
|
- lib/rubyzen/providers/constants_provider.rb
|
|
161
|
+
- lib/rubyzen/providers/enclosing_blocks_provider.rb
|
|
111
162
|
- lib/rubyzen/providers/file_path_provider.rb
|
|
112
163
|
- lib/rubyzen/providers/if_statements_provider.rb
|
|
113
164
|
- lib/rubyzen/providers/line_number_provider.rb
|
|
@@ -116,8 +167,10 @@ files:
|
|
|
116
167
|
- lib/rubyzen/providers/raises_provider.rb
|
|
117
168
|
- lib/rubyzen/providers/requires_provider.rb
|
|
118
169
|
- lib/rubyzen/providers/rescues_provider.rb
|
|
170
|
+
- lib/rubyzen/providers/returns_provider.rb
|
|
119
171
|
- lib/rubyzen/providers/source_code_provider.rb
|
|
120
172
|
- lib/rubyzen/providers/visibility_provider.rb
|
|
173
|
+
- lib/rubyzen/rspec.rb
|
|
121
174
|
- lib/rubyzen/version.rb
|
|
122
175
|
- rubyzen-lint.gemspec
|
|
123
176
|
homepage: https://github.com/perrystreetsoftware/Rubyzen
|
|
@@ -126,7 +179,9 @@ licenses:
|
|
|
126
179
|
metadata:
|
|
127
180
|
source_code_uri: https://github.com/perrystreetsoftware/Rubyzen
|
|
128
181
|
bug_tracker_uri: https://github.com/perrystreetsoftware/Rubyzen/issues
|
|
129
|
-
|
|
182
|
+
documentation_uri: https://perrystreetsoftware.github.io/Rubyzen
|
|
183
|
+
changelog_uri: https://github.com/perrystreetsoftware/Rubyzen/blob/main/CHANGELOG.md
|
|
184
|
+
post_install_message:
|
|
130
185
|
rdoc_options: []
|
|
131
186
|
require_paths:
|
|
132
187
|
- lib
|
|
@@ -141,8 +196,8 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
|
141
196
|
- !ruby/object:Gem::Version
|
|
142
197
|
version: '0'
|
|
143
198
|
requirements: []
|
|
144
|
-
rubygems_version: 3.
|
|
145
|
-
signing_key:
|
|
199
|
+
rubygems_version: 3.5.22
|
|
200
|
+
signing_key:
|
|
146
201
|
specification_version: 4
|
|
147
|
-
summary: Architectural linter for Ruby — write lint rules as
|
|
202
|
+
summary: Architectural linter for Ruby — write lint rules as unit tests
|
|
148
203
|
test_files: []
|
|
@@ -1,176 +0,0 @@
|
|
|
1
|
-
module Rubyzen
|
|
2
|
-
module Matchers
|
|
3
|
-
# Shared helper methods used by Rubyzen's custom RSpec matchers.
|
|
4
|
-
#
|
|
5
|
-
# Provides utilities for normalizing exception lists, extracting item
|
|
6
|
-
# details, matching items against allowlist/baseline entries, and
|
|
7
|
-
# formatting failure messages.
|
|
8
|
-
module MatcherHelpers
|
|
9
|
-
# Normalizes a list of exception entries into unique, non-blank strings.
|
|
10
|
-
#
|
|
11
|
-
# @param entries [Array<String>, String, nil] raw exception entries
|
|
12
|
-
# @return [Array<String>] deduplicated, stripped, non-empty strings
|
|
13
|
-
def normalize_exception_entries(entries)
|
|
14
|
-
Array(entries).flatten.compact.map(&:to_s).map(&:strip).reject(&:empty?).uniq
|
|
15
|
-
end
|
|
16
|
-
|
|
17
|
-
# Extracts identifying details from a declaration item.
|
|
18
|
-
#
|
|
19
|
-
# @param item [Object] a declaration object (e.g., FileDeclaration, ClassDeclaration)
|
|
20
|
-
# @return [Hash{Symbol => String, nil}] hash with :name, :class_name, :file_path, :line
|
|
21
|
-
def item_details(item)
|
|
22
|
-
{
|
|
23
|
-
name: item.respond_to?(:name) ? item.name : nil,
|
|
24
|
-
class_name: item.respond_to?(:class_name) ? item.class_name : nil,
|
|
25
|
-
file_path: item.respond_to?(:file_path) ? item.file_path : 'Unknown file',
|
|
26
|
-
line: item.respond_to?(:line) ? item.line : nil
|
|
27
|
-
}
|
|
28
|
-
end
|
|
29
|
-
|
|
30
|
-
# Returns a list of unique identifier strings for an item, used for matching.
|
|
31
|
-
#
|
|
32
|
-
# @param item [Object] a declaration object
|
|
33
|
-
# @return [Array<String>] identifiers such as name, class name, file path, and file:line
|
|
34
|
-
def item_identifiers(item)
|
|
35
|
-
details = item_details(item)
|
|
36
|
-
identifiers = [details[:name], details[:class_name], details[:file_path]]
|
|
37
|
-
|
|
38
|
-
if details[:line]
|
|
39
|
-
identifiers << "#{details[:file_path]}:#{details[:line]}"
|
|
40
|
-
end
|
|
41
|
-
|
|
42
|
-
identifiers.compact.uniq
|
|
43
|
-
end
|
|
44
|
-
|
|
45
|
-
# Checks whether a given exception entry string matches an item.
|
|
46
|
-
#
|
|
47
|
-
# @param entry [String] an allowlist or baseline entry
|
|
48
|
-
# @param item [Object] a declaration object
|
|
49
|
-
# @return [Boolean] true if the entry matches the item by name, class, or path
|
|
50
|
-
def exception_entry_matches_item?(entry, item)
|
|
51
|
-
normalized_entry = entry.to_s.strip
|
|
52
|
-
return false if normalized_entry.empty?
|
|
53
|
-
|
|
54
|
-
details = item_details(item)
|
|
55
|
-
return true if item_identifiers(item).include?(normalized_entry)
|
|
56
|
-
|
|
57
|
-
file_path = details[:file_path]
|
|
58
|
-
file_path && (file_path.end_with?(normalized_entry) || file_path.end_with?("/#{normalized_entry}"))
|
|
59
|
-
end
|
|
60
|
-
|
|
61
|
-
# Classifies items into violations, baseline matches, allowlist matches,
|
|
62
|
-
# and detects stale entries in either list.
|
|
63
|
-
#
|
|
64
|
-
# @param subject_collection [Array, Object] items to classify
|
|
65
|
-
# @param allowlist [Array<String>, nil] allowed exception entries
|
|
66
|
-
# @param baseline [Array<String>, nil] baseline exception entries
|
|
67
|
-
# @return [Hash{Symbol => Array<String>}] keys: :violations, :baseline, :allowlist,
|
|
68
|
-
# :stale_baseline, :stale_allowlist
|
|
69
|
-
def classify_items(subject_collection, allowlist: nil, baseline: nil)
|
|
70
|
-
items = Array(subject_collection).compact
|
|
71
|
-
normalized_allowlist = normalize_exception_entries(allowlist)
|
|
72
|
-
normalized_baseline = normalize_exception_entries(baseline)
|
|
73
|
-
matched_baseline_entries = []
|
|
74
|
-
matched_allowlist_entries = []
|
|
75
|
-
|
|
76
|
-
grouped_items = items.group_by do |item|
|
|
77
|
-
matching_baseline_entry = normalized_baseline.find do |entry|
|
|
78
|
-
exception_entry_matches_item?(entry, item)
|
|
79
|
-
end
|
|
80
|
-
|
|
81
|
-
if matching_baseline_entry
|
|
82
|
-
matched_baseline_entries << matching_baseline_entry
|
|
83
|
-
:baseline
|
|
84
|
-
else
|
|
85
|
-
matching_allowlist_entry = normalized_allowlist.find do |entry|
|
|
86
|
-
exception_entry_matches_item?(entry, item)
|
|
87
|
-
end
|
|
88
|
-
|
|
89
|
-
if matching_allowlist_entry
|
|
90
|
-
matched_allowlist_entries << matching_allowlist_entry
|
|
91
|
-
:allowlist
|
|
92
|
-
else
|
|
93
|
-
:violations
|
|
94
|
-
end
|
|
95
|
-
end
|
|
96
|
-
end
|
|
97
|
-
|
|
98
|
-
classifications = {
|
|
99
|
-
baseline: Array(grouped_items[:baseline]).map { |item| element_name(item) },
|
|
100
|
-
allowlist: Array(grouped_items[:allowlist]).map { |item| element_name(item) },
|
|
101
|
-
violations: Array(grouped_items[:violations]).map { |item| element_name(item) }
|
|
102
|
-
}
|
|
103
|
-
|
|
104
|
-
classifications.merge(
|
|
105
|
-
stale_baseline: normalized_baseline - matched_baseline_entries.uniq,
|
|
106
|
-
stale_allowlist: normalized_allowlist - matched_allowlist_entries.uniq
|
|
107
|
-
)
|
|
108
|
-
end
|
|
109
|
-
|
|
110
|
-
# Formats a human-readable description of an item for failure messages.
|
|
111
|
-
#
|
|
112
|
-
# @param item [Object] a declaration object
|
|
113
|
-
# @return [String] formatted multi-line description
|
|
114
|
-
def element_name(item)
|
|
115
|
-
details = item_details(item)
|
|
116
|
-
location = [details[:file_path], details[:line]].compact.join(':')
|
|
117
|
-
|
|
118
|
-
case
|
|
119
|
-
when details[:name] && details[:class_name]
|
|
120
|
-
" - element: #{details[:name]}\n - class: #{details[:class_name]}\n - file: #{location}"
|
|
121
|
-
when details[:name]
|
|
122
|
-
" - element: #{details[:name]}\n - file: #{location}"
|
|
123
|
-
when details[:class_name]
|
|
124
|
-
" - class: #{details[:class_name]}\n - file: #{location}"
|
|
125
|
-
else
|
|
126
|
-
" - unknown element in #{location}"
|
|
127
|
-
end
|
|
128
|
-
end
|
|
129
|
-
|
|
130
|
-
# Builds a formatted string of violations and stale entries for failure output.
|
|
131
|
-
#
|
|
132
|
-
# @return [String, nil] formatted sections or nil if no classified items
|
|
133
|
-
def formatted_matcher_groups
|
|
134
|
-
return unless defined?(@classified_items) && @classified_items
|
|
135
|
-
|
|
136
|
-
sections = []
|
|
137
|
-
|
|
138
|
-
if @classified_items[:violations].any?
|
|
139
|
-
sections << "Violations:\n#{@classified_items[:violations].join("\n")}"
|
|
140
|
-
end
|
|
141
|
-
|
|
142
|
-
if @classified_items[:stale_baseline].any?
|
|
143
|
-
stale_entries = @classified_items[:stale_baseline].map { |entry| " - #{entry}" }
|
|
144
|
-
sections << "Stale baseline entries:\n#{stale_entries.join("\n")}"
|
|
145
|
-
end
|
|
146
|
-
|
|
147
|
-
if @classified_items[:stale_allowlist].any?
|
|
148
|
-
stale_entries = @classified_items[:stale_allowlist].map { |entry| " - #{entry}" }
|
|
149
|
-
sections << "Stale allowlist entries:\n#{stale_entries.join("\n")}"
|
|
150
|
-
end
|
|
151
|
-
|
|
152
|
-
sections.join("\n")
|
|
153
|
-
end
|
|
154
|
-
|
|
155
|
-
def self.included(base)
|
|
156
|
-
base.define_method(:message_for_failure) do |base_message|
|
|
157
|
-
return @failure_message if @failure_message
|
|
158
|
-
|
|
159
|
-
details = formatted_matcher_groups
|
|
160
|
-
|
|
161
|
-
if @custom_message
|
|
162
|
-
if details && !details.empty?
|
|
163
|
-
"#{@custom_message}\n#{details}"
|
|
164
|
-
else
|
|
165
|
-
@custom_message
|
|
166
|
-
end
|
|
167
|
-
elsif details && !details.empty?
|
|
168
|
-
"#{base_message}\n#{details}"
|
|
169
|
-
else
|
|
170
|
-
base_message
|
|
171
|
-
end
|
|
172
|
-
end
|
|
173
|
-
end
|
|
174
|
-
end
|
|
175
|
-
end
|
|
176
|
-
end
|