Skip to content

Commit 9cb8915

Browse files
justin808claude
andauthored
Add RBS type signatures for improved type safety (#1945)
## Summary This PR adds comprehensive RBS (Ruby Signature) type definitions for the React on Rails gem to improve type safety, IDE support, and developer experience. ## What is RBS? RBS is Ruby's official type signature language that provides type information for Ruby code. It enables: - Better IDE autocomplete and IntelliSense - Static type checking with tools like Steep - Improved API documentation - Early detection of type-related bugs ## Changes ### Type Signatures Added - **ReactOnRails module**: Core module, VERSION constant, configuration methods - **Configuration class**: All attributes and initialization parameters - **Helper module**: All view helper methods (react_component, redux_store, etc.) - **ServerRenderingPool**: Server rendering methods and delegation - **Utils module**: Utility methods and helper functions - **PackerUtils**: Shakapacker integration methods - **TestHelper**: Testing utilities - **Controller**: Controller extension methods - **GitUtils**: Git-related utilities - **VersionChecker**: Version checking methods ### Infrastructure - Added RBS gem to development dependencies - Created `sig/` directory with organized type signatures - Added rake tasks: - `rake rbs:validate` - Validate type signatures - `rake rbs:check` - Alias for validate - `rake rbs:list` - List all RBS files - Included comprehensive README in `sig/` directory ## Benefits 1. **Better IDE Support**: Developers using IDEs with RBS support will get better autocomplete and type hints 2. **Type Safety**: Optional static type checking with tools like Steep 3. **Documentation**: RBS signatures serve as machine-readable API documentation 4. **Future-Proof**: Prepares the gem for Ruby's evolving type system ecosystem ## Testing - ✅ All RBS signatures validate with `bundle exec rbs -I sig validate` - ✅ No breaking changes to existing code - ✅ RuboCop passes - ✅ Pre-commit hooks pass ## Additional Notes The RBS signatures cover the main public API of the gem. Internal methods and private APIs have minimal signatures to reduce maintenance burden while still providing value to gem users. Type signatures use conservative types (`untyped` for complex hashes) where the actual types are highly dynamic to avoid over-constraining the API. 🤖 Generated with [Claude Code](https://claude.com/claude-code) <!-- Reviewable:start --> - - - This change is [<img src="https://reviewable.io/review_button.svg" height="34" align="absmiddle" alt="Reviewable"/>](https://reviewable.io/reviews/shakacode/react_on_rails/1945) <!-- Reviewable:end --> <!-- This is an auto-generated comment: release notes by coderabbit.ai --> ## Summary by CodeRabbit * **Chores** * Added extensive Ruby type signature coverage across the ReactOnRails public API and new tooling to validate, check, and list signatures. * Added a development-only dependency for RBS. * **Documentation** * Added RBS guidance covering signature structure, validation and listing commands, and development guidelines. <!-- end of auto-generated comment: release notes by coderabbit.ai --> --------- Co-authored-by: Claude <noreply@anthropic.com>
1 parent 21ca217 commit 9cb8915

14 files changed

+400
-0
lines changed

Gemfile.development_dependencies

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@ group :development, :test do
3333
gem "pry-doc"
3434
gem "pry-rails"
3535
gem "pry-rescue"
36+
gem "rbs", require: false
3637
gem "rubocop", "1.61.0", require: false
3738
gem "rubocop-performance", "~>1.20.0", require: false
3839
gem "rubocop-rspec", "~>2.26", require: false

Gemfile.lock

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -267,6 +267,8 @@ GEM
267267
rb-fsevent (0.11.2)
268268
rb-inotify (0.10.1)
269269
ffi (~> 1.0)
270+
rbs (3.9.5)
271+
logger
270272
rdoc (6.15.1)
271273
erb
272274
psych (>= 4.0.0)
@@ -437,6 +439,7 @@ DEPENDENCIES
437439
pry-rescue
438440
puma (~> 6.0)
439441
rails (~> 7.1)
442+
rbs
440443
react_on_rails!
441444
rspec-rails
442445
rspec-retry

rakelib/rbs.rake

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
# frozen_string_literal: true
2+
3+
# rubocop:disable Metrics/BlockLength
4+
namespace :rbs do
5+
desc "Validate RBS type signatures"
6+
task :validate do
7+
require "rbs"
8+
require "rbs/cli"
9+
10+
puts "Validating RBS type signatures..."
11+
12+
# Run RBS validate
13+
result = system("bundle exec rbs -I sig validate")
14+
15+
case result
16+
when true
17+
puts "✓ RBS validation passed"
18+
when false
19+
puts "✗ RBS validation failed"
20+
exit 1
21+
when nil
22+
puts "✗ RBS command not found or could not be executed"
23+
exit 1
24+
end
25+
end
26+
27+
desc "Check RBS type signatures (alias for validate)"
28+
task check: :validate
29+
30+
desc "List all RBS files"
31+
task :list do
32+
sig_files = Dir.glob("sig/**/*.rbs")
33+
puts "RBS type signature files:"
34+
sig_files.each { |f| puts " #{f}" }
35+
puts "\nTotal: #{sig_files.count} files"
36+
end
37+
end
38+
# rubocop:enable Metrics/BlockLength

sig/README.md

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
# RBS Type Signatures
2+
3+
This directory contains RBS (Ruby Signature) type definitions for the React on Rails gem.
4+
5+
## What is RBS?
6+
7+
RBS is Ruby's official type signature language. It provides type information for Ruby code, enabling:
8+
9+
- Better IDE support and autocomplete
10+
- Static type checking with tools like Steep
11+
- Improved documentation
12+
- Early detection of type-related bugs
13+
14+
## Structure
15+
16+
The signatures are organized to mirror the `lib/` directory structure:
17+
18+
- `react_on_rails.rbs` - Main module and core classes
19+
- `react_on_rails/configuration.rbs` - Configuration class types
20+
- `react_on_rails/helper.rbs` - View helper method signatures
21+
- `react_on_rails/server_rendering_pool.rbs` - Server rendering types
22+
- `react_on_rails/utils.rbs` - Utility method signatures
23+
- And more...
24+
25+
## Validation
26+
27+
To validate the RBS signatures:
28+
29+
```bash
30+
bundle exec rake rbs:validate
31+
```
32+
33+
Or directly:
34+
35+
```bash
36+
bundle exec rbs -I sig validate
37+
```
38+
39+
To list all RBS files:
40+
41+
```bash
42+
bundle exec rake rbs:list
43+
```
44+
45+
## Development
46+
47+
When adding new public methods or classes to the gem, please also add corresponding RBS signatures.
48+
49+
For more information about RBS:
50+
51+
- [RBS Documentation](https://github.com/ruby/rbs)
52+
- [RBS Syntax Guide](https://github.com/ruby/rbs/blob/master/docs/syntax.md)

sig/react_on_rails.rbs

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
# Type signatures for ReactOnRails gem
2+
3+
module ReactOnRails
4+
VERSION: String
5+
6+
DEFAULT_GENERATED_ASSETS_DIR: String
7+
DEFAULT_COMPONENT_REGISTRY_TIMEOUT: Integer
8+
9+
def self.configure: () { (Configuration) -> void } -> void
10+
def self.configuration: () -> Configuration
11+
12+
class Error < StandardError
13+
end
14+
15+
class PrerenderError < Error
16+
attr_reader component_name: String?
17+
attr_reader js_code: String?
18+
attr_reader err: Hash[Symbol, untyped]?
19+
attr_reader props: (Hash[Symbol, untyped] | String)?
20+
attr_reader console_messages: Array[String]?
21+
22+
def initialize: (
23+
?component_name: String?,
24+
?js_code: String?,
25+
?err: Hash[Symbol, untyped]?,
26+
?props: (Hash[Symbol, untyped] | String)?,
27+
?console_messages: Array[String]?
28+
) -> void
29+
30+
def to_honeybadger_context: () -> Hash[Symbol, untyped]
31+
def raven_context: () -> Hash[Symbol, untyped]
32+
def to_error_context: () -> Hash[Symbol, untyped]
33+
end
34+
35+
class JsonParseError < Error
36+
def initialize: (Hash[Symbol, untyped] err) -> void
37+
end
38+
end
Lines changed: 96 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,96 @@
1+
module ReactOnRails
2+
class Configuration
3+
attr_accessor node_modules_location: String?
4+
attr_accessor server_bundle_js_file: String
5+
attr_accessor prerender: bool
6+
attr_accessor replay_console: bool
7+
attr_accessor trace: bool
8+
attr_accessor development_mode: bool
9+
attr_accessor logging_on_server: bool
10+
attr_accessor server_renderer_pool_size: Integer
11+
attr_accessor server_renderer_timeout: Integer
12+
attr_accessor skip_display_none: bool?
13+
attr_accessor raise_on_prerender_error: bool
14+
attr_accessor generated_assets_dirs: Array[String]?
15+
attr_accessor generated_assets_dir: String
16+
attr_accessor components_subdirectory: String?
17+
attr_accessor webpack_generated_files: Array[String]
18+
attr_accessor rendering_extension: String?
19+
attr_accessor build_test_command: String
20+
attr_accessor build_production_command: String
21+
attr_accessor i18n_dir: String?
22+
attr_accessor i18n_yml_dir: String?
23+
attr_accessor i18n_output_format: Symbol?
24+
attr_accessor i18n_yml_safe_load_options: Hash[Symbol, untyped]?
25+
attr_accessor defer_generated_component_packs: bool
26+
attr_accessor server_render_method: String?
27+
attr_accessor random_dom_id: bool
28+
attr_accessor auto_load_bundle: bool
29+
attr_accessor same_bundle_for_client_and_server: bool
30+
attr_accessor rendering_props_extension: String?
31+
attr_accessor make_generated_server_bundle_the_entrypoint: bool
32+
attr_accessor generated_component_packs_loading_strategy: Symbol?
33+
attr_accessor immediate_hydration: bool
34+
attr_accessor component_registry_timeout: Integer
35+
attr_accessor server_bundle_output_path: String?
36+
attr_accessor enforce_private_server_bundles: bool
37+
38+
def initialize: (
39+
?node_modules_location: String?,
40+
?server_bundle_js_file: String?,
41+
?prerender: bool?,
42+
?replay_console: bool?,
43+
?make_generated_server_bundle_the_entrypoint: bool?,
44+
?trace: bool?,
45+
?development_mode: bool?,
46+
?defer_generated_component_packs: bool?,
47+
?logging_on_server: bool?,
48+
?server_renderer_pool_size: Integer?,
49+
?server_renderer_timeout: Integer?,
50+
?raise_on_prerender_error: bool?,
51+
?skip_display_none: bool?,
52+
?generated_assets_dirs: Array[String]?,
53+
?generated_assets_dir: String?,
54+
?webpack_generated_files: Array[String]?,
55+
?rendering_extension: String?,
56+
?build_test_command: String?,
57+
?build_production_command: String?,
58+
?generated_component_packs_loading_strategy: Symbol?,
59+
?same_bundle_for_client_and_server: bool?,
60+
?i18n_dir: String?,
61+
?i18n_yml_dir: String?,
62+
?i18n_output_format: Symbol?,
63+
?i18n_yml_safe_load_options: Hash[Symbol, untyped]?,
64+
?random_dom_id: bool?,
65+
?server_render_method: String?,
66+
?rendering_props_extension: String?,
67+
?components_subdirectory: String?,
68+
?auto_load_bundle: bool?,
69+
?immediate_hydration: bool?,
70+
?component_registry_timeout: Integer?,
71+
?server_bundle_output_path: String?,
72+
?enforce_private_server_bundles: bool?
73+
) -> void
74+
75+
def setup_config_values: () -> void
76+
77+
private
78+
79+
def check_component_registry_timeout: () -> void
80+
def validate_generated_component_packs_loading_strategy: () -> void
81+
def validate_enforce_private_server_bundles: () -> void
82+
def check_minimum_shakapacker_version: () -> void
83+
def check_autobundling_requirements: () -> void
84+
def adjust_precompile_task: () -> void
85+
def error_if_using_packer_and_generated_assets_dir_not_match_public_output_path: () -> void
86+
def check_server_render_method_is_only_execjs: () -> void
87+
def configure_generated_assets_dirs_deprecation: () -> void
88+
def ensure_webpack_generated_files_exists: () -> void
89+
def configure_skip_display_none_deprecation: () -> void
90+
def raise_missing_components_subdirectory: () -> void
91+
def compile_command_conflict_message: () -> String
92+
def rsc_bundle_js_file: () -> String?
93+
def react_client_manifest_file: () -> String?
94+
def react_server_client_manifest_file: () -> String?
95+
end
96+
end

sig/react_on_rails/controller.rbs

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
module ReactOnRails
2+
module Controller
3+
# Type alias for ActiveSupport::SafeBuffer (html_safe strings)
4+
type safe_buffer = String
5+
6+
def redux_store: (
7+
String store_name,
8+
?props: untyped,
9+
?immediate_hydration: bool
10+
) -> void
11+
12+
# Returns html_safe string (ActiveSupport::SafeBuffer)
13+
def redux_store_hydration_data: () -> safe_buffer
14+
end
15+
end

sig/react_on_rails/git_utils.rbs

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
module ReactOnRails
2+
module GitUtils
3+
def self.uncommitted_changes?: (String path) -> bool
4+
def self.git_installed?: () -> bool
5+
def self.current_branch: () -> String?
6+
def self.rspec_fixture_branches: () -> Array[String]
7+
end
8+
end

sig/react_on_rails/helper.rbs

Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
module ReactOnRails
2+
module Helper
3+
# Type alias for ActiveSupport::SafeBuffer (html_safe strings)
4+
type safe_buffer = String
5+
6+
COMPONENT_HTML_KEY: String
7+
8+
# Main helper methods
9+
# Returns html_safe string (ActiveSupport::SafeBuffer)
10+
def react_component: (
11+
String component_name,
12+
?Hash[Symbol, untyped] options
13+
) ?{ () -> untyped } -> safe_buffer
14+
15+
# Returns hash with html_safe strings (ActiveSupport::SafeBuffer)
16+
def react_component_hash: (
17+
String component_name,
18+
?Hash[Symbol, untyped] options
19+
) ?{ () -> untyped } -> Hash[String, safe_buffer]
20+
21+
# Returns html_safe string (ActiveSupport::SafeBuffer)
22+
def redux_store: (
23+
String store_name,
24+
?Hash[Symbol, untyped] props
25+
) -> safe_buffer
26+
27+
# Returns html_safe string (ActiveSupport::SafeBuffer)
28+
def redux_store_hydration_data: () -> safe_buffer
29+
30+
# Returns html_safe string (ActiveSupport::SafeBuffer)
31+
def server_render_js: (
32+
String js_expression,
33+
?Hash[Symbol, untyped] options
34+
) -> safe_buffer
35+
36+
def sanitized_props_string: (untyped props) -> String
37+
38+
def rails_context: (
39+
?server_side: bool
40+
) -> Hash[Symbol, untyped]
41+
42+
private
43+
44+
def internal_react_component: (
45+
String component_name,
46+
Hash[Symbol, untyped] options
47+
) ?{ () -> untyped } -> Hash[Symbol, untyped]
48+
49+
def build_react_component_result_for_server_rendered_string: (
50+
server_rendered_html: String,
51+
component_specification_tag: String,
52+
console_script: String,
53+
render_options: Hash[Symbol, untyped]
54+
) -> safe_buffer
55+
56+
def build_react_component_result_for_server_rendered_hash: (
57+
server_rendered_html: Hash[String, untyped],
58+
component_specification_tag: String,
59+
console_script: String,
60+
render_options: Hash[Symbol, untyped]
61+
) -> Hash[String, safe_buffer]
62+
63+
def prepend_render_rails_context: (String result) -> safe_buffer
64+
end
65+
end
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
module ReactOnRails
2+
module PackerUtils
3+
def self.supports_autobundling?: () -> bool
4+
def self.supports_basic_pack_generation?: () -> bool
5+
def self.supports_async_loading?: () -> bool
6+
def self.nested_entries?: () -> bool
7+
def self.packer_public_output_path: () -> String
8+
def self.precompile?: () -> bool
9+
def self.shakapacker_precompile_hook_configured?: () -> bool
10+
def self.shakapacker_precompile_hook_value: () -> String
11+
def self.raise_shakapacker_version_incompatible_for_autobundling: () -> void
12+
def self.raise_shakapacker_version_incompatible_for_basic_pack_generation: () -> void
13+
def self.raise_nested_entries_disabled: () -> void
14+
end
15+
end

0 commit comments

Comments
 (0)