metamorpher 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
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