Skip to content

Commit 71222f6

Browse files
justin808claude
andcommitted
Add RBS validation and runtime type checking to CI
This commit implements comprehensive RBS type checking for both the main gem and Pro package: 1. CI Integration: - Added `rake rbs:validate` to main lint workflow - Added RBS validation to Pro gem lint workflow - Added Steep static type checker to CI pipeline 2. Runtime Type Checking: - Configured RSpec to run with RBS runtime checking for gem tests - Tests now run with RBS_TEST_TARGET='ReactOnRails::*' RUBYOPT='-rrbs/test/setup' - This provides runtime validation of type signatures during test execution 3. Steep Static Type Checker: - Added steep gem to development dependencies - Created Steepfile configuration for static type analysis - Added `rake rbs:steep` task for running static type checks - Added `rake rbs:all` task to run both validation and steep checks 4. Pro Gem RBS Types: - Created sig/ directory structure for Pro gem - Added type signatures for: - ReactOnRailsPro module - Configuration class with all attributes - Error, Cache, and Utils modules - Foundation for expanding type coverage in Pro package 5. Documentation: - Configured Steepfile to check lib/ directory - Set up library dependencies for proper type resolution Follows best practices from Evil Martians' "Climbing Steep Hills" article: - Static validation with rbs validate - Runtime checking with rbs/test/setup - Static analysis with Steep 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <[email protected]>
1 parent 88f1367 commit 71222f6

File tree

12 files changed

+202
-1
lines changed

12 files changed

+202
-1
lines changed

.github/workflows/lint-js-and-ruby.yml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -89,6 +89,10 @@ jobs:
8989
run: bundle check --path=vendor/bundle || bundle _2.5.9_ install --path=vendor/bundle --jobs=4 --retry=3
9090
- name: Lint Ruby
9191
run: bundle exec rubocop
92+
- name: Validate RBS type signatures
93+
run: bundle exec rake rbs:validate
94+
- name: Run Steep type checker
95+
run: bundle exec rake rbs:steep
9296
- name: Install Node modules with Yarn for dummy app
9397
run: cd spec/dummy && yarn install --no-progress --no-emoji --frozen-lockfile
9498
- name: Save dummy app ruby gems to cache

.github/workflows/pro-lint.yml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -123,6 +123,9 @@ jobs:
123123
- name: Lint Ruby
124124
run: bundle exec rubocop
125125

126+
- name: Validate RBS type signatures
127+
run: bundle exec rbs -I sig validate
128+
126129
- name: Lint JS
127130
run: yarn run nps eslint
128131

Gemfile.development_dependencies

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

Gemfile.lock

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -120,6 +120,7 @@ GEM
120120
thor (>= 0.19.4, < 2.0)
121121
tins (~> 1.6)
122122
crass (1.0.6)
123+
csv (3.3.5)
123124
date (3.3.4)
124125
debug (1.9.2)
125126
irb (~> 1.10)
@@ -133,6 +134,7 @@ GEM
133134
erubi (1.13.1)
134135
execjs (2.9.1)
135136
ffi (1.16.3)
137+
fileutils (1.8.0)
136138
gem-release (2.2.2)
137139
generator_spec (0.10.0)
138140
activesupport (>= 3.0.0)
@@ -347,6 +349,7 @@ GEM
347349
sass (~> 3.5, >= 3.5.5)
348350
sdoc (2.6.1)
349351
rdoc (>= 5.0)
352+
securerandom (0.4.1)
350353
selenium-webdriver (4.9.0)
351354
rexml (~> 3.2, >= 3.2.5)
352355
rubyzip (>= 1.2.2, < 3.0)
@@ -373,11 +376,29 @@ GEM
373376
sprockets (>= 3.0.0)
374377
sqlite3 (1.7.3)
375378
mini_portile2 (~> 2.8.0)
379+
steep (1.9.4)
380+
activesupport (>= 5.1)
381+
concurrent-ruby (>= 1.1.10)
382+
csv (>= 3.0.9)
383+
fileutils (>= 1.1.0)
384+
json (>= 2.1.0)
385+
language_server-protocol (>= 3.15, < 4.0)
386+
listen (~> 3.0)
387+
logger (>= 1.3.0)
388+
parser (>= 3.1)
389+
rainbow (>= 2.2.2, < 4.0)
390+
rbs (~> 3.8)
391+
securerandom (>= 0.1)
392+
strscan (>= 1.0.0)
393+
terminal-table (>= 2, < 4)
394+
uri (>= 0.12.0)
376395
stringio (3.1.7)
377396
strscan (3.1.0)
378397
sync (0.5.0)
379398
term-ansicolor (1.8.0)
380399
tins (~> 1.0)
400+
terminal-table (3.0.2)
401+
unicode-display_width (>= 1.1.1, < 3)
381402
thor (1.4.0)
382403
tilt (2.3.0)
383404
timeout (0.4.1)
@@ -397,6 +418,7 @@ GEM
397418
uglifier (4.2.0)
398419
execjs (>= 0.3.0, < 3)
399420
unicode-display_width (2.5.0)
421+
uri (1.1.1)
400422
webdrivers (5.3.0)
401423
nokogiri (~> 1.6)
402424
rubyzip (>= 1.3.0)
@@ -455,6 +477,7 @@ DEPENDENCIES
455477
spring (~> 4.0)
456478
sprockets (~> 4.0)
457479
sqlite3 (~> 1.6)
480+
steep
458481
turbo-rails
459482
turbolinks
460483
uglifier

Steepfile

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
# frozen_string_literal: true
2+
3+
# Steepfile - Configuration for Steep type checker
4+
# See https://github.com/soutaro/steep for documentation
5+
6+
D = Steep::Diagnostic
7+
8+
target :lib do
9+
# Specify the directories to type check
10+
check "lib"
11+
12+
# Specify RBS signature directories
13+
signature "sig"
14+
15+
# Configure libraries (gems) - Steep will load their RBS signatures
16+
configure_code_diagnostics(D::Ruby.default)
17+
18+
# Library configuration
19+
library "pathname"
20+
library "singleton"
21+
library "logger"
22+
library "monitor"
23+
library "securerandom"
24+
end

rakelib/rbs.rake

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,5 +34,26 @@ namespace :rbs do
3434
sig_files.each { |f| puts " #{f}" }
3535
puts "\nTotal: #{sig_files.count} files"
3636
end
37+
38+
desc "Run Steep type checker"
39+
task :steep do
40+
puts "Running Steep type checker..."
41+
42+
result = system("bundle exec steep check")
43+
44+
case result
45+
when true
46+
puts "✓ Steep type checking passed"
47+
when false
48+
puts "✗ Steep type checking failed"
49+
exit 1
50+
when nil
51+
puts "✗ Steep command not found or could not be executed"
52+
exit 1
53+
end
54+
end
55+
56+
desc "Run all RBS checks (validate + steep)"
57+
task all: %i[validate steep]
3758
end
3859
# rubocop:enable Metrics/BlockLength

rakelib/run_rspec.rake

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,9 @@ namespace :run_rspec do
2222

2323
desc "Run RSpec for top level only"
2424
task :gem do
25-
run_tests_in("", rspec_args: File.join("spec", "react_on_rails"))
25+
run_tests_in("",
26+
rspec_args: File.join("spec", "react_on_rails"),
27+
env_vars: "RBS_TEST_TARGET='ReactOnRails::*' RUBYOPT='-rrbs/test/setup'")
2628
end
2729

2830
desc "Runs dummy rspec with turbolinks"
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
module ReactOnRailsPro
2+
def self.configure: () { (Configuration) -> void } -> void
3+
4+
def self.configuration: () -> Configuration
5+
end
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
module ReactOnRailsPro
2+
module Cache
3+
def self.fetch: (String key) { () -> String } -> String
4+
5+
def self.enabled?: () -> bool
6+
end
7+
end
Lines changed: 100 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,100 @@
1+
module ReactOnRailsPro
2+
class Configuration
3+
DEFAULT_RENDERER_URL: String
4+
DEFAULT_RENDERER_METHOD: String
5+
DEFAULT_RENDERER_FALLBACK_EXEC_JS: bool
6+
DEFAULT_RENDERER_HTTP_POOL_SIZE: Integer
7+
DEFAULT_RENDERER_HTTP_POOL_TIMEOUT: Integer
8+
DEFAULT_RENDERER_HTTP_POOL_WARN_TIMEOUT: Float
9+
DEFAULT_SSR_TIMEOUT: Integer
10+
DEFAULT_PRERENDER_CACHING: bool
11+
DEFAULT_TRACING: bool
12+
DEFAULT_DEPENDENCY_GLOBS: Array[untyped]
13+
DEFAULT_EXCLUDED_DEPENDENCY_GLOBS: Array[untyped]
14+
DEFAULT_REMOTE_BUNDLE_CACHE_ADAPTER: nil
15+
DEFAULT_RENDERER_REQUEST_RETRY_LIMIT: Integer
16+
DEFAULT_THROW_JS_ERRORS: bool
17+
DEFAULT_RENDERING_RETURNS_PROMISES: bool
18+
DEFAULT_PROFILE_SERVER_RENDERING_JS_CODE: bool
19+
DEFAULT_RAISE_NON_SHELL_SERVER_RENDERING_ERRORS: bool
20+
DEFAULT_ENABLE_RSC_SUPPORT: bool
21+
DEFAULT_RSC_PAYLOAD_GENERATION_URL_PATH: String
22+
DEFAULT_RSC_BUNDLE_JS_FILE: String
23+
DEFAULT_REACT_CLIENT_MANIFEST_FILE: String
24+
DEFAULT_REACT_SERVER_CLIENT_MANIFEST_FILE: String
25+
26+
attr_accessor renderer_url: String?
27+
attr_accessor renderer_password: String?
28+
attr_accessor tracing: bool?
29+
attr_accessor server_renderer: String?
30+
attr_accessor renderer_use_fallback_exec_js: bool?
31+
attr_accessor prerender_caching: bool?
32+
attr_accessor renderer_http_pool_size: Integer?
33+
attr_accessor renderer_http_pool_timeout: Integer?
34+
attr_accessor renderer_http_pool_warn_timeout: Float?
35+
attr_accessor dependency_globs: Array[String]?
36+
attr_accessor excluded_dependency_globs: Array[String]?
37+
attr_accessor rendering_returns_promises: bool?
38+
attr_accessor remote_bundle_cache_adapter: Module?
39+
attr_accessor ssr_pre_hook_js: String?
40+
attr_accessor assets_to_copy: Array[String]?
41+
attr_accessor renderer_request_retry_limit: Integer?
42+
attr_accessor throw_js_errors: bool?
43+
attr_accessor ssr_timeout: Integer?
44+
attr_accessor profile_server_rendering_js_code: bool?
45+
attr_accessor raise_non_shell_server_rendering_errors: bool?
46+
attr_accessor enable_rsc_support: bool?
47+
attr_accessor rsc_payload_generation_url_path: String?
48+
attr_accessor rsc_bundle_js_file: String?
49+
attr_accessor react_client_manifest_file: String?
50+
attr_accessor react_server_client_manifest_file: String?
51+
52+
def initialize: (
53+
?renderer_url: String?,
54+
?renderer_password: String?,
55+
?server_renderer: String?,
56+
?renderer_use_fallback_exec_js: bool?,
57+
?prerender_caching: bool?,
58+
?renderer_http_pool_size: Integer?,
59+
?renderer_http_pool_timeout: Integer?,
60+
?renderer_http_pool_warn_timeout: Float?,
61+
?tracing: bool?,
62+
?dependency_globs: Array[String]?,
63+
?excluded_dependency_globs: Array[String]?,
64+
?rendering_returns_promises: bool?,
65+
?remote_bundle_cache_adapter: Module?,
66+
?ssr_pre_hook_js: String?,
67+
?assets_to_copy: Array[String]?,
68+
?renderer_request_retry_limit: Integer?,
69+
?throw_js_errors: bool?,
70+
?ssr_timeout: Integer?,
71+
?profile_server_rendering_js_code: bool?,
72+
?raise_non_shell_server_rendering_errors: bool?,
73+
?enable_rsc_support: bool?,
74+
?rsc_payload_generation_url_path: String?,
75+
?rsc_bundle_js_file: String?,
76+
?react_client_manifest_file: String?,
77+
?react_server_client_manifest_file: String?
78+
) -> void
79+
80+
def setup_config_values: () -> void
81+
82+
def check_react_on_rails_support_for_rsc: () -> void
83+
84+
def setup_execjs_profiler_if_needed: () -> void
85+
86+
def node_renderer?: () -> bool
87+
88+
private
89+
90+
def setup_assets_to_copy: () -> void
91+
92+
def configure_default_url_if_not_provided: () -> void
93+
94+
def validate_url: () -> void
95+
96+
def validate_remote_bundle_cache_adapter: () -> void
97+
98+
def setup_renderer_password: () -> void
99+
end
100+
end

0 commit comments

Comments
 (0)