sorbet_view 0.16.1 → 0.17.1
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/lib/sorbet_view/compiler/adapters/erb_adapter.rb +31 -9
- data/lib/sorbet_view/version.rb +1 -1
- data/lib/tapioca/dsl/compilers/sorbet_view.rb +42 -8
- metadata +2 -3
- data/sorbet_view.gemspec +0 -34
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 241f449c7d85bd172dd1f14e69f2dd3c3baf9a2590d932a81437bcb7256697e9
|
|
4
|
+
data.tar.gz: 486cc137199d7a8e0fbda04baf528f139abd43585870108305ca9ece45f99a6e
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 78b9b224b0c202d58ab0f707f9333d8972298b11b707e735efd8f8da58eb93b21c24061f5a2b6fc0ea2fb207e2840df83351b6759a4ae8372d84391a390813c7
|
|
7
|
+
data.tar.gz: faf509cd68a7f7eb35f2e826a3bdc514681afad57e8900ce079269b58ec4d5321d5cba66e1aee7199f8eeb59eec0a58cf97bcc0440e142e351a0ea846f618a22
|
|
@@ -97,13 +97,14 @@ module SorbetView
|
|
|
97
97
|
return unless node.respond_to?(:content) && node.content
|
|
98
98
|
|
|
99
99
|
content_token = node.content
|
|
100
|
-
|
|
100
|
+
raw_value = content_token.value.to_s
|
|
101
|
+
code = raw_value.strip
|
|
101
102
|
return if code.empty?
|
|
102
103
|
|
|
103
|
-
# Herb lines are 1-based, we use 0-based
|
|
104
|
+
# Herb lines are 1-based, we use 0-based; raw content begins right after
|
|
105
|
+
# the opening tag, so advance past leading whitespace stripped from `code`.
|
|
104
106
|
loc = content_token.location
|
|
105
|
-
line = loc.start.line - 1
|
|
106
|
-
column = loc.start.column
|
|
107
|
+
line, column = advance_past_leading_whitespace(raw_value, loc.start.line - 1, loc.start.column)
|
|
107
108
|
|
|
108
109
|
tag = node.respond_to?(:tag_opening) ? node.tag_opening.value.to_s : '<%'
|
|
109
110
|
type = case tag
|
|
@@ -120,10 +121,12 @@ module SorbetView
|
|
|
120
121
|
return unless node.respond_to?(:content) && node.content
|
|
121
122
|
|
|
122
123
|
loc = node.content.location
|
|
124
|
+
raw_value = node.content.value.to_s
|
|
125
|
+
line, column = advance_past_leading_whitespace(raw_value, loc.start.line - 1, loc.start.column)
|
|
123
126
|
segments << RubySegment.new(
|
|
124
127
|
code: 'end',
|
|
125
|
-
line:
|
|
126
|
-
column:
|
|
128
|
+
line: line,
|
|
129
|
+
column: column,
|
|
127
130
|
type: :statement
|
|
128
131
|
)
|
|
129
132
|
end
|
|
@@ -137,14 +140,16 @@ module SorbetView
|
|
|
137
140
|
source.scan(INDICATOR_PATTERN) do
|
|
138
141
|
match = T.must(Regexp.last_match)
|
|
139
142
|
indicator = match[1] || ''
|
|
140
|
-
|
|
143
|
+
raw_code = match[2] || ''
|
|
144
|
+
code = raw_code.strip
|
|
141
145
|
offset = T.must(match.begin(0))
|
|
142
146
|
|
|
143
147
|
next if code.empty?
|
|
144
148
|
|
|
145
149
|
line, column = offset_to_line_column(line_offsets, offset)
|
|
146
150
|
tag_prefix_len = 2 + indicator.length
|
|
147
|
-
|
|
151
|
+
content_column = column + tag_prefix_len
|
|
152
|
+
code_line, code_column = advance_past_leading_whitespace(raw_code, line, content_column)
|
|
148
153
|
|
|
149
154
|
type = case indicator
|
|
150
155
|
when '#' then :comment
|
|
@@ -152,7 +157,7 @@ module SorbetView
|
|
|
152
157
|
else :statement
|
|
153
158
|
end
|
|
154
159
|
|
|
155
|
-
segments << RubySegment.new(code: code, line:
|
|
160
|
+
segments << RubySegment.new(code: code, line: code_line, column: code_column, type: type)
|
|
156
161
|
end
|
|
157
162
|
|
|
158
163
|
segments
|
|
@@ -173,6 +178,23 @@ module SorbetView
|
|
|
173
178
|
column = offset - (line_offsets[line] || 0)
|
|
174
179
|
[line, column]
|
|
175
180
|
end
|
|
181
|
+
|
|
182
|
+
# Returns the (line, column) of the first non-whitespace char in raw_value,
|
|
183
|
+
# given the position where raw_value begins. Used so that stripping leading
|
|
184
|
+
# whitespace from ERB content does not desync the segment's start position.
|
|
185
|
+
sig { params(raw_value: String, line: Integer, column: Integer).returns([Integer, Integer]) }
|
|
186
|
+
def advance_past_leading_whitespace(raw_value, line, column)
|
|
187
|
+
leading = raw_value[/\A\s*/] || ''
|
|
188
|
+
return [line, column] if leading.empty?
|
|
189
|
+
|
|
190
|
+
newlines = leading.count("\n")
|
|
191
|
+
if newlines == 0
|
|
192
|
+
[line, column + leading.length]
|
|
193
|
+
else
|
|
194
|
+
last_nl = T.must(leading.rindex("\n"))
|
|
195
|
+
[line + newlines, leading.length - last_nl - 1]
|
|
196
|
+
end
|
|
197
|
+
end
|
|
176
198
|
end
|
|
177
199
|
end
|
|
178
200
|
end
|
data/lib/sorbet_view/version.rb
CHANGED
|
@@ -28,6 +28,9 @@ module Tapioca
|
|
|
28
28
|
@module_cache = T.let({}, T::Hash[String, RBI::Scope])
|
|
29
29
|
@project = T.let(SrbLens::Project.load_or_index(Dir.pwd), T.untyped)
|
|
30
30
|
|
|
31
|
+
# Ensure all controllers are loaded
|
|
32
|
+
Rails.application.eager_load! if defined?(Rails) && Rails.respond_to?(:application)
|
|
33
|
+
|
|
31
34
|
controllers = ObjectSpace.each_object(Class).select do |klass|
|
|
32
35
|
klass < ::ActionController::Base && !klass.abstract?
|
|
33
36
|
rescue StandardError
|
|
@@ -238,27 +241,34 @@ module Tapioca
|
|
|
238
241
|
sig { params(controller: T.untyped).void }
|
|
239
242
|
def process_controller(controller)
|
|
240
243
|
helper_methods = extract_helper_methods(controller)
|
|
241
|
-
|
|
244
|
+
controller_helper_modules = extract_controller_helper_modules(controller)
|
|
245
|
+
return if helper_methods.empty? && controller_helper_modules.empty?
|
|
242
246
|
|
|
243
247
|
path = controller.controller_path
|
|
244
248
|
parts = path.split('/').map { |p| camelize(p) }
|
|
245
249
|
|
|
246
|
-
# 1) module SorbetView::Helpers::<controller_path> with helper methods
|
|
247
|
-
helper_module_name =
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
250
|
+
# 1) module SorbetView::Helpers::<controller_path> with helper methods from helper_method macro
|
|
251
|
+
helper_module_name = nil
|
|
252
|
+
unless helper_methods.empty?
|
|
253
|
+
helper_module_name = "SorbetView::Helpers::#{parts.join('::')}"
|
|
254
|
+
create_module_from_path(helper_module_name) do |mod|
|
|
255
|
+
helper_methods.each do |method_name, params, return_type|
|
|
256
|
+
mod.create_method(method_name, parameters: params, return_type: return_type)
|
|
257
|
+
end
|
|
251
258
|
end
|
|
252
259
|
end
|
|
253
260
|
|
|
254
|
-
# 2) Each action's template class includes the helper module
|
|
261
|
+
# 2) Each action's template class includes the helper module and controller-specific helper modules
|
|
255
262
|
actions = controller.action_methods.to_a
|
|
256
263
|
actions.each do |action_name|
|
|
257
264
|
next unless action_name.match?(/\A[a-zA-Z_][a-zA-Z0-9_]*\z/)
|
|
258
265
|
|
|
259
266
|
class_name = "SorbetView::Generated::#{parts.join('::')}::#{camelize(action_name)}"
|
|
260
267
|
create_class_from_path(class_name) do |klass|
|
|
261
|
-
klass.create_include(helper_module_name)
|
|
268
|
+
klass.create_include(helper_module_name) if helper_module_name
|
|
269
|
+
controller_helper_modules.each do |mod_name|
|
|
270
|
+
klass.create_include("::#{mod_name}")
|
|
271
|
+
end
|
|
262
272
|
end
|
|
263
273
|
end
|
|
264
274
|
end
|
|
@@ -285,6 +295,30 @@ module Tapioca
|
|
|
285
295
|
end
|
|
286
296
|
end
|
|
287
297
|
|
|
298
|
+
# Returns controller-specific helper module names (modules included via `helper` that are not in ApplicationController)
|
|
299
|
+
sig { params(controller: T.untyped).returns(T::Array[String]) }
|
|
300
|
+
def extract_controller_helper_modules(controller)
|
|
301
|
+
return [] unless controller.respond_to?(:_helpers)
|
|
302
|
+
|
|
303
|
+
base_helpers = if defined?(::ApplicationController) && ::ApplicationController.respond_to?(:_helpers)
|
|
304
|
+
::ApplicationController._helpers.ancestors
|
|
305
|
+
else
|
|
306
|
+
[]
|
|
307
|
+
end
|
|
308
|
+
|
|
309
|
+
controller_helpers = controller._helpers.ancestors
|
|
310
|
+
specific_modules = controller_helpers - base_helpers
|
|
311
|
+
|
|
312
|
+
specific_modules.filter_map do |mod|
|
|
313
|
+
next unless mod.is_a?(Module) && !mod.is_a?(Class)
|
|
314
|
+
name = mod.name
|
|
315
|
+
next if name.nil? || name.empty?
|
|
316
|
+
next if name.include?('HelperMethods')
|
|
317
|
+
next if name.start_with?('ActionController::') || name.start_with?('AbstractController::')
|
|
318
|
+
name
|
|
319
|
+
end
|
|
320
|
+
end
|
|
321
|
+
|
|
288
322
|
sig { params(controller: T.untyped, method_name: String).returns(T.untyped) }
|
|
289
323
|
def find_method_info(controller, method_name)
|
|
290
324
|
methods = @project.find_methods("#{controller.name}##{method_name}")
|
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.17.1
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- kazuma
|
|
8
8
|
bindir: exe
|
|
9
9
|
cert_chain: []
|
|
10
|
-
date: 2026-04-
|
|
10
|
+
date: 2026-04-22 00:00:00.000000000 Z
|
|
11
11
|
dependencies:
|
|
12
12
|
- !ruby/object:Gem::Dependency
|
|
13
13
|
name: herb
|
|
@@ -118,7 +118,6 @@ files:
|
|
|
118
118
|
- lib/sorbet_view/source_map/source_map.rb
|
|
119
119
|
- lib/sorbet_view/version.rb
|
|
120
120
|
- lib/tapioca/dsl/compilers/sorbet_view.rb
|
|
121
|
-
- sorbet_view.gemspec
|
|
122
121
|
- vscode/.gitignore
|
|
123
122
|
- vscode/.vscode/launch.json
|
|
124
123
|
- vscode/.vscodeignore
|
data/sorbet_view.gemspec
DELETED
|
@@ -1,34 +0,0 @@
|
|
|
1
|
-
# frozen_string_literal: true
|
|
2
|
-
|
|
3
|
-
require_relative 'lib/sorbet_view/version'
|
|
4
|
-
|
|
5
|
-
Gem::Specification.new do |spec|
|
|
6
|
-
spec.name = 'sorbet_view'
|
|
7
|
-
spec.version = SorbetView::VERSION
|
|
8
|
-
spec.authors = ['kazuma']
|
|
9
|
-
spec.summary = 'Sorbet type-checking for Rails view templates'
|
|
10
|
-
spec.description = 'Extracts Ruby code from view templates (ERB, etc.) for Sorbet type-checking, with LSP support'
|
|
11
|
-
spec.homepage = 'https://github.com/kazzix14/sorbet_view'
|
|
12
|
-
spec.license = 'MIT'
|
|
13
|
-
spec.required_ruby_version = '>= 3.2'
|
|
14
|
-
|
|
15
|
-
spec.metadata['rubygems_mfa_required'] = 'true'
|
|
16
|
-
spec.metadata['homepage_uri'] = spec.homepage
|
|
17
|
-
spec.metadata['source_code_uri'] = spec.homepage
|
|
18
|
-
|
|
19
|
-
spec.files = Dir.chdir(__dir__) do
|
|
20
|
-
`git ls-files -z`.split("\x0").reject do |f|
|
|
21
|
-
(File.expand_path(f) == __FILE__) ||
|
|
22
|
-
f.start_with?(*%w[bin/ test/ spec/ features/ .git .github Gemfile])
|
|
23
|
-
end
|
|
24
|
-
end
|
|
25
|
-
spec.bindir = 'exe'
|
|
26
|
-
spec.executables = ['sv']
|
|
27
|
-
spec.require_paths = ['lib']
|
|
28
|
-
|
|
29
|
-
spec.add_dependency 'herb'
|
|
30
|
-
spec.add_dependency 'listen', '~> 3.0'
|
|
31
|
-
spec.add_dependency 'psych'
|
|
32
|
-
spec.add_dependency 'sorbet-runtime'
|
|
33
|
-
spec.add_dependency 'srb_lens', '~> 0.5.1'
|
|
34
|
-
end
|