rubocop-legion 0.1.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.
Files changed (47) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +6 -0
  3. data/.rspec +3 -0
  4. data/.rubocop.yml +22 -0
  5. data/CHANGELOG.md +13 -0
  6. data/Gemfile +4 -0
  7. data/LICENSE +21 -0
  8. data/README.md +37 -0
  9. data/Rakefile +9 -0
  10. data/config/default.yml +233 -0
  11. data/lib/rubocop/cop/legion/constant_safety/bare_data_define.rb +55 -0
  12. data/lib/rubocop/cop/legion/constant_safety/bare_json.rb +55 -0
  13. data/lib/rubocop/cop/legion/constant_safety/bare_process.rb +59 -0
  14. data/lib/rubocop/cop/legion/constant_safety/inherit_param.rb +41 -0
  15. data/lib/rubocop/cop/legion/extension/actor_singular_module.rb +73 -0
  16. data/lib/rubocop/cop/legion/extension/core_extend_guard.rb +46 -0
  17. data/lib/rubocop/cop/legion/extension/data_required_without_migrations.rb +37 -0
  18. data/lib/rubocop/cop/legion/extension/llm_ask_kwargs.rb +47 -0
  19. data/lib/rubocop/cop/legion/extension/runner_include_helpers.rb +114 -0
  20. data/lib/rubocop/cop/legion/extension/runner_must_be_module.rb +47 -0
  21. data/lib/rubocop/cop/legion/extension/runner_return_hash.rb +58 -0
  22. data/lib/rubocop/cop/legion/extension/self_contained_actor_runner_class.rb +76 -0
  23. data/lib/rubocop/cop/legion/extension/settings_bracket_multi_arg.rb +55 -0
  24. data/lib/rubocop/cop/legion/extension/settings_key_method.rb +59 -0
  25. data/lib/rubocop/cop/legion/framework/api_string_keys.rb +67 -0
  26. data/lib/rubocop/cop/legion/framework/cache_time_coercion.rb +36 -0
  27. data/lib/rubocop/cop/legion/framework/eager_sequel_model.rb +49 -0
  28. data/lib/rubocop/cop/legion/framework/faraday_xml_middleware.rb +37 -0
  29. data/lib/rubocop/cop/legion/framework/module_function_private.rb +67 -0
  30. data/lib/rubocop/cop/legion/framework/sinatra_host_auth.rb +64 -0
  31. data/lib/rubocop/cop/legion/framework/thor_reserved_run.rb +60 -0
  32. data/lib/rubocop/cop/legion/helper_migration/direct_cache.rb +55 -0
  33. data/lib/rubocop/cop/legion/helper_migration/direct_crypt.rb +52 -0
  34. data/lib/rubocop/cop/legion/helper_migration/direct_json.rb +52 -0
  35. data/lib/rubocop/cop/legion/helper_migration/direct_local_cache.rb +52 -0
  36. data/lib/rubocop/cop/legion/helper_migration/direct_logging.rb +46 -0
  37. data/lib/rubocop/cop/legion/helper_migration/old_logging_methods.rb +55 -0
  38. data/lib/rubocop/cop/legion/rescue_logging/bare_rescue.rb +41 -0
  39. data/lib/rubocop/cop/legion/rescue_logging/no_capture.rb +46 -0
  40. data/lib/rubocop/cop/legion/rescue_logging/silent_capture.rb +68 -0
  41. data/lib/rubocop/cop/legion/singleton/use_instance.rb +47 -0
  42. data/lib/rubocop/legion/plugin.rb +32 -0
  43. data/lib/rubocop/legion/version.rb +7 -0
  44. data/lib/rubocop/legion.rb +17 -0
  45. data/lib/rubocop-legion.rb +50 -0
  46. data/rubocop-legion.gemspec +39 -0
  47. metadata +149 -0
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: 95a23a8136fd63ac2b841d9a8812c04f261854fac9e752f40e9817f3e8288fc0
4
+ data.tar.gz: 4512d622e170d83a4fb5d1911d6f91d12130157bdd4adbbd422d8aca5e401649
5
+ SHA512:
6
+ metadata.gz: febd09cfcb352235365fead149fc2679fec7a4f7c2836ca9f85c06a9e2fc25a3bdb4a6e3914ef79cfc3f636acba681c9d84cdf3b22945be6e43bd8b276a01bd7
7
+ data.tar.gz: efe2d65b67d0bfa191d0db52d894e87f1cec09c14f0a1bab8a286bc496ad67d1bac8ea98e856dff89c16fe61c0793c766e2ef0711867eae5d746d8b5953c3841
data/.gitignore ADDED
@@ -0,0 +1,6 @@
1
+ /.bundle/
2
+ /Gemfile.lock
3
+ /pkg/
4
+ /tmp/
5
+ *.gem
6
+ .rspec_status
data/.rspec ADDED
@@ -0,0 +1,3 @@
1
+ --format documentation
2
+ --color
3
+ --require spec_helper
data/.rubocop.yml ADDED
@@ -0,0 +1,22 @@
1
+ AllCops:
2
+ NewCops: enable
3
+ TargetRubyVersion: 3.4
4
+ SuggestExtensions: false
5
+
6
+ Style/Documentation:
7
+ Enabled: false
8
+
9
+ Metrics/MethodLength:
10
+ Max: 20
11
+
12
+ Naming/FileName:
13
+ Exclude:
14
+ - 'lib/rubocop-legion.rb'
15
+
16
+ Gemspec/DevelopmentDependencies:
17
+ Enabled: false
18
+
19
+ Metrics/BlockLength:
20
+ Exclude:
21
+ - 'spec/**/*'
22
+ - '*.gemspec'
data/CHANGELOG.md ADDED
@@ -0,0 +1,13 @@
1
+ # Changelog
2
+
3
+ ## [0.1.0] - 2026-03-29
4
+
5
+ ### Added
6
+ - Initial release with 31 cops across 6 departments
7
+ - New RuboCop Plugin API (1.72+, lint_roller based)
8
+ - **Legion/HelperMigration** (6 cops): enforce `log.method`, `json_load`/`json_dump`, `cache_get`/`cache_set`, `vault_get`/`vault_exist?` helpers over direct singleton calls. All auto-correctable.
9
+ - **Legion/ConstantSafety** (4 cops): prevent `Data.define`, `Process`, `JSON` namespace resolution bugs inside `module Legion`; enforce `const_defined?(name, false)`. All auto-correctable.
10
+ - **Legion/Singleton** (1 cop): enforce `.instance` over `.new` for configurable list of singleton classes. Auto-correctable.
11
+ - **Legion/RescueLogging** (3 cops): require rescue blocks to capture exceptions and log or re-raise them. Partial auto-correct (adds `=> e` capture).
12
+ - **Legion/Framework** (7 cops): catch Sequel eager model loading, Sinatra 4.0 host auth, Thor reserved `run`, Faraday XML middleware removal, `module_function`+`private` conflict, cache time coercion, API string keys. `ApiStringKeys` auto-correctable.
13
+ - **Legion/Extension** (10 cops): enforce LEX structural conventions — `module Actor` singular, `extend Core` guard, runner module structure, self-contained actor `runner_class`, `Settings` API correctness, `LLM.ask` signature, `data_required?` migration check. 4 auto-correctable.
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ # frozen_string_literal: true
2
+
3
+ source 'https://rubygems.org'
4
+ gemspec
data/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 LegionIO
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,37 @@
1
+ # rubocop-legion
2
+
3
+ LegionIO code quality cops for [RuboCop](https://rubocop.org/).
4
+
5
+ Custom cops for the LegionIO async job engine ecosystem. Enforces helper usage, constant safety, rescue logging, framework conventions, and LEX extension structure.
6
+
7
+ ## Installation
8
+
9
+ Add to your Gemfile:
10
+
11
+ ```ruby
12
+ gem 'rubocop-legion', require: false, group: :development
13
+ ```
14
+
15
+ ## Usage
16
+
17
+ Add to your `.rubocop.yml`:
18
+
19
+ ```yaml
20
+ plugins:
21
+ - rubocop-legion
22
+ ```
23
+
24
+ ## Departments
25
+
26
+ | Department | Cops | Description |
27
+ |---|---|---|
28
+ | `Legion/HelperMigration` | 6 | Use per-extension helpers, not global singletons |
29
+ | `Legion/ConstantSafety` | 4 | Prevent namespace resolution bugs inside `module Legion` |
30
+ | `Legion/Singleton` | 1 | Enforce `.instance` on singleton classes |
31
+ | `Legion/RescueLogging` | 3 | Every rescue must log or re-raise |
32
+ | `Legion/Framework` | 7 | Sequel, Sinatra, Thor, Faraday, and API gotchas |
33
+ | `Legion/Extension` | 10 | LEX structural convention enforcement |
34
+
35
+ ## License
36
+
37
+ MIT
data/Rakefile ADDED
@@ -0,0 +1,9 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'rspec/core/rake_task'
4
+ require 'rubocop/rake_task'
5
+
6
+ RSpec::Core::RakeTask.new(:spec)
7
+ RuboCop::RakeTask.new
8
+
9
+ task default: %i[spec rubocop]
@@ -0,0 +1,233 @@
1
+ # Legion/HelperMigration — use per-extension helpers, not global singletons
2
+
3
+ Legion/HelperMigration:
4
+ Enabled: true
5
+ Include:
6
+ - 'lib/**/*.rb'
7
+ Exclude:
8
+ - 'spec/**/*'
9
+
10
+ Legion/HelperMigration/DirectLogging:
11
+ Description: 'Use `log.method` helper instead of `Legion::Logging.method`.'
12
+ Enabled: true
13
+ Severity: warning
14
+ VersionAdded: '0.1'
15
+
16
+ Legion/HelperMigration/OldLoggingMethods:
17
+ Description: 'Use `log.method` instead of deprecated `log_method` helpers.'
18
+ Enabled: true
19
+ Severity: warning
20
+ VersionAdded: '0.1'
21
+
22
+ Legion/HelperMigration/DirectJson:
23
+ Description: 'Use `json_load`/`json_dump` helpers instead of `Legion::JSON.load`/`.dump`.'
24
+ Enabled: true
25
+ Severity: convention
26
+ VersionAdded: '0.1'
27
+
28
+ Legion/HelperMigration/DirectCache:
29
+ Description: 'Use `cache_get`/`cache_set`/`cache_delete` helpers instead of `Legion::Cache` methods.'
30
+ Enabled: true
31
+ Severity: warning
32
+ VersionAdded: '0.1'
33
+
34
+ Legion/HelperMigration/DirectLocalCache:
35
+ Description: 'Use `local_cache_get`/`local_cache_set` helpers instead of `Legion::Cache::Local` methods.'
36
+ Enabled: true
37
+ Severity: warning
38
+ VersionAdded: '0.1'
39
+
40
+ Legion/HelperMigration/DirectCrypt:
41
+ Description: 'Use `vault_get`/`vault_exist?` helpers instead of `Legion::Crypt` methods.'
42
+ Enabled: true
43
+ Severity: warning
44
+ VersionAdded: '0.1'
45
+
46
+ # Legion/ConstantSafety — prevent namespace resolution bugs inside module Legion
47
+
48
+ Legion/ConstantSafety:
49
+ Enabled: true
50
+
51
+ Legion/ConstantSafety/BareDataDefine:
52
+ Description: 'Use `::Data.define` inside `module Legion` to avoid resolving to `Legion::Data`.'
53
+ Enabled: true
54
+ Severity: error
55
+ VersionAdded: '0.1'
56
+
57
+ Legion/ConstantSafety/BareProcess:
58
+ Description: 'Use `::Process` inside `module Legion` to avoid resolving to `Legion::Process`.'
59
+ Enabled: true
60
+ Severity: error
61
+ VersionAdded: '0.1'
62
+
63
+ Legion/ConstantSafety/BareJson:
64
+ Description: 'Use `::JSON` inside `module Legion` to avoid resolving to `Legion::JSON`.'
65
+ Enabled: true
66
+ Severity: error
67
+ VersionAdded: '0.1'
68
+
69
+ Legion/ConstantSafety/InheritParam:
70
+ Description: 'Pass `false` as inherit param to `const_defined?`/`const_get` on dynamic modules.'
71
+ Enabled: true
72
+ Severity: convention
73
+ VersionAdded: '0.1'
74
+
75
+ # Legion/Singleton — enforce .instance on singleton classes
76
+
77
+ Legion/Singleton:
78
+ Enabled: true
79
+
80
+ Legion/Singleton/UseInstance:
81
+ Description: 'Use `.instance` instead of `.new` for singleton classes.'
82
+ Enabled: true
83
+ Severity: error
84
+ VersionAdded: '0.1'
85
+ SingletonClasses:
86
+ - TokenCache
87
+ - Registry
88
+ - Catalog
89
+
90
+ # Legion/RescueLogging — every rescue must log or re-raise
91
+
92
+ Legion/RescueLogging:
93
+ Enabled: true
94
+ Include:
95
+ - 'lib/**/*.rb'
96
+ Exclude:
97
+ - 'spec/**/*'
98
+
99
+ Legion/RescueLogging/BareRescue:
100
+ Description: 'Bare `rescue` swallows exceptions silently. Capture with `rescue => e`.'
101
+ Enabled: true
102
+ Severity: warning
103
+ VersionAdded: '0.1'
104
+
105
+ Legion/RescueLogging/NoCapture:
106
+ Description: 'Exception class specified but not captured. Use `rescue Error => e`.'
107
+ Enabled: true
108
+ Severity: convention
109
+ VersionAdded: '0.1'
110
+
111
+ Legion/RescueLogging/SilentCapture:
112
+ Description: 'Captured exception variable is never logged or re-raised.'
113
+ Enabled: true
114
+ Severity: warning
115
+ VersionAdded: '0.1'
116
+
117
+ # Legion/Framework — Sequel, Sinatra, Thor, Faraday, concurrency, cache, API gotchas
118
+
119
+ Legion/Framework:
120
+ Enabled: true
121
+
122
+ Legion/Framework/EagerSequelModel:
123
+ Description: '`Sequel::Model(:table)` introspects schema at require time. Use lazy define pattern.'
124
+ Enabled: true
125
+ Severity: warning
126
+ VersionAdded: '0.1'
127
+
128
+ Legion/Framework/SinatraHostAuth:
129
+ Description: 'Sinatra 4.0+ requires `set :host_authorization` or all requests get 403.'
130
+ Enabled: true
131
+ Severity: convention
132
+ VersionAdded: '0.1'
133
+
134
+ Legion/Framework/ThorReservedRun:
135
+ Description: 'Thor 1.5+ reserves `run`. Rename the method or use `map`.'
136
+ Enabled: true
137
+ Severity: warning
138
+ VersionAdded: '0.1'
139
+ Include:
140
+ - 'lib/**/cli/**/*.rb'
141
+
142
+ Legion/Framework/FaradayXmlMiddleware:
143
+ Description: 'Faraday >= 2.0 removed built-in `:xml` middleware.'
144
+ Enabled: true
145
+ Severity: error
146
+ VersionAdded: '0.1'
147
+
148
+ Legion/Framework/ModuleFunctionPrivate:
149
+ Description: '`private` after `module_function` resets visibility. Do not use both together.'
150
+ Enabled: true
151
+ Severity: convention
152
+ VersionAdded: '0.1'
153
+
154
+ Legion/Framework/CacheTimeCoercion:
155
+ Description: 'Time objects become Strings after cache round-trip. Coerce at read boundaries.'
156
+ Enabled: true
157
+ Severity: convention
158
+ VersionAdded: '0.1'
159
+
160
+ Legion/Framework/ApiStringKeys:
161
+ Description: '`Legion::JSON.load` returns symbol keys. Use `body[:key]` not `body["key"]`.'
162
+ Enabled: true
163
+ Severity: warning
164
+ VersionAdded: '0.1'
165
+ Include:
166
+ - 'lib/**/*.rb'
167
+ Exclude:
168
+ - 'spec/**/*'
169
+
170
+ # Legion/Extension — LEX structural convention enforcement
171
+
172
+ Legion/Extension:
173
+ Enabled: true
174
+
175
+ Legion/Extension/ActorSingularModule:
176
+ Description: 'Use `module Actor` (singular). Framework discovers actors in `Actor`, not `Actors`.'
177
+ Enabled: true
178
+ Severity: error
179
+ VersionAdded: '0.1'
180
+
181
+ Legion/Extension/CoreExtendGuard:
182
+ Description: 'Guard `extend Core` with `const_defined?` for standalone test compatibility.'
183
+ Enabled: true
184
+ Severity: error
185
+ VersionAdded: '0.1'
186
+
187
+ Legion/Extension/RunnerMustBeModule:
188
+ Description: 'Runners must be modules, not classes.'
189
+ Enabled: true
190
+ Severity: warning
191
+ VersionAdded: '0.1'
192
+
193
+ Legion/Extension/RunnerIncludeHelpers:
194
+ Description: 'Runner modules need `include Helpers::Lex` or `extend self`.'
195
+ Enabled: true
196
+ Severity: convention
197
+ VersionAdded: '0.1'
198
+
199
+ Legion/Extension/SelfContainedActorRunnerClass:
200
+ Description: 'Self-contained actors must override `runner_class`.'
201
+ Enabled: true
202
+ Severity: warning
203
+ VersionAdded: '0.1'
204
+
205
+ Legion/Extension/RunnerReturnHash:
206
+ Description: 'Runner methods must return a Hash.'
207
+ Enabled: true
208
+ Severity: convention
209
+ VersionAdded: '0.1'
210
+
211
+ Legion/Extension/SettingsKeyMethod:
212
+ Description: '`Legion::Settings` has no `key?` method. Use `!Settings[:key].nil?`.'
213
+ Enabled: true
214
+ Severity: error
215
+ VersionAdded: '0.1'
216
+
217
+ Legion/Extension/SettingsBracketMultiArg:
218
+ Description: '`Settings#[]` takes 1 arg. Use `Settings.dig(...)` for nested access.'
219
+ Enabled: true
220
+ Severity: error
221
+ VersionAdded: '0.1'
222
+
223
+ Legion/Extension/LlmAskKwargs:
224
+ Description: '`Legion::LLM.ask` only accepts `message:`. No extra kwargs.'
225
+ Enabled: true
226
+ Severity: error
227
+ VersionAdded: '0.1'
228
+
229
+ Legion/Extension/DataRequiredWithoutMigrations:
230
+ Description: '`data_required?` returns true but migrations may be missing.'
231
+ Enabled: true
232
+ Severity: warning
233
+ VersionAdded: '0.1'
@@ -0,0 +1,55 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RuboCop
4
+ module Cop
5
+ module Legion
6
+ module ConstantSafety
7
+ # Detects bare `Data.define` inside `module Legion` namespaces where it
8
+ # resolves to `Legion::Data.define` instead of the stdlib `Data.define`.
9
+ #
10
+ # @example
11
+ # # bad (inside module Legion)
12
+ # module Legion
13
+ # Point = Data.define(:x, :y)
14
+ # end
15
+ #
16
+ # # good
17
+ # module Legion
18
+ # Point = ::Data.define(:x, :y)
19
+ # end
20
+ class BareDataDefine < RuboCop::Cop::Base
21
+ extend AutoCorrector
22
+
23
+ MSG = 'Inside `module Legion`, bare `Data.define` resolves to `Legion::Data.define`. ' \
24
+ 'Use `::Data.define`.'
25
+
26
+ RESTRICT_ON_SEND = %i[define].freeze
27
+
28
+ # @!method bare_data_define?(node)
29
+ def_node_matcher :bare_data_define?, <<~PATTERN
30
+ (send (const nil? :Data) :define ...)
31
+ PATTERN
32
+
33
+ def on_send(node)
34
+ return unless bare_data_define?(node)
35
+ return unless inside_legion_namespace?(node)
36
+
37
+ receiver = node.receiver
38
+ add_offense(receiver) do |corrector|
39
+ corrector.replace(receiver.source_range, '::Data')
40
+ end
41
+ end
42
+
43
+ private
44
+
45
+ def inside_legion_namespace?(node)
46
+ node.each_ancestor(:module, :class).any? do |ancestor|
47
+ name = ancestor.identifier.source
48
+ name == 'Legion' || name.start_with?('Legion::')
49
+ end
50
+ end
51
+ end
52
+ end
53
+ end
54
+ end
55
+ end
@@ -0,0 +1,55 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RuboCop
4
+ module Cop
5
+ module Legion
6
+ module ConstantSafety
7
+ # Detects bare `JSON` method calls inside `module Legion` namespaces
8
+ # where `JSON` resolves to `Legion::JSON` instead of the stdlib.
9
+ #
10
+ # @example
11
+ # # bad (inside module Legion)
12
+ # module Legion
13
+ # JSON.parse(raw)
14
+ # end
15
+ #
16
+ # # good
17
+ # module Legion
18
+ # ::JSON.parse(raw)
19
+ # end
20
+ class BareJson < RuboCop::Cop::Base
21
+ extend AutoCorrector
22
+
23
+ MSG = 'Inside `module Legion`, bare `JSON` resolves to `Legion::JSON`. ' \
24
+ 'Use `::JSON` for stdlib.'
25
+
26
+ RESTRICT_ON_SEND = %i[parse generate pretty_generate dump load fast_generate].freeze
27
+
28
+ # @!method bare_json_send?(node)
29
+ def_node_matcher :bare_json_send?, <<~PATTERN
30
+ (send (const nil? :JSON) _ ...)
31
+ PATTERN
32
+
33
+ def on_send(node)
34
+ return unless bare_json_send?(node)
35
+ return unless inside_legion_namespace?(node)
36
+
37
+ receiver = node.receiver
38
+ add_offense(receiver) do |corrector|
39
+ corrector.replace(receiver.source_range, '::JSON')
40
+ end
41
+ end
42
+
43
+ private
44
+
45
+ def inside_legion_namespace?(node)
46
+ node.each_ancestor(:module, :class).any? do |ancestor|
47
+ name = ancestor.identifier.source
48
+ name == 'Legion' || name.start_with?('Legion::')
49
+ end
50
+ end
51
+ end
52
+ end
53
+ end
54
+ end
55
+ end
@@ -0,0 +1,59 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RuboCop
4
+ module Cop
5
+ module Legion
6
+ module ConstantSafety
7
+ # Detects bare `Process` method calls inside `module Legion` namespaces
8
+ # where `Process` resolves to `Legion::Process` instead of the stdlib.
9
+ #
10
+ # @example
11
+ # # bad (inside module Legion)
12
+ # module Legion
13
+ # Process.pid
14
+ # end
15
+ #
16
+ # # good
17
+ # module Legion
18
+ # ::Process.pid
19
+ # end
20
+ class BareProcess < RuboCop::Cop::Base
21
+ extend AutoCorrector
22
+
23
+ MSG = 'Inside `module Legion`, bare `Process` resolves to `Legion::Process`. ' \
24
+ 'Use `::Process`.'
25
+
26
+ RESTRICT_ON_SEND = %i[
27
+ pid ppid kill detach fork wait wait2 waitpid waitpid2
28
+ getpgid setpgid daemon exit spawn times groups
29
+ uid gid euid egid
30
+ ].freeze
31
+
32
+ # @!method bare_process_send?(node)
33
+ def_node_matcher :bare_process_send?, <<~PATTERN
34
+ (send (const nil? :Process) _ ...)
35
+ PATTERN
36
+
37
+ def on_send(node)
38
+ return unless bare_process_send?(node)
39
+ return unless inside_legion_namespace?(node)
40
+
41
+ receiver = node.receiver
42
+ add_offense(receiver) do |corrector|
43
+ corrector.replace(receiver.source_range, '::Process')
44
+ end
45
+ end
46
+
47
+ private
48
+
49
+ def inside_legion_namespace?(node)
50
+ node.each_ancestor(:module, :class).any? do |ancestor|
51
+ name = ancestor.identifier.source
52
+ name == 'Legion' || name.start_with?('Legion::')
53
+ end
54
+ end
55
+ end
56
+ end
57
+ end
58
+ end
59
+ end
@@ -0,0 +1,41 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RuboCop
4
+ module Cop
5
+ module Legion
6
+ module ConstantSafety
7
+ # Detects `const_defined?` or `const_get` called with only one argument.
8
+ # Without `false` as the second argument, Ruby searches the entire inheritance
9
+ # chain including `Object`, which can cause false positives and namespace leaks.
10
+ #
11
+ # @example
12
+ # # bad
13
+ # mod.const_defined?('Foo')
14
+ # mod.const_get('Bar')
15
+ #
16
+ # # good
17
+ # mod.const_defined?('Foo', false)
18
+ # mod.const_get('Bar', false)
19
+ class InheritParam < RuboCop::Cop::Base
20
+ extend AutoCorrector
21
+
22
+ MSG = 'Pass `false` as inherit parameter: `%<method>s(%<arg>s, false)`. ' \
23
+ 'Default `true` leaks through `Object`.'
24
+
25
+ RESTRICT_ON_SEND = %i[const_defined? const_get].freeze
26
+
27
+ def on_send(node)
28
+ return unless node.arguments.size == 1
29
+
30
+ arg = node.first_argument
31
+ message = format(MSG, method: node.method_name, arg: arg.source)
32
+
33
+ add_offense(node, message: message) do |corrector|
34
+ corrector.insert_after(arg.source_range, ', false')
35
+ end
36
+ end
37
+ end
38
+ end
39
+ end
40
+ end
41
+ end
@@ -0,0 +1,73 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RuboCop
4
+ module Cop
5
+ module Legion
6
+ module Extension
7
+ # Detects `module Actors` (plural) inside `Legion::Extensions::*` namespaces
8
+ # and auto-corrects it to `module Actor` (singular), which is what the LEX
9
+ # framework uses when discovering actor classes.
10
+ #
11
+ # @example
12
+ # # bad
13
+ # module Legion::Extensions::Foo
14
+ # module Actors
15
+ # end
16
+ # end
17
+ #
18
+ # # good
19
+ # module Legion::Extensions::Foo
20
+ # module Actor
21
+ # end
22
+ # end
23
+ class ActorSingularModule < RuboCop::Cop::Base
24
+ extend AutoCorrector
25
+
26
+ MSG = 'Use `module Actor` (singular), not `module Actors`. ' \
27
+ 'The framework discovers actors inside `Actor`.'
28
+
29
+ def on_module(node)
30
+ name_node = node.identifier
31
+ return unless name_node.short_name == :Actors
32
+
33
+ return unless inside_legion_extensions_namespace?(node)
34
+
35
+ add_offense(name_node) do |corrector|
36
+ corrector.replace(name_node.loc.name, 'Actor')
37
+ end
38
+ end
39
+
40
+ private
41
+
42
+ def inside_legion_extensions_namespace?(node)
43
+ # Build the full namespace path from all ancestor module/class nodes.
44
+ # This handles both compact (`module Legion::Extensions::Foo`) and
45
+ # nested (`module Legion; module Extensions; module Foo`) forms.
46
+ full_path = ancestor_namespace_parts(node).join('::')
47
+ full_path.include?('Legion') && full_path.include?('Extensions')
48
+ end
49
+
50
+ def ancestor_namespace_parts(node)
51
+ parts = []
52
+ current = node.parent
53
+ while current
54
+ parts.unshift(*resolve_const_parts(current.identifier)) if current.module_type? || current.class_type?
55
+ current = current.parent
56
+ end
57
+ parts
58
+ end
59
+
60
+ def resolve_const_parts(const_node)
61
+ parts = []
62
+ node = const_node
63
+ while node&.const_type?
64
+ parts.unshift(node.short_name.to_s)
65
+ node = node.namespace
66
+ end
67
+ parts
68
+ end
69
+ end
70
+ end
71
+ end
72
+ end
73
+ end