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.
- checksums.yaml +4 -4
- data/.rubocop.yml +10 -1
- data/README.md +54 -2
- data/lib/what_dya_return/ast/builder.rb +52 -0
- data/lib/what_dya_return/ast/node/array_node.rb +8 -0
- data/lib/what_dya_return/ast/node/begin_node.rb +2 -0
- data/lib/what_dya_return/ast/node/block_node.rb +8 -0
- data/lib/what_dya_return/ast/node/break_node.rb +8 -0
- data/lib/what_dya_return/ast/node/case_node.rb +8 -0
- data/lib/what_dya_return/ast/node/def_node.rb +8 -0
- data/lib/what_dya_return/ast/node/ensure_node.rb +8 -0
- data/lib/what_dya_return/ast/node/for_node.rb +8 -0
- data/lib/what_dya_return/{ext/rubocop/ast → ast}/node/if_node.rb +5 -2
- data/lib/what_dya_return/ast/node/next_node.rb +8 -0
- data/lib/what_dya_return/ast/node/resbody_node.rb +8 -0
- data/lib/what_dya_return/ast/node/rescue_node.rb +8 -0
- data/lib/what_dya_return/ast/node/return_node.rb +8 -0
- data/lib/what_dya_return/ast/node/until_node.rb +23 -0
- data/lib/what_dya_return/ast/node/when_node.rb +8 -0
- data/lib/what_dya_return/ast/node/while_node.rb +23 -0
- data/lib/what_dya_return/ast/node.rb +8 -0
- data/lib/what_dya_return/ast/processed_source.rb +23 -0
- data/lib/what_dya_return/ast.rb +25 -0
- data/lib/what_dya_return/ext/rubocop-ast/node.rb +24 -0
- data/lib/what_dya_return/extractor.rb +19 -3
- data/lib/what_dya_return/processor.rb +137 -53
- data/lib/what_dya_return/statement_checker/reachable_to_next_statement.rb +18 -21
- data/lib/what_dya_return/statement_checker/returnable_statement.rb +159 -0
- data/lib/what_dya_return/statement_checker.rb +10 -0
- data/lib/what_dya_return/version.rb +1 -1
- data/lib/what_dya_return.rb +3 -0
- metadata +24 -5
- 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:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 3de4d93782243ac22ab3781e0959bf304ab9b98efa92991b453d67894e1c59b8
|
4
|
+
data.tar.gz: 1e4152cdf2f48a459197dc718cd8488046de245d7f2f14265878145cb6f1c50d
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
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
|
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
|
-
|
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,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,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,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 =
|
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
|
-
|
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
|
-
|
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,
|
29
|
-
|
30
|
-
@return_nodes << node if
|
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
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
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
|
-
|
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
|
68
|
+
def check_begin_node(node)
|
57
69
|
node.children[0..-2].each do |child|
|
58
|
-
check_branch(child,
|
59
|
-
return unless
|
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],
|
74
|
+
check_branch(node.children[-1], node)
|
63
75
|
end
|
64
76
|
|
65
77
|
#
|
66
|
-
# @param [
|
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,
|
82
|
+
check_branch(node.children.first, node)
|
71
83
|
end
|
72
84
|
|
73
85
|
#
|
74
|
-
# @param [
|
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
|
79
|
-
node.if_branch_reachable? && check_branch(node.if_branch,
|
80
|
-
node.else_branch_reachable? && check_branch(node.else_branch,
|
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 [
|
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
|
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,
|
110
|
+
check_branch(when_branch.body, when_branch)
|
101
111
|
end
|
102
112
|
|
103
|
-
check_branch(node.else_branch,
|
113
|
+
check_branch(node.else_branch, node)
|
104
114
|
end
|
105
115
|
|
106
116
|
#
|
107
|
-
# @param [
|
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
|
112
|
-
if node.else_branch &&
|
113
|
-
check_branch(node.else_branch,
|
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,
|
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,
|
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 [
|
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
|
129
|
-
if
|
130
|
-
check_branch(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(
|
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 [
|
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 ::
|
34
|
+
when WhatDyaReturn::AST::IfNode
|
22
35
|
check_if(node)
|
23
|
-
when ::
|
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 [::
|
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 [::
|
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]
|
data/lib/what_dya_return.rb
CHANGED
@@ -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
|
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-
|
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/
|
57
|
-
- lib/what_dya_return/
|
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.
|
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
|