sorbet_view 0.1.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.
Files changed (40) hide show
  1. checksums.yaml +7 -0
  2. data/LICENSE +21 -0
  3. data/Rakefile +8 -0
  4. data/exe/sv +6 -0
  5. data/lib/sorbet_view/cli/runner.rb +206 -0
  6. data/lib/sorbet_view/compiler/adapters/erb_adapter.rb +170 -0
  7. data/lib/sorbet_view/compiler/component_compiler.rb +48 -0
  8. data/lib/sorbet_view/compiler/heredoc_extractor.rb +112 -0
  9. data/lib/sorbet_view/compiler/parser_adapter.rb +16 -0
  10. data/lib/sorbet_view/compiler/ruby_generator.rb +211 -0
  11. data/lib/sorbet_view/compiler/ruby_segment.rb +13 -0
  12. data/lib/sorbet_view/compiler/template_compiler.rb +41 -0
  13. data/lib/sorbet_view/compiler/template_context.rb +170 -0
  14. data/lib/sorbet_view/configuration.rb +34 -0
  15. data/lib/sorbet_view/file_system/file_watcher.rb +61 -0
  16. data/lib/sorbet_view/file_system/output_manager.rb +40 -0
  17. data/lib/sorbet_view/file_system/project_scanner.rb +34 -0
  18. data/lib/sorbet_view/lsp/document_store.rb +56 -0
  19. data/lib/sorbet_view/lsp/position_translator.rb +95 -0
  20. data/lib/sorbet_view/lsp/server.rb +607 -0
  21. data/lib/sorbet_view/lsp/sorbet_process.rb +164 -0
  22. data/lib/sorbet_view/lsp/transport.rb +89 -0
  23. data/lib/sorbet_view/lsp/uri_mapper.rb +105 -0
  24. data/lib/sorbet_view/perf.rb +92 -0
  25. data/lib/sorbet_view/source_map/mapping_entry.rb +12 -0
  26. data/lib/sorbet_view/source_map/position.rb +23 -0
  27. data/lib/sorbet_view/source_map/range.rb +35 -0
  28. data/lib/sorbet_view/source_map/source_map.rb +103 -0
  29. data/lib/sorbet_view/version.rb +6 -0
  30. data/lib/sorbet_view.rb +36 -0
  31. data/lib/tapioca/dsl/compilers/sorbet_view.rb +265 -0
  32. data/sorbet_view.gemspec +34 -0
  33. data/vscode/.gitignore +3 -0
  34. data/vscode/.vscode/launch.json +12 -0
  35. data/vscode/.vscodeignore +4 -0
  36. data/vscode/package-lock.json +140 -0
  37. data/vscode/package.json +61 -0
  38. data/vscode/src/extension.ts +88 -0
  39. data/vscode/tsconfig.json +17 -0
  40. metadata +151 -0
@@ -0,0 +1,164 @@
1
+ # typed: strict
2
+ # frozen_string_literal: true
3
+
4
+ module SorbetView
5
+ module Lsp
6
+ # Manages Sorbet LSP as a child process
7
+ class SorbetProcess
8
+ extend T::Sig
9
+
10
+ sig { returns(T.nilable(Transport)) }
11
+ attr_reader :transport
12
+
13
+ sig { params(config: Configuration, logger: Logger).void }
14
+ def initialize(config:, logger:)
15
+ @config = config
16
+ @logger = logger
17
+ @process = T.let(nil, T.nilable(IO))
18
+ @pid = T.let(nil, T.nilable(Integer))
19
+ @transport = T.let(nil, T.nilable(Transport))
20
+ @read_thread = T.let(nil, T.nilable(Thread))
21
+ @notification_handlers = T.let({}, T::Hash[String, T.proc.params(msg: T::Hash[String, T.untyped]).void])
22
+ @pending_requests = T.let({}, T::Hash[T.untyped, Thread::Queue])
23
+ @pending_mutex = T.let(Mutex.new, Mutex)
24
+ @next_id = T.let(1, Integer)
25
+ end
26
+
27
+ sig { void }
28
+ def start
29
+ cmd = [
30
+ @config.sorbet_path,
31
+ 'tc',
32
+ '--lsp',
33
+ '--enable-all-experimental-lsp-features'
34
+ ]
35
+
36
+ @logger.info("Starting Sorbet: #{cmd.join(' ')}")
37
+
38
+ stdin_r, stdin_w = IO.pipe
39
+ stdout_r, stdout_w = IO.pipe
40
+ err_r, err_w = IO.pipe
41
+
42
+ @pid = Process.spawn(
43
+ *cmd,
44
+ in: stdin_r,
45
+ out: stdout_w,
46
+ err: err_w
47
+ )
48
+
49
+ stdin_r.close
50
+ stdout_w.close
51
+ err_w.close
52
+
53
+ @process = stdin_w
54
+ @transport = Transport.new(input: stdout_r, output: stdin_w)
55
+
56
+ @read_thread = Thread.new { read_loop(stdout_r) }
57
+
58
+ # Log stderr from Sorbet in background
59
+ Thread.new do
60
+ err_r.each_line { |line| @logger.info("Sorbet stderr: #{line.chomp}") }
61
+ err_r.close
62
+ end
63
+ end
64
+
65
+ sig { params(method_name: String, params: T.untyped).returns(T.untyped) }
66
+ def send_request(method_name, params)
67
+ transport = @transport
68
+ raise 'Sorbet process not started' unless transport
69
+
70
+ id = @pending_mutex.synchronize do
71
+ current = @next_id
72
+ @next_id += 1
73
+ current
74
+ end
75
+
76
+ queue = Thread::Queue.new
77
+ @pending_mutex.synchronize { @pending_requests[id] = queue }
78
+
79
+ transport.send_request(id, method_name, params)
80
+
81
+ # Wait for response with timeout
82
+ result = nil
83
+ deadline = Time.now + 30
84
+ loop do
85
+ break unless result.nil?
86
+ break if Time.now > deadline
87
+
88
+ begin
89
+ result = queue.pop(true) # non-blocking
90
+ rescue ThreadError
91
+ sleep 0.05
92
+ end
93
+ end
94
+
95
+ @pending_mutex.synchronize { @pending_requests.delete(id) }
96
+
97
+ if result.nil?
98
+ @logger.error("Sorbet request '#{method_name}' timed out (30s)")
99
+ end
100
+
101
+ result
102
+ end
103
+
104
+ sig { params(method_name: String, params: T.untyped).void }
105
+ def send_notification(method_name, params)
106
+ transport = @transport
107
+ raise 'Sorbet process not started' unless transport
108
+
109
+ transport.send_notification(method_name, params)
110
+ end
111
+
112
+ # Forward a raw message (request or notification) to Sorbet
113
+ sig { params(message: T::Hash[String, T.untyped]).void }
114
+ def forward(message)
115
+ transport = @transport
116
+ raise 'Sorbet process not started' unless transport
117
+
118
+ transport.write_message(message)
119
+ end
120
+
121
+ sig { params(method_name: String, block: T.proc.params(msg: T::Hash[String, T.untyped]).void).void }
122
+ def on_notification(method_name, &block)
123
+ @notification_handlers[method_name] = block
124
+ end
125
+
126
+ sig { void }
127
+ def stop
128
+ if @pid
129
+ Process.kill('TERM', @pid)
130
+ Process.wait(@pid)
131
+ @pid = nil
132
+ end
133
+ @read_thread&.kill
134
+ rescue Errno::ESRCH, Errno::ECHILD
135
+ # Process already gone
136
+ end
137
+
138
+ private
139
+
140
+ sig { params(stdout: IO).void }
141
+ def read_loop(stdout)
142
+ reader = Transport.new(input: stdout, output: File.open(File::NULL, 'w'))
143
+ loop do
144
+ message = reader.read_message
145
+ break if message.nil?
146
+
147
+ if message['id'] && !message.key?('method')
148
+ # Response to our request
149
+ @pending_mutex.synchronize do
150
+ queue = @pending_requests[message['id']]
151
+ queue&.push(message)
152
+ end
153
+ elsif message.key?('method')
154
+ # Notification from Sorbet
155
+ handler = @notification_handlers[message['method']]
156
+ handler&.call(message)
157
+ end
158
+ end
159
+ rescue IOError
160
+ @logger.info('Sorbet process stdout closed')
161
+ end
162
+ end
163
+ end
164
+ end
@@ -0,0 +1,89 @@
1
+ # typed: strict
2
+ # frozen_string_literal: true
3
+
4
+ require 'json'
5
+ require 'stringio'
6
+
7
+ module SorbetView
8
+ module Lsp
9
+ # JSON-RPC over stdio transport for LSP
10
+ class Transport
11
+ extend T::Sig
12
+
13
+ sig { params(input: T.any(IO, StringIO), output: T.any(IO, StringIO)).void }
14
+ def initialize(input: $stdin, output: $stdout)
15
+ @input = T.let(input, T.any(IO, StringIO))
16
+ @output = T.let(output, T.any(IO, StringIO))
17
+ @mutex = T.let(Mutex.new, Mutex)
18
+ end
19
+
20
+ # Read a single JSON-RPC message from the input stream.
21
+ # Returns nil on EOF.
22
+ sig { returns(T.nilable(T::Hash[String, T.untyped])) }
23
+ def read_message
24
+ # Read headers
25
+ content_length = nil
26
+ loop do
27
+ line = @input.gets
28
+ return nil if line.nil? # EOF
29
+
30
+ line = line.strip
31
+ break if line.empty? # End of headers
32
+
33
+ if line.start_with?('Content-Length:')
34
+ content_length = line.split(':').last&.strip&.to_i
35
+ end
36
+ end
37
+
38
+ return nil unless content_length
39
+
40
+ # Read body
41
+ body = @input.read(content_length)
42
+ return nil if body.nil?
43
+
44
+ JSON.parse(body)
45
+ end
46
+
47
+ # Write a JSON-RPC message to the output stream (thread-safe).
48
+ sig { params(message: T::Hash[String, T.untyped]).void }
49
+ def write_message(message)
50
+ body = JSON.generate(message)
51
+ header = "Content-Length: #{body.bytesize}\r\n\r\n"
52
+
53
+ @mutex.synchronize do
54
+ @output.write(header)
55
+ @output.write(body)
56
+ @output.flush
57
+ end
58
+ end
59
+
60
+ # Convenience: send a JSON-RPC response
61
+ sig { params(id: T.untyped, result: T.untyped).void }
62
+ def send_response(id, result)
63
+ write_message({ 'jsonrpc' => '2.0', 'id' => id, 'result' => result })
64
+ end
65
+
66
+ # Convenience: send a JSON-RPC error response
67
+ sig { params(id: T.untyped, code: Integer, message: String).void }
68
+ def send_error(id, code, message)
69
+ write_message({
70
+ 'jsonrpc' => '2.0',
71
+ 'id' => id,
72
+ 'error' => { 'code' => code, 'message' => message }
73
+ })
74
+ end
75
+
76
+ # Convenience: send a JSON-RPC notification (no id)
77
+ sig { params(method_name: String, params: T.untyped).void }
78
+ def send_notification(method_name, params)
79
+ write_message({ 'jsonrpc' => '2.0', 'method' => method_name, 'params' => params })
80
+ end
81
+
82
+ # Convenience: send a JSON-RPC request
83
+ sig { params(id: T.untyped, method_name: String, params: T.untyped).void }
84
+ def send_request(id, method_name, params)
85
+ write_message({ 'jsonrpc' => '2.0', 'id' => id, 'method' => method_name, 'params' => params })
86
+ end
87
+ end
88
+ end
89
+ end
@@ -0,0 +1,105 @@
1
+ # typed: strict
2
+ # frozen_string_literal: true
3
+
4
+ require 'uri'
5
+
6
+ module SorbetView
7
+ module Lsp
8
+ # Maps between template file URIs and generated Ruby file URIs
9
+ class UriMapper
10
+ extend T::Sig
11
+
12
+ TEMPLATE_EXTENSIONS = T.let(%w[.erb .haml .slim].freeze, T::Array[String])
13
+
14
+ sig { params(config: Configuration).void }
15
+ def initialize(config:)
16
+ @output_dir = config.output_dir
17
+ # path_mapping: { "/host/path" => "/container/path" }
18
+ @path_mapping = T.let(config.path_mapping, T::Hash[String, String])
19
+ end
20
+
21
+ sig { params(uri: String).returns(T::Boolean) }
22
+ def template_uri?(uri)
23
+ path = uri_to_path(uri)
24
+ TEMPLATE_EXTENSIONS.any? { |ext| path.end_with?(ext) }
25
+ end
26
+
27
+ sig { params(uri: String).returns(T::Boolean) }
28
+ def generated_ruby_uri?(uri)
29
+ path = uri_to_path(uri)
30
+ path.start_with?(@output_dir) || path.include?("/#{@output_dir}/")
31
+ end
32
+
33
+ # template URI -> generated ruby URI
34
+ sig { params(template_uri: String).returns(String) }
35
+ def template_to_ruby_uri(template_uri)
36
+ path = uri_to_path(template_uri)
37
+ ruby_path = File.join(@output_dir, "#{path}.rb")
38
+ path_to_uri(ruby_path)
39
+ end
40
+
41
+ # component .rb URI -> generated __erb_template.rb URI
42
+ sig { params(component_uri: String).returns(String) }
43
+ def component_to_ruby_uri(component_uri)
44
+ path = uri_to_path(component_uri)
45
+ ruby_path = File.join(@output_dir, "#{path}__erb_template.rb")
46
+ path_to_uri(ruby_path)
47
+ end
48
+
49
+ # generated ruby URI -> template URI
50
+ sig { params(ruby_uri: String).returns(T.nilable(String)) }
51
+ def ruby_to_template_uri(ruby_uri)
52
+ path = uri_to_path(ruby_uri)
53
+
54
+ # Remove output_dir prefix and .rb suffix
55
+ relative = path.sub(%r{^.*/#{Regexp.escape(@output_dir)}/}, '')
56
+ .sub(%r{^#{Regexp.escape(@output_dir)}/}, '')
57
+ return nil unless relative.end_with?('.rb')
58
+
59
+ # Component heredoc: output is foo.rb__erb_template.rb → source is foo.rb
60
+ if relative.end_with?('__erb_template.rb')
61
+ template_path = relative.chomp('__erb_template.rb')
62
+ return path_to_uri(template_path)
63
+ end
64
+
65
+ template_path = relative.chomp('.rb')
66
+ path_to_uri(template_path)
67
+ end
68
+
69
+ sig { params(uri: String).returns(String) }
70
+ def uri_to_path(uri)
71
+ path = if uri.start_with?('file://')
72
+ URI.decode_www_form_component(URI.parse(uri).path || '')
73
+ else
74
+ uri
75
+ end
76
+ # Map host path -> local (container) path
77
+ @path_mapping.each do |from, to|
78
+ if path.start_with?(from)
79
+ path = path.sub(from, to)
80
+ break
81
+ end
82
+ end
83
+ # Make relative to CWD
84
+ cwd = Dir.pwd
85
+ if path.start_with?("#{cwd}/")
86
+ path = path.delete_prefix("#{cwd}/")
87
+ end
88
+ path
89
+ end
90
+
91
+ sig { params(path: String).returns(String) }
92
+ def path_to_uri(path)
93
+ absolute = File.expand_path(path)
94
+ # Map local (container) path -> host path
95
+ @path_mapping.each do |from, to|
96
+ if absolute.start_with?(to)
97
+ absolute = absolute.sub(to, from)
98
+ break
99
+ end
100
+ end
101
+ "file://#{URI.encode_www_form_component(absolute).gsub('%2F', '/')}"
102
+ end
103
+ end
104
+ end
105
+ end
@@ -0,0 +1,92 @@
1
+ # typed: strict
2
+ # frozen_string_literal: true
3
+
4
+ module SorbetView
5
+ module Perf
6
+ extend T::Sig
7
+
8
+ class Metric < T::Struct
9
+ prop :count, Integer, default: 0
10
+ prop :total_ms, Float, default: 0.0
11
+ prop :min_ms, Float, default: Float::INFINITY
12
+ prop :max_ms, Float, default: 0.0
13
+ end
14
+
15
+ @metrics = T.let({}, T::Hash[String, Metric])
16
+ @enabled = T.let(true, T::Boolean)
17
+
18
+ class << self
19
+ extend T::Sig
20
+
21
+ sig { returns(T::Boolean) }
22
+ attr_accessor :enabled
23
+
24
+ sig { returns(T::Hash[String, Metric]) }
25
+ attr_reader :metrics
26
+
27
+ sig do
28
+ type_parameters(:R)
29
+ .params(label: String, blk: T.proc.returns(T.type_parameter(:R)))
30
+ .returns(T.type_parameter(:R))
31
+ end
32
+ def measure(label, &blk)
33
+ unless @enabled
34
+ return yield
35
+ end
36
+
37
+ start = Process.clock_gettime(Process::CLOCK_MONOTONIC)
38
+ result = yield
39
+ elapsed_ms = (Process.clock_gettime(Process::CLOCK_MONOTONIC) - start) * 1000.0
40
+
41
+ metric = @metrics[label] ||= Metric.new
42
+ metric.count += 1
43
+ metric.total_ms += elapsed_ms
44
+ metric.min_ms = elapsed_ms if elapsed_ms < metric.min_ms
45
+ metric.max_ms = elapsed_ms if elapsed_ms > metric.max_ms
46
+
47
+ result
48
+ end
49
+
50
+ sig { void }
51
+ def reset!
52
+ @metrics = {}
53
+ end
54
+
55
+ sig { params(io: IO).void }
56
+ def report(io: $stderr)
57
+ return if @metrics.empty?
58
+
59
+ io.puts "\n=== SorbetView Performance Report ==="
60
+ io.puts format('%-35s %8s %10s %10s %10s %10s', 'Label', 'Count', 'Total(ms)', 'Avg(ms)', 'Min(ms)', 'Max(ms)')
61
+ io.puts '-' * 90
62
+
63
+ @metrics.sort_by { |_, m| -m.total_ms }.each do |label, m|
64
+ avg = m.count > 0 ? m.total_ms / m.count : 0.0
65
+ min_val = m.min_ms == Float::INFINITY ? 0.0 : m.min_ms
66
+ io.puts format('%-35s %8d %10.2f %10.2f %10.2f %10.2f', label, m.count, m.total_ms, avg, min_val, m.max_ms)
67
+ end
68
+
69
+ io.puts '=' * 90
70
+ io.puts ''
71
+ end
72
+
73
+ sig { params(logger: Logger).void }
74
+ def report_to_logger(logger)
75
+ return if @metrics.empty?
76
+
77
+ lines = ["\n=== SorbetView Performance Report ==="]
78
+ lines << format('%-35s %8s %10s %10s %10s %10s', 'Label', 'Count', 'Total(ms)', 'Avg(ms)', 'Min(ms)', 'Max(ms)')
79
+ lines << '-' * 90
80
+
81
+ @metrics.sort_by { |_, m| -m.total_ms }.each do |label, m|
82
+ avg = m.count > 0 ? m.total_ms / m.count : 0.0
83
+ min_val = m.min_ms == Float::INFINITY ? 0.0 : m.min_ms
84
+ lines << format('%-35s %8d %10.2f %10.2f %10.2f %10.2f', label, m.count, m.total_ms, avg, min_val, m.max_ms)
85
+ end
86
+
87
+ lines << '=' * 90
88
+ logger.info(lines.join("\n"))
89
+ end
90
+ end
91
+ end
92
+ end
@@ -0,0 +1,12 @@
1
+ # typed: strict
2
+ # frozen_string_literal: true
3
+
4
+ module SorbetView
5
+ module SourceMap
6
+ class MappingEntry < T::Struct
7
+ const :template_range, Range # position in the template file
8
+ const :ruby_range, Range # position in the generated .rb file
9
+ const :type, Symbol # :code, :expression, :boilerplate
10
+ end
11
+ end
12
+ end
@@ -0,0 +1,23 @@
1
+ # typed: strict
2
+ # frozen_string_literal: true
3
+
4
+ module SorbetView
5
+ module SourceMap
6
+ class Position < T::Struct
7
+ const :line, Integer # 0-based
8
+ const :column, Integer # 0-based
9
+
10
+ extend T::Sig
11
+
12
+ sig { returns(T::Hash[Symbol, Integer]) }
13
+ def to_lsp
14
+ { line: line, character: column }
15
+ end
16
+
17
+ sig { params(lsp_hash: T::Hash[String, Integer]).returns(Position) }
18
+ def self.from_lsp(lsp_hash)
19
+ new(line: lsp_hash['line'] || lsp_hash[:line], column: lsp_hash['character'] || lsp_hash[:character])
20
+ end
21
+ end
22
+ end
23
+ end
@@ -0,0 +1,35 @@
1
+ # typed: strict
2
+ # frozen_string_literal: true
3
+
4
+ module SorbetView
5
+ module SourceMap
6
+ class Range < T::Struct
7
+ const :start, Position
8
+ const :end_, Position
9
+
10
+ extend T::Sig
11
+
12
+ sig { returns(T::Hash[Symbol, T::Hash[Symbol, Integer]]) }
13
+ def to_lsp
14
+ { start: start.to_lsp, end: end_.to_lsp }
15
+ end
16
+
17
+ sig { params(lsp_hash: T::Hash[String, T.untyped]).returns(Range) }
18
+ def self.from_lsp(lsp_hash)
19
+ new(
20
+ start: Position.from_lsp(lsp_hash['start'] || lsp_hash[:start]),
21
+ end_: Position.from_lsp(lsp_hash['end'] || lsp_hash[:end])
22
+ )
23
+ end
24
+
25
+ sig { params(position: Position).returns(T::Boolean) }
26
+ def contains?(position)
27
+ return false if position.line < start.line || position.line > end_.line
28
+ return false if position.line == start.line && position.column < start.column
29
+ return false if position.line == end_.line && position.column > end_.column
30
+
31
+ true
32
+ end
33
+ end
34
+ end
35
+ end
@@ -0,0 +1,103 @@
1
+ # typed: strict
2
+ # frozen_string_literal: true
3
+
4
+ module SorbetView
5
+ module SourceMap
6
+ class SourceMap
7
+ extend T::Sig
8
+
9
+ sig { returns(String) }
10
+ attr_reader :template_path
11
+
12
+ sig { returns(String) }
13
+ attr_reader :ruby_path
14
+
15
+ sig { returns(T::Array[MappingEntry]) }
16
+ attr_reader :entries
17
+
18
+ sig do
19
+ params(
20
+ template_path: String,
21
+ ruby_path: String,
22
+ entries: T::Array[MappingEntry]
23
+ ).void
24
+ end
25
+ def initialize(template_path:, ruby_path:, entries:)
26
+ @template_path = template_path
27
+ @ruby_path = ruby_path
28
+ @entries = entries
29
+ end
30
+
31
+ sig { params(position: Position).returns(T.nilable(Position)) }
32
+ def template_to_ruby(position)
33
+ Perf.measure('sourcemap.template_to_ruby') do
34
+ entry = find_entry_by_template(position)
35
+ next nil unless entry
36
+ next nil if entry.type == :boilerplate
37
+
38
+ translate(position, entry.template_range, entry.ruby_range)
39
+ end
40
+ end
41
+
42
+ sig { params(position: Position).returns(T.nilable(Position)) }
43
+ def ruby_to_template(position)
44
+ Perf.measure('sourcemap.ruby_to_template') do
45
+ entry = find_entry_by_ruby(position)
46
+ next nil unless entry
47
+ next nil if entry.type == :boilerplate
48
+
49
+ translate(position, entry.ruby_range, entry.template_range)
50
+ end
51
+ end
52
+
53
+ sig { params(ruby_range: Range).returns(T.nilable(Range)) }
54
+ def ruby_range_to_template(ruby_range)
55
+ start_pos = ruby_to_template(ruby_range.start)
56
+ end_pos = ruby_to_template(ruby_range.end_)
57
+ return nil unless start_pos && end_pos
58
+
59
+ Range.new(start: start_pos, end_: end_pos)
60
+ end
61
+
62
+ sig { params(template_path: String, ruby_path: String).returns(SourceMap) }
63
+ def self.empty(template_path, ruby_path)
64
+ new(template_path: template_path, ruby_path: ruby_path, entries: [])
65
+ end
66
+
67
+ private
68
+
69
+ sig { params(position: Position).returns(T.nilable(MappingEntry)) }
70
+ def find_entry_by_template(position)
71
+ @entries.find { |e| e.template_range.contains?(position) }
72
+ end
73
+
74
+ sig { params(position: Position).returns(T.nilable(MappingEntry)) }
75
+ def find_entry_by_ruby(position)
76
+ # First try exact match, then fall back to line-only match
77
+ @entries.find { |e| e.ruby_range.contains?(position) } ||
78
+ @entries.find { |e| position.line >= e.ruby_range.start.line && position.line <= e.ruby_range.end_.line }
79
+ end
80
+
81
+ sig do
82
+ params(
83
+ position: Position,
84
+ from_range: Range,
85
+ to_range: Range
86
+ ).returns(Position)
87
+ end
88
+ def translate(position, from_range, to_range)
89
+ delta_line = position.line - from_range.start.line
90
+ delta_col = if delta_line == 0
91
+ position.column - from_range.start.column
92
+ else
93
+ position.column
94
+ end
95
+
96
+ Position.new(
97
+ line: to_range.start.line + delta_line,
98
+ column: (delta_line == 0 ? to_range.start.column : 0) + delta_col
99
+ )
100
+ end
101
+ end
102
+ end
103
+ end
@@ -0,0 +1,6 @@
1
+ # typed: strict
2
+ # frozen_string_literal: true
3
+
4
+ module SorbetView
5
+ VERSION = '0.1.0'
6
+ end
@@ -0,0 +1,36 @@
1
+ # typed: strict
2
+ # frozen_string_literal: true
3
+
4
+ require 'sorbet-runtime'
5
+
6
+ require_relative 'sorbet_view/version'
7
+ require_relative 'sorbet_view/perf'
8
+ require_relative 'sorbet_view/configuration'
9
+ require_relative 'sorbet_view/source_map/position'
10
+ require_relative 'sorbet_view/source_map/range'
11
+ require_relative 'sorbet_view/source_map/mapping_entry'
12
+ require_relative 'sorbet_view/source_map/source_map'
13
+ require_relative 'sorbet_view/compiler/ruby_segment'
14
+ require_relative 'sorbet_view/compiler/parser_adapter'
15
+ require_relative 'sorbet_view/compiler/adapters/erb_adapter'
16
+ require_relative 'sorbet_view/compiler/template_context'
17
+ require_relative 'sorbet_view/compiler/ruby_generator'
18
+ require_relative 'sorbet_view/compiler/template_compiler'
19
+ require_relative 'sorbet_view/compiler/heredoc_extractor'
20
+ require_relative 'sorbet_view/compiler/component_compiler'
21
+ require_relative 'sorbet_view/file_system/output_manager'
22
+ require_relative 'sorbet_view/file_system/project_scanner'
23
+ require_relative 'sorbet_view/file_system/file_watcher'
24
+ require_relative 'sorbet_view/lsp/transport'
25
+ require_relative 'sorbet_view/lsp/uri_mapper'
26
+ require_relative 'sorbet_view/lsp/document_store'
27
+ require_relative 'sorbet_view/lsp/position_translator'
28
+ require_relative 'sorbet_view/lsp/sorbet_process'
29
+ require_relative 'sorbet_view/lsp/server'
30
+ require_relative 'sorbet_view/cli/runner'
31
+
32
+ module SorbetView
33
+ extend T::Sig
34
+
35
+ CONFIG_FILE_NAME = '.sorbet_view.yml'
36
+ end