sorbet_view 0.5.0 → 0.6.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: 2e66c3f2247cf3bcf0c65887aceb780fe6c2048aa733e819a4fe9afaf641c8b3
4
- data.tar.gz: 133411c911ab5b2035288b04c94c92be6077c4b5180a5cffd125a036ce44cc6e
3
+ metadata.gz: c74f0ac2be7deb8aa466c27b4b4558852ebd4de838ada96d434650bb9c5bad0c
4
+ data.tar.gz: 4d807c4126e1f272397d4cad1b38c2a5f3341616df90aab21b3efb621874e992
5
5
  SHA512:
6
- metadata.gz: a463b522df92117937a8c8b4021d7156d7014074985ce0d766b4bbfd2020f54153aa7232e51def28137125943e34cd340b81ab0ea10dc05bf0b07fc714dcea1c
7
- data.tar.gz: '09afa6e9b68798b63680770f8e9d9ef7fb2148f817d55a00c91b77e486693ab014c9a7c2df0729f64c8cf409b0102917b6c1127eb935c60c520375cd6a20595b'
6
+ metadata.gz: b8a488ac1a4299128cd8ec4df072c4098a8b801bd080f34cf7e29353f47303ef4eb05e22b89ba56b5dae32e1e9f7edb5325333c11e132610aa13d4c882a287e2
7
+ data.tar.gz: 2d40c0ce178370b135c91e8f49cd4e3f2f7fec3dc114849497afc2af3a8326523df09b1cf0362e512e4fa2d87b808db8b11c24325736dc582348ef9b0e556444
data/.DS_Store ADDED
Binary file
data/README.md CHANGED
@@ -60,6 +60,9 @@ skip_missing_locals: true
60
60
  | `sorbet_path` | `'srb'` | Path to the Sorbet binary |
61
61
  | `typed_level` | `'true'` | Sorbet `typed` level for generated files |
62
62
  | `component_dirs` | `[]` | Directories to scan for ViewComponent files |
63
+ | `controller_dirs` | `['app/controllers']` | Controller directories to watch for changes (LSP recompiles associated templates) |
64
+ | `path_mapping` | `{}` | Path mapping for remote development (e.g., Docker) |
65
+ | `sorbet_options` | `[]` | Additional options passed to Sorbet |
63
66
 
64
67
  ## Usage
65
68
 
@@ -122,12 +125,24 @@ Components using `erb_template` heredocs are automatically detected and compiled
122
125
 
123
126
  ## Tapioca integration
124
127
 
125
- SorbetView includes a Tapioca DSL compiler that generates RBI files for controller helper methods. Run:
128
+ SorbetView includes a Tapioca DSL compiler that generates RBI files for controller helper methods and extracts instance variable types from controller actions using [srb_lens](https://github.com/kazzix14/srb_lens).
126
129
 
127
130
  ```bash
128
131
  bundle exec tapioca dsl
129
132
  ```
130
133
 
134
+ This generates:
135
+ - RBI files for helper methods
136
+ - `.defined_ivars.json` mapping template paths to instance variable types
137
+
138
+ After running `tapioca dsl`, recompile templates to apply the updated types:
139
+
140
+ ```bash
141
+ bundle exec sv compile
142
+ ```
143
+
144
+ When using the LSP server, templates are automatically recompiled when the corresponding controller file is saved.
145
+
131
146
  ## Requirements
132
147
 
133
148
  - Ruby >= 3.2
@@ -149,6 +149,11 @@ module SorbetView
149
149
  )
150
150
  end
151
151
 
152
+ sig { void }
153
+ def invalidate_ivar_cache!
154
+ @ivar_mapping = nil
155
+ end
156
+
152
157
  private
153
158
 
154
159
  # Collect @variable references from code segments
@@ -169,16 +174,19 @@ module SorbetView
169
174
  end
170
175
  def resolve_ivar_types(segments, context, config, component_mode)
171
176
  all_ivars = collect_ivars(segments)
172
- return [] if all_ivars.empty?
173
177
 
174
178
  if component_mode
179
+ return [] if all_ivars.empty?
175
180
  # Component mode: ivars defined in source are omitted, undefined get NilClass
176
181
  defined_ivars = load_component_defined_ivars(context.template_path)
177
182
  (all_ivars - defined_ivars).map { |ivar| [ivar, 'NilClass'] }
178
183
  else
179
184
  # View mode: use typed ivar mapping from srb-lens
180
185
  ivar_types = load_view_ivar_types(context.template_path, config)
181
- all_ivars.map do |ivar|
186
+ # Include all ivars: those used in the template + those defined in the controller
187
+ combined_ivars = (all_ivars + ivar_types.keys).uniq.sort
188
+ return [] if combined_ivars.empty?
189
+ combined_ivars.map do |ivar|
182
190
  type = ivar_types[ivar]
183
191
  [ivar, type || 'NilClass']
184
192
  end
@@ -13,6 +13,11 @@ module SorbetView
13
13
  @config = config
14
14
  end
15
15
 
16
+ sig { void }
17
+ def invalidate_ivar_cache!
18
+ @generator.invalidate_ivar_cache!
19
+ end
20
+
16
21
  sig { params(template_path: String, source: String).returns(CompileResult) }
17
22
  def compile(template_path, source)
18
23
  Perf.measure('compiler.compile') do
@@ -15,6 +15,7 @@ module SorbetView
15
15
  const :typed_level, String, default: 'true'
16
16
  const :path_mapping, T::Hash[String, String], default: {}
17
17
  const :component_dirs, T::Array[String], default: []
18
+ const :controller_dirs, T::Array[String], default: ['app/controllers']
18
19
  const :sorbet_options, T::Array[String], default: []
19
20
 
20
21
  class << self
@@ -22,7 +22,7 @@ module SorbetView
22
22
 
23
23
  sig { void }
24
24
  def start
25
- dirs = (@config.input_dirs + @config.component_dirs).select { |d| Dir.exist?(d) }
25
+ dirs = (@config.input_dirs + @config.component_dirs + @config.controller_dirs).uniq.select { |d| Dir.exist?(d) }
26
26
  return if dirs.empty?
27
27
 
28
28
  @listener = Listen.to(
@@ -156,6 +156,7 @@ module SorbetView
156
156
  # Forward .rb files to Sorbet; also recompile if they contain erb_template
157
157
  forward_to_sorbet(message)
158
158
  compile_component_if_needed(uri, text)
159
+ recompile_templates_for_controller(uri)
159
160
  end
160
161
  end
161
162
 
@@ -176,6 +177,7 @@ module SorbetView
176
177
  changes = params['contentChanges']
177
178
  text = changes&.last&.fetch('text', nil)
178
179
  compile_component_if_needed(uri, text) if text
180
+ recompile_templates_for_controller(uri)
179
181
  end
180
182
  end
181
183
 
@@ -202,6 +204,7 @@ module SorbetView
202
204
  compile_and_sync(doc) if doc
203
205
  else
204
206
  compile_component_if_needed(uri, text)
207
+ recompile_templates_for_controller(uri)
205
208
  end
206
209
  end
207
210
 
@@ -582,6 +585,7 @@ module SorbetView
582
585
  if path.end_with?('.rb')
583
586
  # Component .rb file — compile and notify Sorbet
584
587
  compile_component_if_needed(template_uri, source)
588
+ recompile_templates_for_controller(template_uri)
585
589
  else
586
590
  result = @compiler.compile(path, source)
587
591
  next if result.source_map.entries.empty? && @config.skip_missing_locals && requires_locals?(path)
@@ -599,6 +603,62 @@ module SorbetView
599
603
  @logger.error("FileWatcher callback error: #{e.message}")
600
604
  end
601
605
 
606
+ # --- Controller → Template Recompilation ---
607
+
608
+ sig { params(uri: String).void }
609
+ def recompile_templates_for_controller(uri)
610
+ path = @uri_mapper.uri_to_path(uri)
611
+ return unless path.end_with?('_controller.rb')
612
+
613
+ controller_relative = extract_controller_relative_path(path)
614
+ return unless controller_relative
615
+
616
+ @logger.debug("Controller changed: #{path} → recompiling templates for #{controller_relative}")
617
+ @compiler.invalidate_ivar_cache!
618
+
619
+ @config.input_dirs.each do |input_dir|
620
+ view_dir = File.join(input_dir, controller_relative)
621
+ next unless Dir.exist?(view_dir)
622
+
623
+ Dir.glob(File.join(view_dir, '**', '*.erb')).each do |template_path|
624
+ source = File.read(template_path)
625
+ result = @compiler.compile(template_path, source)
626
+ @output_manager.write(result)
627
+ @position_translator.register(template_path, result.source_map)
628
+ notify_sorbet_template_changed(template_path, result)
629
+ end
630
+ end
631
+ rescue => e
632
+ @logger.error("recompile_templates_for_controller error: #{e.message}")
633
+ end
634
+
635
+ sig { params(path: String).returns(T.nilable(String)) }
636
+ def extract_controller_relative_path(path)
637
+ match = path.match(%r{controllers/(.+)_controller\.rb\z})
638
+ match ? match[1] : nil
639
+ end
640
+
641
+ sig { params(template_path: String, result: Compiler::CompileResult).void }
642
+ def notify_sorbet_template_changed(template_path, result)
643
+ ruby_uri = @uri_mapper.path_to_uri(result.source_map.ruby_path)
644
+ if @sorbet_open_uris.include?(ruby_uri)
645
+ @sorbet.send_notification('textDocument/didChange', {
646
+ 'textDocument' => { 'uri' => ruby_uri, 'version' => 0 },
647
+ 'contentChanges' => [{ 'text' => result.ruby_source }]
648
+ })
649
+ else
650
+ @sorbet.send_notification('textDocument/didOpen', {
651
+ 'textDocument' => {
652
+ 'uri' => ruby_uri,
653
+ 'languageId' => 'ruby',
654
+ 'version' => 0,
655
+ 'text' => result.ruby_source
656
+ }
657
+ })
658
+ @sorbet_open_uris.add(ruby_uri)
659
+ end
660
+ end
661
+
602
662
  sig { params(message: T::Hash[String, T.untyped]).void }
603
663
  def forward_to_sorbet(message)
604
664
  @sorbet.forward(message)
@@ -2,5 +2,5 @@
2
2
  # frozen_string_literal: true
3
3
 
4
4
  module SorbetView
5
- VERSION = '0.5.0'
5
+ VERSION = '0.6.0'
6
6
  end
data/sorbet_view.gemspec CHANGED
@@ -30,5 +30,5 @@ Gem::Specification.new do |spec|
30
30
  spec.add_dependency 'listen', '~> 3.0'
31
31
  spec.add_dependency 'psych'
32
32
  spec.add_dependency 'sorbet-runtime'
33
- spec.add_dependency 'srb_lens', '~> 0.3.0'
33
+ spec.add_dependency 'srb_lens', '~> 0.5.0'
34
34
  end
metadata CHANGED
@@ -1,13 +1,13 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: sorbet_view
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.5.0
4
+ version: 0.6.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - kazuma
8
8
  bindir: exe
9
9
  cert_chain: []
10
- date: 2026-03-12 00:00:00.000000000 Z
10
+ date: 2026-03-13 00:00:00.000000000 Z
11
11
  dependencies:
12
12
  - !ruby/object:Gem::Dependency
13
13
  name: herb
@@ -71,14 +71,14 @@ dependencies:
71
71
  requirements:
72
72
  - - "~>"
73
73
  - !ruby/object:Gem::Version
74
- version: 0.3.0
74
+ version: 0.5.0
75
75
  type: :runtime
76
76
  prerelease: false
77
77
  version_requirements: !ruby/object:Gem::Requirement
78
78
  requirements:
79
79
  - - "~>"
80
80
  - !ruby/object:Gem::Version
81
- version: 0.3.0
81
+ version: 0.5.0
82
82
  description: Extracts Ruby code from view templates (ERB, etc.) for Sorbet type-checking,
83
83
  with LSP support
84
84
  executables:
@@ -86,6 +86,7 @@ executables:
86
86
  extensions: []
87
87
  extra_rdoc_files: []
88
88
  files:
89
+ - ".DS_Store"
89
90
  - LICENSE
90
91
  - README.md
91
92
  - Rakefile