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 +4 -4
- data/.DS_Store +0 -0
- data/README.md +16 -1
- data/lib/sorbet_view/compiler/ruby_generator.rb +10 -2
- data/lib/sorbet_view/compiler/template_compiler.rb +5 -0
- data/lib/sorbet_view/configuration.rb +1 -0
- data/lib/sorbet_view/file_system/file_watcher.rb +1 -1
- data/lib/sorbet_view/lsp/server.rb +60 -0
- data/lib/sorbet_view/version.rb +1 -1
- data/sorbet_view.gemspec +1 -1
- metadata +5 -4
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: c74f0ac2be7deb8aa466c27b4b4558852ebd4de838ada96d434650bb9c5bad0c
|
|
4
|
+
data.tar.gz: 4d807c4126e1f272397d4cad1b38c2a5f3341616df90aab21b3efb621874e992
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
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.
|
|
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
|
-
|
|
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)
|
data/lib/sorbet_view/version.rb
CHANGED
data/sorbet_view.gemspec
CHANGED
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.
|
|
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-
|
|
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.
|
|
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.
|
|
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
|