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
@@ -1,22 +1,58 @@
1
1
  # coding: utf-8
2
2
 
3
3
  require 'transpec/syntax'
4
- require 'transpec/syntax/expectizable'
5
- require 'transpec/syntax/able_to_allow_no_message'
6
- require 'transpec/syntax/able_to_target_any_instance'
4
+ require 'transpec/syntax/mixin/send'
5
+ require 'transpec/syntax/mixin/monkey_patch'
6
+ require 'transpec/syntax/mixin/expectizable'
7
+ require 'transpec/syntax/mixin/allow_no_message'
8
+ require 'transpec/syntax/mixin/any_instance'
7
9
 
8
10
  module Transpec
9
11
  class Syntax
10
12
  class ShouldReceive < Syntax
11
- include Expectizable, AbleToAllowNoMessage, AbleToTargetAnyInstance
13
+ include Mixin::Send
14
+ include Mixin::MonkeyPatch
15
+ include Mixin::Expectizable
16
+ include Mixin::AllowNoMessage
17
+ include Mixin::AnyInstance
12
18
 
13
19
  alias_method :useless_expectation?, :allow_no_message?
14
20
 
21
+ def self.target_method?(receiver_node, method_name)
22
+ !receiver_node.nil? && [:should_receive, :should_not_receive].include?(method_name)
23
+ end
24
+
25
+ def register_request_for_dynamic_analysis(rewriter)
26
+ register_request_of_syntax_availability_inspection(
27
+ rewriter,
28
+ :expect_to_receive_available?,
29
+ [:expect, :receive]
30
+ )
31
+
32
+ register_request_of_syntax_availability_inspection(
33
+ rewriter,
34
+ :allow_to_receive_available?,
35
+ [:allow, :receive]
36
+ )
37
+ end
38
+
39
+ def expect_to_receive_available?
40
+ check_syntax_availability(__method__)
41
+ end
42
+
43
+ def allow_to_receive_available?
44
+ check_syntax_availability(__method__)
45
+ end
46
+
15
47
  def positive?
16
48
  method_name == :should_receive
17
49
  end
18
50
 
19
51
  def expectize!(negative_form = 'not_to')
52
+ unless expect_to_receive_available?
53
+ fail InvalidContextError.new(selector_range, "##{method_name}", '#expect')
54
+ end
55
+
20
56
  convert_to_syntax!('expect', negative_form)
21
57
  register_record(:expect, negative_form)
22
58
  end
@@ -24,6 +60,10 @@ module Transpec
24
60
  def allowize_useless_expectation!(negative_form = 'not_to')
25
61
  return unless useless_expectation?
26
62
 
63
+ unless allow_to_receive_available?
64
+ fail InvalidContextError.new(selector_range, "##{method_name}", '#allow')
65
+ end
66
+
27
67
  convert_to_syntax!('allow', negative_form)
28
68
  remove_allowance_for_no_message!
29
69
 
@@ -42,10 +82,6 @@ module Transpec
42
82
  private
43
83
 
44
84
  def convert_to_syntax!(syntax, negative_form)
45
- unless context.non_monkey_patch_mock_available?
46
- fail InvalidContextError.new(selector_range, "##{method_name}", "##{syntax}")
47
- end
48
-
49
85
  if any_instance?
50
86
  wrap_class_with_any_instance_of!(syntax)
51
87
  else
@@ -68,14 +104,6 @@ module Transpec
68
104
  end
69
105
  end
70
106
 
71
- def self.target_receiver_node?(node)
72
- !node.nil?
73
- end
74
-
75
- def self.target_method_names
76
- [:should_receive, :should_not_receive]
77
- end
78
-
79
107
  def wrap_class_with_any_instance_of!(syntax)
80
108
  insert_before(subject_range, "#{syntax}_any_instance_of(")
81
109
  map = subject_node.loc
@@ -1,19 +1,27 @@
1
1
  # coding: utf-8
2
2
 
3
- require 'transpec/context'
4
- require 'transpec/report'
3
+ require 'transpec/static_context_inspector'
5
4
  require 'transpec/record'
5
+ require 'transpec/report'
6
6
 
7
7
  module Transpec
8
8
  class Syntax
9
- attr_reader :node, :ancestor_nodes, :source_rewriter, :report
9
+ attr_reader :node, :ancestor_nodes, :source_rewriter, :runtime_data, :report
10
+
11
+ def self.inherited(subclass)
12
+ all_syntaxes << subclass
13
+ end
10
14
 
11
- def self.all
15
+ def self.all_syntaxes
12
16
  @subclasses ||= []
13
17
  end
14
18
 
15
- def self.inherited(subclass)
16
- all << subclass
19
+ def self.standalone_syntaxes
20
+ @standalone_syntaxes ||= all_syntaxes.select(&:standalone?)
21
+ end
22
+
23
+ def self.standalone?
24
+ true
17
25
  end
18
26
 
19
27
  def self.snake_case_name
@@ -23,22 +31,26 @@ module Transpec
23
31
  end
24
32
  end
25
33
 
26
- def self.target_node?(node)
27
- return false unless node.type == :send
28
- receiver_node, method_name, *_ = *node
29
- return false unless target_receiver_node?(receiver_node)
30
- target_method_names.include?(method_name)
34
+ def self.register_request_for_dynamic_analysis(node, rewriter)
31
35
  end
32
36
 
33
- def initialize(node, ancestor_nodes, source_rewriter, report = Report.new)
37
+ def self.target_node?(node, runtime_data = nil)
38
+ false
39
+ end
40
+
41
+ def initialize(node, ancestor_nodes, source_rewriter = nil, runtime_data = nil, report = nil)
34
42
  @node = node
35
43
  @ancestor_nodes = ancestor_nodes
36
44
  @source_rewriter = source_rewriter
37
- @report = report
45
+ @runtime_data = runtime_data
46
+ @report = report || Report.new
47
+ end
48
+
49
+ def register_request_for_dynamic_analysis(rewriter)
38
50
  end
39
51
 
40
- def context
41
- @context ||= Context.new(@ancestor_nodes)
52
+ def static_context_inspector
53
+ @static_context_inspector ||= StaticContextInspector.new(@ancestor_nodes)
42
54
  end
43
55
 
44
56
  def parent_node
@@ -51,12 +63,8 @@ module Transpec
51
63
 
52
64
  private
53
65
 
54
- def self.target_receiver_node?(node)
55
- false
56
- end
57
-
58
- def self.target_method_names
59
- []
66
+ def runtime_node_data(node)
67
+ @runtime_data && @runtime_data[node]
60
68
  end
61
69
 
62
70
  def remove(range)
data/lib/transpec/util.rb CHANGED
@@ -44,14 +44,24 @@ module Transpec
44
44
  map.begin.source.start_with?('<<')
45
45
  end
46
46
 
47
+ def contain_here_document?(node)
48
+ here_document?(node) || node.each_descendent_node.any? { |n| here_document?(n) }
49
+ end
50
+
47
51
  def in_parentheses?(node)
48
52
  return false unless node.type == :begin
49
53
  source = node.loc.expression.source
50
54
  source[0] == '(' && source[-1] == ')'
51
55
  end
52
56
 
53
- def indentation_of_line(node)
54
- line = node.loc.expression.source_line
57
+ def indentation_of_line(arg)
58
+ range = case arg
59
+ when AST::Node then arg.loc.expression
60
+ when Parser::Source::Range then arg
61
+ else fail ArgumentError, "Invalid argument #{arg}"
62
+ end
63
+
64
+ line = range.source_line
55
65
  /^(?<indentation>\s*)\S/ =~ line
56
66
  indentation
57
67
  end
@@ -3,9 +3,9 @@
3
3
  module Transpec
4
4
  # http://semver.org/
5
5
  module Version
6
- MAJOR = 0
7
- MINOR = 2
8
- PATCH = 6
6
+ MAJOR = 1
7
+ MINOR = 0
8
+ PATCH = 0
9
9
 
10
10
  def self.to_s
11
11
  [MAJOR, MINOR, PATCH].join('.')
data/spec/spec_helper.rb CHANGED
@@ -1,13 +1,15 @@
1
1
  # coding: utf-8
2
2
 
3
3
  RSpec.configure do |config|
4
- # Yes, I'm writing specs in should syntax intentionally!
5
- config.expect_with :rspec do |c|
6
- c.syntax = :should
7
- end
4
+ unless ENV['TRANSPEC_TEST']
5
+ # Yes, I'm writing specs in should syntax intentionally!
6
+ config.expect_with :rspec do |c|
7
+ c.syntax = :should
8
+ end
8
9
 
9
- config.mock_with :rspec do |c|
10
- c.syntax = :should
10
+ config.mock_with :rspec do |c|
11
+ c.syntax = :should
12
+ end
11
13
  end
12
14
 
13
15
  config.color_enabled = true
@@ -0,0 +1,50 @@
1
+ # encoding: utf-8
2
+
3
+ require 'digest/sha1'
4
+
5
+ module CacheHelper
6
+ module_function
7
+
8
+ def with_cache(key)
9
+ cache_file_path = cache_file_path(key)
10
+
11
+ if File.exist?(cache_file_path)
12
+ load_cache(cache_file_path)
13
+ else
14
+ data = yield
15
+ save_cache(cache_file_path, data)
16
+ data
17
+ end
18
+ end
19
+
20
+ def load_cache(path)
21
+ File.open(path) do |file|
22
+ Marshal.load(file)
23
+ end
24
+ end
25
+
26
+ def save_cache(path, data)
27
+ File.open(path, 'w') do |file|
28
+ Marshal.dump(data, file)
29
+ end
30
+ end
31
+
32
+ def cache_file_path(key)
33
+ filename = Digest::SHA1.hexdigest(key)
34
+ File.join(cache_dir, filename)
35
+ end
36
+
37
+ def cache_dir
38
+ @cache_dir ||= begin
39
+ spec_dir = File.expand_path(File.join(File.dirname(__FILE__), '..'))
40
+ cache_dir = File.join(spec_dir, 'cache')
41
+
42
+ unless Dir.exist?(cache_dir)
43
+ require 'fileutils'
44
+ FileUtils.mkdir_p(cache_dir)
45
+ end
46
+
47
+ cache_dir
48
+ end
49
+ end
50
+ end
@@ -1,8 +1,10 @@
1
1
  # coding: utf-8
2
2
 
3
+ require 'transpec/dynamic_analyzer'
3
4
  require 'transpec/ast/builder'
4
5
  require 'transpec/ast/scanner'
5
6
  require 'transpec/syntax/should'
7
+ require 'transpec/syntax/expect'
6
8
  require 'parser'
7
9
  require 'parser/current'
8
10
  require 'tmpdir'
@@ -26,6 +28,31 @@ shared_context 'parsed objects' do
26
28
  let(:rewritten_source) { source_rewriter.process }
27
29
  end
28
30
 
31
+ # This context requires `source` to be defined with #let.
32
+ shared_context 'dynamic analysis objects' do
33
+ include_context 'isolated environment'
34
+
35
+ let(:source_path) { 'spec/example_spec.rb' }
36
+
37
+ let(:source_buffer) do
38
+ buffer = Parser::Source::Buffer.new(source_path)
39
+ buffer.source = source
40
+ buffer
41
+ end
42
+
43
+ runtime_data_cache = {}
44
+
45
+ let(:runtime_data) do
46
+ if runtime_data_cache[source]
47
+ runtime_data_cache[source]
48
+ else
49
+ FileHelper.create_file(source_path, source)
50
+ dynamic_analyzer = Transpec::DynamicAnalyzer.new(silent: true)
51
+ runtime_data_cache[source] = dynamic_analyzer.analyze
52
+ end
53
+ end
54
+ end
55
+
29
56
  shared_context 'should object' do
30
57
  let(:should_object) do
31
58
  Transpec::AST::Scanner.scan(ast) do |node, ancestor_nodes|
@@ -33,12 +60,33 @@ shared_context 'should object' do
33
60
  return Transpec::Syntax::Should.new(
34
61
  node,
35
62
  ancestor_nodes,
36
- source_rewriter
63
+ source_rewriter,
64
+ runtime_data
37
65
  )
38
66
  end
39
67
 
40
68
  fail 'No should node is found!'
41
69
  end
70
+
71
+ let(:runtime_data) { nil }
72
+ end
73
+
74
+ shared_context 'expect object' do
75
+ let(:expect_object) do
76
+ Transpec::AST::Scanner.scan(ast) do |node, ancestor_nodes|
77
+ next unless Transpec::Syntax::Expect.target_node?(node)
78
+ return Transpec::Syntax::Expect.new(
79
+ node,
80
+ ancestor_nodes,
81
+ source_rewriter,
82
+ runtime_data
83
+ )
84
+ end
85
+
86
+ fail 'No expect node is found!'
87
+ end
88
+
89
+ let(:runtime_data) { nil }
42
90
  end
43
91
 
44
92
  shared_context 'isolated environment' do
@@ -30,6 +30,71 @@ module Transpec
30
30
  # (send nil :do_something
31
31
  # (lvar :arg_a))))
32
32
 
33
+ describe '#parent_node' do
34
+ context 'when the node has parent' do
35
+ let(:target_node) do
36
+ ast.each_descendent_node do |node|
37
+ return node if node == s(:args)
38
+ end
39
+ end
40
+
41
+ it 'returns the parent node' do
42
+ target_node.parent_node.type.should == :block
43
+ end
44
+ end
45
+
46
+ context 'when the node has parent' do
47
+ it 'returns nil' do
48
+ ast.parent_node.should be_nil
49
+ end
50
+ end
51
+ end
52
+
53
+ describe '#each_ancestor_node' do
54
+ let(:target_node) do
55
+ ast.each_descendent_node do |node|
56
+ return node if node == s(:args)
57
+ end
58
+ end
59
+
60
+ let(:expected_types) { [:block, :def] }
61
+
62
+ context 'when a block is given' do
63
+ it 'yields each ancestor node' do
64
+ index = 0
65
+
66
+ target_node.each_ancestor_node do |node|
67
+ expected_type = expected_types[index]
68
+ node.type.should == expected_type
69
+ index += 1
70
+ end
71
+
72
+ index.should_not == 0
73
+ end
74
+
75
+ it 'returns itself' do
76
+ returned_value = target_node.each_ancestor_node { }
77
+ returned_value.should be(target_node)
78
+ end
79
+ end
80
+
81
+ context 'when no block is given' do
82
+ it 'returns enumerator' do
83
+ target_node.each_ancestor_node.should be_a(Enumerator)
84
+ end
85
+
86
+ describe 'the returned enumerator' do
87
+ it 'enumerates the ancestor nodes' do
88
+ enumerator = target_node.each_ancestor_node
89
+
90
+ expected_types.each do |expected_type|
91
+ enumerator.next.type.should == expected_type
92
+ end
93
+ end
94
+ end
95
+ end
96
+ end
97
+
33
98
  describe '#each_child_node' do
34
99
  let(:expected_types) { [:args, :block] }
35
100