kettle-dev 1.1.59 → 1.2.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 (55) hide show
  1. checksums.yaml +4 -4
  2. checksums.yaml.gz.sig +1 -2
  3. data/.envrc +1 -0
  4. data/.envrc.example +1 -0
  5. data/.envrc.no-osc.example +1 -0
  6. data/.github/workflows/ancient.yml +1 -1
  7. data/.github/workflows/ancient.yml.example +1 -1
  8. data/.github/workflows/codeql-analysis.yml +1 -1
  9. data/.github/workflows/coverage.yml +1 -1
  10. data/.github/workflows/coverage.yml.example +1 -1
  11. data/.github/workflows/current.yml +1 -1
  12. data/.github/workflows/current.yml.example +1 -1
  13. data/.github/workflows/dep-heads.yml +1 -1
  14. data/.github/workflows/dependency-review.yml +1 -1
  15. data/.github/workflows/heads.yml +1 -1
  16. data/.github/workflows/heads.yml.example +1 -1
  17. data/.github/workflows/jruby.yml +1 -1
  18. data/.github/workflows/jruby.yml.example +1 -1
  19. data/.github/workflows/legacy.yml +1 -1
  20. data/.github/workflows/license-eye.yml +1 -1
  21. data/.github/workflows/locked_deps.yml +1 -1
  22. data/.github/workflows/opencollective.yml +1 -1
  23. data/.github/workflows/style.yml +1 -1
  24. data/.github/workflows/supported.yml +1 -1
  25. data/.github/workflows/truffle.yml +1 -1
  26. data/.github/workflows/unlocked_deps.yml +1 -1
  27. data/.github/workflows/unsupported.yml +1 -1
  28. data/CHANGELOG.md +35 -1
  29. data/Gemfile +3 -0
  30. data/Gemfile.example +3 -0
  31. data/README.md +90 -37
  32. data/README.md.example +16 -12
  33. data/README.md.no-osc.example +16 -12
  34. data/Rakefile.example +1 -1
  35. data/gemfiles/modular/style.gemfile.example +1 -1
  36. data/gemfiles/modular/templating.gemfile +3 -0
  37. data/lib/kettle/dev/appraisals_ast_merger.rb +383 -0
  38. data/lib/kettle/dev/changelog_cli.rb +13 -0
  39. data/lib/kettle/dev/modular_gemfiles.rb +11 -3
  40. data/lib/kettle/dev/prism_utils.rb +188 -0
  41. data/lib/kettle/dev/rakelib/spec_test.rake +70 -20
  42. data/lib/kettle/dev/source_merger.rb +345 -0
  43. data/lib/kettle/dev/tasks/template_task.rb +11 -1
  44. data/lib/kettle/dev/template_helpers.rb +70 -226
  45. data/lib/kettle/dev/version.rb +1 -1
  46. data/lib/kettle/dev.rb +2 -0
  47. data/sig/kettle/dev/appraisals_ast_merger.rbs +72 -0
  48. data/sig/kettle/dev/changelog_cli.rbs +64 -0
  49. data/sig/kettle/dev/prism_utils.rbs +56 -0
  50. data/sig/kettle/dev/source_merger.rbs +86 -0
  51. data/sig/kettle/dev/versioning.rbs +21 -0
  52. data.tar.gz.sig +0 -0
  53. metadata +14 -5
  54. metadata.gz.sig +0 -0
  55. /data/sig/kettle/dev/{dvcscli.rbs → dvcs_cli.rbs} +0 -0
@@ -3,11 +3,14 @@
3
3
  # External stdlibs
4
4
  require "find"
5
5
  require "set"
6
+ require "yaml"
6
7
 
7
8
  module Kettle
8
9
  module Dev
9
10
  # Helpers shared by kettle:dev Rake tasks for templating and file ops.
10
11
  module TemplateHelpers
12
+ require "kettle/dev/appraisals_ast_merger"
13
+
11
14
  # Track results of templating actions across a single process run.
12
15
  # Keys: absolute destination paths (String)
13
16
  # Values: Hash with keys: :action (Symbol, one of :create, :replace, :skip, :dir_create, :dir_replace), :timestamp (Time)
@@ -17,6 +20,12 @@ module Kettle
17
20
  # The minimum Ruby supported by setup-ruby GHA
18
21
  MIN_SETUP_RUBY = Gem::Version.create("2.3")
19
22
 
23
+ TEMPLATE_MANIFEST_PATH = File.expand_path("../../..", __dir__) + "/template_manifest.yml"
24
+ RUBY_BASENAMES = %w[Gemfile Rakefile Appraisals Appraisal.root.gemfile].freeze
25
+ RUBY_SUFFIXES = %w[.gemspec .gemfile].freeze
26
+ RUBY_EXTENSIONS = %w[.rb .rake].freeze
27
+ @@manifestation = nil
28
+
20
29
  module_function
21
30
 
22
31
  # Root of the host project where Rake was invoked
@@ -279,43 +288,15 @@ module Kettle
279
288
  # If replacement fails unexpectedly, proceed with content as-is
280
289
  end
281
290
 
282
- # If updating the Appraisals file and a destination already exists,
283
- # merge appraise blocks: augment matching blocks with missing gem/eval_gemfile lines,
284
- # preserve destination-only blocks and comments/preamble.
285
- begin
286
- if dest_exists && File.basename(dest_path.to_s) == "Appraisals" && File.file?(dest_path.to_s)
287
- existing = begin
288
- File.read(dest_path)
289
- rescue
290
- ""
291
- end
292
- content = merge_appraisals(content, existing)
293
- end
294
- rescue StandardError => e
295
- Kettle::Dev.debug_error(e, __method__)
296
- # On any error, fall back to generated content
297
- end
298
-
299
- # If updating a Gemfile or modular .gemfile and the destination already exists,
300
- # merge dependency lines from the source into the destination to preserve any
301
- # user-defined gem entries. We append missing `gem "name"` lines; we never
302
- # alter or remove existing gem lines in the destination.
303
- begin
304
- if dest_exists
305
- dest_str = dest_path.to_s
306
- is_gemfile_like = File.basename(dest_str) == "Gemfile" || dest_str.end_with?(".gemfile")
307
- if is_gemfile_like && File.file?(dest_str)
308
- begin
309
- existing = File.read(dest_str)
310
- content = merge_gemfile_dependencies(content, existing)
311
- rescue StandardError => e
312
- Kettle::Dev.debug_error(e, __method__)
313
- # If merging fails, fall back to writing generated content
314
- end
315
- end
291
+ basename = File.basename(dest_path.to_s)
292
+ content = apply_appraisals_merge(content, dest_path) if basename == "Appraisals"
293
+ if basename == "Appraisal.root.gemfile" && File.exist?(dest_path)
294
+ begin
295
+ prior = File.read(dest_path)
296
+ content = merge_gemfile_dependencies(content, prior)
297
+ rescue StandardError => e
298
+ Kettle::Dev.debug_error(e, __method__)
316
299
  end
317
- rescue StandardError => e
318
- Kettle::Dev.debug_error(e, __method__)
319
300
  end
320
301
 
321
302
  write_file(dest_path, content)
@@ -456,197 +437,17 @@ module Kettle
456
437
  end
457
438
  end
458
439
 
459
- # Merge Appraisals template into existing Appraisals file.
460
- # Rules:
461
- # - For each appraise "name" block in template:
462
- # * If destination has same block, ensure all gem/ eval_gemfile lines from template
463
- # exist in destination (append missing just before end), keep other dest lines.
464
- # Use template's contiguous header comment lines (immediately preceding the appraise line)
465
- # if any; otherwise retain destination's header comments.
466
- # * If destination lacks the block, add the full template block (with its header).
467
- # - Preserve destination-only blocks (not present in template) unchanged and after
468
- # the merged template-ordered blocks.
469
- # - Preamble (content before first appraise) comes from template when present, else destination.
470
- def merge_appraisals(template_content, dest_content)
471
- begin
472
- # Helper: extract contiguous leading header lines (comments and blank lines)
473
- extract_leading_header = lambda do |text|
474
- lines = text.lines
475
- header_lines = []
476
- idx = 0
477
- while idx < lines.length
478
- ln = lines[idx]
479
- break unless ln.strip.empty? || ln.lstrip.start_with?("#")
480
- header_lines << ln
481
- idx += 1
482
- end
483
- body = (idx < lines.length) ? lines[idx..-1].join : ""
484
- {header: header_lines.join, body: body}
485
- end
486
-
487
- parse_blocks = lambda do |text|
488
- lines = text.lines
489
- blocks = []
490
- i = 0
491
- while i < lines.length
492
- line = lines[i]
493
- if line =~ /^\s*appraise\s+["']([^"']+)["']\s+do\s*$/
494
- name = $1
495
- # collect header comment lines immediately preceding (contiguous, no blank between comment group and appraise line)
496
- header_lines = []
497
- j = i - 1
498
- while j >= 0
499
- prev = lines[j]
500
- break if prev.strip.empty?
501
- if prev.lstrip.start_with?("#")
502
- header_lines.unshift(prev)
503
- j -= 1
504
- else
505
- break
506
- end
507
- end
508
- body_lines = []
509
- i += 1
510
- while i < lines.length
511
- l2 = lines[i]
512
- if l2 =~ /^\s*end\s*$/
513
- end_line = l2
514
- blocks << {
515
- name: name,
516
- header: header_lines.dup,
517
- body: body_lines.dup,
518
- end_line: end_line,
519
- raw_order: blocks.length,
520
- original_indices: (j ? (j + 1)..i : i),
521
- }
522
- break
523
- else
524
- body_lines << l2
525
- end
526
- i += 1
527
- end
528
- end
529
- i += 1
530
- end
531
- preamble = if blocks.empty?
532
- text
533
- else
534
- # preamble = lines from start up to first block start (exclusive)
535
- first_block = blocks.first
536
- # Take lines up to first occurrence of the appraise line (supports either quote type)
537
- re = /^\s*appraise\s+["']#{Regexp.escape(first_block[:name])}["']\s+do\s*$/
538
- idx = lines.index { |l| l =~ re } || 0
539
- lines[0...idx].join
540
- end
541
- {blocks: blocks, preamble: preamble}
542
- end
543
-
544
- # Extract leading headers from template and destination and parse their bodies
545
- tmpl_parts = extract_leading_header.call(template_content)
546
- dest_parts = extract_leading_header.call(dest_content)
547
-
548
- # If the template does not provide a leading header, preserve the destination as-is
549
- # so that per-block adjacent header comments (including those at the top of file)
550
- # are still parsed and preserved. Only strip the destination leading header when
551
- # the template has a leading header to replace it.
552
- dest_parse_source = if tmpl_parts[:header].to_s.strip.empty?
553
- dest_content
554
- else
555
- dest_parts[:body]
556
- end
557
-
558
- tmpl = parse_blocks.call(tmpl_parts[:body])
559
- dest = parse_blocks.call(dest_parse_source)
560
- tmpl_blocks = tmpl[:blocks]
561
- dest_blocks = dest[:blocks]
562
- dest_by_name = dest_blocks.map { |b| [b[:name], b] }.to_h
563
-
564
- merged_blocks_strings = []
565
- gem_or_eval_re = /^\s*(?:gem|eval_gemfile)\b/
566
-
567
- tmpl_blocks.each do |tb|
568
- if (db = dest_by_name[tb[:name]])
569
- # Merge lines
570
- existing_lines = db[:body].map(&:rstrip)
571
- existing_set = existing_lines.to_set
572
- # Collect template gem/eval lines
573
- tmpl_needed = tb[:body].select { |l| gem_or_eval_re =~ l }
574
- additions = []
575
- tmpl_needed.each do |l|
576
- line_key = l.rstrip
577
- additions << l unless existing_set.include?(line_key)
578
- end
579
- merged_body = db[:body].dup
580
- unless additions.empty?
581
- # insert before end (just append; body excludes 'end')
582
- merged_body += additions
583
- end
584
- header = tb[:header].any? ? tb[:header] : db[:header]
585
- # If the template provides no leading header and the destination preamble
586
- # already ends with this header, skip emitting the header for this block
587
- # to avoid duplicating it (it will already be present at the top of the file).
588
- header_to_emit = if tmpl_parts[:header].to_s.strip.empty? && !dest[:preamble].to_s.strip.empty? && !header.empty? && dest[:preamble].to_s.end_with?(header.join)
589
- []
590
- else
591
- header
592
- end
593
- block_text = +""
594
- block_text << "\n" unless merged_blocks_strings.empty?
595
- header_to_emit.each { |hl| block_text << hl } if header_to_emit.any?
596
- block_text << "appraise \"#{tb[:name]}\" do\n"
597
- merged_body.each { |bl| block_text << bl }
598
- block_text << db[:end_line]
599
- merged_blocks_strings << block_text
600
- dest_by_name.delete(tb[:name])
601
- else
602
- # New block from template
603
- block_text = +""
604
- block_text << "\n" unless merged_blocks_strings.empty?
605
- tb[:header].each { |hl| block_text << hl } if tb[:header].any?
606
- block_text << "appraise \"#{tb[:name]}\" do\n"
607
- tb[:body].each { |bl| block_text << bl }
608
- block_text << tb[:end_line]
609
- merged_blocks_strings << block_text
610
- end
611
- end
612
- # Append destination-only blocks preserving their original text
613
- dest_remaining_order = dest_blocks.select { |b| dest_by_name.key?(b[:name]) }
614
- dest_remaining_order.each do |b|
615
- block_text = +""
616
- block_text << "\n" unless merged_blocks_strings.empty?
617
- b[:header].each { |hl| block_text << hl } if b[:header].any?
618
- block_text << "appraise \"#{b[:name]}\" do\n"
619
- b[:body].each { |bl| block_text << bl }
620
- block_text << b[:end_line]
621
- merged_blocks_strings << block_text
622
- end
623
-
624
- # Build final preamble:
625
- # - If template provides a leading header, use that header and then prefer the
626
- # template's body preamble when present, otherwise fall back to the
627
- # destination's body preamble.
628
- # - If template does NOT provide a leading header, leave the destination
629
- # preamble (including any leading header/comments) intact.
630
- preamble = +""
631
- if tmpl_parts[:header].to_s.strip.empty?
632
- preamble << dest[:preamble].to_s
633
- else
634
- body_preamble = tmpl[:preamble].to_s.strip.empty? ? dest[:preamble].to_s : tmpl[:preamble].to_s
635
- preamble << tmpl_parts[:header].to_s
636
- preamble << body_preamble.to_s
637
- end
638
-
639
- out = +""
640
- out << preamble unless preamble.nil? || preamble.empty?
641
- out << "\n" unless out.end_with?("\n")
642
- out << merged_blocks_strings.join
643
- out << "\n" unless out.end_with?("\n")
644
- out
645
- rescue StandardError => e
646
- Kettle::Dev.debug_error(e, __method__)
647
- # Fallback: prefer destination (user changes) and append template content to allow manual reconciliation
648
- dest_content + "\n# --- TEMPLATE APPRAISALS (unmerged) ---\n" + template_content
440
+ def apply_appraisals_merge(content, dest_path)
441
+ dest = dest_path.to_s
442
+ existing = if File.exist?(dest)
443
+ File.read(dest)
444
+ else
445
+ ""
649
446
  end
447
+ Kettle::Dev::AppraisalsAstMerger.merge(content, existing)
448
+ rescue StandardError => e
449
+ Kettle::Dev.debug_error(e, __method__)
450
+ content
650
451
  end
651
452
 
652
453
  # Copy a directory tree, prompting before creating or overwriting.
@@ -899,6 +700,49 @@ module Kettle
899
700
  def gemspec_metadata(root = project_root)
900
701
  Kettle::Dev::GemSpecReader.load(root)
901
702
  end
703
+
704
+ def apply_strategy(content, dest_path)
705
+ return content unless ruby_template?(dest_path)
706
+ strategy = strategy_for(dest_path)
707
+ dest_content = File.exist?(dest_path) ? File.read(dest_path) : ""
708
+ Kettle::Dev::SourceMerger.apply(strategy: strategy, src: content, dest: dest_content, path: rel_path(dest_path))
709
+ end
710
+
711
+ def manifestation
712
+ @@manifestation ||= load_manifest
713
+ end
714
+
715
+ def strategy_for(dest_path)
716
+ relative = rel_path(dest_path)
717
+ manifestation.find do |entry|
718
+ File.fnmatch?(entry[:path], relative, File::FNM_PATHNAME | File::FNM_EXTGLOB | File::FNM_DOTMATCH)
719
+ end&.fetch(:strategy, :skip) || :skip
720
+ end
721
+
722
+ def rel_path(path)
723
+ project = project_root.to_s
724
+ path.to_s.sub(/^#{Regexp.escape(project)}\/?/, "")
725
+ end
726
+
727
+ def ruby_template?(dest_path)
728
+ base = File.basename(dest_path.to_s)
729
+ return true if RUBY_BASENAMES.include?(base)
730
+ return true if RUBY_SUFFIXES.any? { |suffix| base.end_with?(suffix) }
731
+ ext = File.extname(base)
732
+ RUBY_EXTENSIONS.include?(ext)
733
+ end
734
+
735
+ def load_manifest
736
+ raw = YAML.load_file(TEMPLATE_MANIFEST_PATH)
737
+ raw.map do |entry|
738
+ {
739
+ path: entry["path"],
740
+ strategy: entry["strategy"].to_s.strip.downcase.to_sym,
741
+ }
742
+ end
743
+ rescue Errno::ENOENT
744
+ []
745
+ end
902
746
  end
903
747
  end
904
748
  end
@@ -6,7 +6,7 @@ module Kettle
6
6
  module Version
7
7
  # The gem version.
8
8
  # @return [String]
9
- VERSION = "1.1.59"
9
+ VERSION = "1.2.0"
10
10
 
11
11
  module_function
12
12
 
data/lib/kettle/dev.rb CHANGED
@@ -21,8 +21,10 @@ module Kettle
21
21
  autoload :GitAdapter, "kettle/dev/git_adapter"
22
22
  autoload :GitCommitFooter, "kettle/dev/git_commit_footer"
23
23
  autoload :InputAdapter, "kettle/dev/input_adapter"
24
+ autoload :PrismUtils, "kettle/dev/prism_utils"
24
25
  autoload :ReadmeBackers, "kettle/dev/readme_backers"
25
26
  autoload :OpenCollectiveConfig, "kettle/dev/open_collective_config"
27
+ autoload :SourceMerger, "kettle/dev/source_merger"
26
28
  autoload :ReleaseCLI, "kettle/dev/release_cli"
27
29
  autoload :PreReleaseCLI, "kettle/dev/pre_release_cli"
28
30
  autoload :SetupCLI, "kettle/dev/setup_cli"
@@ -0,0 +1,72 @@
1
+ # TypeProf 0.21.11
2
+
3
+ module Kettle
4
+ module Dev
5
+ # AST-driven merger for Appraisals files using Prism
6
+ module AppraisalsAstMerger
7
+ TRACKED_METHODS: Array[Symbol]
8
+
9
+ # Merge template and destination Appraisals files preserving comments
10
+ def self.merge: (String template_content, String dest_content) -> String
11
+
12
+ # Extract blocks and preamble from parse result
13
+ def self.extract_blocks: (
14
+ Prism::ParseResult parse_result,
15
+ String source_content
16
+ ) -> [Array[Prism::Comment], Array[Hash[Symbol, untyped]]]
17
+
18
+ # Check if node is an appraise call
19
+ def self.appraise_call?: (Prism::Node node) -> bool
20
+
21
+ # Extract appraise block name from node
22
+ def self.extract_appraise_name: (Prism::Node? node) -> String?
23
+
24
+ # Merge preamble comments from template and destination
25
+ def self.merge_preambles: (
26
+ Array[Prism::Comment] tmpl_comments,
27
+ Array[Prism::Comment] dest_comments
28
+ ) -> Array[String]
29
+
30
+ # Extract block header comments
31
+ def self.extract_block_header: (
32
+ Prism::Node node,
33
+ Array[String] source_lines,
34
+ Array[Hash[Symbol, untyped]] previous_blocks
35
+ ) -> String
36
+
37
+ # Merge blocks from template and destination
38
+ def self.merge_blocks: (
39
+ Array[Hash[Symbol, untyped]] tmpl_blocks,
40
+ Array[Hash[Symbol, untyped]] dest_blocks,
41
+ Prism::ParseResult tmpl_result,
42
+ Prism::ParseResult dest_result
43
+ ) -> Array[Hash[Symbol, untyped]]
44
+
45
+ # Merge statements within a block
46
+ def self.merge_block_statements: (
47
+ Prism::Node? tmpl_body,
48
+ Prism::Node? dest_body,
49
+ Prism::ParseResult dest_result
50
+ ) -> Array[Hash[Symbol, untyped]]
51
+
52
+ # Generate statement key for deduplication
53
+ def self.statement_key: (Prism::Node node) -> [Symbol, String?]?
54
+
55
+ # Build output from preamble and blocks
56
+ def self.build_output: (
57
+ Array[String] preamble_lines,
58
+ Array[Hash[Symbol, untyped]] blocks
59
+ ) -> String
60
+
61
+ # Normalize statement to use parentheses
62
+ def self.normalize_statement: (Prism::Node node) -> String
63
+
64
+ # Normalize argument to canonical format
65
+ def self.normalize_argument: (Prism::Node arg) -> String
66
+
67
+ # Extract original statements from node
68
+ def self.extract_original_statements: (Prism::Node node) -> Array[Hash[Symbol, untyped]]
69
+ end
70
+ end
71
+ end
72
+
@@ -0,0 +1,64 @@
1
+ # TypeProf 0.21.11
2
+
3
+ module Kettle
4
+ module Dev
5
+ # CLI for updating CHANGELOG.md with new version sections
6
+ class ChangelogCLI
7
+ UNRELEASED_SECTION_HEADING: String
8
+
9
+ @root: String
10
+ @changelog_path: String
11
+ @coverage_path: String
12
+
13
+ def initialize: () -> void
14
+
15
+ # Main entry point that updates CHANGELOG.md
16
+ def run: () -> void
17
+
18
+ private
19
+
20
+ # Abort with error message
21
+ def abort: (String msg) -> void
22
+
23
+ # Detect version from lib/**/version.rb
24
+ def detect_version: () -> String
25
+
26
+ # Extract unreleased section from changelog
27
+ def extract_unreleased: (String content) -> [String?, String?, String?]
28
+
29
+ # Detect previous version from after text
30
+ def detect_previous_version: (String after_text) -> String?
31
+
32
+ # Filter unreleased sections keeping only those with content
33
+ def filter_unreleased_sections: (String unreleased_block) -> String
34
+
35
+ # Get coverage lines from coverage.json
36
+ def coverage_lines: () -> [String?, String?]
37
+
38
+ # Get YARD documentation percentage
39
+ def yard_percent_documented: () -> String?
40
+
41
+ # Convert legacy heading tag suffix to list format
42
+ def convert_heading_tag_suffix_to_list: (String text) -> String
43
+
44
+ # Update link references in changelog
45
+ def update_link_refs: (
46
+ String content,
47
+ String? owner,
48
+ String? repo,
49
+ String? prev_version,
50
+ String new_version
51
+ ) -> String
52
+
53
+ # Normalize spacing around headings
54
+ def normalize_heading_spacing: (String text) -> String
55
+
56
+ # Ensure proper footer spacing
57
+ def ensure_footer_spacing: (String text) -> String
58
+
59
+ # Detect initial compare base from changelog
60
+ def detect_initial_compare_base: (Array[String] lines) -> String
61
+ end
62
+ end
63
+ end
64
+
@@ -0,0 +1,56 @@
1
+ # TypeProf 0.21.11
2
+
3
+ module Kettle
4
+ module Dev
5
+ # Shared utilities for working with Prism AST nodes
6
+ module PrismUtils
7
+ # Parse Ruby source code and return Prism parse result with comments
8
+ def self.parse_with_comments: (String source) -> Prism::ParseResult
9
+
10
+ # Extract statements from a Prism body node
11
+ def self.extract_statements: (Prism::Node? body_node) -> Array[Prism::Node]
12
+
13
+ # Generate a unique key for a statement node to identify equivalent statements
14
+ def self.statement_key: (
15
+ Prism::Node node,
16
+ ?tracked_methods: Array[Symbol]
17
+ ) -> [Symbol, String?]?
18
+
19
+ # Extract literal value from string or symbol nodes
20
+ def self.extract_literal_value: (Prism::Node? node) -> (String | Symbol)?
21
+
22
+ # Extract qualified constant name from a constant node
23
+ def self.extract_const_name: (Prism::Node? node) -> String?
24
+
25
+ # Find leading comments for a statement node
26
+ def self.find_leading_comments: (
27
+ Prism::ParseResult parse_result,
28
+ Prism::Node current_stmt,
29
+ Prism::Node? prev_stmt,
30
+ Prism::Node body_node
31
+ ) -> Array[Prism::Comment]
32
+
33
+ # Find inline comments for a statement node
34
+ def self.inline_comments_for_node: (
35
+ Prism::ParseResult parse_result,
36
+ Prism::Node stmt
37
+ ) -> Array[Prism::Comment]
38
+
39
+ # Convert a Prism AST node to Ruby source code
40
+ def self.node_to_source: (Prism::Node? node) -> String
41
+
42
+ # Normalize a call node to use parentheses format
43
+ def self.normalize_call_node: (Prism::Node node) -> String
44
+
45
+ # Normalize an argument node to canonical format
46
+ def self.normalize_argument: (Prism::Node arg) -> String
47
+
48
+ # Check if a node is a specific method call
49
+ def self.call_to?: (Prism::Node node, Symbol method_name) -> bool
50
+
51
+ # Check if a node is a block call to a specific method
52
+ def self.block_call_to?: (Prism::Node node, Symbol method_name) -> bool
53
+ end
54
+ end
55
+ end
56
+
@@ -0,0 +1,86 @@
1
+ # TypeProf 0.21.11
2
+
3
+ module Kettle
4
+ module Dev
5
+ # Prism-based AST merging for templated Ruby files
6
+ module SourceMerger
7
+ FREEZE_START: Regexp
8
+ FREEZE_END: Regexp
9
+ FREEZE_BLOCK: Regexp
10
+ FREEZE_REMINDER: String
11
+ BUG_URL: String
12
+
13
+ # Apply a templating strategy to merge source and destination Ruby files
14
+ def self.apply: (
15
+ strategy: Symbol,
16
+ src: String,
17
+ dest: String,
18
+ path: String
19
+ ) -> String
20
+
21
+ # Ensure freeze reminder comment is present at the top of content
22
+ def self.ensure_reminder: (String content) -> String
23
+
24
+ # Normalize source code while preserving formatting
25
+ def self.normalize_source: (String source) -> String
26
+
27
+ # Check if freeze reminder is present in content
28
+ def self.reminder_present?: (String content) -> bool
29
+
30
+ # Find index where freeze reminder should be inserted
31
+ def self.reminder_insertion_index: (String content) -> Integer
32
+
33
+ # Check if line is a shebang
34
+ def self.shebang?: (String line) -> bool
35
+
36
+ # Check if line is a frozen_string_literal comment
37
+ def self.frozen_comment?: (String line) -> bool
38
+
39
+ # Merge kettle-dev:freeze blocks from destination into source content
40
+ def self.merge_freeze_blocks: (String src_content, String dest_content) -> String
41
+
42
+ # Extract freeze blocks from text
43
+ def self.freeze_blocks: (String? text) -> Array[Hash[Symbol, untyped]]
44
+
45
+ # Normalize strategy symbol
46
+ def self.normalize_strategy: (Symbol? strategy) -> Symbol
47
+
48
+ # Warn about bugs and print error information
49
+ def self.warn_bug: (String path, StandardError error) -> void
50
+
51
+ # Ensure text ends with newline
52
+ def self.ensure_trailing_newline: (String? text) -> String
53
+
54
+ # Apply append strategy
55
+ def self.apply_append: (String src_content, String dest_content) -> String
56
+
57
+ # Apply merge strategy
58
+ def self.apply_merge: (String src_content, String dest_content) -> String
59
+
60
+ # Merge node information
61
+ def self.merge_node_info: (Array[untyped] signature, Hash[Symbol, untyped] dest_node_info, Hash[Symbol, untyped] src_node_info) -> Hash[Symbol, untyped]
62
+
63
+ # Merge block node information
64
+ def self.merge_block_node_info: (Hash[Symbol, untyped] src_node_info) -> Hash[Symbol, untyped]
65
+
66
+ # Perform Prism-based merge with block
67
+ def self.prism_merge: (String src_content, String dest_content) { (Array[Hash[Symbol, untyped]], Array[Hash[Symbol, untyped]], Prism::ParseResult, Prism::ParseResult) -> Array[Hash[Symbol, untyped]] } -> String
68
+
69
+ # Extract nodes with comments from parse result
70
+ def self.extract_nodes_with_comments: (Prism::ParseResult parse_result) -> Array[Hash[Symbol, untyped]]
71
+
72
+ # Build source from node information array
73
+ def self.build_source_from_nodes: (Array[Hash[Symbol, untyped]] node_infos) -> String
74
+
75
+ # Generate signature for node
76
+ def self.node_signature: (Prism::Node? node) -> Array[untyped]
77
+
78
+ # Restore custom leading comments from destination
79
+ def self.restore_custom_leading_comments: (String dest_content, String merged_content) -> String
80
+
81
+ # Extract leading comment block from content
82
+ def self.leading_comment_block: (String content) -> String
83
+ end
84
+ end
85
+ end
86
+
@@ -0,0 +1,21 @@
1
+ # TypeProf 0.21.11
2
+
3
+ module Kettle
4
+ module Dev
5
+ # Shared helpers for version detection and bump classification
6
+ module Versioning
7
+ # Detects a unique VERSION constant declared under lib/**/version.rb
8
+ def self.detect_version: (String root) -> String
9
+
10
+ # Classify the bump type from prev -> cur
11
+ def self.classify_bump: (String prev, String cur) -> Symbol
12
+
13
+ # Whether MAJOR is an EPIC version (strictly > 1000)
14
+ def self.epic_major?: (Integer major) -> bool
15
+
16
+ # Abort via ExitAdapter if available; otherwise Kernel.abort
17
+ def self.abort!: (String msg) -> void
18
+ end
19
+ end
20
+ end
21
+
data.tar.gz.sig CHANGED
Binary file