rubocop-rbs_inline 1.8.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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: e4bebfc6eae9ec29990f1dafdf2fc773737b2cf001c450702b138b1d9ab81dec
4
- data.tar.gz: f9ebb04f3712f5f02cd6b7388ea74b056fdce19027be61ed6b651cf1b5915ca8
3
+ metadata.gz: 6bb68dac2a82e53e5c2b8640e5feab967ca592c9a91927ed91f921c705629c1e
4
+ data.tar.gz: c35f3396438d8776982b2907e5d7cfe8d72f902b20e041158ba985a05f43aa41
5
5
  SHA512:
6
- metadata.gz: 46b9e51e95573630afbe686500cdf154a2cdf2d76bd19f441552fe76267682fb586118f96e7b0c5316352b49fc6e5ddc3b080fd1dc8b6711d31e382694ad767d
7
- data.tar.gz: 9bd7d22ee40b4d009b1c2f2f86d855a37ee32349c12afffdebbda6cf5edf54bf9c474f67bdfb170987ea512c0934deb48d709f1afbc49acef88f968ebff4297a
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
@@ -1,7 +1,4 @@
1
1
  {
2
- "permissions": {
3
- "allow": ["RemoteTrigger"]
4
- },
5
2
  "hooks": {
6
3
  "SessionStart": [
7
4
  {
@@ -9,13 +6,23 @@
9
6
  "hooks": [
10
7
  {
11
8
  "type": "command",
12
- "command": "\"$CLAUDE_PROJECT_DIR\"/.claude/hooks/setup.sh",
9
+ "command": "\"$CLAUDE_PROJECT_DIR\"/.claude/hooks/claude-code-web-session-start.sh",
13
10
  "timeout": 300
14
11
  }
15
12
  ]
16
13
  }
17
14
  ],
18
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
+ },
19
26
  {
20
27
  "matcher": "Bash",
21
28
  "hooks": [
data/.rubocop.yml CHANGED
@@ -44,12 +44,12 @@ 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
 
@@ -62,16 +62,6 @@ RSpec/SpecFilePathFormat:
62
62
  Exclude:
63
63
  - "spec/rubocop/cop/style/rbs_inline/mixin/**/*_spec.rb"
64
64
 
65
- # RbsInline
66
- Style/RbsInline:
67
- Mode: opt_out
68
-
69
- Style/RbsInline/MissingTypeAnnotation:
70
- EnforcedStyle: doc_style_and_return_annotation
71
-
72
- Style/RbsInline/RedundantTypeAnnotation:
73
- EnforcedStyle: doc_style_and_return_annotation
74
-
75
65
  # Style
76
66
  Style/AccessorGrouping:
77
67
  Enabled: false
@@ -85,6 +75,15 @@ Style/Documentation:
85
75
  Style/HashSyntax:
86
76
  EnforcedShorthandSyntax: always
87
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
+
88
87
  Style/StringLiterals:
89
88
  EnforcedStyle: double_quotes
90
89
 
data/CHANGELOG.md CHANGED
@@ -1,5 +1,16 @@
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
+
3
14
  ## 1.8.0 (2026-07-26)
4
15
 
5
16
  ### Enhancements
data/CLAUDE.md CHANGED
@@ -38,7 +38,7 @@ Cops live in `lib/rubocop/cop/style/rbs_inline/`, one per file; the shared modul
38
38
 
39
39
  Conventions:
40
40
 
41
- - Every cop `prepend`s `FileFilter` except `RequireRbsInlineComment`.
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
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.
43
43
 
44
44
  ### Plugin System
@@ -55,6 +55,8 @@ Tests use RuboCop's RSpec support helpers (`expect_offense` / `expect_no_offense
55
55
 
56
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
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
+
58
60
  ## Code Style Notes
59
61
 
60
62
  - `Layout/LeadingCommentSpace` and `Style/CommentedKeyword` are disabled because RBS::Inline `#:` comments violate these rules by design.
data/README.md CHANGED
@@ -31,18 +31,17 @@ rubocop-rbs_inline provides the following cops to validate [RBS::Inline](https:/
31
31
 
32
32
  The `Style/RbsInline` department has a shared `Mode` setting that gates whether each `Style/RbsInline/*` cop reports offenses on a given file:
33
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.
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
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
36
 
38
37
  Declare it once at the department level in your `.rubocop.yml`:
39
38
 
40
39
  ```yaml
41
40
  Style/RbsInline:
42
- Mode: opt_in
41
+ Mode: opt_out
43
42
  ```
44
43
 
45
- Unknown `Mode` values (e.g. `Mode: opt-in`) emit a warning and disable filtering for the run.
44
+ Any other value (e.g. `Mode: opt-in`) fails the run with a configuration error.
46
45
 
47
46
  ### Style/RbsInline/DataClassCommentAlignment
48
47
 
@@ -504,8 +503,6 @@ Style/RbsInline/RequireRbsInlineComment:
504
503
  AllowMissingComment: true
505
504
  ```
506
505
 
507
- The legacy `EnforcedStyle` parameter (`always` / `never`) is deprecated in favor of `Mode`; it will be removed in the next major release.
508
-
509
506
  **Examples (Mode: opt_in):**
510
507
  ```ruby
511
508
  # bad
@@ -659,7 +656,7 @@ Style/RbsInline/MissingTypeAnnotation:
659
656
  Visibility: public
660
657
  ```
661
658
 
662
- To restrict every cop to files that carry `# rbs_inline: enabled`, see [Style/RbsInline (shared configuration)](#stylerbsinline-shared-configuration).
659
+ To check every file rather than only those that carry `# rbs_inline: enabled`, see [Style/RbsInline (shared configuration)](#stylerbsinline-shared-configuration).
663
660
 
664
661
  See [config/default.yml](config/default.yml) for all available configuration options.
665
662
 
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,7 +5,7 @@ Style/RbsInline:
5
5
  Exclude:
6
6
  - 'spec/**/*'
7
7
  - 'test/**/*'
8
- Mode: ~
8
+ Mode: opt_in
9
9
 
10
10
  Style/RbsInline/DataClassCommentAlignment:
11
11
  Description: "Checks that `#:` inline type annotations in `Data.define` blocks are aligned."
@@ -96,7 +96,6 @@ Style/RbsInline/RequireRbsInlineComment:
96
96
  Enabled: true
97
97
  VersionAdded: "1.5.0"
98
98
  AllowMissingComment: false
99
- EnforcedStyle: ~
100
99
 
101
100
  Style/RbsInline/StructClassCommentAlignment:
102
101
  Description: "Checks that `#:` inline type annotations in `Struct.new` blocks are aligned."
@@ -11,6 +11,7 @@ require_relative "style/rbs_inline/mixin/data_struct_helper"
11
11
  require_relative "style/rbs_inline/mixin/missing_class_annotation"
12
12
  require_relative "style/rbs_inline/mixin/class_comment_alignment"
13
13
  require_relative "style/rbs_inline/mixin/data_class_matcher"
14
+ require_relative "style/rbs_inline/mixin/mode_config"
14
15
  require_relative "style/rbs_inline/mixin/file_filter"
15
16
  require_relative "style/rbs_inline/mixin/struct_class_matcher"
16
17
 
@@ -30,10 +30,11 @@ module RuboCop
30
30
  class DataDefineWithBlock < Base
31
31
  prepend FileFilter
32
32
  include DataClassMatcher
33
+ include ASTUtils
33
34
 
34
35
  MSG = "Do not use `Data.define` with a block. RBS::Inline does not parse block contents, " \
35
- "so methods defined in the block will not be recognized. " \
36
- "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."
37
38
 
38
39
  # @rbs node: RuboCop::AST::SendNode
39
40
  def on_send(node) #: void
@@ -42,7 +43,18 @@ module RuboCop
42
43
  block_node = node.parent
43
44
  return unless block_node&.block_type?
44
45
 
45
- add_offense(node)
46
+ add_offense(node, message: format(MSG, destination: destination(block_node)))
47
+ end
48
+
49
+ private
50
+
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
56
+
57
+ "`class #{class_name} ... end` written after it"
46
58
  end
47
59
  end
48
60
  end
@@ -39,6 +39,19 @@ module RuboCop
39
39
  node.source || raise
40
40
  end
41
41
 
42
+ # Returns the name of the constant `node` is assigned to, or `nil` when the name
43
+ # is not statically determinable (e.g. `self::Foo = ...`).
44
+ # @rbs node: RuboCop::AST::Node
45
+ def assigned_constant_name(node) #: String?
46
+ parent = node.parent
47
+ return nil unless parent.is_a?(RuboCop::AST::CasgnNode)
48
+ return nil unless parent.each_path.all? { _1.const_type? || _1.cbase_type? }
49
+
50
+ # `const_name` drops the leading `::`, which would name a different constant
51
+ # when the assignment appears inside a namespace.
52
+ parent.absolute? ? "::#{parent.const_name}" : parent.const_name
53
+ end
54
+
42
55
  #: (RuboCop::AST::SymbolNode) -> Symbol
43
56
  #: (RuboCop::AST::StrNode) -> Symbol
44
57
  #: (RuboCop::AST::Node) -> Symbol?
@@ -8,8 +8,7 @@ module RuboCop
8
8
  #
9
9
  # When `Mode` is `opt_in`, offenses are only reported for files that contain
10
10
  # a `# rbs_inline: enabled` magic comment. When `Mode` is `opt_out`, all files
11
- # are checked unless they contain `# rbs_inline: disabled`. When `Mode` is not
12
- # set, all files are checked (legacy behavior).
11
+ # are checked unless they contain `# rbs_inline: disabled`.
13
12
  #
14
13
  # This module is designed to be `prepend`ed to a cop so that it can short-circuit
15
14
  # the cop's heavy work (annotation parsing via `parse_comments`) and suppress any
@@ -17,32 +16,11 @@ module RuboCop
17
16
  #
18
17
  # @rbs module-self RuboCop::Cop::Base
19
18
  module FileFilter
20
- # @rbs! type mode = :opt_in | :opt_out
19
+ include ModeConfig
21
20
 
22
21
  MAGIC_COMMENT_ENABLED = /\A# rbs_inline: enabled\R?\z/ #: Regexp
23
22
  MAGIC_COMMENT_DISABLED = /\A# rbs_inline: disabled\R?\z/ #: Regexp
24
23
 
25
- SUPPORTED_MODES = %i[opt_in opt_out].freeze #: Array[mode]
26
-
27
- # Tracks Mode values that have already been reported as invalid, so we
28
- # only emit one warning per typo across the whole rubocop run instead of
29
- # one per (file × cop).
30
- # @rbs self.@warned_invalid_modes: Hash[String, bool]
31
-
32
- @warned_invalid_modes = {} # rubocop:disable Style/RbsInline/UntypedInstanceVariable
33
-
34
- # @rbs raw: untyped
35
- def self.warn_invalid_mode(raw) #: void
36
- key = raw.to_s
37
- return if @warned_invalid_modes[key]
38
-
39
- @warned_invalid_modes[key] = true
40
- Kernel.warn(
41
- "[rubocop-rbs_inline] Style/RbsInline Mode #{raw.inspect} is not supported. " \
42
- "Expected one of: #{SUPPORTED_MODES.join(", ")}. Filtering is disabled for this run."
43
- )
44
- end
45
-
46
24
  # @rbs @rbs_inline_skip_file: bool
47
25
 
48
26
  def on_new_investigation #: void
@@ -68,28 +46,12 @@ module RuboCop
68
46
  private
69
47
 
70
48
  def skip_by_mode? #: bool
71
- mode = configured_mode
72
- return false if mode.nil?
73
-
74
49
  # `# rbs_inline: disabled` always opts a file out, regardless of Mode
75
50
  # (matches rbs-inline itself: rbs-inline skips disabled files in both
76
51
  # opt_in and opt_out modes).
77
52
  return true if rbs_inline_disabled?
78
53
 
79
- mode == :opt_in && !rbs_inline_enabled?
80
- end
81
-
82
- def configured_mode #: mode?
83
- raw = cop_config["Mode"]
84
- return nil if raw.nil?
85
-
86
- # `raw.to_s.to_sym` handles YAML-native Integer / Boolean without
87
- # raising NoMethodError on the plain `to_sym`.
88
- mode = raw.to_s.to_sym
89
- return mode if SUPPORTED_MODES.include?(mode)
90
-
91
- FileFilter.warn_invalid_mode(raw)
92
- nil
54
+ configured_mode == :opt_in && !rbs_inline_enabled?
93
55
  end
94
56
 
95
57
  # Iterate the already-materialized `processed_source.comments` so this
@@ -0,0 +1,39 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RuboCop
4
+ module Cop
5
+ module Style
6
+ module RbsInline
7
+ # Resolves the `Mode` setting shared by every cop in the `Style/RbsInline`
8
+ # department.
9
+ #
10
+ # @rbs module-self RuboCop::Cop::Base
11
+ module ModeConfig
12
+ # @rbs! type mode = :opt_in | :opt_out
13
+
14
+ SUPPORTED_MODES = %i[opt_in opt_out].freeze #: Array[mode]
15
+
16
+ # Hook RuboCop calls while mobilizing its cops, so an unsupported `Mode`
17
+ # fails the run as a config error instead of an error on every file.
18
+ def validate_config #: void
19
+ configured_mode
20
+ end
21
+
22
+ private
23
+
24
+ def configured_mode #: mode
25
+ raw = cop_config["Mode"]
26
+ # `raw.to_s.to_sym` handles YAML-native Integer / Boolean without
27
+ # raising NoMethodError on the plain `to_sym`.
28
+ mode = raw.to_s.to_sym
29
+ return mode if SUPPORTED_MODES.include?(mode)
30
+
31
+ raise ValidationError,
32
+ "`Style/RbsInline: Mode: #{raw.inspect}` is not supported. " \
33
+ "Expected one of: #{SUPPORTED_MODES.join(", ")}."
34
+ end
35
+ end
36
+ end
37
+ end
38
+ end
39
+ end
@@ -53,33 +53,20 @@ module RuboCop
53
53
  # end
54
54
  #
55
55
  class RequireRbsInlineComment < Base
56
+ include ModeConfig
56
57
  include RangeHelp
57
58
  extend AutoCorrector
58
59
 
59
60
  MSG_MISSING = "Missing `# rbs_inline:` magic comment."
60
61
  MSG_FORBIDDEN = "Remove `# rbs_inline:` magic comment."
61
62
 
62
- # @rbs self.@enforced_style_deprecation_warned: bool
63
-
64
- @enforced_style_deprecation_warned = false # rubocop:disable Style/RbsInline/UntypedInstanceVariable
65
-
66
- def self.enforced_style_deprecation_warned? #: bool
67
- @enforced_style_deprecation_warned == true
68
- end
69
-
70
- def self.mark_enforced_style_deprecation_warned! #: void
71
- @enforced_style_deprecation_warned = true
72
- end
73
-
74
63
  def on_new_investigation #: void
75
64
  return if processed_source.buffer.source.empty?
76
65
 
77
- warn_deprecated_enforced_style
78
-
79
66
  magic_comment = find_rbs_inline_magic_comment
80
67
  return if disabled?(magic_comment)
81
68
 
82
- case effective_mode
69
+ case configured_mode
83
70
  when :opt_in then check_opt_in(magic_comment)
84
71
  when :opt_out then check_opt_out(magic_comment)
85
72
  end
@@ -154,36 +141,10 @@ module RuboCop
154
141
  comments[last_idx] || raise
155
142
  end
156
143
 
157
- def effective_mode #: FileFilter::mode
158
- mode = cop_config["Mode"]
159
- if mode
160
- # `raw.to_s.to_sym` handles YAML-native Integer / Boolean safely.
161
- sym = mode.to_s.to_sym
162
- return sym if FileFilter::SUPPORTED_MODES.include?(sym)
163
-
164
- FileFilter.warn_invalid_mode(mode)
165
- end
166
-
167
- cop_config["EnforcedStyle"]&.to_sym == :never ? :opt_out : :opt_in
168
- end
169
-
170
144
  def allow_missing_comment? #: bool
171
145
  cop_config["AllowMissingComment"] == true
172
146
  end
173
147
 
174
- def warn_deprecated_enforced_style #: void
175
- return if self.class.enforced_style_deprecation_warned?
176
- return if cop_config["EnforcedStyle"].nil?
177
-
178
- self.class.mark_enforced_style_deprecation_warned!
179
- Kernel.warn(
180
- "[rubocop-rbs_inline] Style/RbsInline/RequireRbsInlineComment.EnforcedStyle is deprecated. " \
181
- "Please migrate to `Style/RbsInline: Mode: opt_in` (was `EnforcedStyle: always`) or " \
182
- "`Style/RbsInline: Mode: opt_out` (was `EnforcedStyle: never`). " \
183
- "EnforcedStyle will be removed in the next major version."
184
- )
185
- end
186
-
187
148
  def first_line_range #: Parser::Source::Range
188
149
  first_line = processed_source.ast&.source_range&.first_line || 1
189
150
  processed_source.buffer.line_range(first_line)
@@ -27,10 +27,11 @@ module RuboCop
27
27
  class StructNewWithBlock < Base
28
28
  prepend FileFilter
29
29
  include StructClassMatcher
30
+ include ASTUtils
30
31
 
31
32
  MSG = "Do not use `Struct.new` 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."
33
+ "so methods defined in the block will not be recognized. Keep the `Struct.new` call " \
34
+ "and move the methods into %<destination>s."
34
35
 
35
36
  # @rbs node: RuboCop::AST::SendNode
36
37
  def on_send(node) #: void
@@ -39,7 +40,18 @@ module RuboCop
39
40
  block_node = node.parent
40
41
  return unless block_node&.block_type?
41
42
 
42
- add_offense(node)
43
+ add_offense(node, message: format(MSG, destination: destination(block_node)))
44
+ end
45
+
46
+ private
47
+
48
+ # The constant the definition is assigned to is the class to reopen.
49
+ # @rbs node: RuboCop::AST::Node
50
+ def destination(node) #: String
51
+ class_name = assigned_constant_name(node)
52
+ return "a `class` that reopens it" unless class_name
53
+
54
+ "`class #{class_name} ... end` written after it"
43
55
  end
44
56
  end
45
57
  end
@@ -2,6 +2,6 @@
2
2
 
3
3
  module RuboCop
4
4
  module RbsInline
5
- VERSION = "1.8.0"
5
+ VERSION = "2.0.0"
6
6
  end
7
7
  end
@@ -6,7 +6,7 @@ gems:
6
6
  source:
7
7
  type: git
8
8
  name: ruby/gem_rbs_collection
9
- revision: ea56a88d1c0ac98e79f8ea5bb19a139da768ba10
9
+ revision: 82762423fc5694b82888ea89a972847cf19838c2
10
10
  remote: https://github.com/ruby/gem_rbs_collection.git
11
11
  repo_dir: gems
12
12
  - name: diff-lcs
@@ -14,7 +14,7 @@ gems:
14
14
  source:
15
15
  type: git
16
16
  name: ruby/gem_rbs_collection
17
- revision: ea56a88d1c0ac98e79f8ea5bb19a139da768ba10
17
+ revision: 82762423fc5694b82888ea89a972847cf19838c2
18
18
  remote: https://github.com/ruby/gem_rbs_collection.git
19
19
  repo_dir: gems
20
20
  - name: fileutils
@@ -34,7 +34,7 @@ gems:
34
34
  source:
35
35
  type: git
36
36
  name: ruby/gem_rbs_collection
37
- revision: ea56a88d1c0ac98e79f8ea5bb19a139da768ba10
37
+ revision: 82762423fc5694b82888ea89a972847cf19838c2
38
38
  remote: https://github.com/ruby/gem_rbs_collection.git
39
39
  repo_dir: gems
40
40
  - name: logger
@@ -42,7 +42,7 @@ gems:
42
42
  source:
43
43
  type: git
44
44
  name: ruby/gem_rbs_collection
45
- revision: ea56a88d1c0ac98e79f8ea5bb19a139da768ba10
45
+ revision: 82762423fc5694b82888ea89a972847cf19838c2
46
46
  remote: https://github.com/ruby/gem_rbs_collection.git
47
47
  repo_dir: gems
48
48
  - name: monitor
@@ -58,7 +58,7 @@ gems:
58
58
  source:
59
59
  type: git
60
60
  name: ruby/gem_rbs_collection
61
- revision: ea56a88d1c0ac98e79f8ea5bb19a139da768ba10
61
+ revision: 82762423fc5694b82888ea89a972847cf19838c2
62
62
  remote: https://github.com/ruby/gem_rbs_collection.git
63
63
  repo_dir: gems
64
64
  - name: parser
@@ -66,7 +66,7 @@ gems:
66
66
  source:
67
67
  type: git
68
68
  name: ruby/gem_rbs_collection
69
- revision: ea56a88d1c0ac98e79f8ea5bb19a139da768ba10
69
+ revision: 82762423fc5694b82888ea89a972847cf19838c2
70
70
  remote: https://github.com/ruby/gem_rbs_collection.git
71
71
  repo_dir: gems
72
72
  - name: prism
@@ -78,7 +78,7 @@ gems:
78
78
  source:
79
79
  type: git
80
80
  name: ruby/gem_rbs_collection
81
- revision: ea56a88d1c0ac98e79f8ea5bb19a139da768ba10
81
+ revision: 82762423fc5694b82888ea89a972847cf19838c2
82
82
  remote: https://github.com/ruby/gem_rbs_collection.git
83
83
  repo_dir: gems
84
84
  - name: rake
@@ -86,7 +86,7 @@ gems:
86
86
  source:
87
87
  type: git
88
88
  name: ruby/gem_rbs_collection
89
- revision: ea56a88d1c0ac98e79f8ea5bb19a139da768ba10
89
+ revision: 82762423fc5694b82888ea89a972847cf19838c2
90
90
  remote: https://github.com/ruby/gem_rbs_collection.git
91
91
  repo_dir: gems
92
92
  - name: rbs
@@ -106,7 +106,7 @@ gems:
106
106
  source:
107
107
  type: git
108
108
  name: ruby/gem_rbs_collection
109
- revision: ea56a88d1c0ac98e79f8ea5bb19a139da768ba10
109
+ revision: 82762423fc5694b82888ea89a972847cf19838c2
110
110
  remote: https://github.com/ruby/gem_rbs_collection.git
111
111
  repo_dir: gems
112
112
  - name: rubocop
@@ -114,7 +114,7 @@ gems:
114
114
  source:
115
115
  type: git
116
116
  name: ruby/gem_rbs_collection
117
- revision: ea56a88d1c0ac98e79f8ea5bb19a139da768ba10
117
+ revision: 82762423fc5694b82888ea89a972847cf19838c2
118
118
  remote: https://github.com/ruby/gem_rbs_collection.git
119
119
  repo_dir: gems
120
120
  - name: rubocop-ast
@@ -122,7 +122,7 @@ gems:
122
122
  source:
123
123
  type: git
124
124
  name: ruby/gem_rbs_collection
125
- revision: ea56a88d1c0ac98e79f8ea5bb19a139da768ba10
125
+ revision: 82762423fc5694b82888ea89a972847cf19838c2
126
126
  remote: https://github.com/ruby/gem_rbs_collection.git
127
127
  repo_dir: gems
128
128
  - name: rubocop-numbered-params
@@ -7,6 +7,12 @@ module Parser
7
7
  end
8
8
 
9
9
  module RuboCop
10
+ class Error < StandardError
11
+ end
12
+
13
+ class ValidationError < Error
14
+ end
15
+
10
16
  module Cop
11
17
  module ConfigurableEnforcedStyle
12
18
  def no_acceptable_style!: () -> void
@@ -31,10 +31,18 @@ module RuboCop
31
31
 
32
32
  include DataClassMatcher
33
33
 
34
+ include ASTUtils
35
+
34
36
  MSG: ::String
35
37
 
36
38
  # @rbs node: RuboCop::AST::SendNode
37
39
  def on_send: (RuboCop::AST::SendNode node) -> void
40
+
41
+ private
42
+
43
+ # The constant the definition is assigned to is the class to reopen.
44
+ # @rbs node: RuboCop::AST::Node
45
+ def destination: (RuboCop::AST::Node node) -> String
38
46
  end
39
47
  end
40
48
  end
@@ -23,6 +23,11 @@ module RuboCop
23
23
  # @rbs node: RuboCop::AST::Node
24
24
  def source!: (RuboCop::AST::Node node) -> String
25
25
 
26
+ # Returns the name of the constant `node` is assigned to, or `nil` when the name
27
+ # is not statically determinable (e.g. `self::Foo = ...`).
28
+ # @rbs node: RuboCop::AST::Node
29
+ def assigned_constant_name: (RuboCop::AST::Node node) -> String?
30
+
26
31
  # : (RuboCop::AST::SymbolNode) -> Symbol
27
32
  # : (RuboCop::AST::StrNode) -> Symbol
28
33
  # : (RuboCop::AST::Node) -> Symbol?
@@ -8,8 +8,7 @@ module RuboCop
8
8
  #
9
9
  # When `Mode` is `opt_in`, offenses are only reported for files that contain
10
10
  # a `# rbs_inline: enabled` magic comment. When `Mode` is `opt_out`, all files
11
- # are checked unless they contain `# rbs_inline: disabled`. When `Mode` is not
12
- # set, all files are checked (legacy behavior).
11
+ # are checked unless they contain `# rbs_inline: disabled`.
13
12
  #
14
13
  # This module is designed to be `prepend`ed to a cop so that it can short-circuit
15
14
  # the cop's heavy work (annotation parsing via `parse_comments`) and suppress any
@@ -17,19 +16,12 @@ module RuboCop
17
16
  #
18
17
  # @rbs module-self RuboCop::Cop::Base
19
18
  module FileFilter : RuboCop::Cop::Base
20
- type mode = :opt_in | :opt_out
19
+ include ModeConfig
21
20
 
22
21
  MAGIC_COMMENT_ENABLED: Regexp
23
22
 
24
23
  MAGIC_COMMENT_DISABLED: Regexp
25
24
 
26
- SUPPORTED_MODES: Array[mode]
27
-
28
- self.@warned_invalid_modes: Hash[String, bool]
29
-
30
- # @rbs raw: untyped
31
- def self.warn_invalid_mode: (untyped raw) -> void
32
-
33
25
  @rbs_inline_skip_file: bool
34
26
 
35
27
  def on_new_investigation: () -> void
@@ -47,8 +39,6 @@ module RuboCop
47
39
 
48
40
  def skip_by_mode?: () -> bool
49
41
 
50
- def configured_mode: () -> mode?
51
-
52
42
  # Iterate the already-materialized `processed_source.comments` so this
53
43
  # matches RequireRbsInlineComment's detection exactly (line endings,
54
44
  # indented comments, heredoc/string content are all handled correctly
@@ -0,0 +1,27 @@
1
+ # Generated from lib/rubocop/cop/style/rbs_inline/mixin/mode_config.rb with RBS::Inline
2
+
3
+ module RuboCop
4
+ module Cop
5
+ module Style
6
+ module RbsInline
7
+ # Resolves the `Mode` setting shared by every cop in the `Style/RbsInline`
8
+ # department.
9
+ #
10
+ # @rbs module-self RuboCop::Cop::Base
11
+ module ModeConfig : RuboCop::Cop::Base
12
+ type mode = :opt_in | :opt_out
13
+
14
+ SUPPORTED_MODES: Array[mode]
15
+
16
+ # Hook RuboCop calls while mobilizing its cops, so an unsupported `Mode`
17
+ # fails the run as a config error instead of an error on every file.
18
+ def validate_config: () -> void
19
+
20
+ private
21
+
22
+ def configured_mode: () -> mode
23
+ end
24
+ end
25
+ end
26
+ end
27
+ end
@@ -52,6 +52,8 @@ module RuboCop
52
52
  # class Foo
53
53
  # end
54
54
  class RequireRbsInlineComment < Base
55
+ include ModeConfig
56
+
55
57
  include RangeHelp
56
58
 
57
59
  extend AutoCorrector
@@ -60,12 +62,6 @@ module RuboCop
60
62
 
61
63
  MSG_FORBIDDEN: ::String
62
64
 
63
- self.@enforced_style_deprecation_warned: bool
64
-
65
- def self.enforced_style_deprecation_warned?: () -> bool
66
-
67
- def self.mark_enforced_style_deprecation_warned!: () -> void
68
-
69
65
  def on_new_investigation: () -> void
70
66
 
71
67
  private
@@ -90,12 +86,8 @@ module RuboCop
90
86
 
91
87
  def find_last_comment_in_first_block: () -> Parser::Source::Comment
92
88
 
93
- def effective_mode: () -> FileFilter::mode
94
-
95
89
  def allow_missing_comment?: () -> bool
96
90
 
97
- def warn_deprecated_enforced_style: () -> void
98
-
99
91
  def first_line_range: () -> Parser::Source::Range
100
92
  end
101
93
  end
@@ -28,10 +28,18 @@ module RuboCop
28
28
 
29
29
  include StructClassMatcher
30
30
 
31
+ include ASTUtils
32
+
31
33
  MSG: ::String
32
34
 
33
35
  # @rbs node: RuboCop::AST::SendNode
34
36
  def on_send: (RuboCop::AST::SendNode node) -> void
37
+
38
+ private
39
+
40
+ # The constant the definition is assigned to is the class to reopen.
41
+ # @rbs node: RuboCop::AST::Node
42
+ def destination: (RuboCop::AST::Node node) -> String
35
43
  end
36
44
  end
37
45
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: rubocop-rbs_inline
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.8.0
4
+ version: 2.0.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Takeshi KOMIYA
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2026-07-26 00:00:00.000000000 Z
11
+ date: 2026-08-05 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: lint_roller
@@ -65,10 +65,11 @@ executables: []
65
65
  extensions: []
66
66
  extra_rdoc_files: []
67
67
  files:
68
+ - ".claude/hooks/claude-code-web-session-start.sh"
68
69
  - ".claude/hooks/pre-commit-check.sh"
70
+ - ".claude/hooks/protect-sig-files.sh"
69
71
  - ".claude/hooks/rbs-inline.sh"
70
72
  - ".claude/hooks/self-review.sh"
71
- - ".claude/hooks/setup.sh"
72
73
  - ".claude/settings.json"
73
74
  - ".rspec"
74
75
  - ".rubocop.yml"
@@ -102,6 +103,7 @@ files:
102
103
  - lib/rubocop/cop/style/rbs_inline/mixin/data_struct_helper.rb
103
104
  - lib/rubocop/cop/style/rbs_inline/mixin/file_filter.rb
104
105
  - lib/rubocop/cop/style/rbs_inline/mixin/missing_class_annotation.rb
106
+ - lib/rubocop/cop/style/rbs_inline/mixin/mode_config.rb
105
107
  - lib/rubocop/cop/style/rbs_inline/mixin/source_code_helper.rb
106
108
  - lib/rubocop/cop/style/rbs_inline/mixin/struct_class_matcher.rb
107
109
  - lib/rubocop/cop/style/rbs_inline/parameters_separator.rb
@@ -140,6 +142,7 @@ files:
140
142
  - sig/rubocop/cop/style/rbs_inline/mixin/data_struct_helper.rbs
141
143
  - sig/rubocop/cop/style/rbs_inline/mixin/file_filter.rbs
142
144
  - sig/rubocop/cop/style/rbs_inline/mixin/missing_class_annotation.rbs
145
+ - sig/rubocop/cop/style/rbs_inline/mixin/mode_config.rbs
143
146
  - sig/rubocop/cop/style/rbs_inline/mixin/source_code_helper.rbs
144
147
  - sig/rubocop/cop/style/rbs_inline/mixin/struct_class_matcher.rbs
145
148
  - sig/rubocop/cop/style/rbs_inline/parameters_separator.rbs