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
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 2d1e226cde065f5be1ba8ed1a21d0cf0a8721791
4
- data.tar.gz: 6e53c222eb7070fe7a958a24615c6fb10071709c
3
+ metadata.gz: e89b721e834bdcbbc84616c93aeb74dcdcd69d29
4
+ data.tar.gz: 5678c00ce5a1bcc36e55cdcc41233fc0bd24d075
5
5
  SHA512:
6
- metadata.gz: 65dad2b78acf24ce17d78d677b99daddc806ef5bc72f5a6ea7f0f9418b40828d0b709bbe313c711a6ce85b0769ff2577097582ae04207e2426c1c7cef4aea99c
7
- data.tar.gz: f02830fbb953af904c67c6a5bdec5cddc20fb57fcc8d536cdde5cdf8da16842e4b6fbf125ae0d489349b49cf2f70eb0723713a8ae36a628f82c6f576fb63b34f
6
+ metadata.gz: b71de56cdff03743ced725d67478c80be77a2772421dd73c1bdc8d8d4a67fc17f6b1907f819b421feb40de91960afa64cf71ab5a9e5dbe3193924fd49ba8289c
7
+ data.tar.gz: 39ca211fe213c2d6858ac2fbd669953d363de542443ceb85c4c0f7d8dc578cde12be976e94eb0a578d718e29cfeb65e29bba1516060f1f1c57279c2563e3f95f
data/.gitignore CHANGED
@@ -12,6 +12,7 @@ lib/bundler/man
12
12
  pkg
13
13
  rdoc
14
14
  spec/reports
15
+ spec/cache
15
16
  test/tmp
16
17
  test/version_tmp
17
18
  tmp
data/.rubocop.yml CHANGED
@@ -50,4 +50,4 @@ IndentationWidth:
50
50
 
51
51
  # TODO: Shorten to 100.
52
52
  ClassLength:
53
- Max: 186
53
+ Max: 194
data/CHANGELOG.md CHANGED
@@ -2,6 +2,16 @@
2
2
 
3
3
  ## Master
4
4
 
5
+ * Now Transpec does dynamic code analysis!
6
+ * Support conversion of `have(n).items` matcher ([#5](https://github.com/yujinakayama/transpec/issues/5))
7
+ * Add `-s/--skip-dynamic-analysis` option that allows to skip dynamic analysis and convert with only static analysis
8
+ * Add `-c/--rspec-command` option that allows to specify command to run RSpec that is used for dynamic analysis
9
+ * Check contexts correctly with runtime information
10
+ * Detect same name but non-RSpec methods with runtime information ([#4](https://github.com/yujinakayama/transpec/issues/4))
11
+ * Consider runtime type information when converting `=~` to `match_array`
12
+ * Rename `-d/--disable` option to `-k/--keep` and change its syntax types
13
+ * Rename `--commit-message` option to `--generate-commit-message`
14
+
5
15
  ## v0.2.6
6
16
 
7
17
  * Fix a bug where `Node#each_descendent_node` enumerates only within depth 2
data/README.md 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
 
@@ -91,9 +91,9 @@ describe Account do
91
91
  end
92
92
  ```
93
93
 
94
- ### Real Examples
94
+ ### Actual Examples
95
95
 
96
- You can see real conversion examples below:
96
+ You can see actual conversion examples below:
97
97
 
98
98
  * https://github.com/yujinakayama/guard/commit/transpec-demo
99
99
  * https://github.com/yujinakayama/mail/commit/transpec-demo
@@ -113,27 +113,32 @@ Before converting your specs:
113
113
  * Run `rspec` and check if all the specs pass.
114
114
  * Ensure the Git repository is clean. (You don't want to mix up your changes and Transpec's changes, right?)
115
115
 
116
- Then, run `transpec` (using `--commit-message` is recommended) in the project root directory:
116
+ Then, run `transpec` (using `--generate-commit-message` is recommended) in the project root directory:
117
117
 
118
118
  ```bash
119
119
  $ cd some-project
120
- $ transpec --commit-message
121
- Processing spec/spec_helper.rb
122
- Processing spec/spec_spec.rb
123
- Processing spec/support/file_helper.rb
124
- Processing spec/support/shared_context.rb
125
- Processing spec/transpec/ast/scanner_spec.rb
126
- Processing spec/transpec/ast/scope_stack_spec.rb
120
+ $ transpec --generate-commit-message
121
+ Copying project for dynamic analysis...
122
+ Running dynamic analysis with command "bundle exec rspec"...
123
+ ...............................................................................
124
+ ...................
125
+
126
+ Finished in 13.07 seconds
127
+ 100 examples, 0 failures
128
+
129
+ Converting spec/spec_helper.rb
130
+ Converting spec/support/cache_helper.rb
131
+ Converting spec/support/file_helper.rb
132
+ Converting spec/support/shared_context.rb
133
+ Converting spec/transpec/ast/node_spec.rb
127
134
  ```
128
135
 
129
- This will convert and overwrite all spec files in the `spec` directory.
136
+ This will run your specs, convert them, and overwrite all spec files in the `spec` directory.
130
137
 
131
138
  After the conversion, run `rspec` again and check whether all pass:
132
139
 
133
140
  ```bash
134
141
  $ bundle exec rspec
135
- # ...
136
- 843 examples, 0 failures
137
142
  ```
138
143
 
139
144
  If all pass, commit the changes with auto-generated message:
@@ -162,7 +167,23 @@ Processing spec/spec_spec.rb
162
167
  Processing spec/support/file_helper.rb
163
168
  ```
164
169
 
165
- ### `-m/--commit-message`
170
+ ### `-s/--skip-dynamic-analysis`
171
+
172
+ Skip dynamic analysis and convert with only static analysis. Note that specifying this option decreases the conversion accuracy.
173
+
174
+ ### `-c/--rspec-command`
175
+
176
+ Specify command to run RSpec that is used for dynamic analysis.
177
+
178
+ Transpec needs to run your specs in copied project directory for dynamic analysis.
179
+ If your project requires some special setup or commands to run specs, use this option.
180
+ `bundle exec rspec` is used by default.
181
+
182
+ ```bash
183
+ $ transpec --rspec-command "./some_special_setup.sh && bundle exec rspec"
184
+ ```
185
+
186
+ ### `-m/--generate-commit-message`
166
187
 
167
188
  Generate commit message that describes conversion summary.
168
189
  Currently only Git is supported.
@@ -173,22 +194,23 @@ When you commit, you need to run the following command to use the generated mess
173
194
  $ git commit -eF .git/COMMIT_EDITMSG
174
195
  ```
175
196
 
176
- ### `-d/--disable`
197
+ ### `-k/--keep`
177
198
 
178
- Disable specific conversions.
199
+ Keep specific syntaxes by disabling conversions.
179
200
 
180
201
  ```bash
181
- $ transpec --disable expect_to_receive,allow_to_receive
202
+ $ transpec --keep should_receive,stub
182
203
  ```
183
204
 
184
- #### Available conversion types
205
+ #### Available syntax types
185
206
 
186
- Conversion Type | Target Syntax | Converted Syntax
187
- --------------------|----------------------------------|----------------------------
188
- `expect_to_matcher` | `obj.should matcher` | `expect(obj).to matcher`
189
- `expect_to_receive` | `obj.should_receive` | `expect(obj).to receive`
190
- `allow_to_receive` | `obj.stub` | `allow(obj).to receive`
191
- `deprecated` | `obj.stub!`, `mock('foo')`, etc. | `obj.stub`, `double('foo')`
207
+ Type | Target Syntax | Converted Syntax
208
+ -----------------|----------------------------------|----------------------------
209
+ `should` | `obj.should matcher` | `expect(obj).to matcher`
210
+ `should_receive` | `obj.should_receive` | `expect(obj).to receive`
211
+ `stub` | `obj.stub` | `allow(obj).to receive`
212
+ `have_items` | `expect(obj).to have(x).items` | `expect(obj.size).to eq(x)`
213
+ `deprecated` | `obj.stub!`, `mock('foo')`, etc. | `obj.stub`, `double('foo')`
192
214
 
193
215
  ### `-n/--negative-form`
194
216
 
@@ -242,7 +264,7 @@ describe 'converted spec with -p/--no-parentheses-matcher-arg option' do
242
264
  end
243
265
  ```
244
266
 
245
- ## Troubleshooting
267
+ ## Inconvertible Specs
246
268
 
247
269
  You might see the following warning while conversion:
248
270
 
@@ -270,11 +292,11 @@ end
270
292
 
271
293
  ### Reason
272
294
 
273
- * `should` is defined on `Kernel` (included by `Object`), so you can use `should` almost everywhere.
274
- * `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`.
295
+ * `should` is defined on `Kernel` module that is included by `Object` class, so you can use `should` almost everywhere.
296
+ * `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).
275
297
 
276
298
  With the above example, in the context of `1.should == 1`, the `self` is an instance of `MyAwesomeTestRunner`.
277
- So Transpec tracks contexts and skips conversion if the target syntax cannot be converted in a case like this.
299
+ Transpec tracks contexts and skips conversion if the target syntax cannot be converted in a case like this.
278
300
 
279
301
  ### Solution
280
302
 
@@ -295,8 +317,8 @@ expect(obj).not_to matcher
295
317
  expect(obj).to_not matcher # with `--negative-form to_not`
296
318
  ```
297
319
 
298
- * Disabled by: `--disable expect_to_matcher`
299
- * Related Information: [Myron Marston » RSpec's New Expectation Syntax](http://myronmars.to/n/dev-blog/2012/06/rspecs-new-expectation-syntax)
320
+ * Disabled by: `--keep should`
321
+ * See also: [Myron Marston » RSpec's New Expectation Syntax](http://myronmars.to/n/dev-blog/2012/06/rspecs-new-expectation-syntax)
300
322
 
301
323
  ### Operator matchers
302
324
 
@@ -316,7 +338,7 @@ expect('string').to match(/^str/)
316
338
  expect([1, 2, 3]).to match_array([2, 1, 3])
317
339
  ```
318
340
 
319
- * 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)
341
+ * 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)
320
342
 
321
343
  ### `be_close` matcher
322
344
 
@@ -328,10 +350,43 @@ expect([1, 2, 3]).to match_array([2, 1, 3])
328
350
  (1.0 / 3.0).should be_within(0.001).of(0.333)
329
351
  ```
330
352
 
331
- * Disabled by: `--disable deprecated`
332
- * Related Information: [New be within matcher and RSpec.deprecate fix · rspec/rspec-expectations](https://github.com/rspec/rspec-expectations/pull/32)
353
+ * Disabled by: `--keep deprecated`
354
+ * See also: [New be within matcher and RSpec.deprecate fix · rspec/rspec-expectations](https://github.com/rspec/rspec-expectations/pull/32)
355
+
356
+ ### `have(n).items` matcher
357
+
358
+ ```ruby
359
+ # Targets
360
+ expect(collection).to have(3).items
361
+ expect(collection).to have_exactly(3).items
362
+ expect(collection).to have_at_least(3).items
363
+ expect(collection).to have_at_most(3).items
364
+ collection.should have(3).items
365
+
366
+ expect(team).to have(3).players
367
+
368
+ # Assume #players is a private method.
369
+ expect(team).to have(3).players
370
+
371
+ # Converted
372
+ expect(collection.size).to eq(3)
373
+ expect(collection.size).to be >= 3
374
+ expect(collection.size).to be <= 3
375
+ collection.size.should == 3 # with `--keep should`
376
+
377
+ expect(team.players.size).to eq(3)
378
+
379
+ # have(n).items matcher invokes #players even if it's a private method.
380
+ expect(team.send(:players).size).to eq(3)
381
+ ```
382
+
383
+ 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`.
384
+ If you choose so, disable this conversion with `--keep have_items`.
385
+
386
+ * Disabled by: `--keep have_items`
387
+ * 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)
333
388
 
334
- ### Expectations on Proc
389
+ ### Expectations on Block
335
390
 
336
391
  ```ruby
337
392
  # Targets
@@ -343,8 +398,8 @@ proc { do_something }.should raise_error
343
398
  expect { do_something }.to raise_error
344
399
  ```
345
400
 
346
- * Disabled by: `--disable expect_to_matcher`
347
- * 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)
401
+ * Disabled by: `--keep should`
402
+ * 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)
348
403
 
349
404
  ### Negative error expectations with specific error
350
405
 
@@ -357,11 +412,11 @@ lambda { do_something }.should_not raise_error(SomeErrorClass)
357
412
 
358
413
  # Converted
359
414
  expect { do_something }.not_to raise_error
360
- lambda { do_something }.should_not raise_error # with `--disable expect_to_matcher`
415
+ lambda { do_something }.should_not raise_error # with `--keep should`
361
416
  ```
362
417
 
363
- * Disabled by: `--disable deprecated`
364
- * Related Information: [Consider deprecating `expect { }.not_to raise_error(SpecificErrorClass)` · rspec/rspec-expectations](https://github.com/rspec/rspec-expectations/issues/231)
418
+ * Disabled by: `--keep deprecated`
419
+ * See also: [Consider deprecating `expect { }.not_to raise_error(SpecificErrorClass)` · rspec/rspec-expectations](https://github.com/rspec/rspec-expectations/issues/231)
365
420
 
366
421
  ### Message expectations
367
422
 
@@ -375,8 +430,8 @@ expect(obj).to receive(:foo)
375
430
  expect_any_instance_of(SomeClass).to receive(:foo)
376
431
  ```
377
432
 
378
- * Disabled by: `--disable expect_to_receive`
379
- * Related Information: [RSpec's new message expectation syntax - Tea is awesome.](http://teaisaweso.me/blog/2013/05/27/rspecs-new-message-expectation-syntax/)
433
+ * Disabled by: `--keep should_receive`
434
+ * See also: [RSpec's new message expectation syntax - Tea is awesome.](http://teaisaweso.me/blog/2013/05/27/rspecs-new-message-expectation-syntax/)
380
435
 
381
436
  ### Message expectations that are actually method stubs
382
437
 
@@ -390,14 +445,14 @@ SomeClass.any_instance.should_receive(:foo).at_least(0)
390
445
 
391
446
  # Converted
392
447
  allow(obj).to receive(:foo)
393
- obj.stub(:foo) # with `--disable allow_to_receive`
448
+ obj.stub(:foo) # with `--keep stub`
394
449
 
395
450
  allow_any_instance_of(SomeClass).to receive(:foo)
396
- SomeClass.any_instance.stub(:foo) # with `--disable allow_to_receive`
451
+ SomeClass.any_instance.stub(:foo) # with `--keep stub`
397
452
  ```
398
453
 
399
- * Disabled by: `--disable deprecated`
400
- * Related Information: [Don't allow at_least(0) · rspec/rspec-mocks](https://github.com/rspec/rspec-mocks/issues/133)
454
+ * Disabled by: `--keep deprecated`
455
+ * See also: [Don't allow at_least(0) · rspec/rspec-mocks](https://github.com/rspec/rspec-mocks/issues/133)
401
456
 
402
457
  ### Method stubs
403
458
 
@@ -422,8 +477,8 @@ allow(obj).to receive(:bar).and_return(2)
422
477
  allow_any_instance_of(SomeClass).to receive(:foo)
423
478
  ```
424
479
 
425
- * Disabled by: `--disable allow_to_receive`
426
- * Related Information: [RSpec's new message expectation syntax - Tea is awesome.](http://teaisaweso.me/blog/2013/05/27/rspecs-new-message-expectation-syntax/)
480
+ * Disabled by: `--keep stub`
481
+ * See also: [RSpec's new message expectation syntax - Tea is awesome.](http://teaisaweso.me/blog/2013/05/27/rspecs-new-message-expectation-syntax/)
427
482
 
428
483
  ### Deprecated method stub aliases
429
484
 
@@ -433,12 +488,12 @@ obj.stub!(:foo)
433
488
  obj.unstub!(:foo)
434
489
 
435
490
  # Converted
436
- obj.stub(:foo) # with `--disable allow_to_receive`
491
+ obj.stub(:foo) # with `--keep stub`
437
492
  obj.unstub(:foo)
438
493
  ```
439
494
 
440
- * Disabled by: `--disable deprecated`
441
- * Related Information: [Consider deprecating and/or removing #stub! and #unstub! at some point · rspec/rspec-mocks](https://github.com/rspec/rspec-mocks/issues/122)
495
+ * Disabled by: `--keep deprecated`
496
+ * See also: [Consider deprecating and/or removing #stub! and #unstub! at some point · rspec/rspec-mocks](https://github.com/rspec/rspec-mocks/issues/122)
442
497
 
443
498
  ### Method stubs with deprecated specification of number of times
444
499
 
@@ -449,11 +504,11 @@ obj.stub(:foo).at_least(0)
449
504
 
450
505
  # Converted
451
506
  allow(obj).to receive(:foo)
452
- obj.stub(:foo) # with `--disable allow_to_receive`
507
+ obj.stub(:foo) # with `--keep stub`
453
508
  ```
454
509
 
455
- * Disabled by: `--disable deprecated`
456
- * Related Information: [Don't allow at_least(0) · rspec/rspec-mocks](https://github.com/rspec/rspec-mocks/issues/133)
510
+ * Disabled by: `--keep deprecated`
511
+ * See also: [Don't allow at_least(0) · rspec/rspec-mocks](https://github.com/rspec/rspec-mocks/issues/133)
457
512
 
458
513
  ### Deprecated test double aliases
459
514
 
@@ -466,8 +521,8 @@ mock('something')
466
521
  double('something')
467
522
  ```
468
523
 
469
- * Disabled by: `--disable deprecated`
470
- * Related Information: [Deprecate "stub" for doubles · rspec/rspec-mocks](https://github.com/rspec/rspec-mocks/issues/214)
524
+ * Disabled by: `--keep deprecated`
525
+ * See also: [Deprecate "stub" for doubles · rspec/rspec-mocks](https://github.com/rspec/rspec-mocks/issues/214)
471
526
 
472
527
  ## Compatibility
473
528