kettle-dev 1.2.3 → 2.0.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 (133) hide show
  1. checksums.yaml +4 -4
  2. checksums.yaml.gz.sig +0 -0
  3. data/CHANGELOG.md +176 -3
  4. data/CITATION.cff +2 -2
  5. data/CONTRIBUTING.md +11 -17
  6. data/README.md +390 -319
  7. data/exe/kettle-dev-setup +12 -63
  8. data/exe/kettle-gh-release +82 -0
  9. data/lib/kettle/dev/gem_spec_reader.rb +2 -2
  10. data/lib/kettle/dev/open_collective_config.rb +12 -0
  11. data/lib/kettle/dev/rakelib/yard.rake +15 -0
  12. data/lib/kettle/dev/tasks/ci_task.rb +4 -4
  13. data/lib/kettle/dev/version.rb +1 -1
  14. data/lib/kettle/dev.rb +4 -12
  15. data/sig/kettle/dev/source_merger.rbs +40 -56
  16. data.tar.gz.sig +0 -0
  17. metadata +15 -144
  18. metadata.gz.sig +0 -0
  19. data/.aiignore.example +0 -19
  20. data/.devcontainer/apt-install/devcontainer-feature.json +0 -9
  21. data/.devcontainer/apt-install/install.sh +0 -11
  22. data/.devcontainer/devcontainer.json +0 -28
  23. data/.env.local.example +0 -31
  24. data/.envrc +0 -47
  25. data/.envrc.example +0 -51
  26. data/.envrc.no-osc.example +0 -51
  27. data/.git-hooks/commit-msg +0 -54
  28. data/.git-hooks/commit-subjects-goalie.txt +0 -8
  29. data/.git-hooks/footer-template.erb.txt +0 -16
  30. data/.git-hooks/prepare-commit-msg +0 -8
  31. data/.github/.codecov.yml.example +0 -14
  32. data/.github/FUNDING.yml +0 -13
  33. data/.github/FUNDING.yml.no-osc.example +0 -13
  34. data/.github/dependabot.yml +0 -13
  35. data/.github/workflows/ancient.yml +0 -83
  36. data/.github/workflows/ancient.yml.example +0 -81
  37. data/.github/workflows/auto-assign.yml +0 -21
  38. data/.github/workflows/codeql-analysis.yml +0 -70
  39. data/.github/workflows/coverage.yml +0 -127
  40. data/.github/workflows/coverage.yml.example +0 -127
  41. data/.github/workflows/current.yml +0 -116
  42. data/.github/workflows/current.yml.example +0 -115
  43. data/.github/workflows/dep-heads.yml +0 -117
  44. data/.github/workflows/dependency-review.yml +0 -20
  45. data/.github/workflows/discord-notifier.yml.example +0 -39
  46. data/.github/workflows/heads.yml +0 -117
  47. data/.github/workflows/heads.yml.example +0 -116
  48. data/.github/workflows/jruby.yml +0 -82
  49. data/.github/workflows/jruby.yml.example +0 -72
  50. data/.github/workflows/legacy.yml +0 -76
  51. data/.github/workflows/license-eye.yml +0 -40
  52. data/.github/workflows/locked_deps.yml +0 -85
  53. data/.github/workflows/opencollective.yml +0 -40
  54. data/.github/workflows/style.yml +0 -67
  55. data/.github/workflows/supported.yml +0 -75
  56. data/.github/workflows/truffle.yml +0 -99
  57. data/.github/workflows/unlocked_deps.yml +0 -84
  58. data/.github/workflows/unsupported.yml +0 -76
  59. data/.gitignore +0 -50
  60. data/.gitlab-ci.yml.example +0 -134
  61. data/.idea/.gitignore +0 -45
  62. data/.junie/guidelines-rbs.md +0 -49
  63. data/.junie/guidelines.md +0 -141
  64. data/.junie/guidelines.md.example +0 -140
  65. data/.licenserc.yaml +0 -7
  66. data/.opencollective.yml +0 -3
  67. data/.opencollective.yml.example +0 -3
  68. data/.qlty/qlty.toml +0 -79
  69. data/.rspec +0 -9
  70. data/.rubocop.yml +0 -13
  71. data/.rubocop_rspec.yml +0 -33
  72. data/.simplecov +0 -16
  73. data/.simplecov.example +0 -11
  74. data/.tool-versions +0 -1
  75. data/.yardignore +0 -13
  76. data/.yardopts +0 -14
  77. data/Appraisal.root.gemfile +0 -10
  78. data/Appraisals +0 -151
  79. data/Appraisals.example +0 -102
  80. data/CHANGELOG.md.example +0 -47
  81. data/CONTRIBUTING.md.example +0 -227
  82. data/FUNDING.md.no-osc.example +0 -63
  83. data/Gemfile +0 -40
  84. data/Gemfile.example +0 -34
  85. data/README.md.example +0 -570
  86. data/README.md.no-osc.example +0 -536
  87. data/Rakefile.example +0 -68
  88. data/gemfiles/modular/coverage.gemfile +0 -6
  89. data/gemfiles/modular/debug.gemfile +0 -13
  90. data/gemfiles/modular/documentation.gemfile +0 -14
  91. data/gemfiles/modular/erb/r2/v3.0.gemfile +0 -1
  92. data/gemfiles/modular/erb/r2.3/default.gemfile +0 -6
  93. data/gemfiles/modular/erb/r2.6/v2.2.gemfile +0 -3
  94. data/gemfiles/modular/erb/r3/v5.0.gemfile +0 -1
  95. data/gemfiles/modular/erb/r3.1/v4.0.gemfile +0 -2
  96. data/gemfiles/modular/erb/vHEAD.gemfile +0 -2
  97. data/gemfiles/modular/injected.gemfile +0 -60
  98. data/gemfiles/modular/mutex_m/r2/v0.3.gemfile +0 -2
  99. data/gemfiles/modular/mutex_m/r2.4/v0.1.gemfile +0 -3
  100. data/gemfiles/modular/mutex_m/r3/v0.3.gemfile +0 -2
  101. data/gemfiles/modular/mutex_m/vHEAD.gemfile +0 -2
  102. data/gemfiles/modular/optional.gemfile +0 -8
  103. data/gemfiles/modular/optional.gemfile.example +0 -5
  104. data/gemfiles/modular/runtime_heads.gemfile +0 -10
  105. data/gemfiles/modular/runtime_heads.gemfile.example +0 -8
  106. data/gemfiles/modular/stringio/r2/v3.0.gemfile +0 -5
  107. data/gemfiles/modular/stringio/r2.4/v0.0.2.gemfile +0 -4
  108. data/gemfiles/modular/stringio/r3/v3.0.gemfile +0 -5
  109. data/gemfiles/modular/stringio/vHEAD.gemfile +0 -2
  110. data/gemfiles/modular/style.gemfile +0 -25
  111. data/gemfiles/modular/style.gemfile.example +0 -25
  112. data/gemfiles/modular/templating.gemfile +0 -3
  113. data/gemfiles/modular/x_std_libs/r2/libs.gemfile +0 -3
  114. data/gemfiles/modular/x_std_libs/r2.3/libs.gemfile +0 -3
  115. data/gemfiles/modular/x_std_libs/r2.4/libs.gemfile +0 -3
  116. data/gemfiles/modular/x_std_libs/r2.6/libs.gemfile +0 -3
  117. data/gemfiles/modular/x_std_libs/r3/libs.gemfile +0 -3
  118. data/gemfiles/modular/x_std_libs/r3.1/libs.gemfile +0 -3
  119. data/gemfiles/modular/x_std_libs/vHEAD.gemfile +0 -3
  120. data/gemfiles/modular/x_std_libs.gemfile +0 -2
  121. data/kettle-dev.gemspec.example +0 -154
  122. data/lib/kettle/dev/modular_gemfiles.rb +0 -119
  123. data/lib/kettle/dev/prism_appraisals.rb +0 -351
  124. data/lib/kettle/dev/prism_gemfile.rb +0 -177
  125. data/lib/kettle/dev/prism_gemspec.rb +0 -284
  126. data/lib/kettle/dev/prism_utils.rb +0 -201
  127. data/lib/kettle/dev/rakelib/install.rake +0 -10
  128. data/lib/kettle/dev/rakelib/template.rake +0 -10
  129. data/lib/kettle/dev/setup_cli.rb +0 -403
  130. data/lib/kettle/dev/source_merger.rb +0 -622
  131. data/lib/kettle/dev/tasks/install_task.rb +0 -553
  132. data/lib/kettle/dev/tasks/template_task.rb +0 -975
  133. data/lib/kettle/dev/template_helpers.rb +0 -685
@@ -1,284 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module Kettle
4
- module Dev
5
- # Prism helpers for gemspec manipulation.
6
- module PrismGemspec
7
- module_function
8
-
9
- # Emit a debug warning for rescued errors when kettle-dev debugging is enabled.
10
- # Controlled by KETTLE_DEV_DEBUG=true (or DEBUG=true as fallback).
11
- # @param error [Exception]
12
- # @param context [String, Symbol, nil] optional label, often __method__
13
- # @return [void]
14
- def debug_error(error, context = nil)
15
- Kettle::Dev.debug_error(error, context)
16
- end
17
-
18
- # Replace scalar or array assignments inside a Gem::Specification.new block.
19
- # `replacements` is a hash mapping symbol field names to string or array values.
20
- # Operates only inside the Gem::Specification block to avoid accidental matches.
21
- def replace_gemspec_fields(content, replacements = {})
22
- return content if replacements.nil? || replacements.empty?
23
-
24
- result = PrismUtils.parse_with_comments(content)
25
- stmts = PrismUtils.extract_statements(result.value.statements)
26
-
27
- gemspec_call = stmts.find do |s|
28
- s.is_a?(Prism::CallNode) && s.block && PrismUtils.extract_const_name(s.receiver) == "Gem::Specification" && s.name == :new
29
- end
30
- return content unless gemspec_call
31
-
32
- call_src = gemspec_call.slice
33
-
34
- # Try to detect block parameter name (e.g., |spec|)
35
- blk_param = nil
36
- begin
37
- if gemspec_call.block && gemspec_call.block.params
38
- # Attempt a few defensive ways to extract a param name
39
- if gemspec_call.block.params.respond_to?(:parameters) && gemspec_call.block.params.parameters.respond_to?(:first)
40
- p = gemspec_call.block.params.parameters.first
41
- blk_param = p.name.to_s if p.respond_to?(:name)
42
- elsif gemspec_call.block.params.respond_to?(:first)
43
- p = gemspec_call.block.params.first
44
- blk_param = p.name.to_s if p && p.respond_to?(:name)
45
- end
46
- end
47
- rescue StandardError
48
- blk_param = nil
49
- end
50
-
51
- # Fallback to crude parse of the call_src header
52
- unless blk_param && !blk_param.to_s.empty?
53
- hdr_m = call_src.match(/do\b[^\n]*\|([^|]+)\|/m)
54
- blk_param = (hdr_m && hdr_m[1]) ? hdr_m[1].strip.split(/,\s*/).first : "spec"
55
- end
56
- blk_param = "spec" if blk_param.nil? || blk_param.empty?
57
-
58
- # Extract AST-level statements inside the block body when available
59
- body_node = gemspec_call.block&.body
60
- body_src = ""
61
- begin
62
- # Try to extract the textual body from call_src using the do|...| ... end capture
63
- body_src = if (m = call_src.match(/do\b[^\n]*\|[^|]*\|\s*(.*)end\s*\z/m))
64
- m[1]
65
- else
66
- # Last resort: attempt to take slice of body node
67
- body_node ? body_node.slice : ""
68
- end
69
- rescue StandardError
70
- body_src = body_node ? body_node.slice : ""
71
- end
72
-
73
- new_body = body_src.dup
74
-
75
- # Helper: build literal text for replacement values
76
- build_literal = lambda do |v|
77
- if v.is_a?(Array)
78
- arr = v.compact.map(&:to_s).map { |e| '"' + e.gsub('"', '\\"') + '"' }
79
- "[" + arr.join(", ") + "]"
80
- else
81
- '"' + v.to_s.gsub('"', '\\"') + '"'
82
- end
83
- end
84
-
85
- # Extract existing statement nodes for more precise matching
86
- stmt_nodes = PrismUtils.extract_statements(body_node)
87
-
88
- replacements.each do |field_sym, value|
89
- # Skip special internal keys that are not actual gemspec fields
90
- next if field_sym == :_remove_self_dependency
91
-
92
- field = field_sym.to_s
93
-
94
- # Find an existing assignment node for this field: look for call nodes where
95
- # receiver slice matches the block param and method name matches assignment
96
- found_node = stmt_nodes.find do |n|
97
- next false unless n.is_a?(Prism::CallNode)
98
- begin
99
- recv = n.receiver
100
- recv_name = recv ? recv.slice.strip : nil
101
- # match receiver variable name or literal slice
102
- recv_name && recv_name.end_with?(blk_param) && n.name.to_s.start_with?(field)
103
- rescue StandardError
104
- false
105
- end
106
- end
107
-
108
- if found_node
109
- # Do not replace if the existing RHS is non-literal (e.g., computed expression)
110
- existing_arg = found_node.arguments&.arguments&.first
111
- existing_literal = begin
112
- PrismUtils.extract_literal_value(existing_arg)
113
- rescue
114
- nil
115
- end
116
- if existing_literal.nil? && !value.nil?
117
- # Skip replacing a non-literal RHS to avoid altering computed expressions.
118
- debug_error(StandardError.new("Skipping replacement for #{field} because existing RHS is non-literal"), __method__)
119
- else
120
- # Replace the found node's slice in the body text with the updated assignment
121
- indent = begin
122
- found_node.slice.lines.first.match(/^(\s*)/)[1]
123
- rescue
124
- " "
125
- end
126
- rhs = build_literal.call(value)
127
- replacement = "#{indent}#{blk_param}.#{field} = #{rhs}"
128
- new_body = new_body.sub(found_node.slice, replacement)
129
- end
130
- else
131
- # No existing assignment; insert after spec.version if present, else append
132
- version_node = stmt_nodes.find do |n|
133
- n.is_a?(Prism::CallNode) && n.name.to_s.start_with?("version", "version=") && n.receiver && n.receiver.slice.strip.end_with?(blk_param)
134
- end
135
-
136
- insert_line = " #{blk_param}.#{field} = #{build_literal.call(value)}\n"
137
- new_body = if version_node
138
- # Insert after the version node slice
139
- new_body.sub(version_node.slice, version_node.slice + "\n" + insert_line)
140
- elsif new_body.rstrip.end_with?('\n')
141
- # Append before the final newline if present, else just append
142
- new_body.rstrip + "\n" + insert_line
143
- else
144
- new_body.rstrip + "\n" + insert_line
145
- end
146
- end
147
- end
148
-
149
- # Handle removal of self-dependency if requested via :_remove_self_dependency
150
- if replacements[:_remove_self_dependency]
151
- name_to_remove = replacements[:_remove_self_dependency].to_s
152
- # Find dependency call nodes to remove (add_dependency/add_development_dependency)
153
- dep_nodes = stmt_nodes.select do |n|
154
- next false unless n.is_a?(Prism::CallNode)
155
- recv = begin
156
- n.receiver
157
- rescue
158
- nil
159
- end
160
- next false unless recv && recv.slice.strip.end_with?(blk_param)
161
- [:add_dependency, :add_development_dependency].include?(n.name)
162
- end
163
- dep_nodes.each do |dn|
164
- # Check first argument literal
165
- first_arg = dn.arguments&.arguments&.first
166
- arg_val = begin
167
- PrismUtils.extract_literal_value(first_arg)
168
- rescue
169
- nil
170
- end
171
- if arg_val && arg_val.to_s == name_to_remove
172
- # Remove this node's slice from new_body
173
- new_body = new_body.sub(dn.slice, "")
174
- end
175
- end
176
- end
177
-
178
- # Reassemble call source by replacing the captured body portion
179
- new_call_src = call_src.sub(body_src, new_body)
180
- content.sub(call_src, new_call_src)
181
- rescue StandardError => e
182
- debug_error(e, __method__)
183
- content
184
- end
185
-
186
- # Remove spec.add_dependency / add_development_dependency calls that name the given gem
187
- # Works by locating the Gem::Specification block and filtering out matching call lines.
188
- def remove_spec_dependency(content, gem_name)
189
- return content if gem_name.to_s.strip.empty?
190
- replace_gemspec_fields(content, _remove_self_dependency: gem_name)
191
- end
192
-
193
- # Ensure development dependency lines in a gemspec match the desired lines.
194
- # `desired` is a hash mapping gem_name => desired_line (string, without leading indentation).
195
- # Returns the modified gemspec content (or original on error).
196
- def ensure_development_dependencies(content, desired)
197
- return content if desired.nil? || desired.empty?
198
- result = PrismUtils.parse_with_comments(content)
199
- stmts = PrismUtils.extract_statements(result.value.statements)
200
- gemspec_call = stmts.find do |s|
201
- s.is_a?(Prism::CallNode) && s.block && PrismUtils.extract_const_name(s.receiver) == "Gem::Specification" && s.name == :new
202
- end
203
-
204
- # If we couldn't locate the Gem::Specification.new block (e.g., empty or
205
- # truncated gemspec), fall back to appending the desired development
206
- # dependency lines to the end of the file so callers still get the
207
- # expected dependency declarations.
208
- unless gemspec_call
209
- begin
210
- out = content.dup
211
- out << "\n" unless out.end_with?("\n") || out.empty?
212
- desired.each do |_gem, line|
213
- out << line.strip + "\n"
214
- end
215
- return out
216
- rescue StandardError => e
217
- debug_error(e, __method__)
218
- return content
219
- end
220
- end
221
-
222
- call_src = gemspec_call.slice
223
- body_node = gemspec_call.block&.body
224
- body_src = begin
225
- if (m = call_src.match(/do\b[^\n]*\|[^|]*\|\s*(.*)end\s*\z/m))
226
- m[1]
227
- else
228
- body_node ? body_node.slice : ""
229
- end
230
- rescue StandardError
231
- body_node ? body_node.slice : ""
232
- end
233
-
234
- new_body = body_src.dup
235
- stmt_nodes = PrismUtils.extract_statements(body_node)
236
-
237
- # Find version node to choose insertion point
238
- version_node = stmt_nodes.find do |n|
239
- n.is_a?(Prism::CallNode) && n.name.to_s.start_with?("version") && n.receiver && n.receiver.slice.strip.end_with?("spec")
240
- end
241
-
242
- desired.each do |gem_name, desired_line|
243
- # Skip commented occurrences - we only act on actual AST nodes
244
- found = stmt_nodes.find do |n|
245
- next false unless n.is_a?(Prism::CallNode)
246
- next false unless [:add_development_dependency, :add_dependency].include?(n.name)
247
- first_arg = n.arguments&.arguments&.first
248
- val = begin
249
- PrismUtils.extract_literal_value(first_arg)
250
- rescue
251
- nil
252
- end
253
- val && val.to_s == gem_name
254
- end
255
-
256
- if found
257
- # Replace existing node slice with desired_line, preserving indent
258
- indent = begin
259
- found.slice.lines.first.match(/^(\s*)/)[1]
260
- rescue
261
- " "
262
- end
263
- replacement = indent + desired_line.strip + "\n"
264
- new_body = new_body.sub(found.slice, replacement)
265
- else
266
- # Insert after version_node if present, else append before end
267
- insert_line = " " + desired_line.strip + "\n"
268
- new_body = if version_node
269
- new_body.sub(version_node.slice, version_node.slice + "\n" + insert_line)
270
- else
271
- new_body.rstrip + "\n" + insert_line
272
- end
273
- end
274
- end
275
-
276
- new_call_src = call_src.sub(body_src, new_body)
277
- content.sub(call_src, new_call_src)
278
- rescue StandardError => e
279
- debug_error(e, __method__)
280
- content
281
- end
282
- end
283
- end
284
- end
@@ -1,201 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require "prism"
4
-
5
- module Kettle
6
- module Dev
7
- # Shared utilities for working with Prism AST nodes.
8
- # Provides parsing, node inspection, and source generation helpers
9
- # used by both PrismMerger and AppraisalsAstMerger.
10
- #
11
- # Uses Prism's native methods for source generation (via .slice) to preserve
12
- # original formatting and comments. For normalized output (e.g., adding parentheses),
13
- # use normalize_call_node instead.
14
- module PrismUtils
15
- module_function
16
-
17
- # Parse Ruby source code and return Prism parse result with comments
18
- # @param source [String] Ruby source code
19
- # @return [Prism::ParseResult] Parse result containing AST and comments
20
- def parse_with_comments(source)
21
- Prism.parse(source)
22
- end
23
-
24
- # Extract statements from a Prism body node
25
- # @param body_node [Prism::Node, nil] Body node (typically StatementsNode)
26
- # @return [Array<Prism::Node>] Array of statement nodes
27
- def extract_statements(body_node)
28
- return [] unless body_node
29
-
30
- if body_node.is_a?(Prism::StatementsNode)
31
- body_node.body.compact
32
- else
33
- [body_node].compact
34
- end
35
- end
36
-
37
- # Generate a unique key for a statement node to identify equivalent statements
38
- # Used for merge/append operations to detect duplicates
39
- # @param node [Prism::Node] Statement node
40
- # @param tracked_methods [Array<Symbol>] Methods to track (default: gem, source, eval_gemfile, git_source)
41
- # @return [Array, nil] Key array like [:gem, "foo"] or nil if not trackable
42
- def statement_key(node, tracked_methods: %i[gem source eval_gemfile git_source])
43
- return unless node.is_a?(Prism::CallNode)
44
- return unless tracked_methods.include?(node.name)
45
-
46
- first_arg = node.arguments&.arguments&.first
47
- arg_value = extract_literal_value(first_arg)
48
-
49
- [node.name, arg_value] if arg_value
50
- end
51
-
52
- # Extract literal value from string or symbol nodes
53
- # @param node [Prism::Node, nil] Node to extract from
54
- # @return [String, Symbol, nil] Literal value or nil
55
- def extract_literal_value(node)
56
- return unless node
57
- case node
58
- when Prism::StringNode then node.unescaped
59
- when Prism::SymbolNode then node.unescaped
60
- else
61
- # Attempt to handle array literals
62
- if node.respond_to?(:elements) && node.elements
63
- arr = node.elements.map do |el|
64
- case el
65
- when Prism::StringNode then el.unescaped
66
- when Prism::SymbolNode then el.unescaped
67
- end
68
- end
69
- return arr if arr.all?
70
- end
71
- nil
72
- end
73
- end
74
-
75
- # Extract qualified constant name from a constant node
76
- # @param node [Prism::Node, nil] Constant node
77
- # @return [String, nil] Qualified name like "Gem::Specification" or nil
78
- def extract_const_name(node)
79
- case node
80
- when Prism::ConstantReadNode
81
- node.name.to_s
82
- when Prism::ConstantPathNode
83
- parent = extract_const_name(node.parent)
84
- child = node.name || node.child&.name
85
- (parent && child) ? "#{parent}::#{child}" : child.to_s
86
- end
87
- end
88
-
89
- # Find leading comments for a statement node
90
- # Leading comments are those that appear after the previous statement
91
- # and before the current statement
92
- # @param parse_result [Prism::ParseResult] Parse result with comments
93
- # @param current_stmt [Prism::Node] Current statement node
94
- # @param prev_stmt [Prism::Node, nil] Previous statement node
95
- # @param body_node [Prism::Node] Body containing the statements
96
- # @return [Array<Prism::Comment>] Leading comments
97
- def find_leading_comments(parse_result, current_stmt, prev_stmt, body_node)
98
- start_line = prev_stmt ? prev_stmt.location.end_line : body_node.location.start_line
99
- end_line = current_stmt.location.start_line
100
-
101
- parse_result.comments.select do |comment|
102
- comment.location.start_line > start_line &&
103
- comment.location.start_line < end_line
104
- end
105
- end
106
-
107
- # Find inline comments for a statement node
108
- # Inline comments are those that appear on the same line as the statement's end
109
- # @param parse_result [Prism::ParseResult] Parse result with comments
110
- # @param stmt [Prism::Node] Statement node
111
- # @return [Array<Prism::Comment>] Inline comments
112
- def inline_comments_for_node(parse_result, stmt)
113
- parse_result.comments.select do |comment|
114
- comment.location.start_line == stmt.location.end_line &&
115
- comment.location.start_offset > stmt.location.end_offset
116
- end
117
- end
118
-
119
- # Convert a Prism AST node to Ruby source code
120
- # Uses Prism's native slice method which preserves the original source exactly.
121
- # This is preferable to Unparser for Prism nodes as it maintains original formatting
122
- # and comments without requiring transformation.
123
- # @param node [Prism::Node] AST node
124
- # @return [String] Ruby source code
125
- def node_to_source(node)
126
- return "" unless node
127
- # Prism nodes have a slice method that returns the original source
128
- node.slice
129
- end
130
-
131
- # Normalize a call node to use parentheses format
132
- # Converts `gem "foo"` to `gem("foo")` style
133
- # @param node [Prism::CallNode] Call node
134
- # @return [String] Normalized source code
135
- def normalize_call_node(node)
136
- return node.slice.strip unless node.is_a?(Prism::CallNode)
137
-
138
- method_name = node.name
139
- args = node.arguments&.arguments || []
140
-
141
- if args.empty?
142
- "#{method_name}()"
143
- else
144
- arg_strings = args.map { |arg| normalize_argument(arg) }
145
- "#{method_name}(#{arg_strings.join(", ")})"
146
- end
147
- end
148
-
149
- # Normalize an argument node to canonical format
150
- # @param arg [Prism::Node] Argument node
151
- # @return [String] Normalized argument source
152
- def normalize_argument(arg)
153
- case arg
154
- when Prism::StringNode
155
- "\"#{arg.unescaped}\""
156
- when Prism::SymbolNode
157
- ":#{arg.unescaped}"
158
- when Prism::KeywordHashNode
159
- # Handle hash arguments like {key: value}
160
- pairs = arg.elements.map do |assoc|
161
- key = case assoc.key
162
- when Prism::SymbolNode then "#{assoc.key.unescaped}:"
163
- when Prism::StringNode then "\"#{assoc.key.unescaped}\" =>"
164
- else "#{assoc.key.slice} =>"
165
- end
166
- value = normalize_argument(assoc.value)
167
- "#{key} #{value}"
168
- end.join(", ")
169
- pairs
170
- when Prism::HashNode
171
- # Handle explicit hash syntax
172
- pairs = arg.elements.map do |assoc|
173
- key_part = normalize_argument(assoc.key)
174
- value_part = normalize_argument(assoc.value)
175
- "#{key_part} => #{value_part}"
176
- end.join(", ")
177
- "{#{pairs}}"
178
- else
179
- # For other types (numbers, arrays, etc.), use the original source
180
- arg.slice.strip
181
- end
182
- end
183
-
184
- # Check if a node is a specific method call
185
- # @param node [Prism::Node] Node to check
186
- # @param method_name [Symbol] Method name to check for
187
- # @return [Boolean] True if node is a call to the specified method
188
- def call_to?(node, method_name)
189
- node.is_a?(Prism::CallNode) && node.name == method_name
190
- end
191
-
192
- # Check if a node is a block call to a specific method
193
- # @param node [Prism::Node] Node to check
194
- # @param method_name [Symbol] Method name to check for
195
- # @return [Boolean] True if node is a block call to the specified method
196
- def block_call_to?(node, method_name)
197
- node.is_a?(Prism::CallNode) && node.name == method_name && !node.block.nil?
198
- end
199
- end
200
- end
201
- end
@@ -1,10 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- namespace :kettle do
4
- namespace :dev do
5
- desc "Install kettle-dev GitHub automation and setup hints into the current project"
6
- task :install do
7
- Kettle::Dev::Tasks::InstallTask.run
8
- end
9
- end
10
- end
@@ -1,10 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- namespace :kettle do
4
- namespace :dev do
5
- desc "Template kettle-dev files into the current project"
6
- task :template do
7
- Kettle::Dev::Tasks::TemplateTask.run
8
- end
9
- end
10
- end