transpec 0.2.6 → 1.0.0

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