transpec 0.2.6 → 1.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 (68) hide show
  1. checksums.yaml +4 -4
  2. data/.gitignore +1 -0
  3. data/.rubocop.yml +1 -1
  4. data/CHANGELOG.md +10 -0
  5. data/README.md +111 -56
  6. data/README.md.erb +117 -62
  7. data/lib/transpec/ast/node.rb +41 -0
  8. data/lib/transpec/base_rewriter.rb +55 -0
  9. data/lib/transpec/cli.rb +43 -153
  10. data/lib/transpec/configuration.rb +13 -9
  11. data/lib/transpec/{rewriter.rb → converter.rb} +44 -71
  12. data/lib/transpec/dynamic_analyzer/rewriter.rb +94 -0
  13. data/lib/transpec/dynamic_analyzer/runtime_data.rb +27 -0
  14. data/lib/transpec/dynamic_analyzer.rb +166 -0
  15. data/lib/transpec/file_finder.rb +53 -0
  16. data/lib/transpec/option_parser.rb +166 -0
  17. data/lib/transpec/{context.rb → static_context_inspector.rb} +2 -2
  18. data/lib/transpec/syntax/be_close.rb +7 -9
  19. data/lib/transpec/syntax/double.rb +6 -10
  20. data/lib/transpec/syntax/expect.rb +35 -0
  21. data/lib/transpec/syntax/have.rb +195 -0
  22. data/lib/transpec/syntax/method_stub.rb +22 -27
  23. data/lib/transpec/syntax/mixin/allow_no_message.rb +73 -0
  24. data/lib/transpec/syntax/mixin/any_instance.rb +22 -0
  25. data/lib/transpec/syntax/mixin/expectizable.rb +26 -0
  26. data/lib/transpec/syntax/mixin/have_matcher.rb +23 -0
  27. data/lib/transpec/syntax/mixin/monkey_patch.rb +37 -0
  28. data/lib/transpec/syntax/mixin/send.rb +109 -0
  29. data/lib/transpec/syntax/{matcher.rb → operator_matcher.rb} +27 -14
  30. data/lib/transpec/syntax/raise_error.rb +6 -10
  31. data/lib/transpec/syntax/rspec_configure.rb +29 -28
  32. data/lib/transpec/syntax/should.rb +45 -15
  33. data/lib/transpec/syntax/should_receive.rb +44 -16
  34. data/lib/transpec/syntax.rb +29 -21
  35. data/lib/transpec/util.rb +12 -2
  36. data/lib/transpec/version.rb +3 -3
  37. data/spec/spec_helper.rb +8 -6
  38. data/spec/support/cache_helper.rb +50 -0
  39. data/spec/support/shared_context.rb +49 -1
  40. data/spec/transpec/ast/node_spec.rb +65 -0
  41. data/spec/transpec/cli_spec.rb +33 -242
  42. data/spec/transpec/commit_message_spec.rb +2 -2
  43. data/spec/transpec/configuration_spec.rb +12 -8
  44. data/spec/transpec/{rewriter_spec.rb → converter_spec.rb} +198 -148
  45. data/spec/transpec/dynamic_analyzer/rewriter_spec.rb +183 -0
  46. data/spec/transpec/dynamic_analyzer_spec.rb +164 -0
  47. data/spec/transpec/file_finder_spec.rb +118 -0
  48. data/spec/transpec/option_parser_spec.rb +185 -0
  49. data/spec/transpec/{context_spec.rb → static_context_inspector_spec.rb} +27 -12
  50. data/spec/transpec/syntax/be_close_spec.rb +8 -4
  51. data/spec/transpec/syntax/double_spec.rb +105 -12
  52. data/spec/transpec/syntax/expect_spec.rb +83 -0
  53. data/spec/transpec/syntax/have_spec.rb +599 -0
  54. data/spec/transpec/syntax/method_stub_spec.rb +276 -115
  55. data/spec/transpec/syntax/{matcher_spec.rb → operator_matcher_spec.rb} +277 -98
  56. data/spec/transpec/syntax/raise_error_spec.rb +92 -46
  57. data/spec/transpec/syntax/should_receive_spec.rb +298 -92
  58. data/spec/transpec/syntax/should_spec.rb +230 -44
  59. data/spec/transpec/util_spec.rb +2 -9
  60. data/tasks/lib/transpec_demo.rb +1 -1
  61. data/tasks/lib/transpec_test.rb +5 -7
  62. data/tasks/test.rake +5 -1
  63. data/transpec.gemspec +1 -1
  64. metadata +46 -22
  65. data/lib/transpec/syntax/able_to_allow_no_message.rb +0 -73
  66. data/lib/transpec/syntax/able_to_target_any_instance.rb +0 -24
  67. data/lib/transpec/syntax/expectizable.rb +0 -27
  68. data/lib/transpec/syntax/send_node_syntax.rb +0 -57
data/README.md.erb CHANGED
@@ -2,7 +2,7 @@
2
2
 
3
3
  # Transpec
4
4
 
5
- **Transpec** automatically converts your specs into latest [RSpec](http://rspec.info/) syntax with static analysis.
5
+ **Transpec** automatically converts your specs into latest [RSpec](http://rspec.info/) syntax with static and dynamic code analysis.
6
6
 
7
7
  This aims to facilitate smooth transition to RSpec 3.
8
8
 
@@ -61,12 +61,12 @@ END
61
61
  Transpec would convert it to the following form:
62
62
 
63
63
  ```ruby
64
- <%= Transpec::Rewriter.new.rewrite(example) -%>
64
+ <%= Transpec::Converter.new.convert(example) -%>
65
65
  ```
66
66
 
67
- ### Real Examples
67
+ ### Actual Examples
68
68
 
69
- You can see real conversion examples below:
69
+ You can see actual conversion examples below:
70
70
 
71
71
  * https://github.com/yujinakayama/guard/commit/transpec-demo
72
72
  * https://github.com/yujinakayama/mail/commit/transpec-demo
@@ -86,27 +86,32 @@ Before converting your specs:
86
86
  * Run `rspec` and check if all the specs pass.
87
87
  * Ensure the Git repository is clean. (You don't want to mix up your changes and Transpec's changes, right?)
88
88
 
89
- Then, run `transpec` (using `--commit-message` is recommended) in the project root directory:
89
+ Then, run `transpec` (using `--generate-commit-message` is recommended) in the project root directory:
90
90
 
91
91
  ```bash
92
92
  $ cd some-project
93
- $ transpec --commit-message
94
- Processing spec/spec_helper.rb
95
- Processing spec/spec_spec.rb
96
- Processing spec/support/file_helper.rb
97
- Processing spec/support/shared_context.rb
98
- Processing spec/transpec/ast/scanner_spec.rb
99
- Processing spec/transpec/ast/scope_stack_spec.rb
93
+ $ transpec --generate-commit-message
94
+ Copying project for dynamic analysis...
95
+ Running dynamic analysis with command "bundle exec rspec"...
96
+ ...............................................................................
97
+ ...................
98
+
99
+ Finished in 13.07 seconds
100
+ 100 examples, 0 failures
101
+
102
+ Converting spec/spec_helper.rb
103
+ Converting spec/support/cache_helper.rb
104
+ Converting spec/support/file_helper.rb
105
+ Converting spec/support/shared_context.rb
106
+ Converting spec/transpec/ast/node_spec.rb
100
107
  ```
101
108
 
102
- This will convert and overwrite all spec files in the `spec` directory.
109
+ This will run your specs, convert them, and overwrite all spec files in the `spec` directory.
103
110
 
104
111
  After the conversion, run `rspec` again and check whether all pass:
105
112
 
106
113
  ```bash
107
114
  $ bundle exec rspec
108
- # ...
109
- 843 examples, 0 failures
110
115
  ```
111
116
 
112
117
  If all pass, commit the changes with auto-generated message:
@@ -135,7 +140,23 @@ Processing spec/spec_spec.rb
135
140
  Processing spec/support/file_helper.rb
136
141
  ```
137
142
 
138
- ### `-m/--commit-message`
143
+ ### `-s/--skip-dynamic-analysis`
144
+
145
+ Skip dynamic analysis and convert with only static analysis. Note that specifying this option decreases the conversion accuracy.
146
+
147
+ ### `-c/--rspec-command`
148
+
149
+ Specify command to run RSpec that is used for dynamic analysis.
150
+
151
+ Transpec needs to run your specs in copied project directory for dynamic analysis.
152
+ If your project requires some special setup or commands to run specs, use this option.
153
+ `bundle exec rspec` is used by default.
154
+
155
+ ```bash
156
+ $ transpec --rspec-command "./some_special_setup.sh && bundle exec rspec"
157
+ ```
158
+
159
+ ### `-m/--generate-commit-message`
139
160
 
140
161
  Generate commit message that describes conversion summary.
141
162
  Currently only Git is supported.
@@ -146,24 +167,25 @@ When you commit, you need to run the following command to use the generated mess
146
167
  $ git commit -eF .git/COMMIT_EDITMSG
147
168
  ```
148
169
 
149
- ### `-d/--disable`
170
+ ### `-k/--keep`
150
171
 
151
- Disable specific conversions.
172
+ Keep specific syntaxes by disabling conversions.
152
173
 
153
174
  ```bash
154
- $ transpec --disable expect_to_receive,allow_to_receive
175
+ $ transpec --keep should_receive,stub
155
176
  ```
156
177
 
157
- #### Available conversion types
178
+ #### Available syntax types
158
179
 
159
- Conversion Type | Target Syntax | Converted Syntax
160
- --------------------|----------------------------------|----------------------------
180
+ Type | Target Syntax | Converted Syntax
181
+ -----------------|----------------------------------|----------------------------
161
182
  <%=
162
183
  conversion_type_table = <<END
163
- `expect_to_matcher` | `obj.should matcher` | `expect(obj).to matcher`
164
- `expect_to_receive` | `obj.should_receive` | `expect(obj).to receive`
165
- `allow_to_receive` | `obj.stub` | `allow(obj).to receive`
166
- `deprecated` | `obj.stub!`, `mock('foo')`, etc. | `obj.stub`, `double('foo')`
184
+ `should` | `obj.should matcher` | `expect(obj).to matcher`
185
+ `should_receive` | `obj.should_receive` | `expect(obj).to receive`
186
+ `stub` | `obj.stub` | `allow(obj).to receive`
187
+ `have_items` | `expect(obj).to have(x).items` | `expect(obj.size).to eq(x)`
188
+ `deprecated` | `obj.stub!`, `mock('foo')`, etc. | `obj.stub`, `double('foo')`
167
189
  END
168
190
 
169
191
  types_in_readme = conversion_type_table.lines.map do |line|
@@ -171,11 +193,11 @@ types_in_readme = conversion_type_table.lines.map do |line|
171
193
  first_column.gsub(/[^\w]/, '').to_sym
172
194
  end.sort
173
195
 
174
- types_in_code = Transpec::CLI::CONFIG_ATTRS_FOR_CLI_TYPES.keys.sort
196
+ types_in_code = Transpec::OptionParser.available_conversion_types.sort
175
197
 
176
198
  unless types_in_readme == types_in_code
177
199
  types_missing_description = types_in_code - types_in_readme
178
- fail "No descriptions for conversion types #{types_missing_description}"
200
+ fail "No descriptions for syntax types #{types_missing_description}"
179
201
  end
180
202
 
181
203
  conversion_type_table
@@ -215,7 +237,7 @@ END
215
237
  -%>
216
238
 
217
239
  <%=
218
- Transpec::Rewriter.new.rewrite(parenthesizing_example).gsub(
240
+ Transpec::Converter.new.convert(parenthesizing_example).gsub(
219
241
  'original spec',
220
242
  'converted spec'
221
243
  )
@@ -224,8 +246,8 @@ Transpec::Rewriter.new.rewrite(parenthesizing_example).gsub(
224
246
  <%=
225
247
  configuration = Transpec::Configuration.new
226
248
  configuration.parenthesize_matcher_arg = false
227
- rewriter = Transpec::Rewriter.new(configuration)
228
- rewriter.rewrite(parenthesizing_example)
249
+ converter = Transpec::Converter.new(configuration)
250
+ converter.convert(parenthesizing_example)
229
251
  .gsub(
230
252
  'original spec',
231
253
  'converted spec with -p/--no-parentheses-matcher-arg option'
@@ -238,7 +260,7 @@ rewriter.rewrite(parenthesizing_example)
238
260
  -%>
239
261
  ```
240
262
 
241
- ## Troubleshooting
263
+ ## Inconvertible Specs
242
264
 
243
265
  You might see the following warning while conversion:
244
266
 
@@ -266,11 +288,11 @@ end
266
288
 
267
289
  ### Reason
268
290
 
269
- * `should` is defined on `Kernel` (included by `Object`), so you can use `should` almost everywhere.
270
- * `expect` is defined on `RSpec::Matchers` (included by `RSpec::Core::ExampleGroup`), so you can use `expect` only where `self` is an instance of `RSpec::Core::ExampleGroup`.
291
+ * `should` is defined on `Kernel` module that is included by `Object` class, so you can use `should` almost everywhere.
292
+ * `expect` is defined on `RSpec::Matchers` module that is included by `RSpec::Core::ExampleGroup` class, so you can use `expect` only where `self` is an instance of `RSpec::Core::ExampleGroup` (i.e. in `it` blocks, `:each` hook blocks or included methods).
271
293
 
272
294
  With the above example, in the context of `1.should == 1`, the `self` is an instance of `MyAwesomeTestRunner`.
273
- So Transpec tracks contexts and skips conversion if the target syntax cannot be converted in a case like this.
295
+ Transpec tracks contexts and skips conversion if the target syntax cannot be converted in a case like this.
274
296
 
275
297
  ### Solution
276
298
 
@@ -291,8 +313,8 @@ expect(obj).not_to matcher
291
313
  expect(obj).to_not matcher # with `--negative-form to_not`
292
314
  ```
293
315
 
294
- * Disabled by: `--disable expect_to_matcher`
295
- * Related Information: [Myron Marston » RSpec's New Expectation Syntax](http://myronmars.to/n/dev-blog/2012/06/rspecs-new-expectation-syntax)
316
+ * Disabled by: `--keep should`
317
+ * See also: [Myron Marston » RSpec's New Expectation Syntax](http://myronmars.to/n/dev-blog/2012/06/rspecs-new-expectation-syntax)
296
318
 
297
319
  ### Operator matchers
298
320
 
@@ -312,7 +334,7 @@ expect('string').to match(/^str/)
312
334
  expect([1, 2, 3]).to match_array([2, 1, 3])
313
335
  ```
314
336
 
315
- * Related Information: [Myron Marston » RSpec's New Expectation Syntax](http://myronmars.to/n/dev-blog/2012/06/rspecs-new-expectation-syntax#almost_all_matchers_are_supported)
337
+ * See also: [(Almost) All Matchers Are Supported - RSpec's New Expectation Syntax](http://myronmars.to/n/dev-blog/2012/06/rspecs-new-expectation-syntax#almost_all_matchers_are_supported)
316
338
 
317
339
  ### `be_close` matcher
318
340
 
@@ -324,10 +346,43 @@ expect([1, 2, 3]).to match_array([2, 1, 3])
324
346
  (1.0 / 3.0).should be_within(0.001).of(0.333)
325
347
  ```
326
348
 
327
- * Disabled by: `--disable deprecated`
328
- * Related Information: [New be within matcher and RSpec.deprecate fix · rspec/rspec-expectations](https://github.com/rspec/rspec-expectations/pull/32)
349
+ * Disabled by: `--keep deprecated`
350
+ * See also: [New be within matcher and RSpec.deprecate fix · rspec/rspec-expectations](https://github.com/rspec/rspec-expectations/pull/32)
351
+
352
+ ### `have(n).items` matcher
353
+
354
+ ```ruby
355
+ # Targets
356
+ expect(collection).to have(3).items
357
+ expect(collection).to have_exactly(3).items
358
+ expect(collection).to have_at_least(3).items
359
+ expect(collection).to have_at_most(3).items
360
+ collection.should have(3).items
361
+
362
+ expect(team).to have(3).players
363
+
364
+ # Assume #players is a private method.
365
+ expect(team).to have(3).players
366
+
367
+ # Converted
368
+ expect(collection.size).to eq(3)
369
+ expect(collection.size).to be >= 3
370
+ expect(collection.size).to be <= 3
371
+ collection.size.should == 3 # with `--keep should`
372
+
373
+ expect(team.players.size).to eq(3)
374
+
375
+ # have(n).items matcher invokes #players even if it's a private method.
376
+ expect(team.send(:players).size).to eq(3)
377
+ ```
378
+
379
+ You have the option to continue using `have(n).items` matcher with [rspec-collection_matchers](https://github.com/rspec/rspec-collection_matchers) that is an external gem extracted from `rspec-expectations`.
380
+ If you choose so, disable this conversion with `--keep have_items`.
381
+
382
+ * Disabled by: `--keep have_items`
383
+ * See also: [Expectations: have(x).items matchers will be moved into an external gem - The Plan for RSpec 3](http://myronmars.to/n/dev-blog/2013/07/the-plan-for-rspec-3#expectations__matchers_will_be_moved_into_an_external_gem)
329
384
 
330
- ### Expectations on Proc
385
+ ### Expectations on Block
331
386
 
332
387
  ```ruby
333
388
  # Targets
@@ -339,8 +394,8 @@ proc { do_something }.should raise_error
339
394
  expect { do_something }.to raise_error
340
395
  ```
341
396
 
342
- * Disabled by: `--disable expect_to_matcher`
343
- * Related Information: [Myron Marston » RSpec's New Expectation Syntax](http://myronmars.to/n/dev-blog/2012/06/rspecs-new-expectation-syntax#unification_of_block_vs_value_syntaxes)
397
+ * Disabled by: `--keep should`
398
+ * See also: [Unification of Block vs. Value Syntaxes - RSpec's New Expectation Syntax](http://myronmars.to/n/dev-blog/2012/06/rspecs-new-expectation-syntax#unification_of_block_vs_value_syntaxes)
344
399
 
345
400
  ### Negative error expectations with specific error
346
401
 
@@ -353,11 +408,11 @@ lambda { do_something }.should_not raise_error(SomeErrorClass)
353
408
 
354
409
  # Converted
355
410
  expect { do_something }.not_to raise_error
356
- lambda { do_something }.should_not raise_error # with `--disable expect_to_matcher`
411
+ lambda { do_something }.should_not raise_error # with `--keep should`
357
412
  ```
358
413
 
359
- * Disabled by: `--disable deprecated`
360
- * Related Information: [Consider deprecating `expect { }.not_to raise_error(SpecificErrorClass)` · rspec/rspec-expectations](https://github.com/rspec/rspec-expectations/issues/231)
414
+ * Disabled by: `--keep deprecated`
415
+ * See also: [Consider deprecating `expect { }.not_to raise_error(SpecificErrorClass)` · rspec/rspec-expectations](https://github.com/rspec/rspec-expectations/issues/231)
361
416
 
362
417
  ### Message expectations
363
418
 
@@ -371,8 +426,8 @@ expect(obj).to receive(:foo)
371
426
  expect_any_instance_of(SomeClass).to receive(:foo)
372
427
  ```
373
428
 
374
- * Disabled by: `--disable expect_to_receive`
375
- * Related Information: [RSpec's new message expectation syntax - Tea is awesome.](http://teaisaweso.me/blog/2013/05/27/rspecs-new-message-expectation-syntax/)
429
+ * Disabled by: `--keep should_receive`
430
+ * See also: [RSpec's new message expectation syntax - Tea is awesome.](http://teaisaweso.me/blog/2013/05/27/rspecs-new-message-expectation-syntax/)
376
431
 
377
432
  ### Message expectations that are actually method stubs
378
433
 
@@ -386,14 +441,14 @@ SomeClass.any_instance.should_receive(:foo).at_least(0)
386
441
 
387
442
  # Converted
388
443
  allow(obj).to receive(:foo)
389
- obj.stub(:foo) # with `--disable allow_to_receive`
444
+ obj.stub(:foo) # with `--keep stub`
390
445
 
391
446
  allow_any_instance_of(SomeClass).to receive(:foo)
392
- SomeClass.any_instance.stub(:foo) # with `--disable allow_to_receive`
447
+ SomeClass.any_instance.stub(:foo) # with `--keep stub`
393
448
  ```
394
449
 
395
- * Disabled by: `--disable deprecated`
396
- * Related Information: [Don't allow at_least(0) · rspec/rspec-mocks](https://github.com/rspec/rspec-mocks/issues/133)
450
+ * Disabled by: `--keep deprecated`
451
+ * See also: [Don't allow at_least(0) · rspec/rspec-mocks](https://github.com/rspec/rspec-mocks/issues/133)
397
452
 
398
453
  ### Method stubs
399
454
 
@@ -418,8 +473,8 @@ allow(obj).to receive(:bar).and_return(2)
418
473
  allow_any_instance_of(SomeClass).to receive(:foo)
419
474
  ```
420
475
 
421
- * Disabled by: `--disable allow_to_receive`
422
- * Related Information: [RSpec's new message expectation syntax - Tea is awesome.](http://teaisaweso.me/blog/2013/05/27/rspecs-new-message-expectation-syntax/)
476
+ * Disabled by: `--keep stub`
477
+ * See also: [RSpec's new message expectation syntax - Tea is awesome.](http://teaisaweso.me/blog/2013/05/27/rspecs-new-message-expectation-syntax/)
423
478
 
424
479
  ### Deprecated method stub aliases
425
480
 
@@ -429,12 +484,12 @@ obj.stub!(:foo)
429
484
  obj.unstub!(:foo)
430
485
 
431
486
  # Converted
432
- obj.stub(:foo) # with `--disable allow_to_receive`
487
+ obj.stub(:foo) # with `--keep stub`
433
488
  obj.unstub(:foo)
434
489
  ```
435
490
 
436
- * Disabled by: `--disable deprecated`
437
- * Related Information: [Consider deprecating and/or removing #stub! and #unstub! at some point · rspec/rspec-mocks](https://github.com/rspec/rspec-mocks/issues/122)
491
+ * Disabled by: `--keep deprecated`
492
+ * See also: [Consider deprecating and/or removing #stub! and #unstub! at some point · rspec/rspec-mocks](https://github.com/rspec/rspec-mocks/issues/122)
438
493
 
439
494
  ### Method stubs with deprecated specification of number of times
440
495
 
@@ -445,11 +500,11 @@ obj.stub(:foo).at_least(0)
445
500
 
446
501
  # Converted
447
502
  allow(obj).to receive(:foo)
448
- obj.stub(:foo) # with `--disable allow_to_receive`
503
+ obj.stub(:foo) # with `--keep stub`
449
504
  ```
450
505
 
451
- * Disabled by: `--disable deprecated`
452
- * Related Information: [Don't allow at_least(0) · rspec/rspec-mocks](https://github.com/rspec/rspec-mocks/issues/133)
506
+ * Disabled by: `--keep deprecated`
507
+ * See also: [Don't allow at_least(0) · rspec/rspec-mocks](https://github.com/rspec/rspec-mocks/issues/133)
453
508
 
454
509
  ### Deprecated test double aliases
455
510
 
@@ -462,8 +517,8 @@ mock('something')
462
517
  double('something')
463
518
  ```
464
519
 
465
- * Disabled by: `--disable deprecated`
466
- * Related Information: [Deprecate "stub" for doubles · rspec/rspec-mocks](https://github.com/rspec/rspec-mocks/issues/214)
520
+ * Disabled by: `--keep deprecated`
521
+ * See also: [Deprecate "stub" for doubles · rspec/rspec-mocks](https://github.com/rspec/rspec-mocks/issues/214)
467
522
 
468
523
  ## Compatibility
469
524
 
@@ -5,6 +5,41 @@ require 'parser'
5
5
  module Transpec
6
6
  module AST
7
7
  class Node < Parser::AST::Node
8
+ def initialize(type, children = [], properties = {})
9
+ @properties = {}
10
+
11
+ super
12
+
13
+ each_child_node do |child_node|
14
+ child_node.parent_node = self
15
+ end
16
+ end
17
+
18
+ def parent_node
19
+ @properties[:parent_node]
20
+ end
21
+
22
+ def parent_node=(node)
23
+ @properties[:parent_node] = node
24
+ end
25
+
26
+ protected :parent_node=
27
+
28
+ def each_ancestor_node(&block)
29
+ return to_enum(__method__) unless block_given?
30
+
31
+ if parent_node
32
+ yield parent_node
33
+ parent_node.each_ancestor_node(&block)
34
+ end
35
+
36
+ self
37
+ end
38
+
39
+ def ancestor_nodes
40
+ each_ancestor_node.to_a
41
+ end
42
+
8
43
  def each_child_node
9
44
  return to_enum(__method__) unless block_given?
10
45
 
@@ -32,6 +67,12 @@ module Transpec
32
67
  def descendent_nodes
33
68
  each_descendent_node.to_a
34
69
  end
70
+
71
+ private
72
+
73
+ def properties
74
+ @properties ||= {}
75
+ end
35
76
  end
36
77
  end
37
78
  end
@@ -0,0 +1,55 @@
1
+ # coding: utf-8
2
+
3
+ require 'transpec/ast/builder'
4
+ require 'parser/current'
5
+
6
+ module Transpec
7
+ class BaseRewriter
8
+ def rewrite_file!(file_path)
9
+ source = File.read(file_path)
10
+ rewritten_source = rewrite(source, file_path)
11
+ return if source == rewritten_source
12
+ File.write(file_path, rewritten_source)
13
+ end
14
+
15
+ def rewrite(source, name = '(string)')
16
+ source_buffer = create_source_buffer(source, name)
17
+ ast = parse(source_buffer)
18
+
19
+ source_rewriter = Parser::Source::Rewriter.new(source_buffer)
20
+ failed_overlapping_rewrite = false
21
+ source_rewriter.diagnostics.consumer = proc do
22
+ failed_overlapping_rewrite = true
23
+ fail OverlappedRewriteError
24
+ end
25
+
26
+ process(ast, source_rewriter)
27
+
28
+ rewritten_source = source_rewriter.process
29
+ rewritten_source = rewrite(rewritten_source, name) if failed_overlapping_rewrite
30
+
31
+ rewritten_source
32
+ end
33
+
34
+ private
35
+
36
+ def process(ast, source_rewriter)
37
+ fail NotImplementedError
38
+ end
39
+
40
+ def create_source_buffer(source, name)
41
+ source_buffer = Parser::Source::Buffer.new(name)
42
+ source_buffer.source = source
43
+ source_buffer
44
+ end
45
+
46
+ def parse(source_buffer)
47
+ builder = AST::Builder.new
48
+ parser = Parser::CurrentRuby.new(builder)
49
+ ast = parser.parse(source_buffer)
50
+ ast
51
+ end
52
+
53
+ class OverlappedRewriteError < StandardError; end
54
+ end
55
+ end