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
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: a8a0b48778cd35569ebf4fd8e6ab0adac11232ba3b59199eb33fa0bb2a5281cf
4
+ data.tar.gz: 40283808c1562e713e70e3ce70d31fa1058841c0d5bffa29bc33124a8b005b98
5
+ SHA512:
6
+ metadata.gz: 24e77a9a6b85ae1e2d33b837ce91e9b8fd694e97d568eea83f942666b46a8806779949baaa8c5c0694ef2fad104abe490d1a09cacbfffc8bb57adc8f52f4d429
7
+ data.tar.gz: 3e8c78986b0d889de56f026a24b4eb79be312b536cd49cf6fca45a8515788cb08fe7f133ec71259994cf831f2a59aa79f04ecf04fdb78f13be4d260e9fc673e3
data/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 @kazzix14 - Kazuma Murata
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
data/Rakefile ADDED
@@ -0,0 +1,8 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'bundler/gem_tasks'
4
+ require 'minitest/test_task'
5
+
6
+ Minitest::TestTask.create
7
+
8
+ task default: :test
data/exe/sv ADDED
@@ -0,0 +1,6 @@
1
+ #!/usr/bin/env ruby
2
+ # frozen_string_literal: true
3
+
4
+ require_relative '../lib/sorbet_view'
5
+
6
+ SorbetView::CLI::Runner.start(ARGV)
@@ -0,0 +1,206 @@
1
+ # typed: strict
2
+ # frozen_string_literal: true
3
+
4
+ module SorbetView
5
+ module CLI
6
+ class Runner
7
+ extend T::Sig
8
+
9
+ COMMANDS = T.let({
10
+ 'tc' => 'Compile templates and run srb tc',
11
+ 'compile' => 'Compile templates to Ruby files',
12
+ 'lsp' => 'Start LSP proxy server',
13
+ 'clean' => 'Remove generated files',
14
+ 'init' => 'Generate .sorbet_view.yml config file'
15
+ }.freeze, T::Hash[String, String])
16
+
17
+ sig { params(argv: T::Array[String]).void }
18
+ def self.start(argv)
19
+ command = argv[0]
20
+ args = argv[1..] || []
21
+
22
+ case command
23
+ when 'tc'
24
+ run_tc(args)
25
+ when 'compile'
26
+ run_compile(args)
27
+ when 'lsp'
28
+ run_lsp(args)
29
+ when 'clean'
30
+ run_clean(args)
31
+ when 'init'
32
+ run_init
33
+ else
34
+ print_usage
35
+ end
36
+ end
37
+
38
+ class << self
39
+ extend T::Sig
40
+
41
+ private
42
+
43
+ # Split args on "--": before is sv args, after is passthrough (e.g. to srb tc)
44
+ sig { params(args: T::Array[String]).returns([T::Array[String], T::Array[String]]) }
45
+ def split_args(args)
46
+ separator = args.index('--')
47
+ if separator
48
+ [args[0...separator], args[(separator + 1)..] || []]
49
+ else
50
+ [args, []]
51
+ end
52
+ end
53
+
54
+ sig { params(args: T::Array[String]).returns(Configuration) }
55
+ def parse_config(args)
56
+ sv_args, _ = split_args(args)
57
+ no_config = sv_args.include?('--no-config')
58
+ input_dirs = T.let([], T::Array[String])
59
+ output_dir = T.let(nil, T.nilable(String))
60
+
61
+ i = 0
62
+ while i < sv_args.length
63
+ case sv_args[i]
64
+ when '--no-config'
65
+ # handled above
66
+ when '-o', '--output'
67
+ output_dir = sv_args[i + 1]
68
+ i += 1
69
+ when /^-/
70
+ # skip unknown flags
71
+ else
72
+ input_dirs << T.must(sv_args[i])
73
+ end
74
+ i += 1
75
+ end
76
+
77
+ base = no_config ? Configuration.new : Configuration.load
78
+
79
+ overrides = {}
80
+ overrides['input_dirs'] = input_dirs unless input_dirs.empty?
81
+ overrides['output_dir'] = output_dir if output_dir
82
+
83
+ if overrides.empty?
84
+ base
85
+ else
86
+ Configuration.from_hash(base.serialize.merge(overrides))
87
+ end
88
+ end
89
+
90
+ sig { params(args: T::Array[String]).void }
91
+ def run_compile(args)
92
+ Perf.reset!
93
+ total_start = Process.clock_gettime(Process::CLOCK_MONOTONIC)
94
+
95
+ config = parse_config(args)
96
+ compiler = Compiler::TemplateCompiler.new(config: config)
97
+ component_compiler = Compiler::ComponentCompiler.new(config: config)
98
+ output_manager = FileSystem::OutputManager.new(config.output_dir)
99
+
100
+ templates = FileSystem::ProjectScanner.scan(config)
101
+ puts "Compiling #{templates.length} template(s)..."
102
+
103
+ templates.each do |path|
104
+ result = compiler.compile_file(path)
105
+
106
+ if result.source_map.entries.empty? && config.skip_missing_locals && requires_locals?(path)
107
+ next
108
+ end
109
+
110
+ output_manager.write(result)
111
+ puts " #{path} -> #{result.source_map.ruby_path}"
112
+ end
113
+
114
+ components = FileSystem::ProjectScanner.scan_components(config)
115
+ if components.any?
116
+ puts "Compiling #{components.length} component(s) with erb_template..."
117
+
118
+ components.each do |path|
119
+ results = component_compiler.compile_file(path)
120
+ results.each do |result|
121
+ output_manager.write(result)
122
+ puts " #{path} -> #{result.source_map.ruby_path}"
123
+ end
124
+ end
125
+ end
126
+
127
+ total_ms = (Process.clock_gettime(Process::CLOCK_MONOTONIC) - total_start) * 1000.0
128
+ puts "Done. (total: #{total_ms.round(1)}ms)"
129
+ Perf.report
130
+ end
131
+
132
+ sig { params(args: T::Array[String]).void }
133
+ def run_tc(args)
134
+ run_compile(args)
135
+
136
+ config = parse_config(args)
137
+ _, sorbet_args = split_args(args)
138
+ puts "\nRunning Sorbet..."
139
+ system(config.sorbet_path, 'tc', *sorbet_args)
140
+ end
141
+
142
+ sig { params(args: T::Array[String]).void }
143
+ def run_lsp(args)
144
+ $stdin.binmode
145
+ $stdout.binmode
146
+ server = Lsp::Server.new
147
+ server.start
148
+ end
149
+
150
+ sig { params(args: T::Array[String]).void }
151
+ def run_clean(args)
152
+ config = parse_config(args)
153
+ output_manager = FileSystem::OutputManager.new(config.output_dir)
154
+ output_manager.clean
155
+ puts "Cleaned #{config.output_dir}"
156
+ end
157
+
158
+ sig { void }
159
+ def run_init
160
+ if File.exist?(SorbetView::CONFIG_FILE_NAME)
161
+ puts "#{SorbetView::CONFIG_FILE_NAME} already exists"
162
+ return
163
+ end
164
+
165
+ File.write(SorbetView::CONFIG_FILE_NAME, <<~YAML)
166
+ # SorbetView configuration
167
+ input_dirs:
168
+ - app/views
169
+
170
+ exclude_paths: []
171
+
172
+ output_dir: sorbet/templates
173
+
174
+ extra_includes: []
175
+
176
+ skip_missing_locals: true
177
+ YAML
178
+
179
+ puts "Created #{SorbetView::CONFIG_FILE_NAME}"
180
+ end
181
+
182
+ sig { void }
183
+ def print_usage
184
+ puts 'Usage: sv <command> [paths...] [options]'
185
+ puts ''
186
+ puts 'Commands:'
187
+ COMMANDS.each do |cmd, desc|
188
+ puts " #{cmd.ljust(12)} #{desc}"
189
+ end
190
+ puts ''
191
+ puts 'Options:'
192
+ puts ' [paths...] Input directories (overrides config)'
193
+ puts ' -o, --output DIR Output directory (overrides config)'
194
+ puts ' --no-config Ignore .sorbet_view.yml'
195
+ puts ' -- [args...] Pass remaining args to srb tc (tc only)'
196
+ end
197
+
198
+ sig { params(path: String).returns(T::Boolean) }
199
+ def requires_locals?(path)
200
+ basename = File.basename(path)
201
+ basename.start_with?('_') || basename.end_with?('.turbo_stream.erb')
202
+ end
203
+ end
204
+ end
205
+ end
206
+ end
@@ -0,0 +1,170 @@
1
+ # typed: strict
2
+ # frozen_string_literal: true
3
+
4
+ module SorbetView
5
+ module Compiler
6
+ module Adapters
7
+ class ErbAdapter
8
+ extend T::Sig
9
+ include ParserAdapter
10
+
11
+ INDICATOR_PATTERN = /<%([=#-]?)(.*?)[-]?%>/m
12
+
13
+ sig { override.params(source: String).returns(T::Array[RubySegment]) }
14
+ def extract_segments(source)
15
+ Perf.measure('erb.extract_segments') do
16
+ if herb_available?
17
+ Perf.measure('erb.extract_with_herb') { extract_with_herb(source) }
18
+ else
19
+ Perf.measure('erb.extract_with_stdlib') { extract_with_stdlib(source) }
20
+ end
21
+ end
22
+ end
23
+
24
+ private
25
+
26
+ sig { returns(T::Boolean) }
27
+ def herb_available?
28
+ @herb_available = T.let(@herb_available, T.nilable(T::Boolean))
29
+ if @herb_available.nil?
30
+ @herb_available = begin
31
+ require 'herb'
32
+ true
33
+ rescue LoadError
34
+ false
35
+ end
36
+ end
37
+ T.must(@herb_available)
38
+ end
39
+
40
+ sig { params(source: String).returns(T::Array[RubySegment]) }
41
+ def extract_with_herb(source)
42
+ result = Herb.parse(source)
43
+ segments = T.let([], T::Array[RubySegment])
44
+ visit_herb_node(result.value, segments)
45
+ segments
46
+ end
47
+
48
+ sig { params(node: T.untyped, segments: T::Array[RubySegment]).void }
49
+ def visit_herb_node(node, segments)
50
+ class_name = node.class.name.to_s
51
+
52
+ if class_name.include?('ERBContentNode')
53
+ add_herb_segment(node, segments)
54
+ return
55
+ end
56
+
57
+ if class_name.include?('ERBEndNode')
58
+ add_herb_end_segment(node, segments)
59
+ return
60
+ end
61
+
62
+ erb_block = class_name.include?('ERBBlockNode') ||
63
+ class_name.include?('ERBIfNode') || class_name.include?('ERBUnlessNode') ||
64
+ class_name.include?('ERBCaseNode') || class_name.include?('ERBForNode') ||
65
+ class_name.include?('ERBWhileNode') || class_name.include?('ERBUntilNode') ||
66
+ class_name.include?('ERBBeginNode')
67
+
68
+ erb_clause = class_name.include?('ERBElsifNode') || class_name.include?('ERBElseNode') ||
69
+ class_name.include?('ERBWhenNode') || class_name.include?('ERBRescueNode') ||
70
+ class_name.include?('ERBEnsureNode')
71
+
72
+ if erb_block || erb_clause
73
+ add_herb_segment(node, segments)
74
+ node.statements.each { |s| visit_herb_node(s, segments) } if node.respond_to?(:statements)
75
+ node.body.each { |s| visit_herb_node(s, segments) } if node.respond_to?(:body)
76
+ visit_herb_node(node.subsequent, segments) if node.respond_to?(:subsequent) && node.subsequent
77
+ add_herb_end_segment(node.end_node, segments) if erb_block && node.respond_to?(:end_node) && node.end_node
78
+ return
79
+ end
80
+
81
+ # Non-ERB nodes: recurse into children and body
82
+ node.children.each { |child| visit_herb_node(child, segments) } if node.respond_to?(:children)
83
+ node.body.each { |child| visit_herb_node(child, segments) } if node.respond_to?(:body)
84
+ end
85
+
86
+ sig { params(node: T.untyped, segments: T::Array[RubySegment]).void }
87
+ def add_herb_segment(node, segments)
88
+ return unless node.respond_to?(:content) && node.content
89
+
90
+ content_token = node.content
91
+ code = content_token.value.to_s.strip
92
+ return if code.empty?
93
+
94
+ # Herb lines are 1-based, we use 0-based
95
+ loc = content_token.location
96
+ line = loc.start.line - 1
97
+ column = loc.start.column
98
+
99
+ tag = node.respond_to?(:tag_opening) ? node.tag_opening.value.to_s : '<%'
100
+ type = case tag
101
+ when '<%=' then :expression
102
+ when '<%#' then :comment
103
+ else :statement
104
+ end
105
+
106
+ segments << RubySegment.new(code: code, line: line, column: column, type: type)
107
+ end
108
+
109
+ sig { params(node: T.untyped, segments: T::Array[RubySegment]).void }
110
+ def add_herb_end_segment(node, segments)
111
+ return unless node.respond_to?(:content) && node.content
112
+
113
+ loc = node.content.location
114
+ segments << RubySegment.new(
115
+ code: 'end',
116
+ line: loc.start.line - 1,
117
+ column: loc.start.column,
118
+ type: :statement
119
+ )
120
+ end
121
+
122
+ # Fallback: regex-based extraction when Herb is not available
123
+ sig { params(source: String).returns(T::Array[RubySegment]) }
124
+ def extract_with_stdlib(source)
125
+ segments = T.let([], T::Array[RubySegment])
126
+ line_offsets = build_line_offsets(source)
127
+
128
+ source.scan(INDICATOR_PATTERN) do
129
+ match = T.must(Regexp.last_match)
130
+ indicator = match[1] || ''
131
+ code = (match[2] || '').strip
132
+ offset = T.must(match.begin(0))
133
+
134
+ next if code.empty?
135
+
136
+ line, column = offset_to_line_column(line_offsets, offset)
137
+ tag_prefix_len = 2 + indicator.length
138
+ code_column = column + tag_prefix_len
139
+
140
+ type = case indicator
141
+ when '#' then :comment
142
+ when '=' then :expression
143
+ else :statement
144
+ end
145
+
146
+ segments << RubySegment.new(code: code, line: line, column: code_column, type: type)
147
+ end
148
+
149
+ segments
150
+ end
151
+
152
+ sig { params(source: String).returns(T::Array[Integer]) }
153
+ def build_line_offsets(source)
154
+ offsets = [0]
155
+ source.each_char.with_index do |char, i|
156
+ offsets << (i + 1) if char == "\n"
157
+ end
158
+ offsets
159
+ end
160
+
161
+ sig { params(line_offsets: T::Array[Integer], offset: Integer).returns([Integer, Integer]) }
162
+ def offset_to_line_column(line_offsets, offset)
163
+ line = line_offsets.rindex { |o| o <= offset } || 0
164
+ column = offset - (line_offsets[line] || 0)
165
+ [line, column]
166
+ end
167
+ end
168
+ end
169
+ end
170
+ end
@@ -0,0 +1,48 @@
1
+ # typed: strict
2
+ # frozen_string_literal: true
3
+
4
+ module SorbetView
5
+ module Compiler
6
+ class ComponentCompiler
7
+ extend T::Sig
8
+
9
+ sig { params(adapter: ParserAdapter, config: Configuration).void }
10
+ def initialize(adapter: Adapters::ErbAdapter.new, config: Configuration.load)
11
+ @adapter = adapter
12
+ @generator = T.let(RubyGenerator.new, RubyGenerator)
13
+ @config = config
14
+ end
15
+
16
+ sig { params(path: String, source: String).returns(T::Array[CompileResult]) }
17
+ def compile(path, source)
18
+ extractions = HeredocExtractor.extract(source, path)
19
+ extractions.map do |extraction|
20
+ segments = @adapter.extract_segments(extraction.erb_source)
21
+
22
+ # Add line_offset/column_offset so source map points to correct positions in the .rb file
23
+ adjusted = segments.map do |seg|
24
+ RubySegment.new(
25
+ code: seg.code,
26
+ line: seg.line + extraction.line_offset,
27
+ column: seg.column + extraction.column_offset,
28
+ type: seg.type
29
+ )
30
+ end
31
+
32
+ context = TemplateContext.resolve_component(
33
+ extraction.component_path,
34
+ extraction.class_name,
35
+ @config
36
+ )
37
+ @generator.generate(segments: adjusted, context: context, config: @config, component_mode: true)
38
+ end
39
+ end
40
+
41
+ sig { params(path: String).returns(T::Array[CompileResult]) }
42
+ def compile_file(path)
43
+ source = File.read(path)
44
+ compile(path, source)
45
+ end
46
+ end
47
+ end
48
+ end
@@ -0,0 +1,112 @@
1
+ # typed: strict
2
+ # frozen_string_literal: true
3
+
4
+ module SorbetView
5
+ module Compiler
6
+ class HeredocExtraction < T::Struct
7
+ const :erb_source, String
8
+ const :line_offset, Integer
9
+ const :column_offset, Integer
10
+ const :class_name, String
11
+ const :component_path, String
12
+ end
13
+
14
+ class HeredocExtractor
15
+ extend T::Sig
16
+
17
+ ERB_TEMPLATE_PATTERN = /erb_template\s+<<~([A-Z_]+)/
18
+
19
+ sig { params(source: String).returns(T::Boolean) }
20
+ def self.contains_erb_template?(source)
21
+ ERB_TEMPLATE_PATTERN.match?(source)
22
+ end
23
+
24
+ sig { params(source: String, file_path: String).returns(T::Array[HeredocExtraction]) }
25
+ def self.extract(source, file_path)
26
+ extractions = T.let([], T::Array[HeredocExtraction])
27
+ lines = source.lines
28
+
29
+ i = 0
30
+ while i < lines.length
31
+ line = T.must(lines[i])
32
+ match = ERB_TEMPLATE_PATTERN.match(line)
33
+ if match
34
+ delimiter = T.must(match[1])
35
+ heredoc_start = i + 1
36
+ heredoc_lines = T.let([], T::Array[String])
37
+
38
+ j = heredoc_start
39
+ while j < lines.length
40
+ heredoc_line = T.must(lines[j])
41
+ break if heredoc_line.strip == delimiter
42
+
43
+ heredoc_lines << heredoc_line
44
+ j += 1
45
+ end
46
+
47
+ erb_source, column_offset = dedent_heredoc(heredoc_lines)
48
+ class_name = class_name_from_source(source)
49
+
50
+ extractions << HeredocExtraction.new(
51
+ erb_source: erb_source,
52
+ line_offset: heredoc_start,
53
+ column_offset: column_offset,
54
+ class_name: class_name,
55
+ component_path: file_path
56
+ )
57
+
58
+ i = j + 1
59
+ else
60
+ i += 1
61
+ end
62
+ end
63
+
64
+ extractions
65
+ end
66
+
67
+ class << self
68
+ extend T::Sig
69
+
70
+ private
71
+
72
+ sig { params(lines: T::Array[String]).returns([String, Integer]) }
73
+ def dedent_heredoc(lines)
74
+ # Reproduce Ruby's <<~ indent stripping behavior
75
+ non_empty_lines = lines.reject { |l| l.strip.empty? }
76
+ return [lines.join, 0] if non_empty_lines.empty?
77
+
78
+ min_indent = T.let(nil, T.nilable(Integer))
79
+ non_empty_lines.each do |line|
80
+ indent = T.must(line.match(/^(\s*)/))[1]&.length || 0
81
+ min_indent = indent if min_indent.nil? || indent < min_indent
82
+ end
83
+ min_indent ||= 0
84
+
85
+ dedented = lines.map do |line|
86
+ if line.strip.empty?
87
+ "\n"
88
+ else
89
+ line[min_indent..] || line
90
+ end
91
+ end.join
92
+
93
+ [dedented, min_indent]
94
+ end
95
+
96
+ sig { params(source: String).returns(String) }
97
+ def class_name_from_source(source)
98
+ parts = T.let([], T::Array[String])
99
+ source.each_line do |line|
100
+ if (m = line.match(/^\s*module\s+([A-Z][\w:]*)/))
101
+ parts << T.must(m[1])
102
+ elsif (m = line.match(/^\s*class\s+([A-Z][\w:]*)/))
103
+ parts << T.must(m[1])
104
+ break
105
+ end
106
+ end
107
+ parts.join('::')
108
+ end
109
+ end
110
+ end
111
+ end
112
+ end
@@ -0,0 +1,16 @@
1
+ # typed: strict
2
+ # frozen_string_literal: true
3
+
4
+ module SorbetView
5
+ module Compiler
6
+ module ParserAdapter
7
+ extend T::Sig
8
+ extend T::Helpers
9
+
10
+ interface!
11
+
12
+ sig { abstract.params(source: String).returns(T::Array[RubySegment]) }
13
+ def extract_segments(source); end
14
+ end
15
+ end
16
+ end