1
- # typed: strict
1
+ # typed: true
2
2
# frozen_string_literal: true
3
3
4
4
begin
17
17
RuboCop ::LSP . enable
18
18
end
19
19
20
+ require "prism/translation/parser/rubocop"
21
+
20
22
module RubyLsp
21
23
module Requests
22
24
module Support
@@ -74,6 +76,7 @@ def initialize(*args)
74
76
@offenses = T . let ( [ ] , T ::Array [ RuboCop ::Cop ::Offense ] )
75
77
@errors = T . let ( [ ] , T ::Array [ String ] )
76
78
@warnings = T . let ( [ ] , T ::Array [ String ] )
79
+ @parse_result = T . let ( nil , T . nilable ( Prism ::ParseResult ) )
77
80
78
81
args += DEFAULT_ARGS
79
82
rubocop_options = ::RuboCop ::Options . new . parse ( args ) . first
@@ -82,14 +85,15 @@ def initialize(*args)
82
85
super ( rubocop_options , config_store )
83
86
end
84
87
85
- sig { params ( path : String , contents : String ) . void }
86
- def run ( path , contents )
88
+ sig { params ( path : String , contents : String , parse_result : Prism :: ParseResult ) . void }
89
+ def run ( path , contents , parse_result )
87
90
# Clear Runner state between runs since we get a single instance of this class
88
91
# on every use site.
89
92
@errors = [ ]
90
93
@warnings = [ ]
91
94
@offenses = [ ]
92
95
@options [ :stdin ] = contents
96
+ @parse_result = parse_result
93
97
94
98
super ( [ path ] )
95
99
@@ -109,6 +113,28 @@ def formatted_source
109
113
@options [ :stdin ]
110
114
end
111
115
116
+ sig { params ( file : String ) . returns ( RuboCop ::ProcessedSource ) }
117
+ def get_processed_source ( file )
118
+ config = @config_store . for_file ( file )
119
+ parser_engine = config . parser_engine
120
+ return super unless parser_engine == :parser_prism
121
+
122
+ processed_source = T . unsafe ( ::RuboCop ::AST ::ProcessedSource ) . new (
123
+ @options [ :stdin ] ,
124
+ Prism ::Translation ::Parser ::VERSION_3_3 ,
125
+ file ,
126
+ parser_engine : parser_engine ,
127
+ prism_result : @parse_result ,
128
+ )
129
+ processed_source . config = config
130
+ processed_source . registry = mobilized_cop_classes ( config )
131
+ # We have to reset the result to nil after returning the processed source the first time. This is needed for
132
+ # formatting because RuboCop will keep re-parsing the same file until no more auto-corrects can be applied. If
133
+ # we didn't reset it, we would end up operating in a stale AST
134
+ @parse_result = nil
135
+ processed_source
136
+ end
137
+
112
138
class << self
113
139
extend T ::Sig
114
140
@@ -138,3 +164,107 @@ def file_finished(_file, offenses)
138
164
end
139
165
end
140
166
end
167
+
168
+ # Processed Source patch so that we can pass the existing AST to RuboCop without having to re-parse files a second time
169
+ module ProcessedSourcePatch
170
+ extend T ::Sig
171
+
172
+ sig do
173
+ params (
174
+ source : String ,
175
+ ruby_version : Float ,
176
+ path : T . nilable ( String ) ,
177
+ parser_engine : Symbol ,
178
+ prism_result : T . nilable ( Prism ::ParseResult ) ,
179
+ ) . void
180
+ end
181
+ def initialize ( source , ruby_version , path = nil , parser_engine : :parser_whitequark , prism_result : nil )
182
+ @prism_result = prism_result
183
+
184
+ # Invoking super will end up invoking our patched version of tokenize, which avoids re-parsing the file
185
+ super ( source , Prism ::Translation ::Parser ::VERSION_3_3 , path , parser_engine : parser_engine )
186
+ end
187
+
188
+ sig { params ( parser : T . untyped ) . returns ( T ::Array [ T . untyped ] ) }
189
+ def tokenize ( parser )
190
+ begin
191
+ ast , comments , tokens = parser . tokenize ( @buffer , parse_result : @prism_result )
192
+ ast ||= nil
193
+ rescue Parser ::SyntaxError
194
+ comments = [ ]
195
+ tokens = [ ]
196
+ end
197
+
198
+ ast &.complete!
199
+ tokens . map! { |t | RuboCop ::AST ::Token . from_parser_token ( t ) }
200
+
201
+ [ ast , comments , tokens ]
202
+ end
203
+
204
+ RuboCop ::AST ::ProcessedSource . prepend ( self )
205
+ end
206
+
207
+ module Prism
208
+ module Translation
209
+ class Parser < ::Parser ::Base
210
+ extend T ::Sig
211
+
212
+ sig do
213
+ params (
214
+ source_buffer : ::Parser ::Source ::Buffer ,
215
+ recover : T ::Boolean ,
216
+ parse_result : T . nilable ( Prism ::ParseResult ) ,
217
+ ) . returns ( T ::Array [ T . untyped ] )
218
+ end
219
+ def tokenize ( source_buffer , recover = false , parse_result : nil )
220
+ @source_buffer = T . let ( source_buffer , T . nilable ( ::Parser ::Source ::Buffer ) )
221
+ source = source_buffer . source
222
+
223
+ offset_cache = build_offset_cache ( source )
224
+ result = if @prism_result
225
+ @prism_result
226
+ else
227
+ begin
228
+ unwrap (
229
+ Prism . parse_lex ( source , filepath : source_buffer . name , version : convert_for_prism ( version ) ) ,
230
+ offset_cache ,
231
+ )
232
+ rescue ::Parser ::SyntaxError
233
+ raise unless recover
234
+ end
235
+ end
236
+
237
+ program , tokens = result . value
238
+ ast = build_ast ( program , offset_cache ) if result . success?
239
+
240
+ [
241
+ ast ,
242
+ build_comments ( result . comments , offset_cache ) ,
243
+ build_tokens ( tokens , offset_cache ) ,
244
+ ]
245
+ ensure
246
+ @source_buffer = nil
247
+ end
248
+
249
+ module ProcessedSource
250
+ extend T ::Sig
251
+ extend T ::Helpers
252
+
253
+ requires_ancestor { Kernel }
254
+
255
+ sig { params ( ruby_version : Float , parser_engine : Symbol ) . returns ( T . untyped ) }
256
+ def parser_class ( ruby_version , parser_engine )
257
+ if ruby_version == Prism ::Translation ::Parser ::VERSION_3_3
258
+ require "prism/translation/parser33"
259
+ Prism ::Translation ::Parser33
260
+ elsif ruby_version == Prism ::Translation ::Parser ::VERSION_3_4
261
+ require "prism/translation/parser34"
262
+ Prism ::Translation ::Parser34
263
+ else
264
+ super
265
+ end
266
+ end
267
+ end
268
+ end
269
+ end
270
+ end
0 commit comments