what_dya_return 0.1.0 → 0.2.1

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 (33) hide show
  1. checksums.yaml +4 -4
  2. data/.rubocop.yml +10 -1
  3. data/README.md +54 -2
  4. data/lib/what_dya_return/ast/builder.rb +52 -0
  5. data/lib/what_dya_return/ast/node/array_node.rb +8 -0
  6. data/lib/what_dya_return/ast/node/begin_node.rb +2 -0
  7. data/lib/what_dya_return/ast/node/block_node.rb +8 -0
  8. data/lib/what_dya_return/ast/node/break_node.rb +8 -0
  9. data/lib/what_dya_return/ast/node/case_node.rb +8 -0
  10. data/lib/what_dya_return/ast/node/def_node.rb +8 -0
  11. data/lib/what_dya_return/ast/node/ensure_node.rb +8 -0
  12. data/lib/what_dya_return/ast/node/for_node.rb +8 -0
  13. data/lib/what_dya_return/{ext/rubocop/ast → ast}/node/if_node.rb +5 -2
  14. data/lib/what_dya_return/ast/node/next_node.rb +8 -0
  15. data/lib/what_dya_return/ast/node/resbody_node.rb +8 -0
  16. data/lib/what_dya_return/ast/node/rescue_node.rb +8 -0
  17. data/lib/what_dya_return/ast/node/return_node.rb +8 -0
  18. data/lib/what_dya_return/ast/node/until_node.rb +23 -0
  19. data/lib/what_dya_return/ast/node/when_node.rb +8 -0
  20. data/lib/what_dya_return/ast/node/while_node.rb +23 -0
  21. data/lib/what_dya_return/ast/node.rb +8 -0
  22. data/lib/what_dya_return/ast/processed_source.rb +23 -0
  23. data/lib/what_dya_return/ast.rb +25 -0
  24. data/lib/what_dya_return/ext/rubocop-ast/node.rb +24 -0
  25. data/lib/what_dya_return/extractor.rb +19 -3
  26. data/lib/what_dya_return/processor.rb +137 -53
  27. data/lib/what_dya_return/statement_checker/reachable_to_next_statement.rb +18 -21
  28. data/lib/what_dya_return/statement_checker/returnable_statement.rb +159 -0
  29. data/lib/what_dya_return/statement_checker.rb +10 -0
  30. data/lib/what_dya_return/version.rb +1 -1
  31. data/lib/what_dya_return.rb +3 -0
  32. metadata +24 -5
  33. data/lib/what_dya_return/ext/rubocop/ast/builder.rb +0 -23
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 0f7dfddc7b09e314b0c96b8e8ad634f517f03e5252722c8aa16f3883caba8293
4
- data.tar.gz: 3f6ec35cc912717543a3811c38bfbce34a92b9256ea753d02a0580e06cb252d8
3
+ metadata.gz: 3de4d93782243ac22ab3781e0959bf304ab9b98efa92991b453d67894e1c59b8
4
+ data.tar.gz: 1e4152cdf2f48a459197dc718cd8488046de245d7f2f14265878145cb6f1c50d
5
5
  SHA512:
6
- metadata.gz: 11effd57e51749aac58251616c68d3494eab40bff6d0df63b4c8f29e182ff17fa4e35210f8c57b298b9efe68699f7822acb4c3a3af761a4f66bd7835e641aad0
7
- data.tar.gz: 1718c98ba23d4801ba67449843e2742d3f4449a867fa00518c683328765f404263704ef4a3389685c0d55ef782fa83b4a0210a0c7936b7e4991b4fff709bd2fe
6
+ metadata.gz: df316fd95cd034fd7b08c9a73c8cc306e307d6f16f8ebe62b2fdc1ba58af2f7abd23d4d8ae1af67b6ea9cf6d9d995c271370219dff0f435cc53a360766d22489
7
+ data.tar.gz: abeabcf9cb05f5a9610229a1c9542bad5b81e2a1bb506f37bcdb935d152190cbf94b004d32bd27dfa323039523e97b27caec0cb4583155eba03f2cef1f402f53
data/.rubocop.yml CHANGED
@@ -1,10 +1,19 @@
1
1
  require:
2
2
  - rubocop-performance
3
3
 
4
+ AllCops:
5
+ SuggestExtensions: false
6
+
4
7
  Metrics/ClassLength:
5
8
  Exclude:
9
+ - 'lib/what_dya_return/extractor.rb'
10
+ - 'lib/what_dya_return/processor.rb'
6
11
  - 'test/what_dya_return/extractor_test.rb'
7
12
 
8
13
  Metrics/MethodLength:
14
+ Max: 30
9
15
  Exclude:
10
- - 'test/what_dya_return/extractor_test.rb'
16
+ - 'test/*'
17
+
18
+ Style/Documentation:
19
+ Enabled: false
data/README.md CHANGED
@@ -21,7 +21,7 @@ end
21
21
  ## Usage
22
22
 
23
23
  ```rb
24
- result = WhatDyaReturn::Extractor.new.extract(<<-CODE)
24
+ WhatDyaReturn::Extractor.new.extract(<<-CODE)
25
25
  def foo
26
26
  if bar
27
27
  42
@@ -30,8 +30,60 @@ result = WhatDyaReturn::Extractor.new.extract(<<-CODE)
30
30
  end
31
31
  end
32
32
  CODE
33
+ # => ['42', "'baz'"]
34
+
35
+ WhatDyaReturn::Extractor.new.extract(<<-CODE)
36
+ def foo
37
+ return 42 if bar # `bar` is not evaluated in this gem
38
+
39
+ 123
40
+ end
41
+ CODE
42
+ # => ['42', '123']
43
+
44
+ WhatDyaReturn::Extractor.new.extract(<<-CODE)
45
+ def foo
46
+ return 42
47
+
48
+ 123
49
+ end
50
+ CODE
51
+ # => ['42']
52
+
53
+ puts WhatDyaReturn::Extractor.new.extract(<<-CODE)
54
+ def foo
55
+ do_something
56
+ rescue
57
+ 2
58
+ else
59
+ 3
60
+ ensure
61
+ 4
62
+ end
63
+ CODE
64
+ # => ['3', '2']
65
+
66
+ puts WhatDyaReturn::Extractor.new.extract(<<-CODE)
67
+ def foo
68
+ for i in 1..10
69
+ 1
70
+ break 2 if baz
71
+ 3
72
+ end
73
+ end
74
+ CODE
75
+ # => ['2', '1..10']
76
+
77
+ puts WhatDyaReturn::Extractor.new.extract(<<-CODE)
78
+ def foo
79
+ 1.times do
80
+ break 2 if bar
81
+ 3
82
+ end
83
+ end
84
+ CODE
85
+ # => ['2', '1.times']
33
86
 
34
- result # => ['42', "'baz'"]
35
87
  ```
36
88
 
37
89
  ## Caution
@@ -0,0 +1,52 @@
1
+ # frozen_string_literal: true
2
+
3
+ module WhatDyaReturn
4
+ module AST
5
+ #
6
+ # This file is based on https://github.com/rubocop/rubocop-ast/blob/v1.28.1/lib/rubocop/ast/builder.rb
7
+ # The original code is licensed under the MIT License.
8
+ #
9
+ # https://github.com/rubocop/rubocop-ast/blob/v1.28.1/LICENSE.txt
10
+ #
11
+ class Builder < Parser::Builders::Default
12
+ NODE_MAP = {
13
+ def: DefNode,
14
+ begin: BeginNode,
15
+ kwbegin: BeginNode,
16
+ array: ArrayNode,
17
+ return: ReturnNode,
18
+ if: IfNode,
19
+ case: CaseNode,
20
+ when: WhenNode,
21
+ rescue: RescueNode,
22
+ resbody: ResbodyNode,
23
+ ensure: EnsureNode,
24
+ while: WhileNode,
25
+ until: UntilNode,
26
+ for: ForNode,
27
+ break: BreakNode,
28
+ next: NextNode,
29
+ block: BlockNode
30
+ }.freeze
31
+
32
+ #
33
+ # NOTE: Reason why wrap with ArrayNode if the node is `return` or `break` and has multiple children.
34
+ #
35
+ # To treat `return 1, 2` like `return [1, 2]`.
36
+ #
37
+ # @return [Node]
38
+ #
39
+ def n(type, children, source_map)
40
+ children = [ArrayNode.new(:array, children)] if %i[return break].include?(type) && children.size > 1
41
+
42
+ node_klass(type).new(type, children, location: source_map)
43
+ end
44
+
45
+ private
46
+
47
+ def node_klass(type)
48
+ NODE_MAP[type] || Node
49
+ end
50
+ end
51
+ end
52
+ end
@@ -0,0 +1,8 @@
1
+ # frozen_string_literal: true
2
+
3
+ module WhatDyaReturn
4
+ module AST
5
+ class ArrayNode < ::RuboCop::AST::ArrayNode
6
+ end
7
+ end
8
+ end
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module WhatDyaReturn
2
4
  module AST
3
5
  class BeginNode < ::RuboCop::AST::Node
@@ -0,0 +1,8 @@
1
+ # frozen_string_literal: true
2
+
3
+ module WhatDyaReturn
4
+ module AST
5
+ class BlockNode < ::RuboCop::AST::BlockNode
6
+ end
7
+ end
8
+ end
@@ -0,0 +1,8 @@
1
+ # frozen_string_literal: true
2
+
3
+ module WhatDyaReturn
4
+ module AST
5
+ class BreakNode < ::RuboCop::AST::BreakNode
6
+ end
7
+ end
8
+ end
@@ -0,0 +1,8 @@
1
+ # frozen_string_literal: true
2
+
3
+ module WhatDyaReturn
4
+ module AST
5
+ class CaseNode < ::RuboCop::AST::CaseNode
6
+ end
7
+ end
8
+ end
@@ -0,0 +1,8 @@
1
+ # frozen_string_literal: true
2
+
3
+ module WhatDyaReturn
4
+ module AST
5
+ class DefNode < ::RuboCop::AST::DefNode
6
+ end
7
+ end
8
+ end
@@ -0,0 +1,8 @@
1
+ # frozen_string_literal: true
2
+
3
+ module WhatDyaReturn
4
+ module AST
5
+ class EnsureNode < ::RuboCop::AST::EnsureNode
6
+ end
7
+ end
8
+ end
@@ -0,0 +1,8 @@
1
+ # frozen_string_literal: true
2
+
3
+ module WhatDyaReturn
4
+ module AST
5
+ class ForNode < ::RuboCop::AST::ForNode
6
+ end
7
+ end
8
+ end
@@ -1,8 +1,11 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- module RuboCop
3
+ module WhatDyaReturn
4
4
  module AST
5
- class RuboCop::AST::IfNode
5
+ #
6
+ # A node extension for `if` nodes.
7
+ #
8
+ class IfNode < ::RuboCop::AST::IfNode
6
9
  #
7
10
  # @example
8
11
  #
@@ -0,0 +1,8 @@
1
+ # frozen_string_literal: true
2
+
3
+ module WhatDyaReturn
4
+ module AST
5
+ class NextNode < ::RuboCop::AST::NextNode
6
+ end
7
+ end
8
+ end
@@ -0,0 +1,8 @@
1
+ # frozen_string_literal: true
2
+
3
+ module WhatDyaReturn
4
+ module AST
5
+ class ResbodyNode < ::RuboCop::AST::ResbodyNode
6
+ end
7
+ end
8
+ end
@@ -0,0 +1,8 @@
1
+ # frozen_string_literal: true
2
+
3
+ module WhatDyaReturn
4
+ module AST
5
+ class RescueNode < ::RuboCop::AST::RescueNode
6
+ end
7
+ end
8
+ end
@@ -0,0 +1,8 @@
1
+ # frozen_string_literal: true
2
+
3
+ module WhatDyaReturn
4
+ module AST
5
+ class ReturnNode < ::RuboCop::AST::ReturnNode
6
+ end
7
+ end
8
+ end
@@ -0,0 +1,23 @@
1
+ # frozen_string_literal: true
2
+
3
+ module WhatDyaReturn
4
+ module AST
5
+ #
6
+ # A node extension for `if` nodes.
7
+ #
8
+ class UntilNode < ::RuboCop::AST::UntilNode
9
+ #
10
+ # @example
11
+ #
12
+ # until true
13
+ # 42 # unreachable
14
+ # end
15
+ #
16
+ # @return [Boolean]
17
+ #
18
+ def body_reachable?
19
+ condition.truthy_literal?.!
20
+ end
21
+ end
22
+ end
23
+ end
@@ -0,0 +1,8 @@
1
+ # frozen_string_literal: true
2
+
3
+ module WhatDyaReturn
4
+ module AST
5
+ class WhenNode < ::RuboCop::AST::WhenNode
6
+ end
7
+ end
8
+ end
@@ -0,0 +1,23 @@
1
+ # frozen_string_literal: true
2
+
3
+ module WhatDyaReturn
4
+ module AST
5
+ #
6
+ # A node extension for `while` nodes.
7
+ #
8
+ class WhileNode < ::RuboCop::AST::WhileNode
9
+ #
10
+ # @example
11
+ #
12
+ # while false # or `nil`
13
+ # 42 # unreachable
14
+ # end
15
+ #
16
+ # @return [Boolean]
17
+ #
18
+ def body_reachable?
19
+ condition.falsey_literal?.!
20
+ end
21
+ end
22
+ end
23
+ end
@@ -0,0 +1,8 @@
1
+ # frozen_string_literal: true
2
+
3
+ module WhatDyaReturn
4
+ module AST
5
+ class Node < ::RuboCop::AST::Node
6
+ end
7
+ end
8
+ end
@@ -0,0 +1,23 @@
1
+ # frozen_string_literal: true
2
+
3
+ module WhatDyaReturn
4
+ module AST
5
+ #
6
+ # Override `RuboCop::AST::ProcessedSource#create_parser` to
7
+ # use `WhatDyaReturn::AST::Builder` instead of `RuboCop::AST::Builder`
8
+ #
9
+ class ProcessedSource < ::RuboCop::AST::ProcessedSource
10
+ def create_parser(ruby_version)
11
+ builder = Builder.new
12
+
13
+ parser_class(ruby_version).new(builder).tap do |parser|
14
+ parser.diagnostics.all_errors_are_fatal = (RUBY_ENGINE != 'ruby')
15
+ parser.diagnostics.ignore_warnings = false
16
+ parser.diagnostics.consumer = lambda do |diagnostic|
17
+ @diagnostics << diagnostic
18
+ end
19
+ end
20
+ end
21
+ end
22
+ end
23
+ end
@@ -0,0 +1,25 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'rubocop-ast'
4
+
5
+ require_relative 'ext/rubocop-ast/node'
6
+
7
+ require_relative 'ast/node'
8
+ require_relative 'ast/node/def_node'
9
+ require_relative 'ast/node/begin_node'
10
+ require_relative 'ast/node/array_node'
11
+ require_relative 'ast/node/return_node'
12
+ require_relative 'ast/node/if_node'
13
+ require_relative 'ast/node/case_node'
14
+ require_relative 'ast/node/when_node'
15
+ require_relative 'ast/node/rescue_node'
16
+ require_relative 'ast/node/resbody_node'
17
+ require_relative 'ast/node/ensure_node'
18
+ require_relative 'ast/node/while_node'
19
+ require_relative 'ast/node/until_node'
20
+ require_relative 'ast/node/for_node'
21
+ require_relative 'ast/node/break_node'
22
+ require_relative 'ast/node/block_node'
23
+ require_relative 'ast/node/next_node'
24
+ require_relative 'ast/builder'
25
+ require_relative 'ast/processed_source'
@@ -0,0 +1,24 @@
1
+ # frozen_string_literal: true
2
+
3
+ module WhatDyaReturn
4
+ #
5
+ # A RuboCop::AST::Node refinement.
6
+ #
7
+ module NodeRefinary
8
+ refine ::RuboCop::AST::Node do
9
+ #
10
+ # @see WhatDyaReturn::StatementChecker#returnable_statement?
11
+ #
12
+ def returnable_statement?
13
+ StatementChecker.returnable_statement?(self)
14
+ end
15
+
16
+ #
17
+ # @see WhatDyaReturn::StatementChecker#reachable_to_next_statement?
18
+ #
19
+ def reachable_to_next_statement?
20
+ StatementChecker.reachable_to_next_statement?(self)
21
+ end
22
+ end
23
+ end
24
+ end
@@ -1,23 +1,39 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require 'rubocop-ast'
4
3
  require 'unparser'
5
4
  require_relative 'processor'
6
5
 
7
6
  module WhatDyaReturn
8
7
  class Extractor
8
+ #
9
+ # Extracts the return value candidates from `source_code`
10
+ #
11
+ # @example
12
+ #
13
+ # WhatDyaReturn::Extractor.new.extract(<<-CODE)
14
+ # def foo
15
+ # if bar
16
+ # 42
17
+ # else
18
+ # 'baz'
19
+ # end
20
+ # end
21
+ # CODE
22
+ # # => ['42', '"baz"']
9
23
  #
10
24
  # @param [String] source_code
11
25
  # @return [Array<String>]
12
26
  # @raise [WhatDyaReturn::SyntaxErrfor] if `source_code` cannot be parsed
13
27
  #
14
28
  def extract(source_code)
15
- processed = ::RuboCop::AST::ProcessedSource.new(source_code, RUBY_VERSION.to_f)
29
+ processed = AST::ProcessedSource.new(source_code, RUBY_VERSION.to_f)
30
+
16
31
  raise WhatDyaReturn::SyntaxError unless processed.valid_syntax?
32
+ raise WhatDyaReturn::ArgumentError if processed.ast.type != :def
17
33
 
18
34
  Processor.new.process(processed.ast).map do |node|
19
35
  node.nil? ? 'nil' : Unparser.unparse(node)
20
- end
36
+ end.uniq
21
37
  end
22
38
  end
23
39
  end
@@ -1,19 +1,40 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require_relative 'ext/rubocop/ast/builder'
4
- require_relative 'ext/rubocop/ast/node/if_node'
5
3
  require_relative 'statement_checker'
6
4
 
7
5
  module WhatDyaReturn
8
6
  class Processor
9
- #
10
- # @param [RuboCop::AST::DefNode] node
7
+ using NodeRefinary
8
+
9
+ # rubocop:disable Layout/HashAlignment
10
+ BRANCH_CHECKERS = {
11
+ WhatDyaReturn::AST::BeginNode => :check_begin_node,
12
+ WhatDyaReturn::AST::ReturnNode => :check_return_node,
13
+ WhatDyaReturn::AST::IfNode => :check_if_node,
14
+ WhatDyaReturn::AST::CaseNode => :check_case_node,
15
+ WhatDyaReturn::AST::RescueNode => :check_rescue_node,
16
+ WhatDyaReturn::AST::EnsureNode => :check_ensure_node,
17
+ WhatDyaReturn::AST::WhileNode => :check_while_node,
18
+ WhatDyaReturn::AST::UntilNode => :check_until_node,
19
+ WhatDyaReturn::AST::ForNode => :check_for_node,
20
+ WhatDyaReturn::AST::BreakNode => :check_break_node,
21
+ WhatDyaReturn::AST::BlockNode => :check_block_node,
22
+ WhatDyaReturn::AST::NextNode => :check_next_node
23
+ }.freeze
24
+ # rubocop:enable Layout/HashAlignment
25
+
26
+ #
27
+ # @param [WhatDyaReturn::AST::DefNode] node
11
28
  # @return [Array<RuboCop::AST::Node>]
12
29
  #
13
30
  def process(node)
14
31
  @return_nodes = []
15
32
 
16
- check_branch(node.body, true)
33
+ if node.body.nil? # `def foo; end`
34
+ @return_nodes << nil
35
+ else
36
+ check_branch(node.body, node)
37
+ end
17
38
 
18
39
  @return_nodes
19
40
  end
@@ -21,31 +42,22 @@ module WhatDyaReturn
21
42
  private
22
43
 
23
44
  #
24
- # @param [RuboCop::AST::Node] node
25
- # @param [Boolean] is_ret_expr Whether current scope is within return expression
45
+ # @param [RuboCop::AST::Node, nil] node
26
46
  # @return [void]
27
47
  #
28
- def check_branch(node, is_ret_expr)
29
- unless node
30
- @return_nodes << node if is_ret_expr
48
+ def check_branch(node, parent)
49
+ if node.nil?
50
+ @return_nodes << node if parent.returnable_statement?
31
51
  return
32
52
  end
33
53
 
34
- case node
35
- when WhatDyaReturn::AST::BeginNode
36
- check_begin_node(node, is_ret_expr)
37
- when ::RuboCop::AST::ReturnNode
38
- check_return_node(node)
39
- when ::RuboCop::AST::IfNode
40
- check_if_node(node, is_ret_expr)
41
- when ::RuboCop::AST::CaseNode
42
- check_case_node(node, is_ret_expr)
43
- when ::RuboCop::AST::RescueNode
44
- check_rescue_node(node, is_ret_expr)
45
- when ::RuboCop::AST::EnsureNode
46
- check_ensure_node(node, is_ret_expr)
54
+ if (checker = BRANCH_CHECKERS[node.class])
55
+ send(checker, node)
56
+ elsif node.is_a?(RuboCop::AST::Node)
57
+ @return_nodes << node if node.returnable_statement?
47
58
  else
48
- @return_nodes << node if is_ret_expr
59
+ # For debug
60
+ raise UnintentionalNodeError, "Unknown node type: #{node.class}"
49
61
  end
50
62
  end
51
63
 
@@ -53,39 +65,37 @@ module WhatDyaReturn
53
65
  # @param [WhatDyaReturn::AST::BeginNode] node
54
66
  # @return [void]
55
67
  #
56
- def check_begin_node(node, is_ret_expr)
68
+ def check_begin_node(node)
57
69
  node.children[0..-2].each do |child|
58
- check_branch(child, false)
59
- return unless StatementChecker.reachable_to_next_statement?(child) # rubocop:disable Lint/NonLocalExitFromIterator
70
+ check_branch(child, node)
71
+ return unless child.reachable_to_next_statement? # rubocop:disable Lint/NonLocalExitFromIterator
60
72
  end
61
73
 
62
- check_branch(node.children[-1], is_ret_expr)
74
+ check_branch(node.children[-1], node)
63
75
  end
64
76
 
65
77
  #
66
- # @param [RuboCop::AST::ReturnNode] node
78
+ # @param [WhatDyaReturn::AST::ReturnNode] node
67
79
  # @return [void]
68
80
  #
69
81
  def check_return_node(node)
70
- check_branch(node.children.first, true)
82
+ check_branch(node.children.first, node)
71
83
  end
72
84
 
73
85
  #
74
- # @param [RuboCop::AST::IfNode] node
75
- # @param [Boolean] is_ret_expr Whether current scope is within return expression
86
+ # @param [WhatDyaReturn::AST::IfNode] node
76
87
  # @return [void]
77
88
  #
78
- def check_if_node(node, is_ret_expr)
79
- node.if_branch_reachable? && check_branch(node.if_branch, is_ret_expr)
80
- node.else_branch_reachable? && check_branch(node.else_branch, is_ret_expr)
89
+ def check_if_node(node)
90
+ node.if_branch_reachable? && check_branch(node.if_branch, node)
91
+ node.else_branch_reachable? && check_branch(node.else_branch, node)
81
92
  end
82
93
 
83
94
  #
84
- # @param [RuboCop::AST::CaseNode] node
85
- # @param [Boolean] is_ret_expr Whether current scope is within return expression
95
+ # @param [WhatDyaReturn::AST::CaseNode] node
86
96
  # @return [void]
87
97
  #
88
- def check_case_node(node, is_ret_expr)
98
+ def check_case_node(node)
89
99
  node.when_branches.each do |when_branch|
90
100
  #
91
101
  # case # no condition
@@ -97,40 +107,114 @@ module WhatDyaReturn
97
107
  #
98
108
  next if node.condition.nil? && when_branch.conditions.all?(&:falsey_literal?)
99
109
 
100
- check_branch(when_branch.body, is_ret_expr)
110
+ check_branch(when_branch.body, when_branch)
101
111
  end
102
112
 
103
- check_branch(node.else_branch, is_ret_expr)
113
+ check_branch(node.else_branch, node)
104
114
  end
105
115
 
106
116
  #
107
- # @param [RuboCop::AST::RescueNode] node
108
- # @param [Boolean] is_ret_expr Whether current scope is within return expression
117
+ # @param [WhatDyaReturn::AST::RescueNode] node
109
118
  # @return [void]
110
119
  #
111
- def check_rescue_node(node, is_ret_expr)
112
- if node.else_branch && StatementChecker.reachable_to_next_statement?(node.body)
113
- check_branch(node.else_branch, is_ret_expr)
120
+ def check_rescue_node(node)
121
+ if node.else_branch && node.body.reachable_to_next_statement?
122
+ check_branch(node.else_branch, node)
114
123
  else
115
- check_branch(node.body, is_ret_expr)
124
+ check_branch(node.body, node)
116
125
  end
117
126
 
118
127
  node.resbody_branches.each do |resbody_branch|
119
- check_branch(resbody_branch.body, is_ret_expr)
128
+ check_branch(resbody_branch.body, resbody_branch)
129
+ end
130
+ end
131
+
132
+ #
133
+ # @param [WhatDyaReturn::AST::EnsureNode] node
134
+ # @return [void]
135
+ #
136
+ def check_ensure_node(node)
137
+ if node.body.reachable_to_next_statement?
138
+ check_branch(node.node_parts[0], node) # begin or rescue node
139
+ else
140
+ check_branch(node.body, node)
141
+ end
142
+ end
143
+
144
+ #
145
+ # @param [WhatDyaReturn::AST::WhileNode] node
146
+ # @return [void]
147
+ #
148
+ def check_while_node(node)
149
+ if node.body_reachable?
150
+ check_branch(node.body, node)
151
+ check_branch(nil, node) if node.body.reachable_to_next_statement?
152
+ else
153
+ check_branch(nil, node)
120
154
  end
121
155
  end
122
156
 
123
157
  #
124
- # @param [RuboCop::AST::EnsureNode] node
125
- # @param [Boolean] is_ret_expr Whether current scope is within return expression
158
+ # @param [WhatDyaReturn::AST::UntilNode] node
126
159
  # @return [void]
127
160
  #
128
- def check_ensure_node(node, is_ret_expr)
129
- if StatementChecker.reachable_to_next_statement?(node.body)
130
- check_branch(node.node_parts[0], is_ret_expr) # begin or rescue node
161
+ def check_until_node(node)
162
+ if node.body_reachable?
163
+ check_branch(node.body, node)
164
+ check_branch(nil, node) if node.body.reachable_to_next_statement?
131
165
  else
132
- check_branch(node.body, false)
166
+ check_branch(nil, node)
133
167
  end
134
168
  end
169
+
170
+ #
171
+ # @param [WhatDyaReturn::AST::ForNode] node
172
+ # @return [void]
173
+ #
174
+ def check_for_node(node)
175
+ check_branch(node.body, node)
176
+ check_branch(node.collection, node) if node.body.reachable_to_next_statement?
177
+ end
178
+
179
+ #
180
+ # @param [WhatDyaReturn::AST::BreakNode] node
181
+ # @return [void]
182
+ #
183
+ def check_break_node(node)
184
+ check_branch(node.children.first, node)
185
+ end
186
+
187
+ #
188
+ # @param [WhatDyaReturn::AST::BlockNode] node
189
+ # @return [void]
190
+ #
191
+ def check_block_node(node)
192
+ check_branch(node.body, node)
193
+ check_branch(node.send_node, node) if node.body.reachable_to_next_statement?
194
+ end
195
+
196
+ #
197
+ # @note The value passed to `next` is not used for the return value of `block`.
198
+ #
199
+ # @example
200
+ #
201
+ # def foo
202
+ # 10.times do |i|
203
+ # next 42 if i == 2
204
+ # end
205
+ # end
206
+ #
207
+ # foo => # 10.times
208
+ #
209
+ #
210
+ # @param [WhatDyaReturn::AST::NextNode] node
211
+ # @return [void]
212
+ #
213
+ def check_next_node(node)
214
+ block_node = node.ancestors.find { |n| n.instance_of?(AST::BlockNode) }
215
+ return if block_node.nil?
216
+
217
+ check_branch(block_node.send_node, node)
218
+ end
135
219
  end
136
220
  end
@@ -8,8 +8,21 @@
8
8
  module WhatDyaReturn
9
9
  module StatementChecker
10
10
  class ReachableToNextStatement
11
+ extend RuboCop::AST::NodePattern::Macros
12
+
13
+ # @!method flow_terminate_command?(node)
14
+ def_node_matcher :flow_terminate_command?, <<~PATTERN
15
+ {
16
+ return next break retry redo
17
+ (send
18
+ {nil? (const {nil? cbase} :Kernel)}
19
+ {:raise :fail :throw :exit :exit! :abort}
20
+ ...)
21
+ }
22
+ PATTERN
23
+
11
24
  #
12
- # @param node [::RuboCop::AST::Node]
25
+ # @param node [RuboCop::AST::Node]
13
26
  # @return [Boolean]
14
27
  #
15
28
  def ok?(node)
@@ -18,9 +31,9 @@ module WhatDyaReturn
18
31
  case node
19
32
  when WhatDyaReturn::AST::BeginNode
20
33
  check_begin(node)
21
- when ::RuboCop::AST::IfNode
34
+ when WhatDyaReturn::AST::IfNode
22
35
  check_if(node)
23
- when ::RuboCop::AST::CaseNode
36
+ when WhatDyaReturn::AST::CaseNode
24
37
  check_case(node)
25
38
  else
26
39
  true
@@ -88,7 +101,7 @@ module WhatDyaReturn
88
101
  #
89
102
  # "b" # unreachable
90
103
  #
91
- # @param node [::RuboCop::AST::IfNode]
104
+ # @param node [WhatDyaReturn::AST::IfNode]
92
105
  # @return [Boolean]
93
106
  #
94
107
  def check_if(node)
@@ -129,7 +142,7 @@ module WhatDyaReturn
129
142
  #
130
143
  # "b" # unreachable
131
144
  #
132
- # @param node [::RuboCop::AST::CaseNode]
145
+ # @param node [WhatDyaReturn::AST::CaseNode]
133
146
  # @return [Boolean]
134
147
  #
135
148
  def check_case(node)
@@ -138,22 +151,6 @@ module WhatDyaReturn
138
151
 
139
152
  node.when_branches.any? { |when_branch| ok?(when_branch.body) }
140
153
  end
141
-
142
- #
143
- # @param node [::RuboCop::AST::Node]
144
- # @return [Boolean]
145
- #
146
- def flow_terminate_command?(node)
147
- ::RuboCop::AST::NodePattern.new(<<-PATTERN).match(node)
148
- {
149
- return next break retry redo
150
- (send
151
- {nil? (const {nil? cbase} :Kernel)}
152
- {:raise :fail :throw :exit :exit! :abort}
153
- ...)
154
- }
155
- PATTERN
156
- end
157
154
  end
158
155
  end
159
156
  end
@@ -0,0 +1,159 @@
1
+ # frozen_string_literal: true
2
+
3
+ module WhatDyaReturn
4
+ module StatementChecker
5
+ class ReturnableStatement
6
+ # rubocop:disable Layout/HashAlignment
7
+ USED_CHECKERS = {
8
+ AST::ReturnNode => :return_node_used?,
9
+ AST::DefNode => :def_node_used?,
10
+ AST::BeginNode => :begin_node_used?,
11
+ AST::BreakNode => :break_node_used?,
12
+ AST::WhileNode => :while_until_node_used?,
13
+ AST::UntilNode => :while_until_node_used?,
14
+ AST::ForNode => :for_node_used?,
15
+ AST::BlockNode => :block_node_used?
16
+ }.freeze
17
+ # rubocop:enable Layout/HashAlignment
18
+
19
+ #
20
+ # Inspired by RuboCop::AST::Node#value_used?
21
+ #
22
+ def ok?(node)
23
+ return false if node.parent.nil?
24
+
25
+ if (checker = USED_CHECKERS[node.parent.class])
26
+ send(checker, node)
27
+ else
28
+ ok?(node.parent)
29
+ end
30
+ end
31
+
32
+ private
33
+
34
+ #
35
+ # @param [RuboCop::AST::Node] node
36
+ # @return [Boolean]
37
+ #
38
+ def return_node_used?(_node)
39
+ true
40
+ end
41
+
42
+ #
43
+ # @param [RuboCop::AST::Node] node
44
+ # @return [Boolean]
45
+ #
46
+ def def_node_used?(node)
47
+ node.sibling_index == node.parent.children.size - 1
48
+ end
49
+
50
+ #
51
+ # @example
52
+ #
53
+ # def foo
54
+ # begin
55
+ # 1 # false
56
+ # 2 # true
57
+ # end
58
+ # end
59
+ #
60
+ # foo # => 2
61
+ #
62
+ # @example
63
+ #
64
+ # def foo
65
+ # begin
66
+ # 1 # false
67
+ # 2 # false
68
+ # end
69
+ #
70
+ # begin
71
+ # 3 # false
72
+ # 4 # true
73
+ # end
74
+ # end
75
+ #
76
+ # foo # => 4
77
+ #
78
+ # @param [RuboCop::AST::Node] node
79
+ # @return [Boolean]
80
+ #
81
+ def begin_node_used?(node)
82
+ node.sibling_index == node.parent.children.size - 1 ? ok?(node.parent) : false
83
+ end
84
+
85
+ #
86
+ # @example
87
+ #
88
+ # def foo
89
+ # while bar
90
+ # break 1 if baz
91
+ # end
92
+ #
93
+ # while qux
94
+ # break 2 if quux
95
+ # end
96
+ # end
97
+ #
98
+ # foo # => 2
99
+ #
100
+ # @param [RuboCop::AST::Node] node
101
+ # @return [Boolean]
102
+ #
103
+ def break_node_used?(node)
104
+ ancestor = node.ancestors.find do |n|
105
+ [AST::WhileNode, AST::UntilNode, AST::ForNode, AST::BlockNode].include?(n.class)
106
+ end
107
+
108
+ ok?(ancestor)
109
+ end
110
+
111
+ #
112
+ # Only with break or return node.
113
+ #
114
+ # @example
115
+ #
116
+ # value = while foo
117
+ # 1 # false
118
+ # return 2 if bar
119
+ # break 3 if baz
120
+ # 4 # false
121
+ # end
122
+ #
123
+ # value # => 2 or 3
124
+ #
125
+ # @param [RuboCop::AST::Node] node
126
+ # @return [Boolean]
127
+ #
128
+ def while_until_node_used?(_node)
129
+ false
130
+ end
131
+
132
+ #
133
+ # value = for variable in collection
134
+ # body
135
+ # end
136
+ #
137
+ # value == collection # => true
138
+ #
139
+ # @param [RuboCop::AST::Node] node
140
+ # @return [Boolean]
141
+ #
142
+ def for_node_used?(node)
143
+ node.parent.collection == node ? ok?(node.parent) : false
144
+ end
145
+
146
+ #
147
+ # [1, 2, 3].each do |n| # send_node == [1, 2, 3].each
148
+ # p n
149
+ # end
150
+ #
151
+ # @param [RuboCop::AST::Node] node
152
+ # @return [Boolean]
153
+ #
154
+ def block_node_used?(node)
155
+ node.parent.send_node == node ? ok?(node.parent) : false
156
+ end
157
+ end
158
+ end
159
+ end
@@ -1,9 +1,19 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require_relative 'statement_checker/reachable_to_next_statement'
4
+ require_relative 'statement_checker/returnable_statement'
4
5
 
5
6
  module WhatDyaReturn
6
7
  module StatementChecker
8
+ #
9
+ # @param node [::RuboCop::AST::Node]
10
+ # @return [Boolean]
11
+ #
12
+ def self.returnable_statement?(node)
13
+ @returnable_statement ||= ReturnableStatement.new
14
+ @returnable_statement.ok?(node)
15
+ end
16
+
7
17
  #
8
18
  # @param node [::RuboCop::AST::Node]
9
19
  # @return [Boolean]
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module WhatDyaReturn
4
- VERSION = '0.1.0'
4
+ VERSION = '0.2.1'
5
5
  end
@@ -1,9 +1,12 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require_relative 'what_dya_return/ast'
3
4
  require_relative 'what_dya_return/extractor'
4
5
  require_relative 'what_dya_return/version'
5
6
 
6
7
  module WhatDyaReturn
7
8
  class Error < StandardError; end
8
9
  class SyntaxError < Error; end
10
+ class UnintentionalNodeError < Error; end
11
+ class ArgumentError < Error; end
9
12
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: what_dya_return
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.0
4
+ version: 0.2.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Wataru MIYAGUNI
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2023-07-19 00:00:00.000000000 Z
11
+ date: 2023-08-02 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: rubocop-ast
@@ -52,13 +52,32 @@ files:
52
52
  - README.md
53
53
  - Rakefile
54
54
  - lib/what_dya_return.rb
55
+ - lib/what_dya_return/ast.rb
56
+ - lib/what_dya_return/ast/builder.rb
57
+ - lib/what_dya_return/ast/node.rb
58
+ - lib/what_dya_return/ast/node/array_node.rb
55
59
  - lib/what_dya_return/ast/node/begin_node.rb
56
- - lib/what_dya_return/ext/rubocop/ast/builder.rb
57
- - lib/what_dya_return/ext/rubocop/ast/node/if_node.rb
60
+ - lib/what_dya_return/ast/node/block_node.rb
61
+ - lib/what_dya_return/ast/node/break_node.rb
62
+ - lib/what_dya_return/ast/node/case_node.rb
63
+ - lib/what_dya_return/ast/node/def_node.rb
64
+ - lib/what_dya_return/ast/node/ensure_node.rb
65
+ - lib/what_dya_return/ast/node/for_node.rb
66
+ - lib/what_dya_return/ast/node/if_node.rb
67
+ - lib/what_dya_return/ast/node/next_node.rb
68
+ - lib/what_dya_return/ast/node/resbody_node.rb
69
+ - lib/what_dya_return/ast/node/rescue_node.rb
70
+ - lib/what_dya_return/ast/node/return_node.rb
71
+ - lib/what_dya_return/ast/node/until_node.rb
72
+ - lib/what_dya_return/ast/node/when_node.rb
73
+ - lib/what_dya_return/ast/node/while_node.rb
74
+ - lib/what_dya_return/ast/processed_source.rb
75
+ - lib/what_dya_return/ext/rubocop-ast/node.rb
58
76
  - lib/what_dya_return/extractor.rb
59
77
  - lib/what_dya_return/processor.rb
60
78
  - lib/what_dya_return/statement_checker.rb
61
79
  - lib/what_dya_return/statement_checker/reachable_to_next_statement.rb
80
+ - lib/what_dya_return/statement_checker/returnable_statement.rb
62
81
  - lib/what_dya_return/version.rb
63
82
  - sig/what_dya_return.rbs
64
83
  homepage: https://github.com/gongo/what_dya_return
@@ -83,7 +102,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
83
102
  - !ruby/object:Gem::Version
84
103
  version: '0'
85
104
  requirements: []
86
- rubygems_version: 3.2.33
105
+ rubygems_version: 3.4.10
87
106
  signing_key:
88
107
  specification_version: 4
89
108
  summary: Predict Ruby code return values.
@@ -1,23 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require_relative '../../../ast/node/begin_node'
4
-
5
- module RuboCop
6
- module AST
7
- class Builder
8
- EXT_NODE_MAP = NODE_MAP.merge(
9
- {
10
- begin: WhatDyaReturn::AST::BeginNode,
11
- kwbegin: WhatDyaReturn::AST::BeginNode
12
- }
13
- )
14
-
15
- #
16
- # @override
17
- #
18
- def node_klass(type)
19
- EXT_NODE_MAP.fetch(type, ::RuboCop::AST::Node)
20
- end
21
- end
22
- end
23
- end