rubocop-legion 0.1.3 → 0.1.6

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: b8cc626a680f38939c6212bd03e40249c354e24e8d4d8415aee6aa00c449e67e
4
- data.tar.gz: 3eaed689593c5ec2ec4e24c89104c856ff0bff298302c278f1ca296d7278161f
3
+ metadata.gz: f841ecd6e5821651ad020bd243791971cf7ab5dca138178d90d216e8a58a48ba
4
+ data.tar.gz: acd221dbf9f924e6f81b21569262ea6a8aebbe42d4753356f9750cd3cc0c13a0
5
5
  SHA512:
6
- metadata.gz: 8ac7d705d82418617f8d9ff3e67524911ff1c947a381c1ab61fcbdc35a1fa5c7537e9dc017f7118ca0037eebe27ab52d352e08a7433fde06cf641b48e8e13006
7
- data.tar.gz: ea1138ce50b98a013ea6b4de431eebb336a087ef3204b6be1c20061786d3e8c43f589958bc03050f1ed4d3f5bb9e9808b8da50eafbeeff323455a494be76e7bd
6
+ metadata.gz: 5933d7f5010bc89b49a95e3d4c0080d752d583fab9a4c11e5fc3a244a0e45938be35eeb6f6851386452d72eca5c135434b6fe1d7dc6cb2a5be163bf7a0213576
7
+ data.tar.gz: 62b31e59d175e345383a02764b3c1c32a7599e70af80e31d8f0685730579c5396d9cdd783e19fb16ca18809e43b33cead79b6ad03f444acc77045fe4c2dd6399
data/CHANGELOG.md CHANGED
@@ -1,5 +1,33 @@
1
1
  # Changelog
2
2
 
3
+ ## [0.1.6] - 2026-03-29
4
+
5
+ ### Added
6
+ - Bundled `rubocop-performance` (52 cops) — Ruby performance anti-pattern detection, all enabled by default
7
+ - Bundled `rubocop-thread_safety` (6 cops) — thread safety analysis for concurrent code
8
+ - Tuned ThreadSafety defaults: `NewThread` excludes service/connection files, `ClassInstanceVariable` excludes singletons, `RackMiddlewareInstanceVariable` disabled, `DirChdir` allows block form
9
+
10
+ ## [0.1.5] - 2026-03-29
11
+
12
+ ### Added
13
+ - New cop `Legion/Framework/MutexNestedSync`: detect nested `synchronize` blocks (deadlock risk)
14
+ - New cop `Legion/Extension/ActorEnabledSideEffects`: flag `enabled?` in actor classes that runs during boot (keep side-effect-free)
15
+
16
+ ## [0.1.4] - 2026-03-29
17
+
18
+ ### Added
19
+ - New cop `Legion/HelperMigration/DirectData`: use `data_connection`/`local_data_*` helpers instead of `Legion::Data::Connection`/`Legion::Data::Local` (auto-fix)
20
+ - New cop `Legion/HelperMigration/DirectLlmEmbed`: use `llm_embed` helper instead of `Legion::LLM.embed` (auto-fix)
21
+ - New cop `Legion/HelperMigration/RequireDefinedGuard`: remove `if defined?(Legion::...)` from require statements (auto-fix)
22
+ - New cop `Legion/HelperMigration/DefinedTransportGuard`: use `transport_connected?` instead of `defined?(Legion::Transport)`
23
+ - New cop `Legion/Extension/RunnerPluralModule`: enforce `module Runners` (plural), auto-correctable
24
+ - New cop `Legion/Extension/ActorInheritance`: actor must inherit from Every, Once, Poll, Subscription, Loop, or Nothing
25
+ - New cop `Legion/Extension/EveryActorRequiresTime`: Every/Poll actors must call `time` DSL
26
+ - New cop `Legion/Extension/HookMissingRunnerClass`: hook classes must override `runner_class`
27
+ - New cop `Legion/Extension/AbsorberMissingPattern`: absorber classes must call `pattern` DSL
28
+ - New cop `Legion/Extension/AbsorberMissingAbsorbMethod`: absorber classes must define `absorb` method
29
+ - New cop `Legion/Extension/DefinitionCallMismatched`: `definition :name` must have matching `def name`
30
+
3
31
  ## [0.1.3] - 2026-03-29
4
32
 
5
33
  ### Added
data/CLAUDE.md CHANGED
@@ -4,27 +4,45 @@
4
4
 
5
5
  ## What is This?
6
6
 
7
- Custom RuboCop plugin gem for the LegionIO ecosystem. Provides 32 AST-based cops across 6 departments. Uses the new RuboCop Plugin API (1.72+, lint_roller-based) with auto-discovery via gemspec metadata.
7
+ Custom RuboCop plugin gem for the LegionIO ecosystem. Provides 45 AST-based cops across 6 departments. Uses the new RuboCop Plugin API (1.72+, lint_roller-based) with auto-discovery via gemspec metadata.
8
8
 
9
9
  **GitHub**: https://github.com/LegionIO/rubocop-legion
10
10
  **RubyGems**: https://rubygems.org/gems/rubocop-legion
11
11
  **License**: MIT
12
12
 
13
+ ## Shared Config Profiles
14
+
15
+ The gem ships shared `.rubocop.yml` profiles so repos don't duplicate config:
16
+
17
+ - `config/base.yml` — all shared settings (AllCops, Layout, Metrics, Style, Naming, Performance, ThreadSafety)
18
+ - `config/lex.yml` — inherits base, adds plugins (rubocop-legion + performance + thread_safety) + `ParameterLists Max: 8`
19
+ - `config/core.yml` — inherits base, adds plugins (rubocop-legion + performance + thread_safety) + `ParameterLists Max: 10, CountKeywordArgs: false`
20
+
21
+ ### Bundled Plugins
22
+
23
+ Both profiles load `rubocop-performance` (52 cops) and `rubocop-thread_safety` (6 cops) as runtime dependencies. ThreadSafety defaults tuned for LegionIO: `NewThread` excludes service/connection files, `ClassInstanceVariable` excludes singletons, `RackMiddlewareInstanceVariable` disabled, `DirChdir` allows block form.
24
+
25
+ **LEX repos**: `inherit_gem: { rubocop-legion: config/lex.yml }`
26
+ **Core repos**: `inherit_gem: { rubocop-legion: config/core.yml }`
27
+
28
+ Repo-specific overrides go below the `inherit_gem` directive. Version-locked with the gem — bump gem version = all repos get updated config.
29
+
13
30
  ## Cop Scoping
14
31
 
15
32
  Cops are scoped by gem type — no per-repo configuration needed:
16
33
 
17
34
  - **Universal** (9 cops): Fire on all LegionIO gems
18
35
  - **Library-specific** (6 cops): Fire on all gems but only trigger when using Sequel, Sinatra, Thor, Faraday, or cache
19
- - **LEX-only** (17 cops): Scoped to `lib/legion/extensions/**/*.rb` via Include directive — never fire on core `legion-*` libraries
36
+ - **LEX-only** (28 cops): Scoped to `lib/legion/extensions/**/*.rb` via Include directive — never fire on core `legion-*` libraries
20
37
 
21
38
  ## Departments and Cops
22
39
 
23
- ### Universal — 9 cops
40
+ ### Universal — 10 cops
24
41
 
25
42
  - **ConstantSafety** (4): `BareDataDefine`, `BareProcess`, `BareJson` (all error, auto-fix) — prefix with `::` inside `module Legion`. `InheritParam` (convention, auto-fix) — pass `false` to `const_defined?`/`const_get`.
26
43
  - **RescueLogging** (3): `BareRescue` (warning, auto-fix) — capture with `=> e`. `NoCapture` (convention, no auto-fix) — exception class without capture. `SilentCapture` (warning, no auto-fix) — captured but never logged/re-raised. Skips `_`-prefixed vars. All skip inline rescue modifiers.
27
44
  - **Singleton** (1): `UseInstance` (error, auto-fix) — `.instance` not `.new` for configurable singleton classes.
45
+ - **Framework/MutexNestedSync** (1): Nested `synchronize` blocks risk deadlock.
28
46
  - **Framework/ModuleFunctionPrivate** (1): `private` after `module_function` resets visibility.
29
47
 
30
48
  ### Library-Specific — 6 cops
@@ -36,10 +54,10 @@ Cops are scoped by gem type — no per-repo configuration needed:
36
54
  - `CacheTimeCoercion` — Time→String after cache round-trip
37
55
  - `ApiStringKeys` — `Legion::JSON.load` returns symbol keys (scoped to `lib/legion/extensions/**/*.rb`)
38
56
 
39
- ### LEX-Only — 17 cops
57
+ ### LEX-Only — 29 cops
40
58
 
41
- - **HelperMigration** (7): `DirectLogging`, `OldLoggingMethods`, `DirectJson`, `DirectCache`, `DirectLocalCache`, `DirectCrypt` (all auto-fix) — use per-extension helpers, not global singletons. `LoggingGuard` (no auto-fix) — remove unnecessary `respond_to?(:log_warn)` / `defined?(Legion::Logging)` guards.
42
- - **Extension** (10): `ActorSingularModule` (auto-fix), `CoreExtendGuard` (auto-fix), `RunnerMustBeModule`, `RunnerIncludeHelpers`, `SelfContainedActorRunnerClass`, `RunnerReturnHash`, `SettingsKeyMethod` (auto-fix), `SettingsBracketMultiArg` (auto-fix), `LlmAskKwargs`, `DataRequiredWithoutMigrations`.
59
+ - **HelperMigration** (11): `DirectLogging`, `OldLoggingMethods`, `DirectJson`, `DirectCache`, `DirectLocalCache`, `DirectCrypt`, `DirectData`, `DirectLlmEmbed`, `RequireDefinedGuard` (all auto-fix) — use per-extension helpers, not global singletons. `LoggingGuard` (no auto-fix) — remove unnecessary `respond_to?(:log_warn)` / `defined?(Legion::Logging)` guards. `DefinedTransportGuard` (no auto-fix) — use `transport_connected?` instead of `defined?(Legion::Transport)`.
60
+ - **Extension** (18): `ActorSingularModule` (auto-fix), `RunnerPluralModule` (auto-fix), `CoreExtendGuard` (auto-fix), `RunnerMustBeModule`, `RunnerIncludeHelpers`, `ActorInheritance`, `EveryActorRequiresTime`, `SelfContainedActorRunnerClass`, `HookMissingRunnerClass`, `AbsorberMissingPattern`, `AbsorberMissingAbsorbMethod`, `DefinitionCallMismatched`, `RunnerReturnHash`, `SettingsKeyMethod` (auto-fix), `SettingsBracketMultiArg` (auto-fix), `LlmAskKwargs`, `ActorEnabledSideEffects`, `DataRequiredWithoutMigrations`.
43
61
 
44
62
  ## Architecture
45
63
 
@@ -58,10 +76,10 @@ rubocop-legion/
58
76
  │ ├── singleton/ # 1 cop (universal)
59
77
  │ ├── rescue_logging/ # 3 cops (universal)
60
78
  │ ├── framework/ # 7 cops (universal + library-specific)
61
- │ └── extension/ # 10 cops (lex-only)
79
+ │ └── extension/ # 17 cops (lex-only)
62
80
  ├── config/
63
81
  │ └── default.yml # All cop defaults, Include/Exclude scoping
64
- └── spec/ # 233 specs, mirrors lib/ structure
82
+ └── spec/ # mirrors lib/ structure
65
83
  ```
66
84
 
67
85
  ## Key Implementation Details
@@ -79,7 +97,7 @@ rubocop-legion/
79
97
 
80
98
  ```bash
81
99
  bundle install
82
- bundle exec rspec # 233 specs
100
+ bundle exec rspec # 322 specs
83
101
  bundle exec rubocop # Self-linting
84
102
  ```
85
103
 
data/README.md CHANGED
@@ -14,15 +14,39 @@ gem 'rubocop-legion', '~> 0.1', require: false, group: :development
14
14
 
15
15
  ## Usage
16
16
 
17
- Add to your `.rubocop.yml`:
17
+ Use a shared config profile that includes the plugin, all standard settings, and cop defaults:
18
+
19
+ **For `lex-*` extension gems:**
18
20
 
19
21
  ```yaml
20
- plugins:
21
- - rubocop-legion
22
+ # .rubocop.yml
23
+ inherit_gem:
24
+ rubocop-legion: config/lex.yml
22
25
  ```
23
26
 
27
+ **For `legion-*` core library gems:**
28
+
29
+ ```yaml
30
+ # .rubocop.yml
31
+ inherit_gem:
32
+ rubocop-legion: config/core.yml
33
+ ```
34
+
35
+ Add repo-specific overrides below the `inherit_gem` directive. For manual setup without shared config, use `plugins: [rubocop-legion]` directly.
36
+
24
37
  Requires RuboCop 1.72+ (Plugin API with lint_roller).
25
38
 
39
+ ### What the shared configs include
40
+
41
+ Both profiles set: `TargetRubyVersion: 3.4`, `NewCops: enable`, `LineLength: 160`, `MethodLength: 50`, `ClassLength: 1500`, `ModuleLength: 1500`, `BlockLength: 40` (spec/gemspec excluded), `AbcSize: 60`, table-aligned hashes, frozen string literals, and disable `Style/Documentation`, `Naming/FileName`, `Naming/PredicateMethod`, and `Gemspec/DevelopmentDependencies`.
42
+
43
+ Both profiles also load **rubocop-performance** (52 cops) and **rubocop-thread_safety** (6 cops) as plugins. Thread safety defaults are tuned for LegionIO's concurrent architecture: `NewThread` excludes service/connection files, `ClassInstanceVariable` excludes singletons, `RackMiddlewareInstanceVariable` is disabled, `DirChdir` allows block form.
44
+
45
+ | Profile | `ParameterLists Max` | `CountKeywordArgs` |
46
+ |---------|---------------------|--------------------|
47
+ | `lex.yml` | 8 | default (true) |
48
+ | `core.yml` | 10 | false |
49
+
26
50
  ## Cop Scoping
27
51
 
28
52
  Cops are automatically scoped based on where they should apply:
@@ -35,7 +59,7 @@ No per-repo configuration needed for scoping. If a cop doesn't apply to your gem
35
59
 
36
60
  ## Cops
37
61
 
38
- ### Universal (all LegionIO gems) — 9 cops
62
+ ### Universal (all LegionIO gems) — 10 cops
39
63
 
40
64
  | Department | Cop | Severity | Auto-fix | Description |
41
65
  |---|---|---|---|---|
@@ -47,6 +71,7 @@ No per-repo configuration needed for scoping. If a cop doesn't apply to your gem
47
71
  | RescueLogging | `NoCapture` | convention | no | Exception class specified but not captured (`rescue Error` without `=> e`) |
48
72
  | RescueLogging | `SilentCapture` | warning | no | Captured exception never logged or re-raised |
49
73
  | Singleton | `UseInstance` | error | yes | Use `.instance` instead of `.new` for singleton classes |
74
+ | Framework | `MutexNestedSync` | warning | no | Nested `synchronize` blocks risk deadlock |
50
75
  | Framework | `ModuleFunctionPrivate` | convention | no | `private` after `module_function` resets visibility |
51
76
 
52
77
  ### Library-Specific (all gems, triggers on library usage) — 6 cops
@@ -60,7 +85,7 @@ No per-repo configuration needed for scoping. If a cop doesn't apply to your gem
60
85
  | Framework | `CacheTimeCoercion` | cache_get | convention | no | Time objects become Strings after cache round-trip |
61
86
  | Framework | `ApiStringKeys` | Legion::JSON.load | warning | yes | `Legion::JSON.load` returns symbol keys — use `body[:key]` |
62
87
 
63
- ### LEX Extensions Only (`lib/legion/extensions/**/*.rb`) — 17 cops
88
+ ### LEX Extensions Only (`lib/legion/extensions/**/*.rb`) — 29 cops
64
89
 
65
90
  | Department | Cop | Severity | Auto-fix | Description |
66
91
  |---|---|---|---|---|
@@ -71,18 +96,37 @@ No per-repo configuration needed for scoping. If a cop doesn't apply to your gem
71
96
  | HelperMigration | `DirectLocalCache` | warning | yes | Use `local_cache_get`/`local_cache_set` instead of `Legion::Cache::Local` |
72
97
  | HelperMigration | `DirectCrypt` | warning | yes | Use `vault_get`/`vault_exist?` instead of `Legion::Crypt` |
73
98
  | HelperMigration | `LoggingGuard` | convention | no | Remove unnecessary `respond_to?(:log_warn)` / `defined?(Legion::Logging)` guards |
99
+ | HelperMigration | `DirectData` | convention | yes | Use `data_connection`/`local_data_*` instead of `Legion::Data::Connection`/`Local` |
100
+ | HelperMigration | `DirectLlmEmbed` | convention | yes | Use `llm_embed` instead of `Legion::LLM.embed` |
101
+ | HelperMigration | `RequireDefinedGuard` | convention | yes | Remove `if defined?(Legion::...)` guard from `require` statements |
102
+ | HelperMigration | `DefinedTransportGuard` | convention | no | Use `transport_connected?` instead of `defined?(Legion::Transport)` |
74
103
  | Extension | `ActorSingularModule` | error | yes | Use `module Actor` (singular) — framework discovers `Actor`, not `Actors` |
104
+ | Extension | `RunnerPluralModule` | error | yes | Use `module Runners` (plural) — framework discovers `Runners`, not `Runner` |
75
105
  | Extension | `CoreExtendGuard` | error | yes | Guard `extend Core` with `const_defined?` for standalone compatibility |
76
106
  | Extension | `RunnerMustBeModule` | warning | no | Runners must be modules, not classes |
77
107
  | Extension | `RunnerIncludeHelpers` | convention | no | Runner modules need `include Helpers::Lex` or `extend self` |
108
+ | Extension | `ActorInheritance` | error | no | Actor must inherit from Every, Once, Poll, Subscription, Loop, or Nothing |
109
+ | Extension | `EveryActorRequiresTime` | warning | no | Every/Poll actors must call `time` DSL to set the interval |
78
110
  | Extension | `SelfContainedActorRunnerClass` | warning | no | Self-contained actors must override `runner_class` |
111
+ | Extension | `HookMissingRunnerClass` | error | no | Hook classes must override `runner_class` |
112
+ | Extension | `AbsorberMissingPattern` | warning | no | Absorber classes must call `pattern` DSL to match events |
113
+ | Extension | `AbsorberMissingAbsorbMethod` | warning | no | Absorber classes must define `absorb` method |
114
+ | Extension | `DefinitionCallMismatched` | error | no | `definition :name` must have matching `def name` method |
79
115
  | Extension | `RunnerReturnHash` | convention | no | Runner methods must return a Hash |
80
116
  | Extension | `SettingsKeyMethod` | error | yes | `Legion::Settings` has no `key?` — use `!Settings[:key].nil?` |
81
117
  | Extension | `SettingsBracketMultiArg` | error | yes | `Settings#[]` takes 1 arg — use `Settings.dig(...)` for nested access |
82
118
  | Extension | `LlmAskKwargs` | error | no | `Legion::LLM.ask` only accepts `message:` — no extra kwargs |
119
+ | Extension | `ActorEnabledSideEffects` | convention | no | `enabled?` runs during boot — keep side-effect-free |
83
120
  | Extension | `DataRequiredWithoutMigrations` | warning | no | `data_required?` returns true but migrations may be missing |
84
121
 
85
- **Total: 32 cops** across 6 departments, 15 auto-correctable.
122
+ **Total: 45 custom cops** across 6 departments, 19 auto-correctable.
123
+
124
+ ### Bundled Plugins
125
+
126
+ Both shared config profiles also load:
127
+
128
+ - **[rubocop-performance](https://github.com/rubocop/rubocop-performance)** — 52 cops for Ruby performance anti-patterns (all enabled)
129
+ - **[rubocop-thread_safety](https://github.com/rubocop/rubocop-thread_safety)** — 6 cops for thread safety analysis (tuned for LegionIO)
86
130
 
87
131
  ## Per-Repo Overrides
88
132
 
data/config/base.yml CHANGED
@@ -58,3 +58,38 @@ Naming/PredicateMethod:
58
58
 
59
59
  Gemspec/DevelopmentDependencies:
60
60
  Enabled: false
61
+
62
+ # --- Performance ---
63
+ # All Performance cops enabled by default (rubocop-performance).
64
+ # Override per-repo only if needed.
65
+
66
+ Performance:
67
+ Enabled: true
68
+
69
+ # --- Thread Safety ---
70
+ # LegionIO is a concurrent async engine. These cops catch real bugs.
71
+
72
+ ThreadSafety:
73
+ Enabled: true
74
+
75
+ ThreadSafety/NewThread:
76
+ Enabled: true
77
+ Exclude:
78
+ - 'lib/**/service.rb'
79
+ - 'lib/**/service/**/*.rb'
80
+ - 'lib/**/connection.rb'
81
+ - 'lib/**/connection/**/*.rb'
82
+ - 'spec/**/*'
83
+
84
+ ThreadSafety/ClassInstanceVariable:
85
+ Enabled: true
86
+ Exclude:
87
+ - 'lib/**/singleton/**/*.rb'
88
+ - 'spec/**/*'
89
+
90
+ ThreadSafety/RackMiddlewareInstanceVariable:
91
+ Enabled: false
92
+
93
+ ThreadSafety/DirChdir:
94
+ Enabled: true
95
+ AllowCallWithBlock: true
data/config/core.yml CHANGED
@@ -11,6 +11,8 @@ inherit_from: base.yml
11
11
 
12
12
  plugins:
13
13
  - rubocop-legion
14
+ - rubocop-performance
15
+ - rubocop-thread_safety
14
16
 
15
17
  Metrics/ParameterLists:
16
18
  Max: 10
data/config/default.yml CHANGED
@@ -49,6 +49,30 @@ Legion/HelperMigration/LoggingGuard:
49
49
  Severity: convention
50
50
  VersionAdded: '0.1.2'
51
51
 
52
+ Legion/HelperMigration/DirectData:
53
+ Description: 'Use `data_connection`/`local_data_*` helpers instead of `Legion::Data::Connection`/`Legion::Data::Local` methods.'
54
+ Enabled: true
55
+ Severity: convention
56
+ VersionAdded: '0.1.4'
57
+
58
+ Legion/HelperMigration/DirectLlmEmbed:
59
+ Description: 'Use `llm_embed` helper instead of `Legion::LLM.embed`.'
60
+ Enabled: true
61
+ Severity: convention
62
+ VersionAdded: '0.1.4'
63
+
64
+ Legion/HelperMigration/RequireDefinedGuard:
65
+ Description: 'Remove `if defined?(Legion::...)` guard from `require`/`require_relative` statements.'
66
+ Enabled: true
67
+ Severity: convention
68
+ VersionAdded: '0.1.4'
69
+
70
+ Legion/HelperMigration/DefinedTransportGuard:
71
+ Description: 'Use `transport_connected?` instead of `defined?(Legion::Transport)`.'
72
+ Enabled: true
73
+ Severity: convention
74
+ VersionAdded: '0.1.4'
75
+
52
76
  # Legion/ConstantSafety — prevent namespace resolution bugs inside module Legion
53
77
 
54
78
  Legion/ConstantSafety:
@@ -163,6 +187,12 @@ Legion/Framework/CacheTimeCoercion:
163
187
  Severity: convention
164
188
  VersionAdded: '0.1'
165
189
 
190
+ Legion/Framework/MutexNestedSync:
191
+ Description: 'Nested `synchronize` blocks risk deadlock if the same mutex is re-acquired.'
192
+ Enabled: true
193
+ Severity: warning
194
+ VersionAdded: '0.1.5'
195
+
166
196
  Legion/Framework/ApiStringKeys:
167
197
  Description: '`Legion::JSON.load` returns symbol keys. Use `body[:key]` not `body["key"]`.'
168
198
  Enabled: true
@@ -241,3 +271,51 @@ Legion/Extension/DataRequiredWithoutMigrations:
241
271
  Enabled: true
242
272
  Severity: warning
243
273
  VersionAdded: '0.1'
274
+
275
+ Legion/Extension/RunnerPluralModule:
276
+ Description: 'Use `module Runners` (plural). Framework discovers runners in `Runners`, not `Runner`.'
277
+ Enabled: true
278
+ Severity: error
279
+ VersionAdded: '0.1.4'
280
+
281
+ Legion/Extension/ActorInheritance:
282
+ Description: 'Actor must inherit from Every, Once, Poll, Subscription, Loop, or Nothing.'
283
+ Enabled: true
284
+ Severity: error
285
+ VersionAdded: '0.1.4'
286
+
287
+ Legion/Extension/EveryActorRequiresTime:
288
+ Description: 'Every/Poll actors must call the `time` DSL method to set the interval.'
289
+ Enabled: true
290
+ Severity: warning
291
+ VersionAdded: '0.1.4'
292
+
293
+ Legion/Extension/HookMissingRunnerClass:
294
+ Description: 'Hook classes must override `runner_class` or the framework dispatches to nil.'
295
+ Enabled: true
296
+ Severity: error
297
+ VersionAdded: '0.1.4'
298
+
299
+ Legion/Extension/AbsorberMissingPattern:
300
+ Description: 'Absorber classes must call the `pattern` DSL method to match events.'
301
+ Enabled: true
302
+ Severity: warning
303
+ VersionAdded: '0.1.4'
304
+
305
+ Legion/Extension/AbsorberMissingAbsorbMethod:
306
+ Description: 'Absorber classes must define an `absorb` method to handle matched events.'
307
+ Enabled: true
308
+ Severity: warning
309
+ VersionAdded: '0.1.4'
310
+
311
+ Legion/Extension/ActorEnabledSideEffects:
312
+ Description: '`enabled?` runs during extension loading. Keep it cheap and side-effect-free.'
313
+ Enabled: true
314
+ Severity: convention
315
+ VersionAdded: '0.1.5'
316
+
317
+ Legion/Extension/DefinitionCallMismatched:
318
+ Description: '`definition :name` must have a matching `def name` method in the same module.'
319
+ Enabled: true
320
+ Severity: error
321
+ VersionAdded: '0.1.4'
data/config/lex.yml CHANGED
@@ -11,6 +11,8 @@ inherit_from: base.yml
11
11
 
12
12
  plugins:
13
13
  - rubocop-legion
14
+ - rubocop-performance
15
+ - rubocop-thread_safety
14
16
 
15
17
  Metrics/ParameterLists:
16
18
  Max: 8
@@ -0,0 +1,62 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RuboCop
4
+ module Cop
5
+ module Legion
6
+ module Extension
7
+ # Detects absorber classes inside an `Absorbers` namespace that do not define
8
+ # the `absorb` instance method. The framework calls `absorb` when a matching
9
+ # event arrives — without it, the absorber silently drops events.
10
+ #
11
+ # @example
12
+ # # bad
13
+ # module Absorbers
14
+ # class Foo
15
+ # pattern 'some.event.*'
16
+ # end
17
+ # end
18
+ #
19
+ # # good
20
+ # module Absorbers
21
+ # class Foo
22
+ # pattern 'some.event.*'
23
+ #
24
+ # def absorb(event)
25
+ # process(event)
26
+ # end
27
+ # end
28
+ # end
29
+ class AbsorberMissingAbsorbMethod < RuboCop::Cop::Base
30
+ MSG = 'Absorber classes must define an `absorb` method to handle matched events.'
31
+
32
+ def on_class(node)
33
+ return unless inside_absorbers_namespace?(node)
34
+ return if defines_absorb?(node)
35
+
36
+ add_offense(node.identifier)
37
+ end
38
+
39
+ private
40
+
41
+ def inside_absorbers_namespace?(node)
42
+ current = node.parent
43
+ while current
44
+ return true if current.module_type? && current.identifier.short_name == :Absorbers
45
+
46
+ current = current.parent
47
+ end
48
+ false
49
+ end
50
+
51
+ def defines_absorb?(node)
52
+ return false unless node.body
53
+
54
+ node.body.each_node(:def).any? do |def_node|
55
+ def_node.method_name == :absorb
56
+ end
57
+ end
58
+ end
59
+ end
60
+ end
61
+ end
62
+ end
@@ -0,0 +1,64 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RuboCop
4
+ module Cop
5
+ module Legion
6
+ module Extension
7
+ # Detects absorber classes inside an `Absorbers` namespace that do not call
8
+ # the `pattern` DSL method. Without `pattern`, the absorber will not match
9
+ # any events and will be silently inactive.
10
+ #
11
+ # @example
12
+ # # bad
13
+ # module Absorbers
14
+ # class Foo
15
+ # def absorb(event)
16
+ # process(event)
17
+ # end
18
+ # end
19
+ # end
20
+ #
21
+ # # good
22
+ # module Absorbers
23
+ # class Foo
24
+ # pattern 'some.event.*'
25
+ #
26
+ # def absorb(event)
27
+ # process(event)
28
+ # end
29
+ # end
30
+ # end
31
+ class AbsorberMissingPattern < RuboCop::Cop::Base
32
+ MSG = 'Absorber classes must call the `pattern` DSL method to match events.'
33
+
34
+ def on_class(node)
35
+ return unless inside_absorbers_namespace?(node)
36
+ return if calls_pattern?(node)
37
+
38
+ add_offense(node.identifier)
39
+ end
40
+
41
+ private
42
+
43
+ def inside_absorbers_namespace?(node)
44
+ current = node.parent
45
+ while current
46
+ return true if current.module_type? && current.identifier.short_name == :Absorbers
47
+
48
+ current = current.parent
49
+ end
50
+ false
51
+ end
52
+
53
+ def calls_pattern?(node)
54
+ return false unless node.body
55
+
56
+ node.body.each_node(:send).any? do |send_node|
57
+ send_node.method_name == :pattern && send_node.receiver.nil?
58
+ end
59
+ end
60
+ end
61
+ end
62
+ end
63
+ end
64
+ end
@@ -0,0 +1,56 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RuboCop
4
+ module Cop
5
+ module Legion
6
+ module Extension
7
+ # Detects `def enabled?` methods inside actor classes. The `enabled?`
8
+ # method runs during extension loading before `delay` is honoured,
9
+ # so it must be cheap and side-effect-free (no network calls, mutex
10
+ # locks, or I/O).
11
+ #
12
+ # @example
13
+ # # bad — network call during boot
14
+ # module Actor
15
+ # class Check < Every
16
+ # def enabled?
17
+ # Legion::Transport.connected?
18
+ # end
19
+ # end
20
+ # end
21
+ #
22
+ # # good — cheap Settings lookup
23
+ # module Actor
24
+ # class Check < Every
25
+ # def enabled?
26
+ # !Legion::Settings[:check].nil?
27
+ # end
28
+ # end
29
+ # end
30
+ class ActorEnabledSideEffects < RuboCop::Cop::Base
31
+ MSG = '`enabled?` runs during extension loading, before `delay`. ' \
32
+ 'Keep it cheap and side-effect-free (no network calls, mutex locks, or I/O).'
33
+
34
+ def on_def(node)
35
+ return unless node.method_name == :enabled?
36
+ return unless inside_actor_namespace?(node)
37
+
38
+ add_offense(node)
39
+ end
40
+
41
+ private
42
+
43
+ def inside_actor_namespace?(node)
44
+ current = node.parent
45
+ while current
46
+ return true if current.module_type? && current.identifier.short_name == :Actor
47
+
48
+ current = current.parent
49
+ end
50
+ false
51
+ end
52
+ end
53
+ end
54
+ end
55
+ end
56
+ end
@@ -0,0 +1,65 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RuboCop
4
+ module Cop
5
+ module Legion
6
+ module Extension
7
+ # Detects actor classes inside an `Actor` namespace that do not inherit from
8
+ # a recognized LEX actor base class. Every actor must inherit from one of:
9
+ # `Every`, `Once`, `Poll`, `Subscription`, `Loop`, or `Nothing`.
10
+ #
11
+ # @example
12
+ # # bad
13
+ # module Actor
14
+ # class Foo
15
+ # end
16
+ # end
17
+ #
18
+ # # bad
19
+ # module Actor
20
+ # class Foo < SomeOtherBase
21
+ # end
22
+ # end
23
+ #
24
+ # # good
25
+ # module Actor
26
+ # class Foo < Legion::Extensions::Actors::Every
27
+ # end
28
+ # end
29
+ class ActorInheritance < RuboCop::Cop::Base
30
+ MSG = 'Actor must inherit from a recognized base: Every, Once, Poll, Subscription, Loop, or Nothing.'
31
+
32
+ RECOGNIZED_BASES = %i[Every Once Poll Subscription Loop Nothing].to_set.freeze
33
+
34
+ def on_class(node)
35
+ return unless inside_actor_namespace?(node)
36
+
37
+ superclass = node.parent_class
38
+ return if superclass && recognized_base?(superclass)
39
+
40
+ add_offense(node.identifier)
41
+ end
42
+
43
+ private
44
+
45
+ def inside_actor_namespace?(node)
46
+ current = node.parent
47
+ while current
48
+ return true if current.module_type? && current.identifier.short_name == :Actor
49
+
50
+ current = current.parent
51
+ end
52
+ false
53
+ end
54
+
55
+ def recognized_base?(superclass_node)
56
+ # Handle both `< Every` and `< Legion::Extensions::Actors::Every`
57
+ leaf = superclass_node
58
+ leaf = leaf.children.last while leaf.const_type? && leaf.children.last.is_a?(Symbol) == false
59
+ RECOGNIZED_BASES.include?(leaf.children.last)
60
+ end
61
+ end
62
+ end
63
+ end
64
+ end
65
+ end