metamorpher 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 (84) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +17 -0
  3. data/.rspec +3 -0
  4. data/.rubocop.yml +16 -0
  5. data/.travis.yml +3 -0
  6. data/Gemfile +5 -0
  7. data/LICENSE.txt +22 -0
  8. data/README.md +541 -0
  9. data/Rakefile +23 -0
  10. data/examples/refactorings/rails/where_first/app.rb +50 -0
  11. data/examples/refactorings/rails/where_first/refactorers/refactor_where_first_mocks.rb +31 -0
  12. data/examples/refactorings/rails/where_first/refactorers/refactor_where_first_not_called_expectations.rb +14 -0
  13. data/examples/refactorings/rails/where_first/refactorers/refactor_where_first_strict_mocks.rb +27 -0
  14. data/examples/refactorings/rails/where_first/refactorers/refactor_where_first_to_find_by.rb +14 -0
  15. data/examples/refactorings/rails/where_first/sample_controller.rb +184 -0
  16. data/lib/metamorpher/builders/ast/builder.rb +50 -0
  17. data/lib/metamorpher/builders/ast/derivation_builder.rb +20 -0
  18. data/lib/metamorpher/builders/ast/greedy_variable_builder.rb +29 -0
  19. data/lib/metamorpher/builders/ast/literal_builder.rb +31 -0
  20. data/lib/metamorpher/builders/ast/variable_builder.rb +29 -0
  21. data/lib/metamorpher/builders/ast.rb +11 -0
  22. data/lib/metamorpher/builders/ruby/builder.rb +38 -0
  23. data/lib/metamorpher/builders/ruby/deriving_visitor.rb +13 -0
  24. data/lib/metamorpher/builders/ruby/ensuring_visitor.rb +13 -0
  25. data/lib/metamorpher/builders/ruby/term.rb +35 -0
  26. data/lib/metamorpher/builders/ruby/uppercase_constant_rewriter.rb +31 -0
  27. data/lib/metamorpher/builders/ruby/uppercase_rewriter.rb +28 -0
  28. data/lib/metamorpher/builders/ruby/variable_replacement_visitor.rb +32 -0
  29. data/lib/metamorpher/builders/ruby.rb +11 -0
  30. data/lib/metamorpher/drivers/parse_error.rb +5 -0
  31. data/lib/metamorpher/drivers/ruby.rb +78 -0
  32. data/lib/metamorpher/matcher/match.rb +26 -0
  33. data/lib/metamorpher/matcher/matching.rb +61 -0
  34. data/lib/metamorpher/matcher/no_match.rb +18 -0
  35. data/lib/metamorpher/matcher.rb +6 -0
  36. data/lib/metamorpher/refactorer/merger.rb +18 -0
  37. data/lib/metamorpher/refactorer/site.rb +29 -0
  38. data/lib/metamorpher/refactorer.rb +48 -0
  39. data/lib/metamorpher/rewriter/replacement.rb +18 -0
  40. data/lib/metamorpher/rewriter/rule.rb +38 -0
  41. data/lib/metamorpher/rewriter/substitution.rb +45 -0
  42. data/lib/metamorpher/rewriter/traverser.rb +26 -0
  43. data/lib/metamorpher/rewriter.rb +12 -0
  44. data/lib/metamorpher/support/map_at.rb +8 -0
  45. data/lib/metamorpher/terms/derived.rb +13 -0
  46. data/lib/metamorpher/terms/literal.rb +47 -0
  47. data/lib/metamorpher/terms/term.rb +40 -0
  48. data/lib/metamorpher/terms/variable.rb +17 -0
  49. data/lib/metamorpher/version.rb +3 -0
  50. data/lib/metamorpher/visitable/visitable.rb +7 -0
  51. data/lib/metamorpher/visitable/visitor.rb +21 -0
  52. data/lib/metamorpher.rb +30 -0
  53. data/metamorpher.gemspec +30 -0
  54. data/spec/integration/ast/builder_spec.rb +13 -0
  55. data/spec/integration/ast/matcher_spec.rb +132 -0
  56. data/spec/integration/ast/rewriter_spec.rb +138 -0
  57. data/spec/integration/ruby/builder_spec.rb +125 -0
  58. data/spec/integration/ruby/refactorer_spec.rb +192 -0
  59. data/spec/spec_helper.rb +29 -0
  60. data/spec/support/helpers/silence_stream.rb +10 -0
  61. data/spec/support/matchers/have_matched_matcher.rb +22 -0
  62. data/spec/support/matchers/have_substitution_matcher.rb +15 -0
  63. data/spec/support/shared_examples/shared_examples_for_derivation_builders.rb +53 -0
  64. data/spec/support/shared_examples/shared_examples_for_greedy_variable_builders.rb +49 -0
  65. data/spec/support/shared_examples/shared_examples_for_literal_builders.rb +93 -0
  66. data/spec/support/shared_examples/shared_examples_for_variable_builders.rb +49 -0
  67. data/spec/unit/builders/ast/derivation_builder_spec.rb +5 -0
  68. data/spec/unit/builders/ast/greedy_variable_builder_spec.rb +9 -0
  69. data/spec/unit/builders/ast/literal_builder_spec.rb +9 -0
  70. data/spec/unit/builders/ast/variable_builder_spec.rb +9 -0
  71. data/spec/unit/builders/ruby/variable_replacement_visitor_spec.rb +48 -0
  72. data/spec/unit/drivers/ruby_spec.rb +91 -0
  73. data/spec/unit/matcher/matching_spec.rb +230 -0
  74. data/spec/unit/metamorpher_spec.rb +22 -0
  75. data/spec/unit/refactorer/merger_spec.rb +84 -0
  76. data/spec/unit/refactorer/site_spec.rb +52 -0
  77. data/spec/unit/rewriter/replacement_spec.rb +73 -0
  78. data/spec/unit/rewriter/substitution_spec.rb +97 -0
  79. data/spec/unit/rewriter/traverser_spec.rb +51 -0
  80. data/spec/unit/support/map_at_spec.rb +18 -0
  81. data/spec/unit/terms/literal_spec.rb +60 -0
  82. data/spec/unit/terms/term_spec.rb +59 -0
  83. data/spec/unit/visitable/visitor_spec.rb +35 -0
  84. metadata +269 -0
@@ -0,0 +1,50 @@
1
+ require "optparse"
2
+ require_relative "refactorers/refactor_where_first_to_find_by.rb"
3
+ require_relative "refactorers/refactor_where_first_not_called_expectations.rb"
4
+ require_relative "refactorers/refactor_where_first_mocks.rb"
5
+ require_relative "refactorers/refactor_where_first_strict_mocks.rb"
6
+
7
+ options = { overwrite: true }
8
+ OptionParser.new do |opts|
9
+ opts.banner = "Usage: refactorer.rb [options]"
10
+
11
+ opts.on("-d", "--dry-run", "Write changes to console, rather than to source files.") do |v|
12
+ options[:overwrite] = false
13
+ end
14
+ end.parse!
15
+
16
+ source_dir = ARGV.first || "."
17
+ base = File.expand_path(File.join("**", "*.rb"), source_dir)
18
+ puts "Refactoring in source directory: #{base}"
19
+
20
+ [
21
+ # Refactor "where(...).first -> find_by(...)"
22
+ RefactorWhereFirstToFindBy,
23
+
24
+ # Refactor ".expect(:where).never" to ".expect(:find_by).never"
25
+ RefactorWhereFirstNotCalledExpectations,
26
+
27
+ # Refactor ".expect(:where).return([X])" to ".expect(:find_by).return(X)"
28
+ # and ".stubs(:where).return([X])" to ".stubs(:find_by).return(X)"
29
+ RefactorWhereFirstMocks,
30
+
31
+ # Refactor ".expect(:where).with(...).return([X])" to ".expect(:find_by).with(...).return(X)"
32
+ RefactorWhereFirstStrictMocks
33
+
34
+ ].each do |refactorer|
35
+ refactorer.new.refactor_files(Dir.glob(base)) do |path, refactored, changes|
36
+ if changes.empty?
37
+ puts "No changes in #{path}"
38
+
39
+ else
40
+ puts "In #{path}:"
41
+
42
+ changes.each do |change|
43
+ puts "\tAt #{change.original_position}, inserting:\n\t\t#{change.refactored_code}"
44
+ puts ""
45
+ end
46
+
47
+ File.open(path, "w") { |f| f.write(refactored) } if options[:overwrite]
48
+ end
49
+ end
50
+ end
@@ -0,0 +1,31 @@
1
+ require "metamorpher"
2
+
3
+ class RefactorWhereFirstMocks
4
+ include Metamorpher::Refactorer
5
+ include Metamorpher::Builders::Ruby
6
+
7
+ def pattern
8
+ builder
9
+ .build("TYPE.DOUBLE_METHOD(:where).returns(ARRAY_VALUE)")
10
+ .ensuring("DOUBLE_METHOD") { |m| m.name == :expects || m.name == :stubs }
11
+ .ensuring("ARRAY_VALUE") { |v| v.name == :array }
12
+ # Doesn't match non-array return types, such as Topic.stubs(:where).returns(Topic)
13
+ end
14
+
15
+ def replacement
16
+ builder
17
+ .build("TYPE.DOUBLE_METHOD(:find_by).returns(SINGLE_VALUE)")
18
+ .deriving("SINGLE_VALUE", "ARRAY_VALUE") { |array_value| take_first(array_value) }
19
+ end
20
+
21
+ private
22
+
23
+ # Refactor the argument from [] to nil, or from [X] to X
24
+ def take_first(array_value)
25
+ if array_value.children.empty?
26
+ builder.build("nil")
27
+ else
28
+ array_value.children.first
29
+ end
30
+ end
31
+ end
@@ -0,0 +1,14 @@
1
+ require "metamorpher"
2
+
3
+ class RefactorWhereFirstNotCalledExpectations
4
+ include Metamorpher::Refactorer
5
+ include Metamorpher::Builders::Ruby
6
+
7
+ def pattern
8
+ builder.build("TYPE.expects(:where).never")
9
+ end
10
+
11
+ def replacement
12
+ builder.build("TYPE.expects(:find_by).never")
13
+ end
14
+ end
@@ -0,0 +1,27 @@
1
+ require "metamorpher"
2
+
3
+ class RefactorWhereFirstStrictMocks
4
+ include Metamorpher::Refactorer
5
+ include Metamorpher::Builders::Ruby
6
+
7
+ def pattern
8
+ builder.build("TYPE.expects(:where).with(PARAMS_).returns(ARRAY_VALUE)")
9
+ end
10
+
11
+ def replacement
12
+ builder
13
+ .build("TYPE.expects(:find_by).with(PARAMS_).returns(SINGLE_VALUE)")
14
+ .deriving("SINGLE_VALUE", "ARRAY_VALUE") { |array_value| take_first(array_value) }
15
+ end
16
+
17
+ private
18
+
19
+ # Refactor the argument from [] to nil, or from [X] to X
20
+ def take_first(array_value)
21
+ if array_value.children.empty?
22
+ builder.build("nil")
23
+ else
24
+ array_value.children.first
25
+ end
26
+ end
27
+ end
@@ -0,0 +1,14 @@
1
+ require "metamorpher"
2
+
3
+ class RefactorWhereFirstToFindBy
4
+ include Metamorpher::Refactorer
5
+ include Metamorpher::Builders::Ruby
6
+
7
+ def pattern
8
+ builder.build("TYPE.where(PARAMS_).first")
9
+ end
10
+
11
+ def replacement
12
+ builder.build("TYPE.find_by(PARAMS_)")
13
+ end
14
+ end
@@ -0,0 +1,184 @@
1
+ # Taken from Discourse: https://github.com/discourse/discourse
2
+
3
+ require_dependency "user_destroyer"
4
+ require_dependency "admin_user_index_query"
5
+ require_dependency "boost_trust_level"
6
+
7
+ class Admin::UsersController < Admin::AdminController
8
+ before_filter :fetch_user, only: [:suspend,
9
+ :unsuspend,
10
+ :refresh_browsers,
11
+ :revoke_admin,
12
+ :grant_admin,
13
+ :revoke_moderation,
14
+ :grant_moderation,
15
+ :approve,
16
+ :activate,
17
+ :deactivate,
18
+ :block,
19
+ :unblock,
20
+ :trust_level,
21
+ :primary_group,
22
+ :generate_api_key,
23
+ :revoke_api_key]
24
+
25
+ def index
26
+ query = ::AdminUserIndexQuery.new(params)
27
+ render_serialized(query.find_users, AdminUserSerializer)
28
+ end
29
+
30
+ def show
31
+ @user = User.where(username_lower: params[:id]).first
32
+ fail Discourse::NotFound.new unless @user
33
+ render_serialized(@user, AdminDetailedUserSerializer, root: false)
34
+ end
35
+
36
+ def delete_all_posts
37
+ @user = User.where(id: params[:user_id]).first
38
+ @user.delete_all_posts!(guardian)
39
+ render nothing: true
40
+ end
41
+
42
+ def suspend
43
+ guardian.ensure_can_suspend!(@user)
44
+ @user.suspended_till = params[:duration].to_i.days.from_now
45
+ @user.suspended_at = DateTime.now
46
+ @user.save!
47
+ StaffActionLogger.new(current_user).log_user_suspend(@user, params[:reason])
48
+ render nothing: true
49
+ end
50
+
51
+ def unsuspend
52
+ guardian.ensure_can_suspend!(@user)
53
+ @user.suspended_till = nil
54
+ @user.suspended_at = nil
55
+ @user.save!
56
+ StaffActionLogger.new(current_user).log_user_unsuspend(@user)
57
+ render nothing: true
58
+ end
59
+
60
+ def refresh_browsers
61
+ MessageBus.publish "/file-change", ["refresh"], user_ids: [@user.id]
62
+ render nothing: true
63
+ end
64
+
65
+ def revoke_admin
66
+ guardian.ensure_can_revoke_admin!(@user)
67
+ @user.revoke_admin!
68
+ render nothing: true
69
+ end
70
+
71
+ def generate_api_key
72
+ api_key = @user.generate_api_key(current_user)
73
+ render_serialized(api_key, ApiKeySerializer)
74
+ end
75
+
76
+ def revoke_api_key
77
+ @user.revoke_api_key
78
+ render nothing: true
79
+ end
80
+
81
+ def grant_admin
82
+ guardian.ensure_can_grant_admin!(@user)
83
+ @user.grant_admin!
84
+ render_serialized(@user, AdminUserSerializer)
85
+ end
86
+
87
+ def revoke_moderation
88
+ guardian.ensure_can_revoke_moderation!(@user)
89
+ @user.revoke_moderation!
90
+ render nothing: true
91
+ end
92
+
93
+ def grant_moderation
94
+ guardian.ensure_can_grant_moderation!(@user)
95
+ @user.grant_moderation!
96
+ render_serialized(@user, AdminUserSerializer)
97
+ end
98
+
99
+ def primary_group
100
+ guardian.ensure_can_change_primary_group!(@user)
101
+ @user.primary_group_id = params[:primary_group_id]
102
+ @user.save!
103
+ render nothing: true
104
+ end
105
+
106
+ def trust_level
107
+ guardian.ensure_can_change_trust_level!(@user)
108
+ logger = StaffActionLogger.new(current_user)
109
+ BoostTrustLevel.new(user: @user, level: params[:level], logger: logger).save!
110
+ render_serialized(@user, AdminUserSerializer)
111
+ end
112
+
113
+ def approve
114
+ guardian.ensure_can_approve!(@user)
115
+ @user.approve(current_user)
116
+ render nothing: true
117
+ end
118
+
119
+ def approve_bulk
120
+ User.where(id: params[:users]).each do |u|
121
+ u.approve(current_user) if guardian.can_approve?(u)
122
+ end
123
+ render nothing: true
124
+ end
125
+
126
+ def activate
127
+ guardian.ensure_can_activate!(@user)
128
+ @user.activate
129
+ render nothing: true
130
+ end
131
+
132
+ def deactivate
133
+ guardian.ensure_can_deactivate!(@user)
134
+ @user.deactivate
135
+ render nothing: true
136
+ end
137
+
138
+ def block
139
+ guardian.ensure_can_block_user! @user
140
+ UserBlocker.block(@user, current_user)
141
+ render nothing: true
142
+ end
143
+
144
+ def unblock
145
+ guardian.ensure_can_unblock_user! @user
146
+ UserBlocker.unblock(@user, current_user)
147
+ render nothing: true
148
+ end
149
+
150
+ def reject_bulk
151
+ d = UserDestroyer.new(current_user)
152
+ success_count = 0
153
+ User.where(id: params[:users]).each do |u|
154
+ success_count += 1 if guardian.can_delete_user?(u) and d.destroy(u, params.slice(:context)) rescue UserDestroyer::PostsExistError
155
+ end
156
+ render json: { success: success_count, failed: (params[:users].try(:size) || 0) - success_count }
157
+ end
158
+
159
+ def destroy
160
+ user = User.where(id: params[:id]).first
161
+ guardian.ensure_can_delete_user!(user)
162
+ begin
163
+ if UserDestroyer.new(current_user).destroy(user, params.slice(:delete_posts, :block_email, :block_urls, :block_ip, :context))
164
+ render json: { deleted: true }
165
+ else
166
+ render json: { deleted: false, user: AdminDetailedUserSerializer.new(user, root: false).as_json }
167
+ end
168
+ rescue UserDestroyer::PostsExistError
169
+ raise Discourse::InvalidAccess.new("User #{user.username} has #{user.post_count} posts, so can't be deleted.")
170
+ end
171
+ end
172
+
173
+ def badges
174
+ end
175
+
176
+ def leader_requirements
177
+ end
178
+
179
+ private
180
+
181
+ def fetch_user
182
+ @user = User.where(id: params[:user_id]).first
183
+ end
184
+ end
@@ -0,0 +1,50 @@
1
+ require "metamorpher/builders/ast/literal_builder"
2
+ require "metamorpher/builders/ast/variable_builder"
3
+ require "metamorpher/builders/ast/greedy_variable_builder"
4
+ require "metamorpher/builders/ast/derivation_builder"
5
+
6
+ module Metamorpher
7
+ module Builders
8
+ module AST
9
+ class Builder
10
+ extend Forwardable
11
+ def_delegator :literal_builder, :literal!
12
+ def_delegator :variable_builder, :variable!
13
+ def_delegator :greedy_variable_builder, :greedy_variable!
14
+ def_delegator :derivation_builder, :derivation!
15
+
16
+ def method_missing(method, *arguments, &block)
17
+ builders_with_shorthand
18
+ .find { |builder| builder.shorthand?(method, *arguments, &block) }
19
+ .method_missing(method, *arguments, &block)
20
+ end
21
+
22
+ private
23
+
24
+ def builders_with_shorthand
25
+ @builders ||= [
26
+ literal_builder,
27
+ variable_builder,
28
+ greedy_variable_builder
29
+ ]
30
+ end
31
+
32
+ def literal_builder
33
+ @literal_builder ||= LiteralBuilder.new
34
+ end
35
+
36
+ def variable_builder
37
+ @variable_builder ||= VariableBuilder.new
38
+ end
39
+
40
+ def greedy_variable_builder
41
+ @greedy_variable_builder ||= GreedyVariableBuilder.new
42
+ end
43
+
44
+ def derivation_builder
45
+ @derivation_builder ||= DerivationBuilder.new
46
+ end
47
+ end
48
+ end
49
+ end
50
+ end
@@ -0,0 +1,20 @@
1
+ require "metamorpher/terms/derived"
2
+ require "metamorpher/builders/ast/builder"
3
+
4
+ module Metamorpher
5
+ module Builders
6
+ module AST
7
+ class DerivationBuilder
8
+ def derivation!(*base, &block)
9
+ fail ArgumentError, "wrong number of arguments (0)" if base.empty?
10
+ fail ArgumentError, "a block must be provided" if block.nil?
11
+
12
+ Terms::Derived.new(
13
+ base: base,
14
+ derivation: ->(*args) { block.call(*args, Builder.new) }
15
+ )
16
+ end
17
+ end
18
+ end
19
+ end
20
+ end
@@ -0,0 +1,29 @@
1
+ require "metamorpher/terms/variable"
2
+
3
+ module Metamorpher
4
+ module Builders
5
+ module AST
6
+ class GreedyVariableBuilder
7
+ def greedy_variable!(name, &block)
8
+ if block
9
+ Terms::Variable.new(name: name, greedy?: true, condition: block)
10
+ else
11
+ Terms::Variable.new(name: name, greedy?: true)
12
+ end
13
+ end
14
+
15
+ def shorthand?(method, *arguments, &block)
16
+ !method[/\p{Lower}/] && method.to_s.end_with?("_")
17
+ end
18
+
19
+ def method_missing(method, *arguments, &block)
20
+ if shorthand?(method, *arguments, &block)
21
+ greedy_variable!(method.to_s.chomp("_").downcase.to_sym, *arguments, &block)
22
+ else
23
+ super.method_missing(method, *arguments, &block)
24
+ end
25
+ end
26
+ end
27
+ end
28
+ end
29
+ end
@@ -0,0 +1,31 @@
1
+ require "metamorpher/terms/literal"
2
+
3
+ module Metamorpher
4
+ module Builders
5
+ module AST
6
+ class LiteralBuilder
7
+ def literal!(name, *children)
8
+ Terms::Literal.new(name: name, children: children.map { |c| termify(c) })
9
+ end
10
+
11
+ def shorthand?(method, *arguments, &block)
12
+ !method[/\p{Upper}/]
13
+ end
14
+
15
+ def method_missing(method, *arguments, &block)
16
+ if shorthand?(method, *arguments, &block)
17
+ literal!(method, *arguments)
18
+ else
19
+ super.method_missing(method, *arguments, &block)
20
+ end
21
+ end
22
+
23
+ private
24
+
25
+ def termify(item)
26
+ item.kind_of?(Terms::Term) ? item : literal!(item)
27
+ end
28
+ end
29
+ end
30
+ end
31
+ end
@@ -0,0 +1,29 @@
1
+ require "metamorpher/terms/variable"
2
+
3
+ module Metamorpher
4
+ module Builders
5
+ module AST
6
+ class VariableBuilder
7
+ def variable!(name, &block)
8
+ if block
9
+ Terms::Variable.new(name: name, condition: block)
10
+ else
11
+ Terms::Variable.new(name: name)
12
+ end
13
+ end
14
+
15
+ def shorthand?(method, *arguments, &block)
16
+ !method[/\p{Lower}/] && !method.to_s.end_with?("_")
17
+ end
18
+
19
+ def method_missing(method, *arguments, &block)
20
+ if shorthand?(method, *arguments, &block)
21
+ variable!(method.downcase.to_sym, *arguments, &block)
22
+ else
23
+ super.method_missing(method, *arguments, &block)
24
+ end
25
+ end
26
+ end
27
+ end
28
+ end
29
+ end
@@ -0,0 +1,11 @@
1
+ require "metamorpher/builders/ast/builder"
2
+
3
+ module Metamorpher
4
+ module Builders
5
+ module AST
6
+ def builder
7
+ @builder ||= Builder.new
8
+ end
9
+ end
10
+ end
11
+ end
@@ -0,0 +1,38 @@
1
+ require "metamorpher/drivers/ruby"
2
+ require "metamorpher/builders/ruby/term"
3
+ require "metamorpher/builders/ruby/uppercase_constant_rewriter"
4
+ require "metamorpher/builders/ruby/uppercase_rewriter"
5
+
6
+ module Metamorpher
7
+ module Builders
8
+ module Ruby
9
+ class Builder
10
+ def build(source)
11
+ decorate(rewrite(parse(source)))
12
+ end
13
+
14
+ private
15
+
16
+ def decorate(term)
17
+ term.extend(Term)
18
+ end
19
+
20
+ def rewrite(parsed)
21
+ rewriters.reduce(parsed) { |a, e| e.reduce(a) }
22
+ end
23
+
24
+ def parse(source)
25
+ driver.parse(source)
26
+ end
27
+
28
+ def rewriters
29
+ @rewriters ||= [UppercaseConstantRewriter.new, UppercaseRewriter.new]
30
+ end
31
+
32
+ def driver
33
+ @driver ||= Drivers::Ruby.new
34
+ end
35
+ end
36
+ end
37
+ end
38
+ end
@@ -0,0 +1,13 @@
1
+ require "metamorpher/builders/ruby/variable_replacement_visitor"
2
+
3
+ module Metamorpher
4
+ module Builders
5
+ module Ruby
6
+ class DerivingVisitor < VariableReplacementVisitor
7
+ def initialize(variable_name, *base, derivation)
8
+ super(variable_name, Terms::Derived.new(base: base, derivation: derivation))
9
+ end
10
+ end
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,13 @@
1
+ require "metamorpher/builders/ruby/variable_replacement_visitor"
2
+
3
+ module Metamorpher
4
+ module Builders
5
+ module Ruby
6
+ class EnsuringVisitor < VariableReplacementVisitor
7
+ def initialize(variable_name, condition)
8
+ super(variable_name, Terms::Variable.new(name: variable_name, condition: condition))
9
+ end
10
+ end
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,35 @@
1
+ require "metamorpher/builders/ruby/ensuring_visitor"
2
+ require "metamorpher/builders/ruby/deriving_visitor"
3
+
4
+ module Metamorpher
5
+ module Builders
6
+ module Ruby
7
+ module Term
8
+ def ensuring(variable_name, &condition)
9
+ accept_and_decorate(
10
+ EnsuringVisitor.new(
11
+ variable_name.downcase.to_sym,
12
+ condition
13
+ )
14
+ )
15
+ end
16
+
17
+ def deriving(variable_name, *base_names, &derivation)
18
+ accept_and_decorate(
19
+ DerivingVisitor.new(
20
+ variable_name.downcase.to_sym,
21
+ *base_names.map { |n| n.downcase.to_sym },
22
+ derivation
23
+ )
24
+ )
25
+ end
26
+
27
+ private
28
+
29
+ def accept_and_decorate(visitor)
30
+ accept(visitor).extend(Term)
31
+ end
32
+ end
33
+ end
34
+ end
35
+ end
@@ -0,0 +1,31 @@
1
+ require "metamorpher/builders/ast"
2
+
3
+ module Metamorpher
4
+ module Builders
5
+ module Ruby
6
+ class UppercaseConstantRewriter
7
+ include Metamorpher::Rewriter
8
+ include Metamorpher::Builders::AST
9
+
10
+ def pattern
11
+ builder.const(
12
+ builder.literal!(nil),
13
+ builder.VARIABLE_TO_BE { |v| v.name && v.name.to_s[/^[A-Z_]*$/] }
14
+ )
15
+ end
16
+
17
+ def replacement
18
+ builder.derivation!(:variable_to_be) do |variable_to_be, builder|
19
+ name = variable_to_be.name.to_s
20
+
21
+ if name.end_with?("_")
22
+ builder.greedy_variable! name.chomp("_").downcase.to_sym
23
+ else
24
+ builder.variable! name.downcase.to_sym
25
+ end
26
+ end
27
+ end
28
+ end
29
+ end
30
+ end
31
+ end
@@ -0,0 +1,28 @@
1
+ require "metamorpher/builders/ast"
2
+
3
+ module Metamorpher
4
+ module Builders
5
+ module Ruby
6
+ class UppercaseRewriter
7
+ include Metamorpher::Rewriter
8
+ include Metamorpher::Builders::AST
9
+
10
+ def pattern
11
+ builder.VARIABLE_TO_BE { |v| v.name && v.name.to_s[/^[A-Z_]*$/] }
12
+ end
13
+
14
+ def replacement
15
+ builder.derivation!(:variable_to_be) do |variable_to_be, builder|
16
+ name = variable_to_be.name.to_s
17
+
18
+ if name.end_with?("_")
19
+ builder.greedy_variable! name.chomp("_").downcase.to_sym
20
+ else
21
+ builder.variable! name.downcase.to_sym
22
+ end
23
+ end
24
+ end
25
+ end
26
+ end
27
+ end
28
+ end