rubocop-rbs_inline 1.6.1 → 1.8.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 (75) hide show
  1. checksums.yaml +4 -4
  2. data/.claude/hooks/self-review.sh +89 -0
  3. data/.claude/settings.json +8 -0
  4. data/.rubocop.yml +9 -3
  5. data/CHANGELOG.md +27 -0
  6. data/CLAUDE.md +9 -14
  7. data/README.md +124 -6
  8. data/config/default.yml +18 -4
  9. data/lib/rubocop/cop/rbs_inline_cops.rb +17 -3
  10. data/lib/rubocop/cop/style/rbs_inline/data_class_comment_alignment.rb +5 -93
  11. data/lib/rubocop/cop/style/rbs_inline/data_define_with_block.rb +4 -11
  12. data/lib/rubocop/cop/style/rbs_inline/embedded_rbs_spacing.rb +3 -2
  13. data/lib/rubocop/cop/style/rbs_inline/invalid_comment.rb +2 -2
  14. data/lib/rubocop/cop/style/rbs_inline/invalid_types.rb +1 -0
  15. data/lib/rubocop/cop/style/rbs_inline/keyword_separator.rb +2 -2
  16. data/lib/rubocop/cop/style/rbs_inline/method_comment_spacing.rb +3 -2
  17. data/lib/rubocop/cop/style/rbs_inline/missing_data_class_annotation.rb +5 -142
  18. data/lib/rubocop/cop/style/rbs_inline/missing_struct_class_annotation.rb +42 -0
  19. data/lib/rubocop/cop/style/rbs_inline/missing_type_annotation.rb +4 -17
  20. data/lib/rubocop/cop/style/rbs_inline/mixin/annotation_keywords.rb +16 -0
  21. data/lib/rubocop/cop/style/rbs_inline/{ast_utils.rb → mixin/ast_utils.rb} +16 -0
  22. data/lib/rubocop/cop/style/rbs_inline/mixin/class_comment_alignment.rb +66 -0
  23. data/lib/rubocop/cop/style/rbs_inline/{comment_parser.rb → mixin/comment_parser.rb} +8 -0
  24. data/lib/rubocop/cop/style/rbs_inline/mixin/data_class_matcher.rb +22 -0
  25. data/lib/rubocop/cop/style/rbs_inline/mixin/data_struct_helper.rb +106 -0
  26. data/lib/rubocop/cop/style/rbs_inline/mixin/file_filter.rb +110 -0
  27. data/lib/rubocop/cop/style/rbs_inline/mixin/missing_class_annotation.rb +85 -0
  28. data/lib/rubocop/cop/style/rbs_inline/{source_code_helper.rb → mixin/source_code_helper.rb} +3 -5
  29. data/lib/rubocop/cop/style/rbs_inline/mixin/struct_class_matcher.rb +32 -0
  30. data/lib/rubocop/cop/style/rbs_inline/parameters_separator.rb +2 -2
  31. data/lib/rubocop/cop/style/rbs_inline/redundant_annotation_with_skip.rb +2 -11
  32. data/lib/rubocop/cop/style/rbs_inline/redundant_instance_variable_annotation.rb +1 -0
  33. data/lib/rubocop/cop/style/rbs_inline/redundant_type_annotation.rb +6 -15
  34. data/lib/rubocop/cop/style/rbs_inline/require_rbs_inline_comment.rb +74 -23
  35. data/lib/rubocop/cop/style/rbs_inline/struct_class_comment_alignment.rb +44 -0
  36. data/lib/rubocop/cop/style/rbs_inline/struct_new_with_block.rb +48 -0
  37. data/lib/rubocop/cop/style/rbs_inline/unmatched_annotations.rb +31 -6
  38. data/lib/rubocop/cop/style/rbs_inline/untyped_instance_variable.rb +16 -6
  39. data/lib/rubocop/cop/style/rbs_inline/variable_comment_spacing.rb +3 -2
  40. data/lib/rubocop/rbs_inline/version.rb +1 -1
  41. data/rbs_collection.lock.yaml +5 -1
  42. data/sig/gems/rubocop/rubocop.rbs +8 -0
  43. data/sig/rubocop/cop/style/rbs_inline/data_class_comment_alignment.rbs +3 -32
  44. data/sig/rubocop/cop/style/rbs_inline/data_define_with_block.rbs +4 -5
  45. data/sig/rubocop/cop/style/rbs_inline/embedded_rbs_spacing.rbs +2 -0
  46. data/sig/rubocop/cop/style/rbs_inline/invalid_comment.rbs +4 -3
  47. data/sig/rubocop/cop/style/rbs_inline/invalid_types.rbs +2 -0
  48. data/sig/rubocop/cop/style/rbs_inline/keyword_separator.rbs +4 -3
  49. data/sig/rubocop/cop/style/rbs_inline/method_comment_spacing.rbs +2 -0
  50. data/sig/rubocop/cop/style/rbs_inline/missing_data_class_annotation.rbs +3 -48
  51. data/sig/rubocop/cop/style/rbs_inline/missing_struct_class_annotation.rbs +40 -0
  52. data/sig/rubocop/cop/style/rbs_inline/missing_type_annotation.rbs +4 -8
  53. data/sig/rubocop/cop/style/rbs_inline/mixin/annotation_keywords.rbs +16 -0
  54. data/sig/rubocop/cop/style/rbs_inline/{ast_utils.rbs → mixin/ast_utils.rbs} +9 -1
  55. data/sig/rubocop/cop/style/rbs_inline/mixin/class_comment_alignment.rbs +34 -0
  56. data/sig/rubocop/cop/style/rbs_inline/{comment_parser.rbs → mixin/comment_parser.rbs} +5 -1
  57. data/sig/rubocop/cop/style/rbs_inline/mixin/data_class_matcher.rbs +18 -0
  58. data/sig/rubocop/cop/style/rbs_inline/mixin/data_struct_helper.rbs +60 -0
  59. data/sig/rubocop/cop/style/rbs_inline/mixin/file_filter.rbs +63 -0
  60. data/sig/rubocop/cop/style/rbs_inline/mixin/missing_class_annotation.rbs +41 -0
  61. data/sig/rubocop/cop/style/rbs_inline/{source_code_helper.rbs → mixin/source_code_helper.rbs} +1 -1
  62. data/sig/rubocop/cop/style/rbs_inline/mixin/struct_class_matcher.rbs +26 -0
  63. data/sig/rubocop/cop/style/rbs_inline/parameters_separator.rbs +4 -3
  64. data/sig/rubocop/cop/style/rbs_inline/redundant_annotation_with_skip.rbs +4 -4
  65. data/sig/rubocop/cop/style/rbs_inline/redundant_instance_variable_annotation.rbs +2 -0
  66. data/sig/rubocop/cop/style/rbs_inline/redundant_type_annotation.rbs +4 -4
  67. data/sig/rubocop/cop/style/rbs_inline/require_rbs_inline_comment.rbs +30 -5
  68. data/sig/rubocop/cop/style/rbs_inline/struct_class_comment_alignment.rbs +42 -0
  69. data/sig/rubocop/cop/style/rbs_inline/struct_new_with_block.rbs +39 -0
  70. data/sig/rubocop/cop/style/rbs_inline/unmatched_annotations.rbs +13 -0
  71. data/sig/rubocop/cop/style/rbs_inline/untyped_instance_variable.rbs +10 -0
  72. data/sig/rubocop/cop/style/rbs_inline/variable_comment_spacing.rbs +2 -0
  73. metadata +30 -11
  74. data/sig/rubocop/cop/style/rbs_inline/redundant_argument_type.rbs +0 -85
  75. data/sig/rubocop/cop/style/rbs_inline/redundant_return_type.rbs +0 -109
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: bd596a937ef29c9dd1053c9b874725aebde2e9fab387053243268810bb0a8103
4
- data.tar.gz: '03865a85b9ef1783eb85816c386aee96370e48e7169994ea5cc96c1a323aacd9'
3
+ metadata.gz: e4bebfc6eae9ec29990f1dafdf2fc773737b2cf001c450702b138b1d9ab81dec
4
+ data.tar.gz: f9ebb04f3712f5f02cd6b7388ea74b056fdce19027be61ed6b651cf1b5915ca8
5
5
  SHA512:
6
- metadata.gz: a5c053242db495271ee4751bcba23bc67d876d8d668e0984634d1154452a87476ff82330ea9010c48cd0f285918124d2dc42145bb750a3a8d0f25368e8ee8e22
7
- data.tar.gz: 0e78d76dc58a9ba77439f65013cf1aa46492ee84337a43c6630e81d18c02d5fba23b0fab88c42f7a73d35d3c3bf02c4cfc9bee50f9337fb4d0d2a5189ddfdbf1
6
+ metadata.gz: 46b9e51e95573630afbe686500cdf154a2cdf2d76bd19f441552fe76267682fb586118f96e7b0c5316352b49fc6e5ddc3b080fd1dc8b6711d31e382694ad767d
7
+ data.tar.gz: 9bd7d22ee40b4d009b1c2f2f86d855a37ee32349c12afffdebbda6cf5edf54bf9c474f67bdfb170987ea512c0934deb48d709f1afbc49acef88f968ebff4297a
@@ -0,0 +1,89 @@
1
+ #!/bin/bash
2
+ #
3
+ # PreToolUse hook: review code before `git commit`.
4
+ #
5
+ # Fires before a Bash `git commit` command and blocks the first commit attempt
6
+ # for a given change-set, asking Claude to review the code before committing.
7
+ # The review uses the `self-review` skill when available, and falls back to the
8
+ # bundled `code-review` skill otherwise — so the hook works even in projects
9
+ # that don't ship the custom self-review skill.
10
+ #
11
+ # A per-change-set marker (keyed by a hash of the diff, stored under .git/)
12
+ # prevents an infinite block loop: once a change-set has been surfaced for
13
+ # review, the matching commit is allowed through. If the review leads to fixes,
14
+ # the diff changes, so the new state is reviewed once more before it commits.
15
+ #
16
+
17
+ set -euo pipefail
18
+
19
+ # Read JSON input from stdin
20
+ input=$(cat)
21
+
22
+ tool_name=$(echo "$input" | jq -r '.tool_name // empty')
23
+ command=$(echo "$input" | jq -r '.tool_input.command // empty')
24
+
25
+ # Only gate the Bash tool running a git commit.
26
+ if [[ "$tool_name" != "Bash" ]]; then
27
+ exit 0
28
+ fi
29
+ # Match `git commit`, or `git -C <path> commit`.
30
+ commit_re='git[[:space:]]+(-C[[:space:]]+[^[:space:]]+[[:space:]]+)?commit'
31
+ if [[ ! "$command" =~ $commit_re ]]; then
32
+ exit 0
33
+ fi
34
+
35
+ # If the command targets a specific repo via `git -C <path>`, operate there too.
36
+ work_dir="."
37
+ if [[ "$command" =~ git[[:space:]]+-C[[:space:]]+([^[:space:]]+) ]]; then
38
+ work_dir="${BASH_REMATCH[1]}"
39
+ fi
40
+
41
+ # Not a git repo → nothing to do.
42
+ git_dir=$(git -C "$work_dir" rev-parse --absolute-git-dir 2>/dev/null || true)
43
+ if [[ -z "$git_dir" ]]; then
44
+ exit 0
45
+ fi
46
+
47
+ # Hash what will be committed. `git status --porcelain` is included so a commit
48
+ # that only adds untracked files isn't mistaken for empty; `git diff HEAD` adds
49
+ # tracked content. On the first commit there is no HEAD, so use the working state.
50
+ if git -C "$work_dir" rev-parse --verify HEAD >/dev/null 2>&1; then
51
+ changeset=$(git -C "$work_dir" status --porcelain; git -C "$work_dir" diff HEAD)
52
+ else
53
+ changeset=$(git -C "$work_dir" status --porcelain; git -C "$work_dir" diff; git -C "$work_dir" diff --cached)
54
+ fi
55
+
56
+ # Nothing to review (e.g. a message-only `--amend`) → let it proceed.
57
+ if [[ -z "$changeset" ]]; then
58
+ exit 0
59
+ fi
60
+
61
+ hash=$(printf '%s' "$changeset" | git hash-object --stdin)
62
+ state_file="$git_dir/self-review-reviewed"
63
+
64
+ # Already surfaced for review → allow the commit.
65
+ if [[ -f "$state_file" ]] && grep -qxF "$hash" "$state_file"; then
66
+ exit 0
67
+ fi
68
+
69
+ # Record this change-set and block once to force a review first.
70
+ echo "$hash" >> "$state_file"
71
+
72
+ review_prompt='SELF-REVIEW REQUIRED before committing. Review the changes that are
73
+ about to be committed BEFORE running git commit again:
74
+
75
+ - If the `self-review` skill is available, invoke it (Skill tool, skill "self-review").
76
+ - Otherwise, run the bundled `/code-review` skill on the working diff.
77
+
78
+ If the review finds issues, fix them and review again. Once it is clean, run the same
79
+ git commit command again to proceed — it will be allowed through.'
80
+
81
+ jq -n --arg reason "$review_prompt" '{
82
+ "hookSpecificOutput": {
83
+ "hookEventName": "PreToolUse",
84
+ "permissionDecision": "deny",
85
+ "permissionDecisionReason": $reason
86
+ }
87
+ }'
88
+
89
+ exit 0
@@ -1,4 +1,7 @@
1
1
  {
2
+ "permissions": {
3
+ "allow": ["RemoteTrigger"]
4
+ },
2
5
  "hooks": {
3
6
  "SessionStart": [
4
7
  {
@@ -19,6 +22,11 @@
19
22
  {
20
23
  "type": "command",
21
24
  "command": "\"$CLAUDE_PROJECT_DIR\"/.claude/hooks/pre-commit-check.sh"
25
+ },
26
+ {
27
+ "type": "command",
28
+ "command": "\"$CLAUDE_PROJECT_DIR\"/.claude/hooks/self-review.sh",
29
+ "timeout": 10000
22
30
  }
23
31
  ]
24
32
  }
data/.rubocop.yml CHANGED
@@ -56,16 +56,22 @@ RSpec/NamedSubject:
56
56
  RSpec/NestedGroups:
57
57
  Max: 5
58
58
 
59
+ # Mixin specs mirror `lib/.../rbs_inline/mixin/`, but the modules keep the
60
+ # `RuboCop::Cop::Style::RbsInline` namespace, so the path never matches the constant.
61
+ RSpec/SpecFilePathFormat:
62
+ Exclude:
63
+ - "spec/rubocop/cop/style/rbs_inline/mixin/**/*_spec.rb"
64
+
59
65
  # RbsInline
66
+ Style/RbsInline:
67
+ Mode: opt_out
68
+
60
69
  Style/RbsInline/MissingTypeAnnotation:
61
70
  EnforcedStyle: doc_style_and_return_annotation
62
71
 
63
72
  Style/RbsInline/RedundantTypeAnnotation:
64
73
  EnforcedStyle: doc_style_and_return_annotation
65
74
 
66
- Style/RbsInline/RequireRbsInlineComment:
67
- EnforcedStyle: never
68
-
69
75
  # Style
70
76
  Style/AccessorGrouping:
71
77
  Enabled: false
data/CHANGELOG.md CHANGED
@@ -1,5 +1,32 @@
1
1
  # Changelog
2
2
 
3
+ ## 1.8.0 (2026-07-26)
4
+
5
+ ### Enhancements
6
+
7
+ - **Style/RbsInline**: Added a shared `Mode` setting (`opt_in` / `opt_out`) at the department level to filter which files each cop checks based on the `# rbs_inline: enabled` / `disabled` magic comment.
8
+ - **Style/RbsInline/RequireRbsInlineComment**: Added the `AllowMissingComment` option, which lets projects use rbs-inline on only some of their files while keeping the opt-in filter for the rest.
9
+
10
+ ### Bug Fixes
11
+
12
+ - **Style/RbsInline/DataClassCommentAlignment**, **Style/RbsInline/StructClassCommentAlignment**: Fixed the expected annotation column being derived from arguments that cannot carry an annotation (a splat, or for `Struct.new` a leading struct name or `keyword_init:`), which reported false positives on already aligned code and made autocorrect over-pad the annotations.
13
+ - **Style/RbsInline/MissingDataClassAnnotation**, **Style/RbsInline/MissingStructClassAnnotation**: Fixed autocorrect padding the inserted annotations to the width of an argument that cannot carry one (a splat, or for `Struct.new` a leading struct name or `keyword_init:`).
14
+ - **Style/RbsInline/UnmatchedAnnotations**: Fixed a crash on method definitions taking a destructuring argument (ex. `def foo((a, b))`). The crash also aborted the inspection of the method definition, causing false positives on its valid annotations.
15
+ - **Style/RbsInline/RequireRbsInlineComment**: Fixed a crash (`IndexError`) on a file whose leading comment block ends without a trailing newline. The offense is now reported and the magic comment is inserted on its own line.
16
+ - **Style/RbsInline/RedundantTypeAnnotation**: Fixed `--auto-gen-config` generating an `EnforcedStyle` that does not resolve the offenses (including the unsupported `return_type_annotation`, which made RuboCop fail to start on the generated `.rubocop_todo.yml`). No style can make redundant annotations offense-free, so the offending files are now excluded instead.
17
+
18
+ ### Deprecations
19
+
20
+ - **Style/RbsInline/RequireRbsInlineComment**: `EnforcedStyle` is deprecated in favor of the department-level `Mode`. Migrate `always` → `Style/RbsInline: Mode: opt_in`, `never` → `Style/RbsInline: Mode: opt_out`. Will be removed in the next major version.
21
+
22
+ ## 1.7.0 (2026-07-20)
23
+
24
+ ### New Features
25
+
26
+ - **Style/RbsInline/MissingStructClassAnnotation**: New cop that checks each attribute passed to `Struct.new` has a trailing `#:` inline type annotation. Supports autocorrect.
27
+ - **Style/RbsInline/StructClassCommentAlignment**: New cop that checks `#:` inline type annotations in a multiline `Struct.new` call are aligned. Supports autocorrect.
28
+ - **Style/RbsInline/StructNewWithBlock**: New cop that checks for `Struct.new` calls with a block, which RBS::Inline does not parse.
29
+
3
30
  ## 1.6.1 (2026-07-10)
4
31
 
5
32
  ### Bug Fixes
data/CLAUDE.md CHANGED
@@ -23,7 +23,7 @@ bundle exec rake rubocop:autocorrect_all # all autocorrect
23
23
  bundle exec rake steep # runs steep check
24
24
  bundle exec rake rbs:validate # validates RBS signatures
25
25
 
26
- # Default rake task (runs specs, rubocop, and type check)
26
+ # Default rake task (rubocop, specs, steep, rbs:validate)
27
27
  bundle exec rake
28
28
 
29
29
  # Generate a new cop
@@ -34,32 +34,27 @@ bundle exec rake 'new_cop[Style/RbsInline/CopName]'
34
34
 
35
35
  ### Cop Structure
36
36
 
37
- All cops live under `RuboCop::Cop::Style::RbsInline` namespace in `lib/rubocop/cop/style/rbs_inline/`. Each cop validates a different aspect of RBS::Inline annotations:
37
+ Cops live in `lib/rubocop/cop/style/rbs_inline/`, one per file; the shared modules they mix in live in `lib/rubocop/cop/style/rbs_inline/mixin/`. Both are under the `RuboCop::Cop::Style::RbsInline` namespace — only the directory separates them, as in `rubocop`'s `cop/mixin/`. `config/default.yml` lists every cop and its options; read it instead of a list kept here.
38
38
 
39
- - **InvalidComment** - Validates `#:` and `# @rbs` comment syntax
40
- - **InvalidTypes** - Validates RBS type syntax in annotations
41
- - **KeywordSeparator** - Ensures keywords like `module-self` aren't followed by `:`
42
- - **ParametersSeparator** - Ensures parameter annotations use `:` separator
43
- - **RedundantArgumentType** - Detects redundant argument type specs (configurable style)
44
- - **RedundantReturnType** - Detects redundant return type specs (3 style options)
45
- - **UnmatchedAnnotations** - Ensures annotation parameters match actual method parameters
39
+ Conventions:
46
40
 
47
- ### Shared CommentParser Module
48
-
49
- `comment_parser.rb` is a module mixed into cops that need to parse RBS::Inline annotations. It uses `RBS::Inline::AnnotationParser` and Prism to parse comments, and provides helpers for finding leading annotations, inline comments, and return annotations relative to method definitions.
41
+ - Every cop `prepend`s `FileFilter` except `RequireRbsInlineComment`.
42
+ - `Data.define` and `Struct.new` cops come in pairs sharing a base module. Behavior belongs in the module; only the node matcher and `MSG` in the cop.
50
43
 
51
44
  ### Plugin System
52
45
 
53
- The gem integrates with RuboCop via LintRoller (`lib/rubocop/rbs_inline/plugin.rb`). Default cop configuration is in `config/default.yml`.
46
+ The gem integrates with RuboCop via LintRoller (`lib/rubocop/rbs_inline/plugin.rb`), which points RuboCop at `config/default.yml`.
54
47
 
55
48
  ### Type Signatures
56
49
 
57
- RBS type signatures for all source files are maintained in `sig/` directory. The project uses Steep for static type checking, configured via `Steepfile`.
50
+ `sig/rubocop/` is generated from `lib/` by the hooks in `.claude/hooks/`. Never write anything there. `sig/gems/` is hand-written stubs for third-party gems and is *not* regenerated do not delete it when refreshing signatures.
58
51
 
59
52
  ### Testing Pattern
60
53
 
61
54
  Tests use RuboCop's RSpec support helpers (`expect_offense` / `expect_no_offenses`). Test files mirror the cop file structure under `spec/rubocop/cop/style/rbs_inline/`.
62
55
 
56
+ `:config` specs build a bare `RuboCop::Config`, so `config/default.yml` and RuboCop's department-to-cop defaults are not merged in. A spec that sets a value at the department level proves nothing about how it resolves in a real run.
57
+
63
58
  ## Code Style Notes
64
59
 
65
60
  - `Layout/LeadingCommentSpace` and `Style/CommentedKeyword` are disabled because RBS::Inline `#:` comments violate these rules by design.
data/README.md CHANGED
@@ -27,6 +27,23 @@ plugins:
27
27
 
28
28
  rubocop-rbs_inline provides the following cops to validate [RBS::Inline](https://github.com/soutaro/rbs-inline) annotations:
29
29
 
30
+ ### Style/RbsInline (shared configuration)
31
+
32
+ The `Style/RbsInline` department has a shared `Mode` setting that gates whether each `Style/RbsInline/*` cop reports offenses on a given file:
33
+
34
+ - `Mode: opt_in` — cops only check files that carry `# rbs_inline: enabled`. Files with `# rbs_inline: disabled` or no magic comment are skipped. Matches RBS::Inline's opt-in mode.
35
+ - `Mode: opt_out` — cops check every file except those with `# rbs_inline: disabled`. Matches RBS::Inline's opt-out mode.
36
+ - unset — cops check every file (legacy behavior; the default will change to `opt_in` in the next major release).
37
+
38
+ Declare it once at the department level in your `.rubocop.yml`:
39
+
40
+ ```yaml
41
+ Style/RbsInline:
42
+ Mode: opt_in
43
+ ```
44
+
45
+ Unknown `Mode` values (e.g. `Mode: opt-in`) emit a warning and disable filtering for the run.
46
+
30
47
  ### Style/RbsInline/DataClassCommentAlignment
31
48
 
32
49
  Checks that `#:` inline type annotations in a multiline `Data.define` call are aligned to the same column. The expected column is determined by the longest attribute name (plus its trailing comma). Folded `Data.define` calls (where multiple attributes share a line) are excluded.
@@ -198,6 +215,32 @@ MethodEntry = Data.define(
198
215
  )
199
216
  ```
200
217
 
218
+ ### Style/RbsInline/MissingStructClassAnnotation
219
+
220
+ Checks that each attribute passed to `Struct.new` has a trailing `#:` inline type annotation on the same line.
221
+
222
+ For folded `Struct.new` calls (where multiple attributes share a line), the cop will suggest rewriting as a multiline call so each attribute can be annotated individually. A leading string argument (the struct name) and the `keyword_init:` keyword argument are not attributes and are ignored.
223
+
224
+ Supports autocorrect.
225
+
226
+ **Examples:**
227
+ ```ruby
228
+ # bad
229
+ Point = Struct.new(:x, :y)
230
+
231
+ # bad - missing annotation for :y
232
+ Point = Struct.new(
233
+ :x, #: Integer
234
+ :y
235
+ )
236
+
237
+ # good
238
+ Point = Struct.new(
239
+ :x, #: Integer
240
+ :y #: Integer
241
+ )
242
+ ```
243
+
201
244
  ### Style/RbsInline/MissingTypeAnnotation
202
245
 
203
246
  Enforces that method definitions and `attr_*` declarations have RBS inline type annotations.
@@ -212,7 +255,11 @@ Enforces that method definitions and `attr_*` declarations have RBS inline type
212
255
  - `all`: Checks all methods regardless of visibility
213
256
  - `public`: Only checks public methods and `attr_*` declarations
214
257
 
215
- Methods annotated with `# @rbs skip` are always excluded.
258
+ Always excluded, regardless of `EnforcedStyle`:
259
+
260
+ - Methods annotated with `# @rbs skip` or `# @rbs override`.
261
+ - Parameters whose name starts with `_`.
262
+ - The `...` forward-all parameter.
216
263
 
217
264
  **Examples (EnforcedStyle: doc_style):**
218
265
  ```ruby
@@ -440,13 +487,26 @@ end
440
487
 
441
488
  ### Style/RbsInline/RequireRbsInlineComment
442
489
 
443
- Enforces presence or absence of `# rbs_inline:` magic comment for consistency.
490
+ Enforces the presence or absence of the `# rbs_inline:` magic comment, based on the shared [`Mode`](#stylerbsinline-shared-configuration) setting:
444
491
 
445
- **Configuration:** `EnforcedStyle` (default: `always`)
446
- - `always`: Requires `# rbs_inline: enabled` or `# rbs_inline: disabled`
447
- - `never`: Forbids `# rbs_inline: enabled` (allows `# rbs_inline: disabled`)
492
+ - Under `Mode: opt_in`, files must carry `# rbs_inline: enabled` (or `# rbs_inline: disabled`).
493
+ - Under `Mode: opt_out`, files must not carry `# rbs_inline: enabled`.
494
+
495
+ **Configuration:**
496
+
497
+ - `AllowMissingComment` (default: `false`) — under `Mode: opt_in`, set to `true` to skip enforcement of the magic comment on individual files. Useful when only part of the project uses rbs-inline: the shared `Mode: opt_in` filter still applies to every other cop (they only report on files that opt in), but this cop stops requiring the magic comment on files that intentionally do not use rbs-inline.
498
+
499
+ ```yaml
500
+ Style/RbsInline:
501
+ Mode: opt_in
448
502
 
449
- **Examples (EnforcedStyle: always):**
503
+ Style/RbsInline/RequireRbsInlineComment:
504
+ AllowMissingComment: true
505
+ ```
506
+
507
+ The legacy `EnforcedStyle` parameter (`always` / `never`) is deprecated in favor of `Mode`; it will be removed in the next major release.
508
+
509
+ **Examples (Mode: opt_in):**
450
510
  ```ruby
451
511
  # bad
452
512
  class Foo
@@ -458,6 +518,62 @@ class Foo
458
518
  end
459
519
  ```
460
520
 
521
+ **Examples (Mode: opt_in, AllowMissingComment: true):**
522
+ ```ruby
523
+ # good - the magic comment is not enforced per file
524
+ class Foo
525
+ end
526
+
527
+ # good
528
+ # rbs_inline: enabled
529
+ class Foo
530
+ end
531
+ ```
532
+
533
+ ### Style/RbsInline/StructClassCommentAlignment
534
+
535
+ Checks that `#:` inline type annotations in a multiline `Struct.new` call are aligned to the same column. The expected column is determined by the longest attribute name (plus its trailing comma). Folded `Struct.new` calls (where multiple attributes share a line) are excluded.
536
+
537
+ Supports autocorrect.
538
+
539
+ **Examples:**
540
+ ```ruby
541
+ # bad
542
+ Point = Struct.new(
543
+ :x, #: Integer
544
+ :long_attr #: Integer
545
+ )
546
+
547
+ # good
548
+ Point = Struct.new(
549
+ :x, #: Integer
550
+ :long_attr #: Integer
551
+ )
552
+ ```
553
+
554
+ ### Style/RbsInline/StructNewWithBlock
555
+
556
+ Checks for `Struct.new` calls with a block. RBS::Inline does not parse block contents, so any methods defined inside the block will not be recognized for type checking. Instead, call `Struct.new` without a block and reopen the class separately to add methods.
557
+
558
+ **Examples:**
559
+ ```ruby
560
+ # bad
561
+ User = Struct.new(:name, :role) do
562
+ def admin?
563
+ role == :admin
564
+ end
565
+ end
566
+
567
+ # good
568
+ User = Struct.new(:name, :role)
569
+
570
+ class User
571
+ def admin? #: bool
572
+ role == :admin
573
+ end
574
+ end
575
+ ```
576
+
461
577
  ### Style/RbsInline/UnmatchedAnnotations
462
578
 
463
579
  Verifies that annotation parameters match the actual method parameters.
@@ -543,6 +659,8 @@ Style/RbsInline/MissingTypeAnnotation:
543
659
  Visibility: public
544
660
  ```
545
661
 
662
+ To restrict every cop to files that carry `# rbs_inline: enabled`, see [Style/RbsInline (shared configuration)](#stylerbsinline-shared-configuration).
663
+
546
664
  See [config/default.yml](config/default.yml) for all available configuration options.
547
665
 
548
666
  ## Development
data/config/default.yml CHANGED
@@ -5,6 +5,7 @@ Style/RbsInline:
5
5
  Exclude:
6
6
  - 'spec/**/*'
7
7
  - 'test/**/*'
8
+ Mode: ~
8
9
 
9
10
  Style/RbsInline/DataClassCommentAlignment:
10
11
  Description: "Checks that `#:` inline type annotations in `Data.define` blocks are aligned."
@@ -46,6 +47,11 @@ Style/RbsInline/MissingDataClassAnnotation:
46
47
  Enabled: true
47
48
  VersionAdded: '1.5.0'
48
49
 
50
+ Style/RbsInline/MissingStructClassAnnotation:
51
+ Description: "Checks that `Struct.new` attributes have inline type annotations."
52
+ Enabled: true
53
+ VersionAdded: '1.7.0'
54
+
49
55
  Style/RbsInline/MissingTypeAnnotation:
50
56
  Description: "Checks that method definitions and attr_* declarations have RBS inline type annotations."
51
57
  Enabled: true
@@ -89,10 +95,18 @@ Style/RbsInline/RequireRbsInlineComment:
89
95
  Description: "Checks the presence or absence of `# rbs_inline: enabled` magic comment."
90
96
  Enabled: true
91
97
  VersionAdded: "1.5.0"
92
- EnforcedStyle: always
93
- SupportedStyles:
94
- - always
95
- - never
98
+ AllowMissingComment: false
99
+ EnforcedStyle: ~
100
+
101
+ Style/RbsInline/StructClassCommentAlignment:
102
+ Description: "Checks that `#:` inline type annotations in `Struct.new` blocks are aligned."
103
+ Enabled: true
104
+ VersionAdded: '1.7.0'
105
+
106
+ Style/RbsInline/StructNewWithBlock:
107
+ Description: "Checks for `Struct.new` calls with a block, which RBS::Inline does not parse."
108
+ Enabled: true
109
+ VersionAdded: '1.7.0'
96
110
 
97
111
  Style/RbsInline/UnmatchedAnnotations:
98
112
  Description: "Checks the rbs-inline annotation comment is surely matched."
@@ -1,8 +1,19 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require_relative "style/rbs_inline/ast_utils"
4
- require_relative "style/rbs_inline/source_code_helper"
5
- require_relative "style/rbs_inline/comment_parser"
3
+ # Mixins first, in dependency order. Cops follow, alphabetically: `rake new_cop` sorts a
4
+ # generated require only against the others in the same directory, so the two blocks have
5
+ # to stay apart for it to land in the right place.
6
+ require_relative "style/rbs_inline/mixin/annotation_keywords"
7
+ require_relative "style/rbs_inline/mixin/ast_utils"
8
+ require_relative "style/rbs_inline/mixin/source_code_helper"
9
+ require_relative "style/rbs_inline/mixin/comment_parser"
10
+ require_relative "style/rbs_inline/mixin/data_struct_helper"
11
+ require_relative "style/rbs_inline/mixin/missing_class_annotation"
12
+ require_relative "style/rbs_inline/mixin/class_comment_alignment"
13
+ require_relative "style/rbs_inline/mixin/data_class_matcher"
14
+ require_relative "style/rbs_inline/mixin/file_filter"
15
+ require_relative "style/rbs_inline/mixin/struct_class_matcher"
16
+
6
17
  require_relative "style/rbs_inline/data_class_comment_alignment"
7
18
  require_relative "style/rbs_inline/data_define_with_block"
8
19
  require_relative "style/rbs_inline/embedded_rbs_spacing"
@@ -11,12 +22,15 @@ require_relative "style/rbs_inline/invalid_types"
11
22
  require_relative "style/rbs_inline/keyword_separator"
12
23
  require_relative "style/rbs_inline/method_comment_spacing"
13
24
  require_relative "style/rbs_inline/missing_data_class_annotation"
25
+ require_relative "style/rbs_inline/missing_struct_class_annotation"
14
26
  require_relative "style/rbs_inline/missing_type_annotation"
15
27
  require_relative "style/rbs_inline/parameters_separator"
16
28
  require_relative "style/rbs_inline/redundant_annotation_with_skip"
17
29
  require_relative "style/rbs_inline/redundant_instance_variable_annotation"
18
30
  require_relative "style/rbs_inline/redundant_type_annotation"
19
31
  require_relative "style/rbs_inline/require_rbs_inline_comment"
32
+ require_relative "style/rbs_inline/struct_class_comment_alignment"
33
+ require_relative "style/rbs_inline/struct_new_with_block"
20
34
  require_relative "style/rbs_inline/unmatched_annotations"
21
35
  require_relative "style/rbs_inline/untyped_instance_variable"
22
36
  require_relative "style/rbs_inline/variable_comment_spacing"
@@ -26,106 +26,18 @@ module RuboCop
26
26
  # )
27
27
  #
28
28
  class DataClassCommentAlignment < Base
29
- include ASTUtils
30
- include RangeHelp
31
- include SourceCodeHelper
29
+ prepend FileFilter
30
+ include ClassCommentAlignment
31
+ include DataClassMatcher
32
32
  extend AutoCorrector
33
33
 
34
34
  MSG = "Misaligned inline type annotation for Data attribute."
35
35
 
36
36
  # @rbs node: RuboCop::AST::SendNode
37
37
  def on_send(node) #: void
38
- return unless data_define?(node)
39
- return if folded_data_class?(node)
38
+ return unless struct_like_class?(node)
40
39
 
41
- check_annotation_alignment(node)
42
- end
43
-
44
- private
45
-
46
- # @rbs node: RuboCop::AST::SendNode
47
- def data_define?(node) #: bool
48
- return false unless node.method_name == :define
49
-
50
- case (r = node.receiver)
51
- when RuboCop::AST::ConstNode
52
- r.short_name == :Data
53
- else
54
- false
55
- end
56
- end
57
-
58
- # @rbs arg: RuboCop::AST::Node
59
- def data_attribute?(arg) #: bool
60
- arg.sym_type? || arg.str_type?
61
- end
62
-
63
- # @rbs node: RuboCop::AST::SendNode
64
- def data_attributes(node) #: Array[RuboCop::AST::Node]
65
- node.arguments.select { data_attribute?(_1) }
66
- end
67
-
68
- # @rbs node: RuboCop::AST::SendNode
69
- def folded_data_class?(node) #: bool
70
- args = node.arguments
71
- return false if args.empty?
72
-
73
- lines = args.map { _1.location.line }
74
- lines.uniq.length < args.length || lines.include?(node.location.line)
75
- end
76
-
77
- # @rbs node: RuboCop::AST::SendNode
78
- def check_annotation_alignment(node) #: void
79
- annotated = data_attributes(node).filter_map do |arg|
80
- comment = inline_type_comment(arg.location.line)
81
- [arg, comment] if comment #: [RuboCop::AST::Node, Parser::Source::Comment]
82
- end
83
- return if annotated.size < 2
84
-
85
- expected_col = annotation_column(node)
86
- annotated.each do |arg, comment|
87
- actual_col = comment.location.column
88
- next if actual_col == expected_col
89
-
90
- add_offense(comment.source_range) do |corrector|
91
- correct_alignment(corrector, arg, comment, expected_col)
92
- end
93
- end
94
- end
95
-
96
- # @rbs line: Integer
97
- def inline_type_comment(line) #: Parser::Source::Comment?
98
- comment = comment_at(line)
99
- comment if comment&.text&.match?(/\A#:/)
100
- end
101
-
102
- # @rbs node: RuboCop::AST::SendNode
103
- def annotation_column(node) #: Integer
104
- last_arg = node.arguments.last
105
- max_end_col = node.arguments.map do |arg|
106
- comma_length = arg.equal?(last_arg) ? 0 : 1
107
- arg.location.column + source!(arg).length + comma_length
108
- end.max || 0
109
-
110
- max_end_col + 2
111
- end
112
-
113
- # @rbs corrector: RuboCop::Cop::Corrector
114
- # @rbs arg: RuboCop::AST::Node
115
- # @rbs comment: Parser::Source::Comment
116
- # @rbs expected_col: Integer
117
- def correct_alignment(corrector, arg, comment, expected_col) #: void # rubocop:disable Metrics/AbcSize
118
- line = arg.location.line
119
- line_source = source_code_at(line)
120
- source = line_source[...comment.location.column] || raise
121
- content_end_col = source.rstrip.length
122
- padding = [expected_col - content_end_col, 1].max || raise
123
-
124
- line_begin = processed_source.buffer.line_range(line).begin_pos
125
- replace_start = line_begin + content_end_col
126
- replace_end = line_begin + comment.location.column
127
-
128
- corrector.replace(range_between(replace_start, replace_end), " " * padding)
40
+ check_comment_alignment(node)
129
41
  end
130
42
  end
131
43
  end
@@ -28,29 +28,22 @@ module RuboCop
28
28
  # end
29
29
  #
30
30
  class DataDefineWithBlock < Base
31
+ prepend FileFilter
32
+ include DataClassMatcher
33
+
31
34
  MSG = "Do not use `Data.define` with a block. RBS::Inline does not parse block contents, " \
32
35
  "so methods defined in the block will not be recognized. " \
33
36
  "Use a separate class definition instead."
34
37
 
35
38
  # @rbs node: RuboCop::AST::SendNode
36
39
  def on_send(node) #: void
37
- return unless data_define?(node)
40
+ return unless struct_like_class?(node)
38
41
 
39
42
  block_node = node.parent
40
43
  return unless block_node&.block_type?
41
44
 
42
45
  add_offense(node)
43
46
  end
44
-
45
- private
46
-
47
- # @rbs node: RuboCop::AST::SendNode
48
- def data_define?(node) #: bool
49
- return false unless node.method_name == :define
50
-
51
- receiver = node.receiver
52
- receiver.is_a?(RuboCop::AST::ConstNode) && receiver.short_name == :Data
53
- end
54
47
  end
55
48
  end
56
49
  end
@@ -1,7 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require_relative "source_code_helper"
4
- require_relative "comment_parser"
3
+ require_relative "mixin/source_code_helper"
4
+ require_relative "mixin/comment_parser"
5
5
 
6
6
  module RuboCop
7
7
  module Cop
@@ -26,6 +26,7 @@ module RuboCop
26
26
  # end
27
27
  #
28
28
  class EmbeddedRbsSpacing < Base
29
+ prepend FileFilter
29
30
  extend AutoCorrector
30
31
  include SourceCodeHelper
31
32
  include CommentParser
@@ -20,12 +20,12 @@ module RuboCop
20
20
  # # @rbs param: String
21
21
  #
22
22
  class InvalidComment < Base
23
+ prepend FileFilter
23
24
  extend AutoCorrector
25
+ include AnnotationKeywords
24
26
 
25
27
  MSG = "Invalid RBS annotation comment found."
26
28
 
27
- # refs: https://github.com/soutaro/rbs-inline/blob/main/lib/rbs/inline/annotation_parser/tokenizer.rb
28
- RBS_INLINE_KEYWORDS = %w[inherits override use module-self generic skip module class].freeze #: Array[String]
29
29
  RBS_INLINE_KEYWORD_PATTERN = RBS_INLINE_KEYWORDS.join("|") #: String
30
30
 
31
31
  SIGNATURE_PATTERN = '\(.*\)\s*(\??\s*{.*?}\s*)?->\s*.*'