rubocop-rbs_inline 1.7.0 → 2.0.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 (82) hide show
  1. checksums.yaml +4 -4
  2. data/.claude/hooks/pre-commit-check.sh +42 -2
  3. data/.claude/hooks/protect-sig-files.sh +45 -0
  4. data/.claude/hooks/self-review.sh +89 -0
  5. data/.claude/settings.json +16 -1
  6. data/.rubocop.yml +17 -12
  7. data/CHANGELOG.md +30 -0
  8. data/CLAUDE.md +11 -14
  9. data/README.md +51 -6
  10. data/Rakefile +12 -3
  11. data/config/default.yml +2 -4
  12. data/lib/rubocop/cop/rbs_inline_cops.rb +16 -7
  13. data/lib/rubocop/cop/style/rbs_inline/data_class_comment_alignment.rb +2 -9
  14. data/lib/rubocop/cop/style/rbs_inline/data_define_with_block.rb +13 -7
  15. data/lib/rubocop/cop/style/rbs_inline/embedded_rbs_spacing.rb +3 -2
  16. data/lib/rubocop/cop/style/rbs_inline/invalid_comment.rb +2 -2
  17. data/lib/rubocop/cop/style/rbs_inline/invalid_types.rb +1 -0
  18. data/lib/rubocop/cop/style/rbs_inline/keyword_separator.rb +2 -2
  19. data/lib/rubocop/cop/style/rbs_inline/method_comment_spacing.rb +3 -2
  20. data/lib/rubocop/cop/style/rbs_inline/missing_data_class_annotation.rb +2 -9
  21. data/lib/rubocop/cop/style/rbs_inline/missing_struct_class_annotation.rb +2 -16
  22. data/lib/rubocop/cop/style/rbs_inline/missing_type_annotation.rb +3 -16
  23. data/lib/rubocop/cop/style/rbs_inline/mixin/annotation_keywords.rb +16 -0
  24. data/lib/rubocop/cop/style/rbs_inline/mixin/ast_utils.rb +75 -0
  25. data/lib/rubocop/cop/style/rbs_inline/{comment_parser.rb → mixin/comment_parser.rb} +8 -0
  26. data/lib/rubocop/cop/style/rbs_inline/mixin/data_class_matcher.rb +22 -0
  27. data/lib/rubocop/cop/style/rbs_inline/{data_struct_helper.rb → mixin/data_struct_helper.rb} +14 -5
  28. data/lib/rubocop/cop/style/rbs_inline/mixin/file_filter.rb +72 -0
  29. data/lib/rubocop/cop/style/rbs_inline/mixin/mode_config.rb +39 -0
  30. data/lib/rubocop/cop/style/rbs_inline/{source_code_helper.rb → mixin/source_code_helper.rb} +3 -5
  31. data/lib/rubocop/cop/style/rbs_inline/mixin/struct_class_matcher.rb +32 -0
  32. data/lib/rubocop/cop/style/rbs_inline/parameters_separator.rb +2 -2
  33. data/lib/rubocop/cop/style/rbs_inline/redundant_annotation_with_skip.rb +2 -11
  34. data/lib/rubocop/cop/style/rbs_inline/redundant_instance_variable_annotation.rb +1 -0
  35. data/lib/rubocop/cop/style/rbs_inline/redundant_type_annotation.rb +6 -15
  36. data/lib/rubocop/cop/style/rbs_inline/require_rbs_inline_comment.rb +35 -23
  37. data/lib/rubocop/cop/style/rbs_inline/struct_class_comment_alignment.rb +2 -16
  38. data/lib/rubocop/cop/style/rbs_inline/struct_new_with_block.rb +13 -7
  39. data/lib/rubocop/cop/style/rbs_inline/unmatched_annotations.rb +31 -6
  40. data/lib/rubocop/cop/style/rbs_inline/untyped_instance_variable.rb +16 -6
  41. data/lib/rubocop/cop/style/rbs_inline/variable_comment_spacing.rb +3 -2
  42. data/lib/rubocop/rbs_inline/version.rb +1 -1
  43. data/rbs_collection.lock.yaml +11 -11
  44. data/sig/gems/rubocop/rubocop.rbs +14 -0
  45. data/sig/rubocop/cop/style/rbs_inline/data_class_comment_alignment.rbs +4 -5
  46. data/sig/rubocop/cop/style/rbs_inline/data_define_with_block.rbs +9 -2
  47. data/sig/rubocop/cop/style/rbs_inline/embedded_rbs_spacing.rbs +2 -0
  48. data/sig/rubocop/cop/style/rbs_inline/invalid_comment.rbs +4 -3
  49. data/sig/rubocop/cop/style/rbs_inline/invalid_types.rbs +2 -0
  50. data/sig/rubocop/cop/style/rbs_inline/keyword_separator.rbs +4 -3
  51. data/sig/rubocop/cop/style/rbs_inline/method_comment_spacing.rbs +2 -0
  52. data/sig/rubocop/cop/style/rbs_inline/missing_data_class_annotation.rbs +4 -5
  53. data/sig/rubocop/cop/style/rbs_inline/missing_struct_class_annotation.rbs +4 -10
  54. data/sig/rubocop/cop/style/rbs_inline/missing_type_annotation.rbs +4 -8
  55. data/sig/rubocop/cop/style/rbs_inline/mixin/annotation_keywords.rbs +16 -0
  56. data/sig/rubocop/cop/style/rbs_inline/{ast_utils.rbs → mixin/ast_utils.rbs} +14 -1
  57. data/sig/rubocop/cop/style/rbs_inline/{class_comment_alignment.rbs → mixin/class_comment_alignment.rbs} +1 -1
  58. data/sig/rubocop/cop/style/rbs_inline/{comment_parser.rbs → mixin/comment_parser.rbs} +5 -1
  59. data/sig/rubocop/cop/style/rbs_inline/mixin/data_class_matcher.rbs +18 -0
  60. data/sig/rubocop/cop/style/rbs_inline/{data_struct_helper.rbs → mixin/data_struct_helper.rbs} +6 -1
  61. data/sig/rubocop/cop/style/rbs_inline/mixin/file_filter.rbs +53 -0
  62. data/sig/rubocop/cop/style/rbs_inline/{missing_class_annotation.rbs → mixin/missing_class_annotation.rbs} +1 -1
  63. data/sig/rubocop/cop/style/rbs_inline/mixin/mode_config.rbs +27 -0
  64. data/sig/rubocop/cop/style/rbs_inline/{source_code_helper.rbs → mixin/source_code_helper.rbs} +1 -1
  65. data/sig/rubocop/cop/style/rbs_inline/mixin/struct_class_matcher.rbs +26 -0
  66. data/sig/rubocop/cop/style/rbs_inline/parameters_separator.rbs +4 -3
  67. data/sig/rubocop/cop/style/rbs_inline/redundant_annotation_with_skip.rbs +4 -4
  68. data/sig/rubocop/cop/style/rbs_inline/redundant_instance_variable_annotation.rbs +2 -0
  69. data/sig/rubocop/cop/style/rbs_inline/redundant_type_annotation.rbs +4 -4
  70. data/sig/rubocop/cop/style/rbs_inline/require_rbs_inline_comment.rbs +22 -5
  71. data/sig/rubocop/cop/style/rbs_inline/struct_class_comment_alignment.rbs +4 -10
  72. data/sig/rubocop/cop/style/rbs_inline/struct_new_with_block.rbs +9 -2
  73. data/sig/rubocop/cop/style/rbs_inline/unmatched_annotations.rbs +13 -0
  74. data/sig/rubocop/cop/style/rbs_inline/untyped_instance_variable.rbs +10 -0
  75. data/sig/rubocop/cop/style/rbs_inline/variable_comment_spacing.rbs +2 -0
  76. metadata +28 -18
  77. data/lib/rubocop/cop/style/rbs_inline/ast_utils.rb +0 -46
  78. data/sig/rubocop/cop/style/rbs_inline/redundant_argument_type.rbs +0 -85
  79. data/sig/rubocop/cop/style/rbs_inline/redundant_return_type.rbs +0 -109
  80. /data/.claude/hooks/{setup.sh → claude-code-web-session-start.sh} +0 -0
  81. /data/lib/rubocop/cop/style/rbs_inline/{class_comment_alignment.rb → mixin/class_comment_alignment.rb} +0 -0
  82. /data/lib/rubocop/cop/style/rbs_inline/{missing_class_annotation.rb → mixin/missing_class_annotation.rb} +0 -0
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 5f1bf0f9d17e73042a1c33ae69ffe0cf079bedb1423119b36a524d085cfe6f6a
4
- data.tar.gz: '0797210c998bb891bcd07b68f3d4cc9e779cf775ea051e6529c8b052ed58642a'
3
+ metadata.gz: 6bb68dac2a82e53e5c2b8640e5feab967ca592c9a91927ed91f921c705629c1e
4
+ data.tar.gz: c35f3396438d8776982b2907e5d7cfe8d72f902b20e041158ba985a05f43aa41
5
5
  SHA512:
6
- metadata.gz: bb484322f4605db7c86dddf047de1ad1dbe477faf11b7d736edaa544132b87c1b7c0481cd13a6154662a0e318daeaf4880102882288cf02b84ba88eb8d3372f5
7
- data.tar.gz: 141d659c28c9c9b2bef362b7f5b57bccf68aaec436b553e19785d97d162a0293c94d0d79ad7d29b7300f0b2583cce353c3152503ec965ab521eeff6fe3bb1127
6
+ metadata.gz: d53b2590db9f7dad7887c594c687fd51e9470d7c6a6c2dfbf76b8b002389fb5c5410d05477a4e6956e5ff0ca5cc9bddecee970eb81e7e5af563091bea86348af
7
+ data.tar.gz: f3d1f4a2c7eec7e5cf6a6a1c9bb397a8cde822d9056df9c7e45fef360f244b8731c5afb5b6feaac3f97f664f058140e5c173c344186e85263df1efa667332f7f
@@ -18,12 +18,52 @@ fi
18
18
 
19
19
  echo "Running pre-commit checks..." >&2
20
20
 
21
- # Generate RBS and run all checks
22
- if ! bundle exec rbs-inline --opt-out --output=sig/ lib/ >&2; then
21
+ # Regenerate RBS from scratch so that files orphaned by deleted lib/ sources are dropped too
22
+ if ! bundle exec rake rbs:regenerate >&2; then
23
23
  echo "Error: RBS generation failed" >&2
24
24
  exit 2
25
25
  fi
26
26
 
27
+ # True when $command stages the whole of sig/ itself, which the check below cannot see because
28
+ # this hook runs first. Staging only part of sig/ does not count - the rest still drops out.
29
+ stages_sig() {
30
+ local segment tokens token stages_everything pathspec_given
31
+
32
+ while IFS= read -r segment; do
33
+ segment=${segment//[\"\']/}
34
+ read -ra tokens <<<"${segment#*add}"
35
+ stages_everything=0
36
+ pathspec_given=0
37
+
38
+ for token in "${tokens[@]}"; do
39
+ case "$token" in
40
+ --) ;;
41
+ -*A*|--all) stages_everything=1 ;;
42
+ -*) ;;
43
+ .|./|sig|sig/|./sig|./sig/) return 0 ;;
44
+ *) pathspec_given=1 ;;
45
+ esac
46
+ done
47
+
48
+ # -A alone stages every path; given a pathspec it stages that path only
49
+ [ "$stages_everything" = 1 ] && [ "$pathspec_given" = 0 ] && return 0
50
+ done < <(grep -oE '(^|[;&|(])[[:space:]]*git[[:space:]]+add[^;&|(]*' <<<"$command")
51
+
52
+ return 1
53
+ }
54
+
55
+ # Stop rather than let the RBS just regenerated above drop out of the commit unnoticed
56
+ if [[ "$command" =~ git[[:space:]]+commit([[:space:]]|$) ]] && ! stages_sig; then
57
+ unstaged_sig=$(git status --porcelain --untracked-files=all -- sig/ | awk 'substr($0, 2, 1) != " "')
58
+
59
+ if [ -n "$unstaged_sig" ]; then
60
+ echo "Error: sig/ has changes that are not staged:" >&2
61
+ echo "$unstaged_sig" >&2
62
+ echo "Run 'git add sig/' and commit again." >&2
63
+ exit 2
64
+ fi
65
+ fi
66
+
27
67
  if ! bundle exec rake >&2; then
28
68
  echo "Error: rake checks failed" >&2
29
69
  exit 2
@@ -0,0 +1,45 @@
1
+ #!/bin/bash
2
+ # Prevents direct editing of auto-generated RBS type definition files.
3
+ # Type definitions in the project's own sig/ are managed by PostToolUse hooks.
4
+ # Hand-written stubs for third-party gems under sig/gems/ are exempt.
5
+
6
+ # Hook input is JSON from stdin
7
+ input=$(cat)
8
+ tool_name=$(echo "$input" | jq -r '.tool_name // ""')
9
+
10
+ # Block Write/Edit operations on sig/ files
11
+ if [[ "$tool_name" == "Write" || "$tool_name" == "Edit" ]]; then
12
+ file_path=$(echo "$input" | jq -r '.tool_input.file_path // ""')
13
+
14
+ # Judge the path relative to the project root: only the sig/ there is
15
+ # generated. A sig/ nested elsewhere is hand-written and stays editable.
16
+ project_root="${CLAUDE_PROJECT_DIR:-$PWD}"
17
+ rel_path="${file_path#"${project_root}/"}"
18
+
19
+ if [[ "$rel_path" == sig/gems/* ]]; then
20
+ exit 0
21
+ fi
22
+
23
+ if [[ "$rel_path" == sig/* ]]; then
24
+ echo "❌ ERROR: Direct editing of generated sig/ files is not allowed." >&2
25
+ echo "📝 This repository's own types are generated: fix the inline RBS comments in the .rb file." >&2
26
+ echo "💡 Hand-written files are allowed only under sig/gems/ (stubs for third-party gems)." >&2
27
+ exit 2
28
+ fi
29
+ fi
30
+
31
+ # Block manual rbs-inline execution
32
+ if [[ "$tool_name" == "Bash" ]]; then
33
+ command=$(echo "$input" | jq -r '.tool_input.command // ""')
34
+
35
+ # Match rbs-inline only at a command position, not as an argument.
36
+ rbs_inline_re=$'(^|[;&|(\n])[[:space:]]*(bundle[[:space:]]+exec[[:space:]]+)?([^[:space:]]*/)?rbs-inline([[:space:]]|[;&|)]|$)'
37
+
38
+ if [[ "$command" =~ $rbs_inline_re ]]; then
39
+ echo "❌ ERROR: Manual rbs-inline execution is not allowed." >&2
40
+ echo "🔄 Type definitions are auto-generated by hooks." >&2
41
+ exit 2
42
+ fi
43
+ fi
44
+
45
+ exit 0
@@ -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
@@ -6,19 +6,34 @@
6
6
  "hooks": [
7
7
  {
8
8
  "type": "command",
9
- "command": "\"$CLAUDE_PROJECT_DIR\"/.claude/hooks/setup.sh",
9
+ "command": "\"$CLAUDE_PROJECT_DIR\"/.claude/hooks/claude-code-web-session-start.sh",
10
10
  "timeout": 300
11
11
  }
12
12
  ]
13
13
  }
14
14
  ],
15
15
  "PreToolUse": [
16
+ {
17
+ "matcher": "Edit|Write|Bash",
18
+ "hooks": [
19
+ {
20
+ "type": "command",
21
+ "command": "\"$CLAUDE_PROJECT_DIR\"/.claude/hooks/protect-sig-files.sh",
22
+ "timeout": 5000
23
+ }
24
+ ]
25
+ },
16
26
  {
17
27
  "matcher": "Bash",
18
28
  "hooks": [
19
29
  {
20
30
  "type": "command",
21
31
  "command": "\"$CLAUDE_PROJECT_DIR\"/.claude/hooks/pre-commit-check.sh"
32
+ },
33
+ {
34
+ "type": "command",
35
+ "command": "\"$CLAUDE_PROJECT_DIR\"/.claude/hooks/self-review.sh",
36
+ "timeout": 10000
22
37
  }
23
38
  ]
24
39
  }
data/.rubocop.yml CHANGED
@@ -44,27 +44,23 @@ Naming/FileName:
44
44
  RSpec/ExampleLength:
45
45
  Enabled: false
46
46
 
47
- RSpec/MultipleMemoizedHelpers:
48
- Max: 10
49
-
50
47
  RSpec/MultipleExpectations:
51
48
  Enabled: false
52
49
 
50
+ RSpec/MultipleMemoizedHelpers:
51
+ Max: 10
52
+
53
53
  RSpec/NamedSubject:
54
54
  Enabled: false
55
55
 
56
56
  RSpec/NestedGroups:
57
57
  Max: 5
58
58
 
59
- # RbsInline
60
- Style/RbsInline/MissingTypeAnnotation:
61
- EnforcedStyle: doc_style_and_return_annotation
62
-
63
- Style/RbsInline/RedundantTypeAnnotation:
64
- EnforcedStyle: doc_style_and_return_annotation
65
-
66
- Style/RbsInline/RequireRbsInlineComment:
67
- EnforcedStyle: never
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"
68
64
 
69
65
  # Style
70
66
  Style/AccessorGrouping:
@@ -79,6 +75,15 @@ Style/Documentation:
79
75
  Style/HashSyntax:
80
76
  EnforcedShorthandSyntax: always
81
77
 
78
+ Style/RbsInline:
79
+ Mode: opt_out
80
+
81
+ Style/RbsInline/MissingTypeAnnotation:
82
+ EnforcedStyle: doc_style_and_return_annotation
83
+
84
+ Style/RbsInline/RedundantTypeAnnotation:
85
+ EnforcedStyle: doc_style_and_return_annotation
86
+
82
87
  Style/StringLiterals:
83
88
  EnforcedStyle: double_quotes
84
89
 
data/CHANGELOG.md CHANGED
@@ -1,5 +1,35 @@
1
1
  # Changelog
2
2
 
3
+ ## 2.0.0 (2026-08-05)
4
+
5
+ ### Breaking Changes
6
+
7
+ - **Style/RbsInline**: `Mode` now defaults to `opt_in`, and any value other than `opt_in` / `opt_out` fails the run.
8
+ - **Style/RbsInline/RequireRbsInlineComment**: Removed the deprecated `EnforcedStyle`. Use `Style/RbsInline: Mode` instead: `always` → `opt_in`, `never` → `opt_out`.
9
+
10
+ ### Enhancements
11
+
12
+ - **Style/RbsInline/DataDefineWithBlock**, **Style/RbsInline/StructNewWithBlock**: Clarified the offense message: it now says to keep the `Data.define` / `Struct.new` call and move the block's methods into a class that reopens it, and names that class when the definition is assigned to a constant.
13
+
14
+ ## 1.8.0 (2026-07-26)
15
+
16
+ ### Enhancements
17
+
18
+ - **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.
19
+ - **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.
20
+
21
+ ### Bug Fixes
22
+
23
+ - **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.
24
+ - **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:`).
25
+ - **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.
26
+ - **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.
27
+ - **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.
28
+
29
+ ### Deprecations
30
+
31
+ - **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.
32
+
3
33
  ## 1.7.0 (2026-07-20)
4
34
 
5
35
  ### New Features
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,29 @@ 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`, which reads `Mode` through `ModeConfig` instead — it has to run on the files the filter skips.
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
+
58
+ The department-level `Mode` is not merged in either, and the cops have no default for it, so `spec_helper.rb` declares `Mode: opt_out` for every `:config` spec, and `rbs_inline_config` for the specs written against the default configuration. A spec that builds its own config has to declare it itself.
59
+
63
60
  ## Code Style Notes
64
61
 
65
62
  - `Layout/LeadingCommentSpace` and `Style/CommentedKeyword` are disabled because RBS::Inline `#:` comments violate these rules by design.
data/README.md CHANGED
@@ -27,6 +27,22 @@ 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` (default) — 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
+
37
+ Declare it once at the department level in your `.rubocop.yml`:
38
+
39
+ ```yaml
40
+ Style/RbsInline:
41
+ Mode: opt_out
42
+ ```
43
+
44
+ Any other value (e.g. `Mode: opt-in`) fails the run with a configuration error.
45
+
30
46
  ### Style/RbsInline/DataClassCommentAlignment
31
47
 
32
48
  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.
@@ -238,7 +254,11 @@ Enforces that method definitions and `attr_*` declarations have RBS inline type
238
254
  - `all`: Checks all methods regardless of visibility
239
255
  - `public`: Only checks public methods and `attr_*` declarations
240
256
 
241
- Methods annotated with `# @rbs skip` are always excluded.
257
+ Always excluded, regardless of `EnforcedStyle`:
258
+
259
+ - Methods annotated with `# @rbs skip` or `# @rbs override`.
260
+ - Parameters whose name starts with `_`.
261
+ - The `...` forward-all parameter.
242
262
 
243
263
  **Examples (EnforcedStyle: doc_style):**
244
264
  ```ruby
@@ -466,13 +486,24 @@ end
466
486
 
467
487
  ### Style/RbsInline/RequireRbsInlineComment
468
488
 
469
- Enforces presence or absence of `# rbs_inline:` magic comment for consistency.
489
+ Enforces the presence or absence of the `# rbs_inline:` magic comment, based on the shared [`Mode`](#stylerbsinline-shared-configuration) setting:
470
490
 
471
- **Configuration:** `EnforcedStyle` (default: `always`)
472
- - `always`: Requires `# rbs_inline: enabled` or `# rbs_inline: disabled`
473
- - `never`: Forbids `# rbs_inline: enabled` (allows `# rbs_inline: disabled`)
491
+ - Under `Mode: opt_in`, files must carry `# rbs_inline: enabled` (or `# rbs_inline: disabled`).
492
+ - Under `Mode: opt_out`, files must not carry `# rbs_inline: enabled`.
493
+
494
+ **Configuration:**
495
+
496
+ - `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.
497
+
498
+ ```yaml
499
+ Style/RbsInline:
500
+ Mode: opt_in
474
501
 
475
- **Examples (EnforcedStyle: always):**
502
+ Style/RbsInline/RequireRbsInlineComment:
503
+ AllowMissingComment: true
504
+ ```
505
+
506
+ **Examples (Mode: opt_in):**
476
507
  ```ruby
477
508
  # bad
478
509
  class Foo
@@ -484,6 +515,18 @@ class Foo
484
515
  end
485
516
  ```
486
517
 
518
+ **Examples (Mode: opt_in, AllowMissingComment: true):**
519
+ ```ruby
520
+ # good - the magic comment is not enforced per file
521
+ class Foo
522
+ end
523
+
524
+ # good
525
+ # rbs_inline: enabled
526
+ class Foo
527
+ end
528
+ ```
529
+
487
530
  ### Style/RbsInline/StructClassCommentAlignment
488
531
 
489
532
  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.
@@ -613,6 +656,8 @@ Style/RbsInline/MissingTypeAnnotation:
613
656
  Visibility: public
614
657
  ```
615
658
 
659
+ To check every file rather than only those that carry `# rbs_inline: enabled`, see [Style/RbsInline (shared configuration)](#stylerbsinline-shared-configuration).
660
+
616
661
  See [config/default.yml](config/default.yml) for all available configuration options.
617
662
 
618
663
  ## Development
data/Rakefile CHANGED
@@ -35,14 +35,23 @@ task :new_cop, [:cop] do |_task, args|
35
35
  end
36
36
 
37
37
  namespace :rbs do
38
- desc "Install RBS signatures"
38
+ # sig/gems holds hand-written types for external gems, so it is never regenerated
39
+ kept = ->(path) { path == "sig/gems" || path.start_with?("sig/gems/") }
40
+
41
+ desc "Install RBS collection"
39
42
  task :install do
40
43
  sh "bundle", "exec", "rbs", "collection", "install", "--frozen"
41
44
  end
42
45
 
43
- desc "Generate RBS files"
44
- task :generate do
46
+ desc "Regenerate all RBS files under sig/ from scratch (run manually after editing lib/)"
47
+ task :regenerate do
48
+ Dir.glob("sig/**/*.rbs").reject(&kept).each { rm _1 }
49
+
45
50
  sh "rbs-inline", "--opt-out", "--output=sig", "lib"
51
+
52
+ # Drop the directories the deletion above left empty, deepest first
53
+ dirs = Dir.glob("sig/**/*").select { File.directory?(_1) }
54
+ dirs.reject(&kept).sort.reverse_each { rmdir _1 if Dir.empty?(_1) }
46
55
  end
47
56
 
48
57
  desc "Validate RBS files"
data/config/default.yml CHANGED
@@ -5,6 +5,7 @@ Style/RbsInline:
5
5
  Exclude:
6
6
  - 'spec/**/*'
7
7
  - 'test/**/*'
8
+ Mode: opt_in
8
9
 
9
10
  Style/RbsInline/DataClassCommentAlignment:
10
11
  Description: "Checks that `#:` inline type annotations in `Data.define` blocks are aligned."
@@ -94,10 +95,7 @@ Style/RbsInline/RequireRbsInlineComment:
94
95
  Description: "Checks the presence or absence of `# rbs_inline: enabled` magic comment."
95
96
  Enabled: true
96
97
  VersionAdded: "1.5.0"
97
- EnforcedStyle: always
98
- SupportedStyles:
99
- - always
100
- - never
98
+ AllowMissingComment: false
101
99
 
102
100
  Style/RbsInline/StructClassCommentAlignment:
103
101
  Description: "Checks that `#:` inline type annotations in `Struct.new` blocks are aligned."
@@ -1,11 +1,20 @@
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"
6
- require_relative "style/rbs_inline/data_struct_helper"
7
- require_relative "style/rbs_inline/missing_class_annotation"
8
- require_relative "style/rbs_inline/class_comment_alignment"
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/mode_config"
15
+ require_relative "style/rbs_inline/mixin/file_filter"
16
+ require_relative "style/rbs_inline/mixin/struct_class_matcher"
17
+
9
18
  require_relative "style/rbs_inline/data_class_comment_alignment"
10
19
  require_relative "style/rbs_inline/data_define_with_block"
11
20
  require_relative "style/rbs_inline/embedded_rbs_spacing"
@@ -14,6 +23,7 @@ require_relative "style/rbs_inline/invalid_types"
14
23
  require_relative "style/rbs_inline/keyword_separator"
15
24
  require_relative "style/rbs_inline/method_comment_spacing"
16
25
  require_relative "style/rbs_inline/missing_data_class_annotation"
26
+ require_relative "style/rbs_inline/missing_struct_class_annotation"
17
27
  require_relative "style/rbs_inline/missing_type_annotation"
18
28
  require_relative "style/rbs_inline/parameters_separator"
19
29
  require_relative "style/rbs_inline/redundant_annotation_with_skip"
@@ -22,7 +32,6 @@ require_relative "style/rbs_inline/redundant_type_annotation"
22
32
  require_relative "style/rbs_inline/require_rbs_inline_comment"
23
33
  require_relative "style/rbs_inline/struct_class_comment_alignment"
24
34
  require_relative "style/rbs_inline/struct_new_with_block"
25
- require_relative "style/rbs_inline/missing_struct_class_annotation"
26
35
  require_relative "style/rbs_inline/unmatched_annotations"
27
36
  require_relative "style/rbs_inline/untyped_instance_variable"
28
37
  require_relative "style/rbs_inline/variable_comment_spacing"
@@ -26,7 +26,9 @@ module RuboCop
26
26
  # )
27
27
  #
28
28
  class DataClassCommentAlignment < Base
29
+ prepend FileFilter
29
30
  include ClassCommentAlignment
31
+ include DataClassMatcher
30
32
  extend AutoCorrector
31
33
 
32
34
  MSG = "Misaligned inline type annotation for Data attribute."
@@ -37,15 +39,6 @@ module RuboCop
37
39
 
38
40
  check_comment_alignment(node)
39
41
  end
40
-
41
- private
42
-
43
- # @rbs node: RuboCop::AST::SendNode
44
- def struct_like_class?(node) #: bool
45
- return false unless node.method_name == :define
46
-
47
- (r = node.receiver).is_a?(RuboCop::AST::ConstNode) && r.short_name == :Data
48
- end
49
42
  end
50
43
  end
51
44
  end
@@ -28,9 +28,13 @@ module RuboCop
28
28
  # end
29
29
  #
30
30
  class DataDefineWithBlock < Base
31
+ prepend FileFilter
32
+ include DataClassMatcher
33
+ include ASTUtils
34
+
31
35
  MSG = "Do not use `Data.define` with a block. RBS::Inline does not parse block contents, " \
32
- "so methods defined in the block will not be recognized. " \
33
- "Use a separate class definition instead."
36
+ "so methods defined in the block will not be recognized. Keep the `Data.define` call " \
37
+ "and move the methods into %<destination>s."
34
38
 
35
39
  # @rbs node: RuboCop::AST::SendNode
36
40
  def on_send(node) #: void
@@ -39,16 +43,18 @@ module RuboCop
39
43
  block_node = node.parent
40
44
  return unless block_node&.block_type?
41
45
 
42
- add_offense(node)
46
+ add_offense(node, message: format(MSG, destination: destination(block_node)))
43
47
  end
44
48
 
45
49
  private
46
50
 
47
- # @rbs node: RuboCop::AST::SendNode
48
- def struct_like_class?(node) #: bool
49
- return false unless node.method_name == :define
51
+ # The constant the definition is assigned to is the class to reopen.
52
+ # @rbs node: RuboCop::AST::Node
53
+ def destination(node) #: String
54
+ class_name = assigned_constant_name(node)
55
+ return "a `class` that reopens it" unless class_name
50
56
 
51
- (r = node.receiver).is_a?(RuboCop::AST::ConstNode) && r.short_name == :Data
57
+ "`class #{class_name} ... end` written after it"
52
58
  end
53
59
  end
54
60
  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