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