tryouts 3.2.1 → 3.3.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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 95b7078a356f605ba8df53a52afcb2209d260c473787692c4b70c0d88e8c5620
4
- data.tar.gz: f6d47e78b51ee13d754c03525329a1857a44767324abc6237a4896f81688ecc2
3
+ metadata.gz: a7ef307bf73a8bd6c70f52c47337d550ae7715e0b64d15c7f708c9fd407f3df2
4
+ data.tar.gz: ccd166f03573bee1d31e3843ff95964cf5065ab4a1b2386e9769ec065a6958f5
5
5
  SHA512:
6
- metadata.gz: e49a47fd56ba73dc0a80deeb0d3559558b4dbefcc31904c21358a906e7f542d6a8caa7479b8d95e66af6e52f7ea4760d3df76e7e0b310f037f3a369adc9f3cbf
7
- data.tar.gz: 8de293b074a3599af4c543b05b3d49d58508b154eb4cce396e55027343042fb5289020cbdd8277d03abf667303ec935d59d3ece8f3e50ec308e150b9541dbda8
6
+ metadata.gz: 22b0d458cfadd632495c4df95f41cca3c06c6e6b3e219b18afb0fd21bfc14d1d9a872b165fcd2dd1c26052a9f57b84dad77c4a5a14c3efa08f62d74f0ab34467
7
+ data.tar.gz: 8cd6db263235736e26163b84568ad870de5835da3002e6f71226d961fc21e19d9eaef3cf2e02598a3907cb2701665e72a7e21ecea05b540f10ba37daa530a391
data/README.md CHANGED
@@ -134,7 +134,7 @@ try -D # debug mode
134
134
 
135
135
  ### Core Components
136
136
 
137
- - **Prism Parser**: Native Ruby parsing with pattern matching for line classification
137
+ - **Prism Parser**: Inhouse Ruby parsing with pattern matching for line classification
138
138
  - **Data Structures**: Immutable `Data.define` classes for test representation
139
139
  - **Framework Translators**: Convert tryouts to RSpec/Minitest format
140
140
  - **CLI**: Modern command-line interface with framework selection
data/exe/try CHANGED
@@ -75,6 +75,6 @@ begin
75
75
  exit cli.run(files, **options)
76
76
  rescue StandardError => ex
77
77
  warn "Error: #{ex.message}"
78
- warn ex.backtrace.join("\n") if options[:debug]
78
+ warn ex.backtrace.join("\n") if options&.dig(:debug)
79
79
  exit 1
80
80
  end
@@ -63,10 +63,10 @@ class Tryouts
63
63
 
64
64
  # Include line number with file path for easy copying/clicking
65
65
  location = if failure.line_number > 0
66
- "#{pretty_path}:#{failure.line_number}"
66
+ "#{pretty_path}:#{failure.line_number + 1}"
67
67
  else
68
68
  pretty_path
69
- end
69
+ end
70
70
 
71
71
  puts " #{location}"
72
72
  puts " #{Console.color(:red, '✗')} #{failure.description}"
@@ -6,7 +6,7 @@ require_relative 'tty_status_display'
6
6
  class Tryouts
7
7
  class CLI
8
8
  # Centralized manager for live status display across all formatters
9
- # Replaces the decorator pattern with native integration
9
+ # Replaces the decorator pattern with inhouse integration
10
10
  class LiveStatusManager
11
11
  def initialize(formatter, options = {})
12
12
  @formatter = formatter
@@ -20,7 +20,7 @@ class Tryouts
20
20
  @display = TTYStatusDisplay.new(@formatter.stdout, options)
21
21
  @status_reserved = false
22
22
 
23
- debug_log('LiveStatusManager: Enabled with native integration')
23
+ debug_log('LiveStatusManager: Enabled with inhouse integration')
24
24
  end
25
25
 
26
26
  def enabled?
@@ -53,6 +53,7 @@ class Tryouts
53
53
 
54
54
  puts
55
55
  write '=' * 50
56
+ puts
56
57
  puts Console.color(:red, 'Failed Tests:')
57
58
 
58
59
  failure_collector.failures_by_file.each do |file_path, failures|
@@ -61,7 +62,7 @@ class Tryouts
61
62
 
62
63
  # Include line number with file path for easy copying/clicking
63
64
  if failure.line_number > 0
64
- location = "#{pretty_path}:#{failure.line_number}"
65
+ location = "#{pretty_path}:#{failure.line_number + 1}"
65
66
  else
66
67
  location = pretty_path
67
68
  end
@@ -66,6 +66,10 @@ class Tryouts
66
66
  opts.on('-c', '--compact', 'Compact single-line output') { options[:compact] = true }
67
67
  opts.on('-l', '--live', 'Live status display') { options[:live_status] = true }
68
68
 
69
+ opts.separator "\nParser Options:"
70
+ opts.on('--enhanced-parser', 'Use enhanced parser with inhouse comment extraction') { options[:parser] = :enhanced }
71
+ opts.on('--legacy-parser', 'Use legacy parser (current default)') { options[:parser] = :prism }
72
+
69
73
  opts.separator "\nInspection Options:"
70
74
  opts.on('-i', '--inspect', 'Inspect file structure without running tests') { options[:inspect] = true }
71
75
 
@@ -87,7 +91,6 @@ class Tryouts
87
91
  Tryouts.trace "Parsed files: #{files.inspect}, options: #{options.inspect}"
88
92
  [files, options]
89
93
  rescue OptionParser::InvalidOption => ex
90
- Tryouts.info Console.color(:red, "Invalid option error: #{ex.message}")
91
94
  warn "Error: #{ex.message}"
92
95
  warn "Try 'try --help' for more information."
93
96
  exit 1
@@ -0,0 +1,461 @@
1
+ # Enhanced parser using Prism's inhouse comment extraction capabilities
2
+ # Drop-in replacement for PrismParser that eliminates HEREDOC parsing issues
3
+
4
+ require 'prism'
5
+ require_relative 'test_case'
6
+
7
+ class Tryouts
8
+ # Enhanced parser that replaces manual line-by-line parsing with inhouse Prism APIs
9
+ # while maintaining full compatibility with the original parser's logic structure
10
+ class EnhancedParser
11
+ def initialize(source_path)
12
+ @source_path = source_path
13
+ @source = File.read(source_path)
14
+ @lines = @source.lines.map(&:chomp)
15
+ @prism_result = Prism.parse(@source)
16
+ @parsed_at = Time.now
17
+ end
18
+
19
+ def parse
20
+ return handle_syntax_errors if @prism_result.failure?
21
+
22
+ # Use inhouse comment extraction instead of line-by-line regex parsing
23
+ # This automatically excludes HEREDOC content!
24
+ tokens = tokenize_content_with_inhouse_extraction
25
+ test_boundaries = find_test_case_boundaries(tokens)
26
+ tokens = classify_potential_descriptions_with_boundaries(tokens, test_boundaries)
27
+ test_blocks = group_into_test_blocks(tokens)
28
+ process_test_blocks(test_blocks)
29
+ end
30
+
31
+ private
32
+
33
+ # Inhouse comment extraction - replaces the manual regex parsing
34
+ def tokenize_content_with_inhouse_extraction
35
+ tokens = []
36
+
37
+ # Get all comments using inhouse Prism extraction
38
+ comments = Prism.parse_comments(@source)
39
+ comment_by_line = comments.group_by { |comment| comment.location.start_line }
40
+
41
+ # Process each line, handling multiple comments per line
42
+ @lines.each_with_index do |line, index|
43
+ line_number = index + 1
44
+
45
+ if (comments_for_line = comment_by_line[line_number]) && !comments_for_line.empty?
46
+ emitted_code = false
47
+ comments_for_line.sort_by! { |c| c.location.start_column }
48
+ comments_for_line.each do |comment|
49
+ comment_content = comment.slice.strip
50
+ if comment.location.start_column > 0
51
+ unless emitted_code
52
+ tokens << { type: :code, content: line, line: index, ast: parse_ruby_line(line) }
53
+ emitted_code = true
54
+ end
55
+ # Inline comment may carry expectations; classify it too
56
+ tokens << classify_comment_inhousely(comment_content, line_number)
57
+ else
58
+ tokens << classify_comment_inhousely(comment_content, line_number)
59
+ end
60
+ end
61
+ next
62
+ end
63
+
64
+ # Handle non-comment lines (blank lines and code)
65
+ token = case line
66
+ when /^\s*$/
67
+ { type: :blank, line: index }
68
+ else
69
+ { type: :code, content: line, line: index, ast: parse_ruby_line(line) }
70
+ end
71
+ tokens << token
72
+ end
73
+
74
+ tokens
75
+ end
76
+
77
+ # Inhouse comment classification - replaces complex regex patterns
78
+ def classify_comment_inhousely(content, line_number)
79
+ case content
80
+ when /^##\s*(.*)$/
81
+ { type: :description, content: $1.strip, line: line_number - 1 }
82
+ when /^#\s*TEST\s*\d*:\s*(.*)$/
83
+ { type: :description, content: $1.strip, line: line_number - 1 }
84
+ when /^#\s*=!>\s*(.*)$/
85
+ { type: :exception_expectation, content: $1.strip, line: line_number - 1, ast: parse_expectation($1.strip) }
86
+ when /^#\s*=<>\s*(.*)$/
87
+ { type: :intentional_failure_expectation, content: $1.strip, line: line_number - 1, ast: parse_expectation($1.strip) }
88
+ when /^#\s*==>\s*(.*)$/
89
+ { type: :true_expectation, content: $1.strip, line: line_number - 1, ast: parse_expectation($1.strip) }
90
+ when %r{^#\s*=/=>\s*(.*)$}
91
+ { type: :false_expectation, content: $1.strip, line: line_number - 1, ast: parse_expectation($1.strip) }
92
+ when /^#\s*=\|>\s*(.*)$/
93
+ { type: :boolean_expectation, content: $1.strip, line: line_number - 1, ast: parse_expectation($1.strip) }
94
+ when /^#\s*=:>\s*(.*)$/
95
+ { type: :result_type_expectation, content: $1.strip, line: line_number - 1, ast: parse_expectation($1.strip) }
96
+ when /^#\s*=~>\s*(.*)$/
97
+ { type: :regex_match_expectation, content: $1.strip, line: line_number - 1, ast: parse_expectation($1.strip) }
98
+ when /^#\s*=%>\s*(.*)$/
99
+ { type: :performance_time_expectation, content: $1.strip, line: line_number - 1, ast: parse_expectation($1.strip) }
100
+ when /^#\s*=(\d+)>\s*(.*)$/
101
+ { type: :output_expectation, content: $2.strip, pipe: $1.to_i, line: line_number - 1, ast: parse_expectation($2.strip) }
102
+ when /^#\s*=>\s*(.*)$/
103
+ { type: :expectation, content: $1.strip, line: line_number - 1, ast: parse_expectation($1.strip) }
104
+ when /^##\s*=>\s*(.*)$/
105
+ { type: :comment, content: '=>' + $1.strip, line: line_number - 1 }
106
+ when /^#\s*(.*)$/
107
+ { type: :potential_description, content: $1.strip, line: line_number - 1 }
108
+ else
109
+ { type: :comment, content: content.sub(/^#\s*/, ''), line: line_number - 1 }
110
+ end
111
+ end
112
+
113
+ # Copy the rest of the methods from PrismParser to maintain identical behavior
114
+
115
+ def find_test_case_boundaries(tokens)
116
+ boundaries = []
117
+
118
+ tokens.each_with_index do |token, index|
119
+ if token[:type] == :description
120
+ start_line = token[:line]
121
+ end_line = find_test_case_end(tokens, index)
122
+ boundaries << { start: start_line, end: end_line } if end_line
123
+ end
124
+ end
125
+
126
+ boundaries
127
+ end
128
+
129
+ def find_test_case_end(tokens, start_index)
130
+ last_expectation_line = nil
131
+
132
+ (start_index + 1).upto(tokens.length - 1) do |i|
133
+ token = tokens[i]
134
+ break if token[:type] == :description
135
+
136
+ if is_expectation_type?(token[:type])
137
+ last_expectation_line = token[:line]
138
+ end
139
+ end
140
+
141
+ last_expectation_line
142
+ end
143
+
144
+ def classify_potential_descriptions_with_boundaries(tokens, test_boundaries)
145
+ tokens.map.with_index do |token, index|
146
+ if token[:type] == :potential_description
147
+ line_num = token[:line]
148
+ within_test_case = test_boundaries.any? do |boundary|
149
+ line_num >= boundary[:start] && line_num <= boundary[:end]
150
+ end
151
+
152
+ if within_test_case
153
+ token.merge(type: :comment)
154
+ else
155
+ content = token[:content].strip
156
+
157
+ looks_like_test_description = content.match?(/test|example|demonstrate|show|should|when|given/i) &&
158
+ content.length > 10
159
+
160
+ prev_token = index > 0 ? tokens[index - 1] : nil
161
+ has_code_before = prev_token && prev_token[:type] == :code
162
+
163
+ if has_code_before || !looks_like_test_description
164
+ token.merge(type: :comment)
165
+ else
166
+ following_tokens = tokens[(index + 1)..]
167
+ meaningful_following = following_tokens.reject { |t| [:blank, :comment].include?(t[:type]) }
168
+ test_window = meaningful_following.first(5)
169
+ has_code = test_window.any? { |t| t[:type] == :code }
170
+ has_expectation = test_window.any? { |t| is_expectation_type?(t[:type]) }
171
+
172
+ if has_code && has_expectation && looks_like_test_description
173
+ token.merge(type: :description)
174
+ else
175
+ token.merge(type: :comment)
176
+ end
177
+ end
178
+ end
179
+ else
180
+ token
181
+ end
182
+ end
183
+ end
184
+
185
+ def is_expectation_type?(type)
186
+ [
187
+ :expectation, :exception_expectation, :intentional_failure_expectation,
188
+ :true_expectation, :false_expectation, :boolean_expectation,
189
+ :result_type_expectation, :regex_match_expectation,
190
+ :performance_time_expectation, :output_expectation
191
+ ].include?(type)
192
+ end
193
+
194
+ def group_into_test_blocks(tokens)
195
+ blocks = []
196
+ current_block = new_test_block
197
+
198
+ tokens.each do |token|
199
+ case [current_block, token]
200
+ in [_, { type: :description, content: String => desc, line: Integer => line_num }]
201
+ if !current_block[:description].empty? && current_block[:code].empty? && current_block[:expectations].empty?
202
+ current_block[:description] = [current_block[:description], desc].join(' ').strip
203
+ else
204
+ blocks << current_block if block_has_content?(current_block)
205
+ current_block = new_test_block.merge(description: desc, start_line: line_num)
206
+ end
207
+
208
+ in [{ expectations: [], start_line: nil }, { type: :code, content: String => code, line: Integer => line_num }]
209
+ current_block[:code] << token
210
+ current_block[:start_line] = line_num
211
+
212
+ in [{ expectations: [] }, { type: :code, content: String => code }]
213
+ current_block[:code] << token
214
+
215
+ in [{ expectations: Array => exps }, { type: :code }] if !exps.empty?
216
+ blocks << current_block
217
+ current_block = new_test_block.merge(code: [token], start_line: token[:line])
218
+
219
+ in [_, { type: :expectation }]
220
+ current_block[:expectations] << token
221
+
222
+ in [_, { type: :exception_expectation }]
223
+ current_block[:expectations] << token
224
+
225
+ in [_, { type: :intentional_failure_expectation }]
226
+ current_block[:expectations] << token
227
+
228
+ in [_, { type: :true_expectation }]
229
+ current_block[:expectations] << token
230
+
231
+ in [_, { type: :false_expectation }]
232
+ current_block[:expectations] << token
233
+
234
+ in [_, { type: :boolean_expectation }]
235
+ current_block[:expectations] << token
236
+
237
+ in [_, { type: :result_type_expectation }]
238
+ current_block[:expectations] << token
239
+
240
+ in [_, { type: :regex_match_expectation }]
241
+ current_block[:expectations] << token
242
+
243
+ in [_, { type: :performance_time_expectation }]
244
+ current_block[:expectations] << token
245
+
246
+ in [_, { type: :output_expectation }]
247
+ current_block[:expectations] << token
248
+
249
+ in [_, { type: :comment | :blank }]
250
+ add_context_to_block(current_block, token)
251
+ end
252
+ end
253
+
254
+ blocks << current_block if block_has_content?(current_block)
255
+ classify_blocks(blocks)
256
+ end
257
+
258
+ def process_test_blocks(classified_blocks)
259
+ setup_blocks = classified_blocks.filter { |block| block[:type] == :setup }
260
+ test_blocks = classified_blocks.filter { |block| block[:type] == :test }
261
+ teardown_blocks = classified_blocks.filter { |block| block[:type] == :teardown }
262
+
263
+ Testrun.new(
264
+ setup: build_setup(setup_blocks),
265
+ test_cases: test_blocks.map { |block| build_test_case(block) },
266
+ teardown: build_teardown(teardown_blocks),
267
+ source_file: @source_path,
268
+ metadata: { parsed_at: @parsed_at, parser: :enhanced },
269
+ )
270
+ end
271
+
272
+ def build_setup(setup_blocks)
273
+ return Setup.new(code: '', line_range: 0..0, path: @source_path) if setup_blocks.empty?
274
+
275
+ Setup.new(
276
+ code: extract_pure_code_from_blocks(setup_blocks),
277
+ line_range: calculate_block_range(setup_blocks),
278
+ path: @source_path,
279
+ )
280
+ end
281
+
282
+ def build_teardown(teardown_blocks)
283
+ return Teardown.new(code: '', line_range: 0..0, path: @source_path) if teardown_blocks.empty?
284
+
285
+ Teardown.new(
286
+ code: extract_pure_code_from_blocks(teardown_blocks),
287
+ line_range: calculate_block_range(teardown_blocks),
288
+ path: @source_path,
289
+ )
290
+ end
291
+
292
+ def extract_pure_code_from_blocks(blocks)
293
+ blocks
294
+ .flat_map { |block| block[:code] }
295
+ .filter_map do |token|
296
+ case token
297
+ in { type: :code, content: String => content }
298
+ content
299
+ else
300
+ nil
301
+ end
302
+ end
303
+ .join("\n")
304
+ end
305
+
306
+ def calculate_block_range(blocks)
307
+ return 0..0 if blocks.empty?
308
+
309
+ valid_blocks = blocks.filter { |block| block[:start_line] && block[:end_line] }
310
+ return 0..0 if valid_blocks.empty?
311
+
312
+ line_ranges = valid_blocks.map { |block| block[:start_line]..block[:end_line] }
313
+ line_ranges.first.first..line_ranges.last.last
314
+ end
315
+
316
+ def extract_code_content(code_tokens)
317
+ code_tokens
318
+ .filter_map do |token|
319
+ case token
320
+ in { type: :code, content: String => content }
321
+ content
322
+ else
323
+ nil
324
+ end
325
+ end
326
+ .join("\n")
327
+ end
328
+
329
+ def parse_ruby_line(line)
330
+ return nil if line.strip.empty?
331
+
332
+ result = Prism.parse(line.strip)
333
+ case result
334
+ in { errors: [] => errors, value: { body: { body: [ast] } } }
335
+ ast
336
+ in { errors: Array => errors } if errors.any?
337
+ { type: :parse_error, errors: errors, raw: line }
338
+ else
339
+ nil
340
+ end
341
+ end
342
+
343
+ def parse_expectation(expr)
344
+ parse_ruby_line(expr)
345
+ end
346
+
347
+ def new_test_block
348
+ {
349
+ description: '',
350
+ code: [],
351
+ expectations: [],
352
+ comments: [],
353
+ start_line: nil,
354
+ end_line: nil,
355
+ }
356
+ end
357
+
358
+ def block_has_content?(block)
359
+ case block
360
+ in { description: String => desc, code: Array => code, expectations: Array => exps }
361
+ !desc.empty? || !code.empty? || !exps.empty?
362
+ else
363
+ false
364
+ end
365
+ end
366
+
367
+ def add_context_to_block(block, token)
368
+ case [block[:expectations].empty?, token]
369
+ in [true, { type: :comment | :blank }]
370
+ block[:code] << token
371
+ in [false, { type: :comment | :blank }]
372
+ block[:comments] << token
373
+ end
374
+ end
375
+
376
+ def classify_blocks(blocks)
377
+ blocks.map.with_index do |block, index|
378
+ block_type = case block
379
+ in { expectations: [] } if index == 0
380
+ :setup
381
+ in { expectations: [] } if index == blocks.size - 1
382
+ :teardown
383
+ in { expectations: Array => exps } if !exps.empty?
384
+ :test
385
+ else
386
+ :preamble
387
+ end
388
+
389
+ block.merge(type: block_type, end_line: calculate_end_line(block))
390
+ end
391
+ end
392
+
393
+ def calculate_end_line(block)
394
+ content_tokens = [*block[:code], *block[:expectations]]
395
+ return block[:start_line] if content_tokens.empty?
396
+
397
+ content_tokens.map { |token| token[:line] }.max || block[:start_line]
398
+ end
399
+
400
+ def build_test_case(block)
401
+ case block
402
+ in {
403
+ type: :test,
404
+ description: String => desc,
405
+ code: Array => code_tokens,
406
+ expectations: Array => exp_tokens,
407
+ start_line: Integer => start_line,
408
+ end_line: Integer => end_line
409
+ }
410
+ source_lines = @lines[start_line..end_line]
411
+ first_expectation_line = exp_tokens.empty? ? start_line : exp_tokens.first[:line]
412
+
413
+ TestCase.new(
414
+ description: desc,
415
+ code: extract_code_content(code_tokens),
416
+ expectations: exp_tokens.map do |token|
417
+ type = case token[:type]
418
+ when :exception_expectation then :exception
419
+ when :intentional_failure_expectation then :intentional_failure
420
+ when :true_expectation then :true # rubocop:disable Lint/BooleanSymbol
421
+ when :false_expectation then :false # rubocop:disable Lint/BooleanSymbol
422
+ when :boolean_expectation then :boolean
423
+ when :result_type_expectation then :result_type
424
+ when :regex_match_expectation then :regex_match
425
+ when :performance_time_expectation then :performance_time
426
+ when :output_expectation then :output
427
+ else :regular
428
+ end
429
+
430
+ if token[:type] == :output_expectation
431
+ OutputExpectation.new(content: token[:content], type: type, pipe: token[:pipe])
432
+ else
433
+ Expectation.new(content: token[:content], type: type)
434
+ end
435
+ end,
436
+ line_range: start_line..end_line,
437
+ path: @source_path,
438
+ source_lines: source_lines,
439
+ first_expectation_line: first_expectation_line,
440
+ )
441
+ else
442
+ raise "Invalid test block structure: #{block}"
443
+ end
444
+ end
445
+
446
+ def handle_syntax_errors
447
+ errors = @prism_result.errors.map do |error|
448
+ line_context = @lines[error.location.start_line - 1] || ''
449
+
450
+ TryoutSyntaxError.new(
451
+ error.message,
452
+ line_number: error.location.start_line,
453
+ context: line_context,
454
+ source_file: @source_path,
455
+ )
456
+ end
457
+
458
+ raise errors.first if errors.any?
459
+ end
460
+ end
461
+ end
@@ -9,8 +9,8 @@ class Tryouts
9
9
  # Data structure for a single failure entry
10
10
  FailureEntry = Data.define(:file_path, :test_case, :result_packet) do
11
11
  def line_number
12
- # Use last line of range (expectation line) for failure display
13
- test_case.line_range&.last || test_case.first_expectation_line || 0
12
+ # Use first expectation line for consistency with main error display
13
+ test_case.first_expectation_line || test_case.line_range&.first || 0
14
14
  end
15
15
 
16
16
  def description
@@ -1,12 +1,15 @@
1
1
  # lib/tryouts/file_processor.rb
2
2
 
3
3
  require_relative 'prism_parser'
4
+ require_relative 'enhanced_parser'
4
5
  require_relative 'test_executor'
5
6
  require_relative 'cli/modes/inspect'
6
7
  require_relative 'cli/modes/generate'
7
8
 
8
9
  class Tryouts
9
10
  class FileProcessor
11
+ # Supported parser types for validation and documentation
12
+ PARSER_TYPES = [:enhanced, :prism].freeze
10
13
  def initialize(file:, options:, output_manager:, translator:, global_tally:)
11
14
  @file = file
12
15
  @options = options
@@ -16,7 +19,7 @@ class Tryouts
16
19
  end
17
20
 
18
21
  def process
19
- testrun = PrismParser.new(@file).parse
22
+ testrun = create_parser(@file, @options).parse
20
23
  @global_tally[:file_count] += 1
21
24
  @output_manager.file_parsed(@file, testrun.total_tests)
22
25
 
@@ -35,6 +38,21 @@ class Tryouts
35
38
 
36
39
  private
37
40
 
41
+ def create_parser(file, options)
42
+ parser_type = options[:parser] || :prism # default to legacy for safe rollout
43
+
44
+ unless PARSER_TYPES.include?(parser_type)
45
+ raise ArgumentError, "Unknown parser: #{parser_type}. Allowed types: #{PARSER_TYPES.join(', ')}"
46
+ end
47
+
48
+ case parser_type
49
+ when :enhanced
50
+ EnhancedParser.new(file)
51
+ when :prism
52
+ PrismParser.new(file)
53
+ end
54
+ end
55
+
38
56
  def handle_inspect_mode(testrun)
39
57
  mode = Tryouts::CLI::InspectMode.new(@file, testrun, @options, @output_manager, @translator)
40
58
  mode.handle
@@ -11,6 +11,7 @@ class Tryouts
11
11
  @source = File.read(source_path)
12
12
  @lines = @source.lines.map(&:chomp)
13
13
  @prism_result = Prism.parse(@source)
14
+ @parsed_at = Time.now
14
15
  end
15
16
 
16
17
  def parse
@@ -308,7 +309,7 @@ class Tryouts
308
309
  test_cases: test_blocks.map { |block| build_test_case(block) },
309
310
  teardown: build_teardown(teardown_blocks),
310
311
  source_file: @source_path,
311
- metadata: { parsed_at: Time.now, parser: :prism_v2_fixed },
312
+ metadata: { parsed_at: @parsed_at, parser: :prism_v2_fixed },
312
313
  )
313
314
  end
314
315
 
@@ -1,5 +1,5 @@
1
1
  # lib/tryouts/version.rb
2
2
 
3
3
  class Tryouts
4
- VERSION = '3.2.1'
4
+ VERSION = '3.3.0'
5
5
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: tryouts
3
3
  version: !ruby/object:Gem::Version
4
- version: 3.2.1
4
+ version: 3.3.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Delano Mandelbaum
@@ -137,6 +137,7 @@ files:
137
137
  - lib/tryouts/cli/opts.rb
138
138
  - lib/tryouts/cli/tty_detector.rb
139
139
  - lib/tryouts/console.rb
140
+ - lib/tryouts/enhanced_parser.rb
140
141
  - lib/tryouts/expectation_evaluators/base.rb
141
142
  - lib/tryouts/expectation_evaluators/boolean.rb
142
143
  - lib/tryouts/expectation_evaluators/exception.rb