kimera 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 (171) hide show
  1. checksums.yaml +7 -0
  2. data/.kimera.yml.example +59 -0
  3. data/CHANGELOG.md +36 -0
  4. data/LICENSE.txt +21 -0
  5. data/README.md +528 -0
  6. data/exe/kimera +22 -0
  7. data/lib/kimera/audit/operator_audit.rb +71 -0
  8. data/lib/kimera/cli/argv.rb +15 -0
  9. data/lib/kimera/cli/baseline.rb +87 -0
  10. data/lib/kimera/cli/changed.rb +27 -0
  11. data/lib/kimera/cli/ci.rb +27 -0
  12. data/lib/kimera/cli/completion.rb +37 -0
  13. data/lib/kimera/cli/doctor.rb +115 -0
  14. data/lib/kimera/cli/flag.rb +55 -0
  15. data/lib/kimera/cli/help_text.rb +45 -0
  16. data/lib/kimera/cli/init.rb +105 -0
  17. data/lib/kimera/cli/mutant.rb +82 -0
  18. data/lib/kimera/cli/report_file.rb +20 -0
  19. data/lib/kimera/cli/run/arguments.rb +58 -0
  20. data/lib/kimera/cli/run/cycle.rb +78 -0
  21. data/lib/kimera/cli/run/digest.rb +49 -0
  22. data/lib/kimera/cli/run/emission.rb +45 -0
  23. data/lib/kimera/cli/run/gate.rb +43 -0
  24. data/lib/kimera/cli/run/options.rb +85 -0
  25. data/lib/kimera/cli/run/pass.rb +100 -0
  26. data/lib/kimera/cli/run/sources.rb +56 -0
  27. data/lib/kimera/cli/run.rb +52 -0
  28. data/lib/kimera/cli/skill.rb +23 -0
  29. data/lib/kimera/cli/suggestion.rb +36 -0
  30. data/lib/kimera/cli/survivors/panel.rb +81 -0
  31. data/lib/kimera/cli/survivors.rb +89 -0
  32. data/lib/kimera/cli/synthesize.rb +66 -0
  33. data/lib/kimera/cli/workflows.rb +8 -0
  34. data/lib/kimera/cli.rb +132 -0
  35. data/lib/kimera/error.rb +3 -0
  36. data/lib/kimera/execution/baseline_failure.rb +47 -0
  37. data/lib/kimera/execution/baseline_pass.rb +123 -0
  38. data/lib/kimera/execution/boot.rb +50 -0
  39. data/lib/kimera/execution/callback.rb +33 -0
  40. data/lib/kimera/execution/child_process.rb +48 -0
  41. data/lib/kimera/execution/composite_isolation.rb +24 -0
  42. data/lib/kimera/execution/harness.rb +150 -0
  43. data/lib/kimera/execution/isolated.rb +64 -0
  44. data/lib/kimera/execution/isolated_child.rb +15 -0
  45. data/lib/kimera/execution/isolated_child_command.rb +44 -0
  46. data/lib/kimera/execution/isolated_child_minitest.rb +34 -0
  47. data/lib/kimera/execution/isolated_plan.rb +54 -0
  48. data/lib/kimera/execution/isolated_scheduling.rb +54 -0
  49. data/lib/kimera/execution/isolated_test_selection.rb +22 -0
  50. data/lib/kimera/execution/isolated_verdict.rb +72 -0
  51. data/lib/kimera/execution/isolated_watchdog.rb +59 -0
  52. data/lib/kimera/execution/isolation.rb +17 -0
  53. data/lib/kimera/execution/null_progress.rb +17 -0
  54. data/lib/kimera/execution/overlay_guard_modules.rb +95 -0
  55. data/lib/kimera/execution/overlay_guards.rb +56 -0
  56. data/lib/kimera/execution/parallel_test_databases.rb +64 -0
  57. data/lib/kimera/execution/pool_driver.rb +101 -0
  58. data/lib/kimera/execution/priority.rb +23 -0
  59. data/lib/kimera/execution/reload.rb +104 -0
  60. data/lib/kimera/execution/rig.rb +41 -0
  61. data/lib/kimera/execution/schedule.rb +105 -0
  62. data/lib/kimera/execution/schemata.rb +88 -0
  63. data/lib/kimera/execution/shift/attempt.rb +34 -0
  64. data/lib/kimera/execution/shift/coverage_channel.rb +43 -0
  65. data/lib/kimera/execution/shift/killer_memory.rb +20 -0
  66. data/lib/kimera/execution/shift/leak_guard.rb +34 -0
  67. data/lib/kimera/execution/shift.rb +137 -0
  68. data/lib/kimera/execution/sweep.rb +30 -0
  69. data/lib/kimera/execution/transaction_isolation.rb +32 -0
  70. data/lib/kimera/execution/trial.rb +16 -0
  71. data/lib/kimera/execution/verdicts.rb +78 -0
  72. data/lib/kimera/execution/worker_pool/fleet.rb +104 -0
  73. data/lib/kimera/execution/worker_pool/stillborn_guard.rb +40 -0
  74. data/lib/kimera/execution/worker_pool/worker.rb +32 -0
  75. data/lib/kimera/execution/worker_pool.rb +101 -0
  76. data/lib/kimera/frameworks/adapter.rb +82 -0
  77. data/lib/kimera/frameworks/minitest_adapter.rb +102 -0
  78. data/lib/kimera/frameworks/rspec_adapter.rb +123 -0
  79. data/lib/kimera/frameworks/rspec_group_index.rb +45 -0
  80. data/lib/kimera/incremental/git_diff.rb +98 -0
  81. data/lib/kimera/incremental/selection.rb +27 -0
  82. data/lib/kimera/incremental/session.rb +109 -0
  83. data/lib/kimera/memoization.rb +104 -0
  84. data/lib/kimera/operators/argument_drop.rb +19 -0
  85. data/lib/kimera/operators/arithmetic.rb +12 -0
  86. data/lib/kimera/operators/audit.rb +3 -0
  87. data/lib/kimera/operators/base.rb +40 -0
  88. data/lib/kimera/operators/binary_swap.rb +23 -0
  89. data/lib/kimera/operators/boolean.rb +26 -0
  90. data/lib/kimera/operators/chain_link_deletion.rb +25 -0
  91. data/lib/kimera/operators/collection_literal.rb +18 -0
  92. data/lib/kimera/operators/comparison.rb +13 -0
  93. data/lib/kimera/operators/conditional.rb +36 -0
  94. data/lib/kimera/operators/default_argument.rb +17 -0
  95. data/lib/kimera/operators/element_drop.rb +31 -0
  96. data/lib/kimera/operators/index_fetch.rb +15 -0
  97. data/lib/kimera/operators/kernel_coercion.rb +17 -0
  98. data/lib/kimera/operators/method_unwrap.rb +20 -0
  99. data/lib/kimera/operators/negation.rb +15 -0
  100. data/lib/kimera/operators/node_swap.rb +20 -0
  101. data/lib/kimera/operators/numeric_literal.rb +39 -0
  102. data/lib/kimera/operators/op_assign.rb +18 -0
  103. data/lib/kimera/operators/plugins.rb +27 -0
  104. data/lib/kimera/operators/rails_association.rb +24 -0
  105. data/lib/kimera/operators/rails_callback.rb +49 -0
  106. data/lib/kimera/operators/rails_declaration.rb +16 -0
  107. data/lib/kimera/operators/rails_permit.rb +34 -0
  108. data/lib/kimera/operators/rails_validation.rb +17 -0
  109. data/lib/kimera/operators/range.rb +15 -0
  110. data/lib/kimera/operators/regexp_literal.rb +16 -0
  111. data/lib/kimera/operators/respond_to_guard.rb +24 -0
  112. data/lib/kimera/operators/return_value.rb +36 -0
  113. data/lib/kimera/operators/safe_navigation.rb +15 -0
  114. data/lib/kimera/operators/selector_swap.rb +24 -0
  115. data/lib/kimera/operators/statement_deletion.rb +26 -0
  116. data/lib/kimera/operators/string_literal.rb +22 -0
  117. data/lib/kimera/operators/symbol_literal.rb +26 -0
  118. data/lib/kimera/operators/vocabulary.rb +82 -0
  119. data/lib/kimera/operators.rb +141 -0
  120. data/lib/kimera/plugins.rb +3 -0
  121. data/lib/kimera/registry/builder.rb +59 -0
  122. data/lib/kimera/registry/mutation_point.rb +146 -0
  123. data/lib/kimera/registry/numbering.rb +15 -0
  124. data/lib/kimera/registry/registry.rb +120 -0
  125. data/lib/kimera/registry/source_file.rb +71 -0
  126. data/lib/kimera/registry/tally.rb +11 -0
  127. data/lib/kimera/registry/walking.rb +55 -0
  128. data/lib/kimera/report/actions.rb +38 -0
  129. data/lib/kimera/report/coloring.rb +25 -0
  130. data/lib/kimera/report/formats.rb +78 -0
  131. data/lib/kimera/report/live.rb +56 -0
  132. data/lib/kimera/report/log.rb +46 -0
  133. data/lib/kimera/report/progress.rb +55 -0
  134. data/lib/kimera/report/screen.rb +44 -0
  135. data/lib/kimera/report/sections.rb +50 -0
  136. data/lib/kimera/report/tally.rb +84 -0
  137. data/lib/kimera/report/text.rb +104 -0
  138. data/lib/kimera/results/result.rb +72 -0
  139. data/lib/kimera/results/run_report.rb +114 -0
  140. data/lib/kimera/rewrite/ast_walk.rb +20 -0
  141. data/lib/kimera/rewrite/directive.rb +61 -0
  142. data/lib/kimera/rewrite/directive_extra_handlers.rb +52 -0
  143. data/lib/kimera/rewrite/directive_handlers.rb +138 -0
  144. data/lib/kimera/rewrite/directive_kwarg_handlers.rb +44 -0
  145. data/lib/kimera/rewrite/numbered_params.rb +50 -0
  146. data/lib/kimera/runtime.rb +51 -0
  147. data/lib/kimera/scope/config.rb +90 -0
  148. data/lib/kimera/scope/file_set.rb +51 -0
  149. data/lib/kimera/scope/ignore_list.rb +55 -0
  150. data/lib/kimera/self_protection.rb +56 -0
  151. data/lib/kimera/support/duration.rb +13 -0
  152. data/lib/kimera/support/errors.rb +8 -0
  153. data/lib/kimera/support/operator_protocol.rb +10 -0
  154. data/lib/kimera/support/syntax.rb +68 -0
  155. data/lib/kimera/support/syntax_types.rb +20 -0
  156. data/lib/kimera/support/unparse.rb +15 -0
  157. data/lib/kimera/support/version.rb +5 -0
  158. data/lib/kimera/support/warnings.rb +15 -0
  159. data/lib/kimera/synthesis/file_weave.rb +78 -0
  160. data/lib/kimera/synthesis/guard_weaver.rb +77 -0
  161. data/lib/kimera/synthesis/guardrail_dispatch.rb +68 -0
  162. data/lib/kimera/synthesis/guardrail_interpolation.rb +59 -0
  163. data/lib/kimera/synthesis/guardrail_value_objects.rb +46 -0
  164. data/lib/kimera/synthesis/overlay.rb +52 -0
  165. data/lib/kimera/synthesis/overlay_splice.rb +101 -0
  166. data/lib/kimera/synthesis/project.rb +58 -0
  167. data/lib/kimera/synthesis/source_map.rb +42 -0
  168. data/lib/kimera/version.rb +3 -0
  169. data/lib/kimera.rb +13 -0
  170. data/skills/kimera/SKILL.md +118 -0
  171. metadata +259 -0
@@ -0,0 +1,12 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "binary_swap"
4
+
5
+ class Kimera::Operators::Arithmetic < Kimera::Operators::BinarySwap
6
+ MUTATIONS = { :+ => [:-], :- => [:+], :* => [:/] }.merge({ :/ => [:*], :% => [:*], :** => [:*] }).freeze
7
+ DIRECTIVE = "op_swap"
8
+
9
+ class << self
10
+ def key = "arithmetic"
11
+ end
12
+ end
@@ -0,0 +1,3 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "../audit/operator_audit"
@@ -0,0 +1,40 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "../support/syntax_types"
4
+ require_relative "vocabulary"
5
+
6
+ class Kimera::Operators::Base
7
+ SYNTAX = ->(node) { Kimera::Syntax.view(node) }
8
+ include Kimera::SyntaxTypes
9
+ include Kimera::Operators::Vocabulary
10
+
11
+ class << self
12
+ def key = raise(NotImplementedError, "#{self} must define .key")
13
+
14
+ def statement? = false
15
+
16
+ def body? = false
17
+ end
18
+
19
+ def key
20
+ self.class.key
21
+ end
22
+
23
+ def statement?
24
+ self.class.statement?
25
+ end
26
+
27
+ def body?
28
+ self.class.body?
29
+ end
30
+
31
+ def variants(node, position:)
32
+ raise(NotImplementedError, "#{self} must define #variants")
33
+ end
34
+
35
+ def pairs(node, position:)
36
+ Array(variants(node, position: position)).map { |variant| [key, variant] }
37
+ end
38
+
39
+ def syntax(node) = self.class::SYNTAX.call(node)
40
+ end
@@ -0,0 +1,23 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "base"
4
+
5
+ class Kimera::Operators::BinarySwap < Kimera::Operators::Base
6
+ def variants(node, **)
7
+ return unless swappable?(node)
8
+ name = node.name
9
+ self.class::MUTATIONS[name].map { |to| swap(name, to) }
10
+ end
11
+
12
+ private
13
+
14
+ def swappable?(node)
15
+ return false unless node.is_a?(CallNode)
16
+ return false unless arguments(node).length == 1
17
+ self.class::MUTATIONS.key?(node.name)
18
+ end
19
+
20
+ def swap(name, to)
21
+ Kimera::Operators::Variant.new(label: "#{name} => #{to}", directive: directive(self.class::DIRECTIVE, to: to.to_s))
22
+ end
23
+ end
@@ -0,0 +1,26 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "node_swap"
4
+
5
+ module Kimera
6
+ module Operators
7
+ BOOLEAN_OPERATORS = {
8
+ BooleanConnective: [
9
+ "boolean_connective",
10
+ {
11
+ AndNode => { label: "&& => ||", to: "or" },
12
+ OrNode => { label: "|| => &&", to: "and" }
13
+ }
14
+ ],
15
+ BooleanLiteral: [
16
+ "boolean_literal",
17
+ {
18
+ TrueNode => { label: "true => false", to: "false" },
19
+ FalseNode => { label: "false => true", to: "true" }
20
+ }
21
+ ]
22
+ }.freeze
23
+
24
+ BOOLEAN_OPERATORS.each { |name, (key, swaps)| const_set(name, NodeSwap.define(key, swaps)) }
25
+ end
26
+ end
@@ -0,0 +1,25 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "prism"
4
+ require_relative "base"
5
+
6
+ class Kimera::Operators::ChainLinkDeletion < Kimera::Operators::Base
7
+ class << self
8
+ def key = "chain_link_deletion"
9
+ end
10
+
11
+ def variants(node, **)
12
+ return unless deletable?(node)
13
+ solo("drop chain link `.#{node.receiver.name}`", "drop_receiver_link")
14
+ end
15
+
16
+ private
17
+
18
+ def deletable?(node)
19
+ return false unless node.is_a?(CallNode)
20
+ link = node.receiver
21
+ return false unless link.is_a?(CallNode) && link.receiver
22
+ return false if link.block.is_a?(BlockNode)
23
+ identifier?(link)
24
+ end
25
+ end
@@ -0,0 +1,18 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "base"
4
+
5
+ class Kimera::Operators::CollectionLiteral < Kimera::Operators::Base
6
+ EMPTIED = { ArrayNode => "array => []", HashNode => "hash => {}" }.freeze
7
+
8
+ class << self
9
+ def key = "collection_literal"
10
+ end
11
+
12
+ def variants(node, **)
13
+ case node
14
+ when ArrayNode, HashNode
15
+ solo(EMPTIED[node.class], "empty_collection") unless node.elements.empty?
16
+ end
17
+ end
18
+ end
@@ -0,0 +1,13 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "binary_swap"
4
+
5
+ class Kimera::Operators::Comparison < Kimera::Operators::BinarySwap
6
+ MUTATIONS = { :> => %i[>= <], :>= => %i[> <=], :< => %i[<= >] }
7
+ .merge({ :<= => %i[< >=], :== => [:!=], :!= => [:==] }).freeze
8
+ DIRECTIVE = "comparison"
9
+
10
+ class << self
11
+ def key = "comparison"
12
+ end
13
+ end
@@ -0,0 +1,36 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "base"
4
+
5
+ class Kimera::Operators::Conditional < Kimera::Operators::Base
6
+ class << self
7
+ def key = "conditional"
8
+ end
9
+
10
+ def variants(node, **)
11
+ return unless conditional?(node)
12
+ return if elsif?(node)
13
+ return if memoized?(node)
14
+ %w[true false].map { |to| flip(to) }
15
+ end
16
+
17
+ private
18
+
19
+ def flip(to)
20
+ Kimera::Operators::Variant.new(label: "condition => #{to}", directive: directive("condition", to: to))
21
+ end
22
+
23
+ def conditional?(node)
24
+ node.is_a?(IfNode) || node.is_a?(UnlessNode)
25
+ end
26
+
27
+ def elsif?(node)
28
+ node.is_a?(IfNode) && node.if_keyword_loc&.slice == "elsif"
29
+ end
30
+
31
+ def memoized?(node)
32
+ predicate = node.predicate
33
+ predicate.is_a?(DefinedNode) &&
34
+ predicate.value.is_a?(InstanceVariableReadNode)
35
+ end
36
+ end
@@ -0,0 +1,17 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "base"
4
+
5
+ class Kimera::Operators::DefaultArgument < Kimera::Operators::Base
6
+ class << self
7
+ def key = "default_argument"
8
+ end
9
+
10
+ def variants(node, **)
11
+ case node
12
+ when OptionalParameterNode, OptionalKeywordParameterNode
13
+ name = node.name
14
+ solo("default => required (#{name})", "required_argument", name: name.to_s)
15
+ end
16
+ end
17
+ end
@@ -0,0 +1,31 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "base"
4
+
5
+ class Kimera::Operators::ElementDrop < Kimera::Operators::Base
6
+ SPLATS = { ArrayNode => SplatNode, HashNode => AssocSplatNode }.freeze
7
+
8
+ class << self
9
+ def key = "element_drop"
10
+ end
11
+
12
+ def variants(node, **)
13
+ parts = collection(node)
14
+ drop(*parts) if parts
15
+ end
16
+
17
+ private
18
+
19
+ def collection(node)
20
+ syntax(node).collection(SPLATS)
21
+ end
22
+
23
+ def drop(elements, splat)
24
+ return unless droppable?(elements, splat)
25
+ drops(elements, "drop_element", label: "drop")
26
+ end
27
+
28
+ def droppable?(elements, splat)
29
+ elements.length > 1 && elements.none?(splat)
30
+ end
31
+ end
@@ -0,0 +1,15 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "base"
4
+
5
+ class Kimera::Operators::IndexFetch < Kimera::Operators::Base
6
+ class << self
7
+ def key = "index_fetch"
8
+ end
9
+
10
+ def variants(node, **)
11
+ return unless call(node).chained?(:[])
12
+ return unless unary?(node)
13
+ solo("[] => fetch", "index_to_fetch")
14
+ end
15
+ end
@@ -0,0 +1,17 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "base"
4
+
5
+ class Kimera::Operators::KernelCoercion < Kimera::Operators::Base
6
+ COERCIONS = %i[Array String Integer Float].freeze
7
+
8
+ class << self
9
+ def key = "kernel_coercion"
10
+ end
11
+
12
+ def variants(node, **)
13
+ return unless call(node).bare?(COERCIONS)
14
+ return unless unary?(node)
15
+ solo("delete #{node.name}()", "unwrap_argument")
16
+ end
17
+ end
@@ -0,0 +1,20 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "base"
4
+
5
+ class Kimera::Operators::MethodUnwrap < Kimera::Operators::Base
6
+ UNWRAPPABLE = %i[
7
+ dup clone freeze to_s to_sym to_i to_f to_a to_h
8
+ strip chomp lstrip rstrip downcase upcase capitalize
9
+ uniq compact flatten sort reverse presence
10
+ ].freeze
11
+
12
+ class << self
13
+ def key = "method_unwrap"
14
+ end
15
+
16
+ def variants(node, **)
17
+ name = syntax(node).unwrap(UNWRAPPABLE)
18
+ solo("delete .#{name}", "unwrap_receiver") if name
19
+ end
20
+ end
@@ -0,0 +1,15 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "base"
4
+
5
+ class Kimera::Operators::Negation < Kimera::Operators::Base
6
+ class << self
7
+ def key = "negation"
8
+ end
9
+
10
+ def variants(node, **)
11
+ return unless call(node).chained?(:!)
12
+ return if node.arguments
13
+ solo("delete !", "unwrap_receiver")
14
+ end
15
+ end
@@ -0,0 +1,20 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "base"
4
+
5
+ class Kimera::Operators::NodeSwap < Kimera::Operators::Base
6
+ class << self
7
+ def define(key, mutations)
8
+ Class.new(self) do
9
+ const_set(:MUTATIONS, mutations.freeze)
10
+ define_singleton_method(:key) { key }
11
+ end
12
+ end
13
+ end
14
+
15
+ def variants(node, **)
16
+ swap = self.class::MUTATIONS[node.class]
17
+ return unless swap
18
+ solo(swap[:label], key, to: swap[:to])
19
+ end
20
+ end
@@ -0,0 +1,39 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "base"
4
+
5
+ class Kimera::Operators::NumericLiteral < Kimera::Operators::Base
6
+ IntegerTargets =
7
+ Data.define(:value) do
8
+ include Enumerable
9
+ def each(&) = targets.uniq.each(&)
10
+ private
11
+ def targets
12
+ [value - 1, value + 1].tap { |all| all << 0 unless value.zero? }
13
+ end
14
+ end
15
+ class << self
16
+ def key = "numeric_literal"
17
+ end
18
+
19
+ def variants(node, **)
20
+ case node
21
+ in IntegerNode(value:) then integers(value)
22
+ in FloatNode(value:) then floats(value)
23
+ else nil
24
+ end
25
+ end
26
+
27
+ private
28
+
29
+ def integers(value)
30
+ IntegerTargets.new(value).map do |to|
31
+ Kimera::Operators::Variant.new(label: "#{value} => #{to}", directive: directive("integer_literal", value: to))
32
+ end
33
+ end
34
+
35
+ def floats(value)
36
+ shifted = value + 1.0
37
+ solo("#{value} => #{shifted}", "float_literal", value: shifted)
38
+ end
39
+ end
@@ -0,0 +1,18 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "base"
4
+
5
+ class Kimera::Operators::OpAssign < Kimera::Operators::Base
6
+ MUTATIONS = { :+ => :-, :- => :+, :* => :/ }.merge({ :/ => :*, :% => :* }).freeze
7
+
8
+ class << self
9
+ def key = "op_assign"
10
+ end
11
+
12
+ def variants(node, **)
13
+ operator = syntax(node).operator
14
+ to = MUTATIONS[operator]
15
+ return unless to
16
+ solo("#{operator}= => #{to}=", "op_asgn_swap", to: to.to_s)
17
+ end
18
+ end
@@ -0,0 +1,27 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "../error"
4
+ require_relative "../operators"
5
+
6
+ module Kimera
7
+ module Plugins
8
+ module_function
9
+
10
+ def load!(paths, root: ".")
11
+ Array(paths).each { |path| pull(path, root) }
12
+ Kimera::Operators.custom
13
+ end
14
+
15
+ def pull(path, root)
16
+ require(resolve(path, root))
17
+ rescue Kimera::Error
18
+ raise
19
+ rescue ScriptError, StandardError => error
20
+ raise(UsageError, "cannot load #{path}: #{error.class}: #{error.message}")
21
+ end
22
+
23
+ def resolve(path, root)
24
+ File.expand_path(path, File.expand_path(root))
25
+ end
26
+ end
27
+ end
@@ -0,0 +1,24 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "rails_declaration"
4
+
5
+ class Kimera::Operators::RailsAssociation < Kimera::Operators::RailsDeclaration
6
+ NAMES = %i[has_many has_one belongs_to has_and_belongs_to_many].freeze
7
+
8
+ class << self
9
+ def key = "rails_association"
10
+ end
11
+
12
+ private
13
+
14
+ def declaration(node)
15
+ return unless dependent?(node)
16
+ solo("#{first(node)}: drop dependent:", "kwarg_pair_drop", key: "dependent")
17
+ end
18
+
19
+ def dependent?(node)
20
+ keywords(node)&.elements.to_a.any? do |assoc|
21
+ assoc.is_a?(AssocNode) && unescaped(assoc.key) == "dependent"
22
+ end
23
+ end
24
+ end
@@ -0,0 +1,49 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "rails_declaration"
4
+
5
+ class Kimera::Operators::RailsCallback < Kimera::Operators::RailsDeclaration
6
+ NAMES = %i[
7
+ before_action after_action around_action
8
+ before_save after_save before_create after_create
9
+ before_update after_update before_destroy after_destroy
10
+ before_validation after_validation after_commit after_rollback
11
+ after_initialize after_find after_touch
12
+ ].freeze
13
+
14
+ class << self
15
+ def key = "rails_callback"
16
+ end
17
+
18
+ private
19
+
20
+ def declaration(node)
21
+ deletion(node) + scopes(node)
22
+ end
23
+
24
+ def scopes(node)
25
+ keywords(node)&.elements.to_a.flat_map { |assoc| scope(node, assoc) }
26
+ end
27
+
28
+ def scope(node, assoc)
29
+ return [] unless scopable?(assoc)
30
+ indexed(node, unescaped(assoc.key), assoc.value.elements)
31
+ end
32
+
33
+ def scopable?(assoc)
34
+ return false unless assoc.is_a?(AssocNode)
35
+ return false unless %w[only except].include?(unescaped(assoc.key))
36
+ assoc.value.is_a?(ArrayNode)
37
+ end
38
+
39
+ def indexed(node, key, elements)
40
+ elements.each_index.map { |index| variant(node, key, elements, index) }
41
+ end
42
+
43
+ def variant(node, key, elements, index)
44
+ Kimera::Operators::Variant.new(
45
+ label: "#{node.name}: drop #{elements[index].location.slice} from #{key}:",
46
+ directive: directive("kwarg_element_drop", key: key, index: index)
47
+ )
48
+ end
49
+ end
@@ -0,0 +1,16 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "base"
4
+
5
+ class Kimera::Operators::RailsDeclaration < Kimera::Operators::Base
6
+ class << self
7
+ def body? = true
8
+
9
+ def statement? = true
10
+ end
11
+
12
+ def variants(node, **)
13
+ return unless call(node).bare?(self.class::NAMES)
14
+ declaration(node)
15
+ end
16
+ end
@@ -0,0 +1,34 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "base"
4
+
5
+ class Kimera::Operators::RailsPermit < Kimera::Operators::Base
6
+ class << self
7
+ def key = "rails_permit"
8
+ end
9
+
10
+ def variants(node, **)
11
+ return unless call(node).chained?(:permit)
12
+ args = arguments(node)
13
+ return unless args.all? { |a| plain?(a) }
14
+ indexed(args)
15
+ end
16
+
17
+ private
18
+
19
+ def indexed(args)
20
+ args.each_index.map { |index| variant(args, index) }
21
+ end
22
+
23
+ def variant(args, index)
24
+ Kimera::Operators::Variant.new(
25
+ label: "permit: drop #{args[index].location.slice}",
26
+ directive: directive("drop_argument", index: index)
27
+ )
28
+ end
29
+
30
+ def plain?(node)
31
+ !node.is_a?(SplatNode) && !node.is_a?(KeywordHashNode) &&
32
+ !node.is_a?(BlockArgumentNode)
33
+ end
34
+ end
@@ -0,0 +1,17 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "rails_declaration"
4
+
5
+ class Kimera::Operators::RailsValidation < Kimera::Operators::RailsDeclaration
6
+ NAMES = %i[validates validate validates_with validates_each].freeze
7
+
8
+ class << self
9
+ def key = "rails_validation"
10
+ end
11
+
12
+ private
13
+
14
+ def declaration(node)
15
+ deletion(node)
16
+ end
17
+ end
@@ -0,0 +1,15 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "base"
4
+
5
+ class Kimera::Operators::Range < Kimera::Operators::Base
6
+ class << self
7
+ def key = "range"
8
+ end
9
+
10
+ def variants(node, **)
11
+ return unless node.is_a?(RangeNode)
12
+ return solo("... => ..", "range_flip", to: "irange") if node.exclude_end?
13
+ solo(".. => ...", "range_flip", to: "erange")
14
+ end
15
+ end
@@ -0,0 +1,16 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "base"
4
+
5
+ class Kimera::Operators::RegexpLiteral < Kimera::Operators::Base
6
+ class << self
7
+ def key = "regexp"
8
+ end
9
+
10
+ def variants(node, **)
11
+ return unless node.is_a?(RegularExpressionNode)
12
+ return if node.unescaped.empty?
13
+ solo("regexp => // (match all)", "regexp_literal", source: "") +
14
+ solo("regexp => /(?!)/ (match none)", "regexp_literal", source: "(?!)")
15
+ end
16
+ end
@@ -0,0 +1,24 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "base"
4
+
5
+ class Kimera::Operators::RespondToGuard < Kimera::Operators::Base
6
+ class << self
7
+ def key = "respond_to_guard"
8
+ end
9
+
10
+ def variants(node, **)
11
+ return unless guard?(node)
12
+ solo("delete .respond_to?", "unwrap_receiver")
13
+ end
14
+
15
+ private
16
+
17
+ def guard?(node)
18
+ return false unless call(node).chained?(:respond_to?) && !node.block
19
+ args = arguments(node)
20
+ return false unless (1..2).cover?(args.length)
21
+ target = args.first
22
+ target.is_a?(SymbolNode) || target.is_a?(StringNode)
23
+ end
24
+ end
@@ -0,0 +1,36 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "base"
4
+
5
+ class Kimera::Operators::ReturnValue < Kimera::Operators::Base
6
+ class << self
7
+ def key = "return_value"
8
+ end
9
+
10
+ SKIPPED = [NilNode, ParenthesesNode].freeze
11
+ POSITIONS = Hash.new(->(_operator, _node) {}).merge(tail: ->(operator, node) { operator.tail(node) }).freeze
12
+
13
+ def variants(node, position:)
14
+ return returned(node) if node.is_a?(ReturnNode)
15
+ POSITIONS[position].call(self, node)
16
+ end
17
+
18
+ private
19
+
20
+ def tail(node)
21
+ solo("tail => nil", Kimera::Operators::STATEMENT_DELETION) if nilable?(node)
22
+ end
23
+
24
+ public :tail
25
+
26
+ def nilable?(node)
27
+ return false if identifier?(node)
28
+ SKIPPED.none? { |kind| node.is_a?(kind) }
29
+ end
30
+
31
+ def returned(node)
32
+ args = arguments(node)
33
+ return if args.empty? || (args in [NilNode])
34
+ solo("return => return nil", "return_nil")
35
+ end
36
+ end